init commit
This commit is contained in:
parent
5bddffeae7
commit
089ecbba6d
0
Dockerfile
Normal file
0
Dockerfile
Normal file
68
bot.py
Normal file
68
bot.py
Normal file
@ -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())
|
6
config.py
Normal file
6
config.py
Normal file
@ -0,0 +1,6 @@
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
TOKEN = os.getenv('TOKEN')
|
0
docker-compose.yml
Normal file
0
docker-compose.yml
Normal file
68
handlers/daily_type.py
Normal file
68
handlers/daily_type.py
Normal file
@ -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'<u>Cумма к добавлению в расчет:</u><b> {k * days}</b> ₽', parse_mode="HTML", reply_markup=to_start.as_markup())
|
430
handlers/expenses.py
Normal file
430
handlers/expenses.py
Normal file
@ -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"<b>Статус отчёта:</b> подтверждён ✅\n"
|
||||
f"<b>Дата чека:</b> {data['date']}\n"
|
||||
f"<b>ФД:</b> {data['fd']}\n"
|
||||
f"<b>Итого:</b> {data['total']}\n"
|
||||
f"<b>НДС:</b> {data['nds']}\n"
|
||||
f"<b>Сумма без НДС:</b> {data['sum']}\n"
|
||||
)
|
||||
|
||||
return formatted_text
|
||||
|
||||
|
||||
def check_fake_info(data):
|
||||
formatted_text = (
|
||||
f"<b>Статус отчёта:</b> не подтвержден ❌\n"
|
||||
f"<b>Дата чека:</b> {data['date']} {data['time']}\n"
|
||||
f"<b>ФД:</b> {data['fd']}\n"
|
||||
f"<b>Итого:</b> {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
|
84
handlers/get_check_info.py
Normal file
84
handlers/get_check_info.py
Normal file
@ -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))
|
68
keyboards/inline.py
Normal file
68
keyboards/inline.py
Normal file
@ -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"))
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user