Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invoicing #3

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
215 changes: 174 additions & 41 deletions src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import os
from typing import TYPE_CHECKING

from telegram import ReplyKeyboardMarkup
from telegram import Update
from telegram.ext import CallbackQueryHandler
from telegram.ext import CommandHandler
from telegram.ext import ConversationHandler
from telegram.ext import Dispatcher
Expand All @@ -12,10 +12,14 @@
from telegram.ext import Updater

from .clients.dadata import get_by_inn
from .clients.tinkoff import get_invoice
from .keyboards import keyboards as kb
from .models.item import DATABASE
from .models.item import Item
from .models.item import parse_input

INN, CONFIRM_LEGAL_ENTITY, ADD_ITEM, CHANGE_PRICE_OR_AMOUNT_OF_LAST_ITEM = range(4)
MAIN_MENU, SELECT_COURSE, SELECT_ITEMS, FIND_LEGAL_ENTITY, SELECT_LEGAL_ENTITY, CONFIRM_LEGAL_ENTITY, SEND_INVOICE = range(
7)

if TYPE_CHECKING:
from .t import CallbackContext
Expand All @@ -28,65 +32,187 @@ def enable_logging() -> None:
)


def start(update: Update, context: 'CallbackContext') -> int:
update.message.reply_text('Введите ИНН')
def button_main_menu(update: Update, context: 'CallbackContext') -> int:
update.callback_query.message.reply_text(
text='Привет! Я бот-помощник Школы сильных программистов. '
'С моей помощью вы можете получить счет для оплаты '
'обучения сотрудников от юрлица',
reply_markup=kb.main_menu_keyboard(),
)
return SELECT_COURSE


def command_main_menu(update: Update, context: 'CallbackContext') -> int:
update.message.reply_text(
text='Привет! Я бот-помощник Школы сильных программистов. '
'С моей помощью вы можете получить счет для оплаты '
'обучения сотрудников от юрлица',
reply_markup=kb.main_menu_keyboard(),
)
return SELECT_COURSE


def select_course(update: Update, context: 'CallbackContext') -> int:
update.callback_query.message.reply_text(
text='Доступ к какому курсу вы хотите приобрести?',
reply_markup=kb.select_course_keyboard(),
)

update.callback_query.answer()

return SELECT_ITEMS


def item_initial_load(update: Update, context: 'CallbackContext') -> None:
context.user_data['invoice'] = {
'legal_entity': None, # type: ignore
'items': [],
}

return INN
items_initial_load = [parse_input(str(item['user_name'])) for item in DATABASE.values()]

context.user_data['invoice']['items'] = items_initial_load

update.callback_query.message.reply_text(
text='Выберите тариф и кол-во участников',
reply_markup=kb.select_item_keyboard(items_initial_load),
)


def update_item_amount(callback: str, items: list[Item], update: Update, context: 'CallbackContext') -> None:
sign = callback.split('_')[1]
item_to_update = callback.split('_')[2]

for item in items:
if item.user_name != item_to_update:
continue

if sign == 'minus' and item.amount > 0:
item.amount -= 1
elif sign == 'plus':
item.amount += 1

update.callback_query.answer()

update.callback_query.message.edit_text(
text='Выберите тариф и кол-во участников',
reply_markup=kb.select_item_keyboard(items),
)


def item_confirm_message(update: Update, context: 'CallbackContext', items: list[Item]) -> None:
selected_items = ''
total_amount = 0

for item in items:
if item.amount > 0:
selected_items += str(item) + '\n'

total_amount += item.amount * item.price

if total_amount == 0:
text = 'Похоже вы ничего не выбрали'
show_approve = False
else:
text = f'Отлично, давайте проверим.\n\nВы выбрали:\n\n{selected_items}\n\n' \
f'Итоговая сумма: {total_amount}\n\nВсе верно?'
show_approve = True

def inn(update: Update, context: 'CallbackContext') -> int | None:
update.callback_query.message.reply_text(
text=text,
reply_markup=kb.confirm_items_keyboard(show_approve=show_approve))


def select_item(update: Update, context: 'CallbackContext') -> int:
callback = update.callback_query.data

# initial load of items
if callback == 'course_aa':
item_initial_load(update=update, context=context)
update.callback_query.answer()
return SELECT_ITEMS

# buttons with no action
if callback == 'item_noaction':
update.callback_query.answer()
return SELECT_ITEMS

items = context.user_data['invoice']['items']

# "Готово" button
if callback == 'item_finish':
item_confirm_message(update=update,
context=context,
items=items)
update.callback_query.answer()
return FIND_LEGAL_ENTITY

# update amount of items
update_item_amount(callback=callback, items=items, update=update, context=context)
update.callback_query.answer()
return SELECT_ITEMS


def find_legal_entity_by_inn(update: Update, context: 'CallbackContext') -> int:
update.callback_query.message.reply_text(
text='Введите ИНН компании, чтобы мы могли выставить вам счет',
)

update.callback_query.answer()

return SELECT_LEGAL_ENTITY


def select_legal_entity_by_kpp(update: Update, context: 'CallbackContext') -> int | None:
inn: str = update.message.text

entities = get_by_inn(inn)
context.user_data['entities'] = get_by_inn(inn)

if len(entities) == 1:
context.user_data['invoice']['legal_entity'] = entities[0]
if len(context.user_data['entities']) > 0:
update.message.reply_text(
text=f'{entities[0]}. Если ошиблись — наберите /start',
reply_markup=ReplyKeyboardMarkup([[i['user_name']] for i in DATABASE.values()]), # type: ignore
text='Мы нашли вот эти организации, выберите вашу',
reply_markup=kb.select_by_kpp_keyboard(entities=context.user_data['entities']),
)
return ADD_ITEM

if len(entities) == 0:
update.message.reply_text('Чё-т ничего не нашлось :( Попробуйте ещё раз или напишите Феде')
elif len(entities) > 2:
update.message.reply_text('WIP, пока не умеем работать с несколькими юрлицами')
return CONFIRM_LEGAL_ENTITY

if len(context.user_data['entities']) == 0:
update.message.reply_text(text='Чё-т ничего не нашлось :( Попробуйте ещё раз или напишите Феде',
reply_markup=kb.wrong_inn_keyboard())

def add_item(update: Update, context: 'CallbackContext') -> int:
item = parse_input(update.message.text)
context.user_data['invoice']['items'] = [item]
return SELECT_LEGAL_ENTITY

legal_entity = context.user_data['invoice']['legal_entity']

update.message.reply_text(
text=f'Ок, {item}. Выставляем на {legal_entity.name}? Если хотите поменять цену или количество — напишите мне.',
reply_markup=ReplyKeyboardMarkup([['Выставляем!']]),
)
def confirm_legal_entity(update: Update, context: 'CallbackContext') -> int:
entities = context.user_data['entities']

return CHANGE_PRICE_OR_AMOUNT_OF_LAST_ITEM
if len(entities) == 1: # when legal entity doesn't have kpp (ИП)
legal_entity = entities[0]
else:
legal_entity = [entity for entity in entities if entity.kpp == update.callback_query.data][0]

context.user_data['invoice']['legal_entity'] = legal_entity

def change_price_or_amount_of_last_item(update: Update, context: 'CallbackContext') -> None:
number = int(update.message.text)
update.callback_query.message.reply_text(text=f'Вы выбрали: {legal_entity}\nВсе верно?',
reply_markup=kb.confirm_legal_entity_and_get_invoice_keyboard())

if number < 100:
context.user_data['invoice']['items'][0].amount = number
else:
context.user_data['invoice']['items'][0].price = number
return SEND_INVOICE


def send_invoice(update: Update, context: 'CallbackContext') -> int:
items = [item for item in context.user_data['invoice']['items'] if item.amount > 0]

item = context.user_data['invoice']['items'][0]
legal_entity = context.user_data['invoice']['legal_entity']

update.message.reply_text(
text=f'Ок, {item}. Выставляем на {legal_entity.name}? Если хотите поменять цену или количество — напишите мне.',
reply_markup=ReplyKeyboardMarkup([['Выставляем!']]),
)
text = 'Ура! Вас счет готов.' \
'Если возникли ошибки — напишите пожалуйста на [email protected].'

update.callback_query.message.reply_text(text=text,
reply_markup=kb.link_to_invoice_keyboard(
url=get_invoice(legal_entity=legal_entity,
items=items)))

return MAIN_MENU


def main() -> None:
Expand All @@ -100,13 +226,20 @@ def main() -> None:

dispatcher.add_handler(
ConversationHandler(
entry_points=[CommandHandler('start', callback=start)],
entry_points=[CommandHandler('start', callback=command_main_menu)],
states={
INN: [MessageHandler(callback=inn, filters=Filters.text)],
ADD_ITEM: [MessageHandler(callback=add_item, filters=Filters.text)],
CHANGE_PRICE_OR_AMOUNT_OF_LAST_ITEM: [MessageHandler(callback=change_price_or_amount_of_last_item, filters=Filters.text)],
SELECT_COURSE: [CallbackQueryHandler(callback=select_course, pattern='invoice')],
SELECT_ITEMS: [CallbackQueryHandler(callback=select_item, pattern='^item_.|course_aa')],
FIND_LEGAL_ENTITY: [CallbackQueryHandler(callback=find_legal_entity_by_inn, pattern='approved')],
SELECT_LEGAL_ENTITY: [MessageHandler(callback=select_legal_entity_by_kpp, filters=Filters.text)],
CONFIRM_LEGAL_ENTITY: [CallbackQueryHandler(callback=confirm_legal_entity)],
SEND_INVOICE: [CallbackQueryHandler(callback=send_invoice, pattern='approved')],
},
fallbacks=[],
fallbacks=[
CallbackQueryHandler(callback=button_main_menu, pattern='main_menu'),
CallbackQueryHandler(callback=find_legal_entity_by_inn, pattern='return_to_legal_entity'),
CallbackQueryHandler(callback=select_item, pattern='return_to_select_item'),
],
allow_reentry=True,
),
)
Expand Down
10 changes: 9 additions & 1 deletion src/clients/tinkoff.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from datetime import date
import os
from random import randint
from urllib.parse import urljoin

from dotenv import load_dotenv
Expand Down Expand Up @@ -35,7 +37,7 @@ def get_invoice(legal_entity: LegalEntity, items: list[Item]) -> str:
result = post(
url='v1/invoice/send',
payload={
'invoiceNumber': '100501',
'invoiceNumber': generate_invoice_number(),
'payer': legal_entity.to_tinkoff(),
'items': [item.to_tinkoff() for item in items],
},
Expand All @@ -47,6 +49,12 @@ def get_invoice(legal_entity: LegalEntity, items: list[Item]) -> str:
return result['pdfUrl']


def generate_invoice_number() -> str:
today = date.today().strftime('%d%m%Y')
digits = str(randint(1000, 9999))
return digits + today


if __name__ == '__main__':
invoice = get_invoice(
legal_entity=LegalEntity(name='ООО Федя и Самат', inn='7722496158', kpp='772201001'),
Expand Down
Loading