diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e69de29
diff --git a/bot.py b/bot.py
new file mode 100644
index 0000000..df40200
--- /dev/null
+++ b/bot.py
@@ -0,0 +1,68 @@
+import asyncio
+import logging
+
+from aiogram import Bot, Dispatcher, types, F
+from aiogram.filters.command import Command
+from aiogram.fsm.context import FSMContext
+from aiogram.types import CallbackQuery
+
+from config import TOKEN
+from handlers.daily_type import daily_router
+from handlers.expenses import expenses_router
+from keyboards.inline import start_options
+
+logging.basicConfig(level=logging.INFO)
+
+bot = Bot(token=TOKEN)
+dp = Dispatcher()
+dp.include_routers(daily_router, expenses_router)
+
+
+@dp.message(Command("start", ignore_case=True))
+async def cmd_start(message: types.Message, state: FSMContext):
+ """
+ Запуск бота
+ :param message:
+ :param state:
+ """
+ data = await state.get_data()
+ report_id = data["report"] if 'report' in data else message.message_id - 90
+ try:
+ await bot.delete_messages(message.chat.id, list(range(max(1, message.message_id - 90, report_id + 1), message.message_id + 1)))
+ except Exception:
+ pass
+ await state.clear()
+ await state.update_data(report=report_id)
+ await message.answer(text='Выберите тип расхода', reply_markup=start_options.as_markup())
+
+
+@dp.callback_query(F.data == "start")
+async def return_to_menu(call: CallbackQuery, state: FSMContext):
+ """
+ Возврат к начальной позиции
+ :param call:
+ :param state:
+ """
+ data = await state.get_data()
+ await bot.edit_message_reply_markup(chat_id=call.message.chat.id, message_id=call.message.message_id, reply_markup=None)
+ report_id = data["report"] if 'report' in data else call.message.message_id - 90
+ try:
+ await bot.delete_messages(call.message.chat.id,
+ list(range(max(1, call.message.message_id - 90, report_id + 1), call.message.message_id + 1)))
+ except Exception:
+ pass
+ await state.clear()
+ await state.update_data(report=report_id)
+ await call.message.answer(text='Выберите тип расхода', reply_markup=start_options.as_markup())
+
+
+async def main():
+ """
+ Запуск бота
+ :return:
+ """
+ await dp.start_polling(bot)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..89b949c
--- /dev/null
+++ b/config.py
@@ -0,0 +1,6 @@
+from dotenv import load_dotenv
+import os
+
+load_dotenv()
+
+TOKEN = os.getenv('TOKEN')
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e69de29
diff --git a/handlers/daily_type.py b/handlers/daily_type.py
new file mode 100644
index 0000000..9a65202
--- /dev/null
+++ b/handlers/daily_type.py
@@ -0,0 +1,68 @@
+from aiogram import F, types, Router, Bot
+from aiogram.fsm.context import FSMContext
+from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup
+from aiogram.fsm.state import StatesGroup, State
+from aiogram.utils.keyboard import InlineKeyboardBuilder
+
+from handlers.expenses import edit_prev_msg
+from keyboards.inline import choose_country, to_start, daily_back1
+
+daily_router = Router()
+
+
+class DailExpensesState(StatesGroup):
+ """ Состояния """
+ set_days = State()
+ set_geo = State()
+ calculate = State()
+
+
+@daily_router.callback_query(F.data == 'daily_back1')
+async def go_to_prev_daily(call: CallbackQuery, state: FSMContext):
+ await state.set_state(DailExpensesState.set_days)
+ try:
+ await call.message.edit_text('Укажите кол-во дней', reply_markup=daily_back1.as_markup())
+ except Exception:
+ await call.message.answer('Укажите кол-во дней', reply_markup=daily_back1.as_markup())
+
+
+@daily_router.callback_query(F.data == 'var1')
+async def set_days(call: CallbackQuery, state: FSMContext):
+ await state.set_state(DailExpensesState.set_days)
+ msg = await call.message.edit_text('Укажите кол-во дней', reply_markup=daily_back1.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@daily_router.message(DailExpensesState.set_days)
+async def set_geo(message: Message, state: FSMContext, bot: Bot):
+
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+
+ try:
+ days = int(message.text)
+ except ValueError:
+ msg = await message.answer('Пожалуйста, укажите число', reply_markup=daily_back1.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+ await state.set_state(DailExpensesState.set_geo)
+ await state.update_data(days=days)
+ await message.answer('Укажите страну', reply_markup=choose_country.as_markup())
+
+
+@daily_router.message(DailExpensesState.set_geo)
+async def set_country(message: Message, state: FSMContext, bot):
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+ await message.answer('Укажите страну', reply_markup=choose_country.as_markup())
+
+
+@daily_router.callback_query(DailExpensesState.set_geo)
+async def calculate_bill(call: CallbackQuery, state: FSMContext):
+ await state.set_state(DailExpensesState.calculate)
+ data = await state.get_data()
+ days = data['days']
+ k = 1445 if call.data == 'russian' else 2750
+ await call.message.edit_text(f'Cумма к добавлению в расчет: {k * days} ₽', parse_mode="HTML", reply_markup=to_start.as_markup())
\ No newline at end of file
diff --git a/handlers/expenses.py b/handlers/expenses.py
new file mode 100644
index 0000000..865f03c
--- /dev/null
+++ b/handlers/expenses.py
@@ -0,0 +1,430 @@
+import re
+import cv2
+import io
+import numpy as np
+
+from aiogram import F, types, Router, Bot
+from decimal import Decimal
+from datetime import datetime
+
+from aiogram.exceptions import TelegramBadRequest
+from aiogram.fsm.context import FSMContext
+from aiogram.types import Message, CallbackQuery
+from aiogram.fsm.state import StatesGroup, State
+from qreader import QReader
+
+from handlers.get_check_info import NalogRuPython
+from keyboards.inline import expenses, to_start, fill_check, check_failed, input_check_back1, input_check_back2, \
+ input_check_back3, input_check_back4, input_check_back5, input_check_back6
+
+expenses_router = Router()
+qreader = QReader()
+
+
+go_back = [
+ 'input_check_back1',
+ 'input_check_back2',
+ 'input_check_back3',
+ 'input_check_back4',
+ 'input_check_back5',
+ 'input_check_back6'
+]
+
+
+class ExpensesState(StatesGroup):
+ """ Состояния """
+ choose_expense = State()
+ choose_other = State()
+ load_check = State()
+
+
+class InputCheckState(StatesGroup):
+ """ Состояния """
+ input_date = State()
+ input_time = State()
+ input_sum = State()
+ input_fn = State()
+ input_fd = State()
+ input_fp = State()
+
+
+def check_info(ticket):
+ date = ticket["operation"]["date"]
+ parsed_date = datetime.fromisoformat(date)
+ formatted_date = parsed_date.strftime("%d.%m.%Y %H:%M")
+ total = Decimal(ticket["operation"]["sum"]) / Decimal(100)
+ nds = Decimal(ticket["ticket"]["document"]["receipt"]["nds18"]) / Decimal(100)
+ dif = total - nds
+ data = {
+ "date": formatted_date,
+ "fd": ticket["query"]["documentId"],
+ "total": f"{total:.2f}",
+ "nds": f"{nds:.2f}",
+ "sum": f"{dif:.2f}"
+ }
+ print(data)
+
+ formatted_text = (
+ f"Статус отчёта: подтверждён ✅\n"
+ f"Дата чека: {data['date']}\n"
+ f"ФД: {data['fd']}\n"
+ f"Итого: {data['total']}\n"
+ f"НДС: {data['nds']}\n"
+ f"Сумма без НДС: {data['sum']}\n"
+ )
+
+ return formatted_text
+
+
+def check_fake_info(data):
+ formatted_text = (
+ f"Статус отчёта: не подтвержден ❌\n"
+ f"Дата чека: {data['date']} {data['time']}\n"
+ f"ФД: {data['fd']}\n"
+ f"Итого: {data['sum']}\n"
+ )
+
+ return formatted_text
+
+
+async def edit_prev_msg(data, bot: Bot, message, state: FSMContext):
+ if data["delkeyboard"]:
+ prev_msg = data["delkeyboard"]
+ try:
+ await bot.edit_message_reply_markup(chat_id=message.chat.id, message_id=prev_msg, reply_markup=None)
+ except Exception as e:
+ print(e)
+
+
+async def cmd_clear(message: Message, bot: Bot, report_id) -> None:
+ await bot.delete_messages(message.chat.id, list(range(max(1, message.message_id - 90, report_id + 1), message.message_id + 1)))
+
+
+@expenses_router.callback_query(F.data == 'var2')
+async def choose_expense(call: CallbackQuery, state: FSMContext):
+ await state.set_state(ExpensesState.choose_expense)
+ try:
+ await call.message.edit_text('Выберите тип расхода', reply_markup=expenses.as_markup())
+ except Exception:
+ await call.message.answer('Выберите тип расхода', reply_markup=expenses.as_markup())
+
+
+@expenses_router.callback_query(ExpensesState.choose_expense, F.data == 'ex5')
+async def add_category(call: CallbackQuery, state: FSMContext):
+ await state.update_data(ex_type=call.data)
+ await state.set_state(ExpensesState.choose_other)
+ try:
+ await call.message.edit_text('Введите тип расхода')
+ except Exception:
+ await call.message.answer('Введите тип расхода')
+
+
+@expenses_router.message(ExpensesState.choose_other)
+async def handle_post(message: Message, state: FSMContext):
+ await state.update_data(ex_type=message.text)
+ await state.set_state(ExpensesState.load_check)
+ await message.answer('Отправьте фото чека, чтобы на нем было видно QR-код')
+
+
+@expenses_router.message(ExpensesState.choose_expense)
+async def handle_post(message: Message, state: FSMContext):
+ await state.set_state(ExpensesState.choose_expense)
+ try:
+ await message.edit_text('Выберите тип расхода', reply_markup=expenses.as_markup())
+ except Exception:
+ await message.answer('Выберите тип расхода', reply_markup=expenses.as_markup())
+
+
+@expenses_router.callback_query(ExpensesState.choose_expense)
+async def load_check(call: CallbackQuery, state: FSMContext):
+ await state.update_data(ex_type=call.data)
+ await state.set_state(ExpensesState.load_check)
+ try:
+ await call.message.edit_text('Отправьте фото чека, чтобы на нем было видно QR-код')
+ except Exception:
+ await call.message.answer('Отправьте фото чека, чтобы на нем было видно QR-код')
+
+
+@expenses_router.callback_query(lambda call: call.data in go_back)
+async def go_to_prev_check_input(call: CallbackQuery, state: FSMContext):
+ msg = None
+ if call.data == 'input_check_back1':
+ await state.set_state(ExpensesState.load_check)
+ try:
+ msg = await call.message.edit_text('Отправьте фото чека, чтобы на нем было видно QR-код')
+ except Exception:
+ msg = await call.message.answer('Отправьте фото чека, чтобы на нем было видно QR-код')
+ elif call.data == 'input_check_back2':
+ await state.set_state(InputCheckState.input_date)
+ try:
+ msg = await call.message.edit_text('Введите дату с чека в формате: ДД.ММ.ГГГГ (например: 02.02.2020)',
+ reply_markup=input_check_back1.as_markup())
+ except Exception:
+ msg = await call.message.answer('Введите дату с чека в формате: ДД.ММ.ГГГГ (например: 02.02.2020)',
+ reply_markup=input_check_back1.as_markup())
+ elif call.data == 'input_check_back3':
+ await state.set_state(InputCheckState.input_time)
+ try:
+ msg = await call.message.edit_text('Введите время с чека в формате: ЧЧ:ММ (например: 15:30)',
+ reply_markup=input_check_back2.as_markup())
+ except Exception:
+ msg = await call.message.answer('Введите время с чека в формате: ЧЧ:ММ (например: 15:30)',
+ reply_markup=input_check_back2.as_markup())
+ elif call.data == 'input_check_back4':
+ await state.set_state(InputCheckState.input_sum)
+ try:
+ msg = await call.message.edit_text('Введите сумму с чека например 157.00 (157 рублей 00 копеек)',
+ reply_markup=input_check_back3.as_markup())
+ except Exception:
+ msg = await call.message.answer('Введите сумму с чека например 157.00 (157 рублей 00 копеек)',
+ reply_markup=input_check_back3.as_markup())
+ elif call.data == 'input_check_back5':
+ await state.set_state(InputCheckState.input_fn)
+ try:
+ msg = await call.message.edit_text('Введите ФН', reply_markup=input_check_back4.as_markup())
+ except Exception:
+ msg = await call.message.answer('Введите ФН', reply_markup=input_check_back4.as_markup())
+ elif call.data == 'input_check_back6':
+ await state.set_state(InputCheckState.input_fd)
+ try:
+ msg = await call.message.edit_text('Введите ФД', reply_markup=input_check_back5.as_markup())
+ except Exception:
+ msg = await call.message.answer('Введите ФД', reply_markup=input_check_back5.as_markup())
+ if msg:
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@expenses_router.callback_query(ExpensesState.load_check, F.data == 'fill_check')
+async def input_check_data(call: CallbackQuery, state: FSMContext):
+ await state.set_state(InputCheckState.input_date)
+ try:
+ msg = await call.message.edit_text('Введите дату с чека в формате: ДД.ММ.ГГГГ (например: 02.02.2020)',
+ reply_markup=input_check_back1.as_markup())
+ except Exception:
+ msg = await call.message.answer('Введите дату с чека в формате: ДД.ММ.ГГГГ (например: 02.02.2020)', reply_markup=input_check_back1.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@expenses_router.message(InputCheckState.input_date)
+async def input_check_time(message: Message, state: FSMContext, bot: Bot):
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+
+ date_text = message.text.strip()
+
+ if not re.match(r"^\d{2}\.\d{2}\.\d{4}$", date_text):
+ msg = await message.reply("Неверный формат даты. Введите дату в формате: ДД.ММ.ГГГГ (например: 02.02.2020)", reply_markup=input_check_back1.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ try:
+ datetime.strptime(date_text, "%d.%m.%Y")
+ except ValueError:
+ msg = await message.reply("Некорректная дата. Проверьте правильность ввода и попробуйте снова.", reply_markup=input_check_back1.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ await state.set_state(InputCheckState.input_time)
+ await state.update_data(date=message.text)
+ msg = await message.answer('Введите время с чека в формате: ЧЧ:ММ (например: 15:30)', reply_markup=input_check_back2.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@expenses_router.message(InputCheckState.input_time)
+async def input_check_sum(message: Message, state: FSMContext, bot: Bot):
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+
+ time_text = message.text.strip()
+
+ if not re.match(r"^\d{2}:\d{2}$", time_text):
+ msg = await message.reply("Неверный формат времени. Введите время в формате: ЧЧ:ММ (например: 15:30)", reply_markup=input_check_back2.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ try:
+ datetime.strptime(time_text, "%H:%M")
+ except ValueError:
+ msg = await message.reply("Некорректное время. Проверьте правильность ввода и попробуйте снова.", reply_markup=input_check_back2.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ await state.set_state(InputCheckState.input_sum)
+ await state.update_data(time=message.text)
+ msg = await message.answer('Введите сумму с чека например 157.00 (157 рублей 00 копеек)', reply_markup=input_check_back3.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@expenses_router.message(InputCheckState.input_sum)
+async def input_check_fn(message: Message, state: FSMContext, bot: Bot):
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+
+ sum_text = message.text.strip()
+
+ try:
+ sum_value = float(sum_text)
+ if sum_value < 0:
+ raise ValueError("Сумма не может быть отрицательной. Повторите ввод")
+ formatted_sum = f"{sum_value:.2f}"
+
+ except ValueError:
+ msg = await message.reply(
+ "Некорректный формат суммы. Введите сумму в виде целого числа или с двумя знаками после точки (например: 157 или 157.00).", reply_markup=input_check_back3.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ await state.set_state(InputCheckState.input_fn)
+ await state.update_data(sum=formatted_sum)
+ msg = await message.answer('Введите ФН', reply_markup=input_check_back4.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@expenses_router.message(InputCheckState.input_fn)
+async def input_check_fd(message: Message, state: FSMContext, bot: Bot):
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+
+ fn_text = message.text.strip()
+
+ if not (fn_text.isdigit() and len(fn_text) == 16):
+ msg = await message.reply("Некорректный формат ФН. Убедитесь, что он состоит ровно из 16 цифр.", reply_markup=input_check_back4.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ await state.set_state(InputCheckState.input_fd)
+ await state.update_data(fn=message.text)
+ msg = await message.answer('Введите ФД', reply_markup=input_check_back5.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@expenses_router.message(InputCheckState.input_fd)
+async def input_check_fp(message: Message, state: FSMContext, bot: Bot):
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+
+ fd_text = message.text.strip()
+
+ if not fd_text.isdigit():
+ msg = await message.reply("Некорректный формат ФД. Убедитесь, что он состоит из цифр.", reply_markup=input_check_back5.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ await state.set_state(InputCheckState.input_fp)
+ await state.update_data(fd=message.text)
+ msg = await message.answer('Введите ФП', reply_markup=input_check_back6.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+
+
+@expenses_router.message(InputCheckState.input_fp)
+async def input_check_load_data(message: Message, state: FSMContext, bot: Bot):
+ data = await state.get_data()
+ await edit_prev_msg(data, bot, message, state)
+ await state.update_data(delkeyboard=None)
+
+ fp_text = message.text.strip()
+
+ if not fp_text.isdigit():
+ msg = await message.reply("Некорректный формат ФП. Убедитесь, что он состоит из цифр.", reply_markup=input_check_back6.as_markup())
+ await state.update_data(delkeyboard=msg.message_id)
+ return
+
+ await state.update_data(fp=message.text)
+ await state.set_state(ExpensesState.load_check)
+ data = await state.get_data()
+
+ date_obj = datetime.strptime(data['date'], "%d.%m.%Y")
+ time_obj = datetime.strptime(data['time'], "%H:%M").time()
+
+ combined_datetime = datetime.combine(date_obj.date(), time_obj)
+ iso_format = combined_datetime.strftime("%Y%m%dT%H%M")
+
+ sum_total = data['sum']
+ fn = data['fn']
+ fd = data['fd']
+ fp = data['fp']
+ query = f"t={iso_format}&s={sum_total}&fn={fn}&i={fd}&fp={fp}&n=1"
+ try:
+ client = NalogRuPython()
+ ticket = client.get_ticket(query)
+ ans = check_info(ticket)
+ report_id = data["report"] if 'report' in data else message.message_id - 90
+ await cmd_clear(message, bot, report_id)
+ report = await message.answer(ans, parse_mode="HTML", reply_markup=to_start.as_markup())
+ await state.update_data(report=report.message_id)
+
+ except Exception as e:
+ try:
+ await message.edit_text("Не удалось получить информацию о чеке\nПроверьте введенные данные и попробуйте снова\nЛибо можете сформировать отчёт на основе введённых данных", reply_markup=check_failed.as_markup())
+ except Exception:
+ await message.answer(
+ "Не удалось получить информацию о чеке\nПроверьте введенные данные и попробуйте снова\nЛибо можете сформировать отчёт на основе введённых данных",
+ reply_markup=check_failed.as_markup())
+ print(e)
+ return
+
+
+@expenses_router.callback_query(ExpensesState.load_check, F.data == 'make_report')
+async def make_fake_report(call: CallbackQuery, state: FSMContext, bot: Bot):
+ data = await state.get_data()
+ ans = check_fake_info(data)
+ report_id = data["report"] if 'report' in data else call.message.message_id - 90
+ await cmd_clear(call.message, bot, report_id)
+ try:
+ report = await call.message.edit_text(ans, parse_mode="HTML", reply_markup=to_start.as_markup())
+ await state.update_data(report=report.message_id)
+ except Exception:
+ report = await call.message.answer(ans, parse_mode="HTML", reply_markup=to_start.as_markup())
+ await state.update_data(report=report.message_id)
+
+
+@expenses_router.message(ExpensesState.load_check)
+async def handle_post(message: Message, state: FSMContext):
+ if not message.photo:
+ await message.answer('Неподдерживаемый формат, пожалуйста, отправьте фото')
+ return
+
+ loading = await message.answer('Пожалуйста подождите, идет получение данных о чеке...')
+ check_photo = message.photo[-1]
+ bot = message.bot
+ photo_bytes = io.BytesIO()
+ await bot.download(check_photo.file_id, photo_bytes)
+ photo_bytes.seek(0)
+ nparr = np.frombuffer(photo_bytes.read(), np.uint8)
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
+ reader = QReader()
+ decoded_text = reader.detect_and_decode(image=image)
+
+ if not decoded_text or not decoded_text[0]:
+ try:
+ await message.edit_text("QR-код не распознан.\nМожете заполнить данные о чеке самостоятельно", reply_markup=fill_check.as_markup())
+ except Exception:
+ await bot.delete_message(chat_id=loading.chat.id, message_id=loading.message_id)
+ await message.answer("QR-код не распознан.\nМожете заполнить данные о чеке самостоятельно", reply_markup=fill_check.as_markup())
+ finally:
+ return
+ else:
+ try:
+ client = NalogRuPython()
+ ticket = client.get_ticket(decoded_text[0])
+ ans = check_info(ticket)
+ await bot.delete_message(chat_id=loading.chat.id, message_id=loading.message_id)
+ data = await state.get_data()
+ report_id = data["report"] if 'report' in data else message.message_id - 90
+ await cmd_clear(message, bot, report_id)
+ report = await message.answer(ans, parse_mode="HTML", reply_markup=to_start.as_markup())
+ await state.update_data(report=report.message_id)
+ except Exception as e:
+ try:
+ await message.edit_text("Не удалось получить информацию о чеке\nМожете заполнить данные о чеке самостоятельно", reply_markup=fill_check.as_markup())
+ except Exception:
+ await bot.delete_message(chat_id=loading.chat.id, message_id=loading.message_id)
+ await message.answer("Не удалось получить информацию о чеке\nМожете заполнить данные о чеке самостоятельно", reply_markup=fill_check.as_markup())
+ finally:
+ print(e)
+ return
diff --git a/handlers/get_check_info.py b/handlers/get_check_info.py
new file mode 100644
index 0000000..9b2866a
--- /dev/null
+++ b/handlers/get_check_info.py
@@ -0,0 +1,84 @@
+import os
+import json
+import requests
+from dotenv import load_dotenv
+
+
+class NalogRuPython:
+ HOST = 'irkkt-mobile.nalog.ru:8888'
+ DEVICE_OS = 'iOS'
+ CLIENT_VERSION = '2.9.0'
+ DEVICE_ID = '7C82010F-16CC-446B-8F66-FC4080C66521'
+ ACCEPT = '*/*'
+ USER_AGENT = 'billchecker/2.9.0 (iPhone; iOS 13.6; Scale/2.00)'
+ ACCEPT_LANGUAGE = 'ru-RU;q=1, en-US;q=0.9'
+
+ def __init__(self):
+ load_dotenv()
+ self.__session_id = None
+ self.set_session_id()
+
+ def set_session_id(self) -> None:
+ if os.getenv('CLIENT_SECRET') is None:
+ raise ValueError('OS environments not content "CLIENT_SECRET"')
+ if os.getenv('INN') is None:
+ raise ValueError('OS environments not content "INN"')
+ if os.getenv('PASSWORD') is None:
+ raise ValueError('OS environments not content "PASSWORD"')
+
+ url = f'https://{self.HOST}/v2/mobile/users/lkfl/auth'
+ payload = {
+ 'inn': os.getenv('INN'),
+ 'client_secret': os.getenv('CLIENT_SECRET'),
+ 'password': os.getenv('PASSWORD')
+ }
+ headers = {
+ 'Host': self.HOST,
+ 'Accept': self.ACCEPT,
+ 'Device-OS': self.DEVICE_OS,
+ 'Device-Id': self.DEVICE_ID,
+ 'clientVersion': self.CLIENT_VERSION,
+ 'Accept-Language': self.ACCEPT_LANGUAGE,
+ 'User-Agent': self.USER_AGENT,
+ }
+
+ resp = requests.post(url, json=payload, headers=headers)
+ self.__session_id = resp.json()['sessionId']
+
+ def _get_ticket_id(self, qr: str) -> str:
+ url = f'https://{self.HOST}/v2/ticket'
+ payload = {'qr': qr}
+ headers = {
+ 'Host': self.HOST,
+ 'Accept': self.ACCEPT,
+ 'Device-OS': self.DEVICE_OS,
+ 'Device-Id': self.DEVICE_ID,
+ 'clientVersion': self.CLIENT_VERSION,
+ 'Accept-Language': self.ACCEPT_LANGUAGE,
+ 'sessionId': self.__session_id,
+ 'User-Agent': self.USER_AGENT,
+ }
+ resp = requests.post(url, json=payload, headers=headers)
+ return resp.json()["id"]
+
+ def get_ticket(self, qr: str) -> dict:
+ ticket_id = self._get_ticket_id(qr)
+ url = f'https://{self.HOST}/v2/tickets/{ticket_id}'
+ headers = {
+ 'Host': self.HOST,
+ 'sessionId': self.__session_id,
+ 'Device-OS': self.DEVICE_OS,
+ 'clientVersion': self.CLIENT_VERSION,
+ 'Device-Id': self.DEVICE_ID,
+ 'Accept': self.ACCEPT,
+ 'User-Agent': self.USER_AGENT,
+ 'Accept-Language': self.ACCEPT_LANGUAGE,
+ }
+ resp = requests.get(url, headers=headers)
+ return resp.json()
+
+if __name__ == '__main__':
+ client = NalogRuPython()
+ qr_code = "t=20241101T1545&s=249.00&fn=7284440700462594&i=20521&fp=3844281219&n=1"
+ ticket = client.get_ticket(qr_code)
+ print(json.dumps(ticket, indent=4))
\ No newline at end of file
diff --git a/keyboards/inline.py b/keyboards/inline.py
new file mode 100644
index 0000000..49c8c83
--- /dev/null
+++ b/keyboards/inline.py
@@ -0,0 +1,68 @@
+import datetime
+from aiogram import types
+from typing import Optional, Set
+from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
+from aiogram.utils.keyboard import InlineKeyboardBuilder
+
+
+start_options = InlineKeyboardBuilder()
+start_options.add(
+ InlineKeyboardButton(text="Суточные", callback_data="var1"),
+ InlineKeyboardButton(text="Бензин и прочие расходы по автомобилю", callback_data="var2"),
+)
+start_options.adjust(1, 1)
+
+choose_country = InlineKeyboardBuilder()
+choose_country.add(
+ InlineKeyboardButton(text="РФ", callback_data="russian"),
+ InlineKeyboardButton(text="Не РФ", callback_data="not_russian"),
+ InlineKeyboardButton(text="◀️ Назад", callback_data="daily_back1"),
+)
+choose_country.adjust(1, 1, 1)
+
+expenses = InlineKeyboardBuilder()
+expenses.add(
+ InlineKeyboardButton(text="Бензин", callback_data="ex1"),
+ InlineKeyboardButton(text="Стеклоомыватель", callback_data="ex2"),
+ InlineKeyboardButton(text="Парковка", callback_data="ex3"),
+ InlineKeyboardButton(text="Мойка", callback_data="ex4"),
+ InlineKeyboardButton(text="Прочее", callback_data="ex5"),
+ InlineKeyboardButton(text="◀️ Назад", callback_data="start"),
+)
+expenses.adjust(1, 1, 1, 1, 1, 1)
+
+to_start = InlineKeyboardBuilder()
+to_start.add(types.InlineKeyboardButton(text="◀️ В начало", callback_data="start"))
+
+fill_check = InlineKeyboardBuilder()
+fill_check.add(types.InlineKeyboardButton(text="Заполнить данные", callback_data="fill_check"))
+
+check_failed = InlineKeyboardBuilder()
+check_failed.add(
+ InlineKeyboardButton(text="Заполнить данные", callback_data="fill_check"),
+ InlineKeyboardButton(text="Сформировать отчет", callback_data="make_report"),
+ InlineKeyboardButton(text="◀️ В начало", callback_data="start")
+)
+check_failed.adjust(1, 1, 1)
+
+daily_back1 = InlineKeyboardBuilder()
+daily_back1.add(types.InlineKeyboardButton(text="◀️ Назад", callback_data="start"))
+
+
+input_check_back1 = InlineKeyboardBuilder()
+input_check_back1.add(types.InlineKeyboardButton(text="◀️ Назад", callback_data="input_check_back1"))
+
+input_check_back2 = InlineKeyboardBuilder()
+input_check_back2.add(types.InlineKeyboardButton(text="◀️ Назад", callback_data="input_check_back2"))
+
+input_check_back3 = InlineKeyboardBuilder()
+input_check_back3.add(types.InlineKeyboardButton(text="◀️ Назад", callback_data="input_check_back3"))
+
+input_check_back4 = InlineKeyboardBuilder()
+input_check_back4.add(types.InlineKeyboardButton(text="◀️ Назад", callback_data="input_check_back4"))
+
+input_check_back5 = InlineKeyboardBuilder()
+input_check_back5.add(types.InlineKeyboardButton(text="◀️ Назад", callback_data="input_check_back5"))
+
+input_check_back6 = InlineKeyboardBuilder()
+input_check_back6.add(types.InlineKeyboardButton(text="◀️ Назад", callback_data="input_check_back6"))
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..d724b5a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+aiogram==3.12.0
+python-dotenv==1.0.1
+pillow==11.0.0
+qreader==3.14
+opencv-python~=4.10.0.84
+requests~=2.32.3
+numpy~=2.1.3
\ No newline at end of file