aboutsummaryrefslogtreecommitdiff
path: root/handlers/user/invoices.py
diff options
context:
space:
mode:
authorTolmachev Igor <me@igorek.dev>2026-03-26 16:23:52 +0300
committerTolmachev Igor <me@igorek.dev>2026-03-26 16:23:52 +0300
commit9c905f22de4fa2e2f60ea9d473c14cb075a244e2 (patch)
tree366fa6c5fb5bbe1d29e383effa7df816cadf22a4 /handlers/user/invoices.py
parent4d0f8a48502dfa6bc7e9b39444573fe7377bdfce (diff)
downloadvpn_manager_bot-9c905f22de4fa2e2f60ea9d473c14cb075a244e2.tar.gz
vpn_manager_bot-9c905f22de4fa2e2f60ea9d473c14cb075a244e2.zip
Add invoices command
Diffstat (limited to 'handlers/user/invoices.py')
-rw-r--r--handlers/user/invoices.py179
1 files changed, 179 insertions, 0 deletions
diff --git a/handlers/user/invoices.py b/handlers/user/invoices.py
new file mode 100644
index 0000000..cc071bb
--- /dev/null
+++ b/handlers/user/invoices.py
@@ -0,0 +1,179 @@
1from math import ceil
2
3from aiogram import Bot, Router
4from aiogram.filters import Command
5from aiogram.types import (
6 CallbackQuery,
7 InlineKeyboardButton,
8 InlineKeyboardMarkup,
9 Message,
10)
11from sqlalchemy import select
12from sqlalchemy.ext.asyncio import AsyncSession
13from sqlalchemy.sql.functions import count
14
15from libs.invoice import (
16 InvoiceStatus,
17 get_invoice_payments,
18 get_payment_status,
19)
20from libs.msg import eclipse_text
21from libs.user import mention
22from models import Invoice, PaymentStatus, User
23from models.callback_data import InvoiceItemClb, InvoicePageClb, PayInvoiceClb
24
25router = Router(name="invoices")
26PAGE_SIZE = 5
27PAYMENT_STATUS = {
28 PaymentStatus.PENDING: "🟡",
29 PaymentStatus.ACCEPTED: "🟢",
30 PaymentStatus.REJECTED: "🔴",
31}
32INVOICE_STATUS = {
33 InvoiceStatus.PAID: "🟢",
34 InvoiceStatus.UNPAID: "🔴",
35}
36
37
38def get_text(user: User) -> str:
39 if user.is_admin():
40 return "Выберете счёт для просмотра информации:"
41 else:
42 return "Выберете счёт для оплаты:"
43
44
45async def get_reply_markup(
46 page: int,
47 user: User,
48 session: AsyncSession,
49) -> InlineKeyboardMarkup | None:
50 total = await session.scalar(
51 select(count()).select_from(Invoice).where(Invoice.datetime >= user.datetime)
52 )
53 assert total is not None
54 total_pages = ceil(total / PAGE_SIZE)
55
56 if total == 0:
57 return None
58
59 page = max(0, min(page, total_pages - 1))
60 query = (
61 select(Invoice)
62 .where(Invoice.datetime >= user.datetime)
63 .offset(PAGE_SIZE * page)
64 .limit(PAGE_SIZE)
65 .order_by(Invoice.id.desc())
66 )
67 invoices = await session.scalars(query)
68
69 invoice_buttons = []
70 for i in invoices:
71 if user.is_admin():
72 invoice_payments = await get_invoice_payments(session, i)
73 status = INVOICE_STATUS[invoice_payments.status]
74 callback_data = InvoiceItemClb(page=page, invoice_id=i.id).pack()
75 else:
76 status = PAYMENT_STATUS[await get_payment_status(session, i.id, user.id)]
77 callback_data = PayInvoiceClb(invoice_id=i.id).pack()
78
79 button = InlineKeyboardButton(
80 text=(
81 f"{status} "
82 f"{eclipse_text(i.message.text, 10)} "
83 f"({i.datetime.strftime('%d %b %y г.')})"
84 ),
85 callback_data=callback_data,
86 )
87
88 invoice_buttons.append([button])
89
90 page_buttons = []
91 if page > 0:
92 page_buttons.append(
93 InlineKeyboardButton(
94 text="◀️",
95 callback_data=InvoicePageClb(page=page - 1).pack(),
96 )
97 )
98 if page < total_pages - 1:
99 page_buttons.append(
100 InlineKeyboardButton(
101 text="▶️",
102 callback_data=InvoicePageClb(page=page + 1).pack(),
103 )
104 )
105
106 return InlineKeyboardMarkup(inline_keyboard=[*invoice_buttons, page_buttons])
107
108
109@router.message(Command("invoices"))
110async def command(msg: Message, session: AsyncSession, user: User) -> None:
111 reply_markup = await get_reply_markup(0, user, session)
112
113 if reply_markup is None:
114 await msg.answer("Нету счетов для оплаты.")
115 return
116
117 await msg.answer(get_text(user), reply_markup=reply_markup)
118
119
120@router.callback_query(InvoicePageClb.filter())
121async def page(
122 clb: CallbackQuery,
123 callback_data: InvoicePageClb,
124 session: AsyncSession,
125 user: User,
126) -> None:
127 assert isinstance(clb.message, Message)
128 await clb.message.edit_text(
129 get_text(user),
130 reply_markup=await get_reply_markup(callback_data.page, user, session),
131 )
132
133 await clb.answer()
134
135
136@router.callback_query(InvoiceItemClb.filter())
137async def item(
138 clb: CallbackQuery,
139 bot: Bot,
140 callback_data: InvoiceItemClb,
141 session: AsyncSession,
142 user: User,
143) -> None:
144 assert isinstance(clb.message, Message)
145 if not user.is_admin():
146 await clb.answer("У вас нет прав для данного действия.", show_alert=True)
147 return
148
149 invoice = await session.get(Invoice, callback_data.invoice_id)
150 assert invoice is not None
151 invoice_payments = await get_invoice_payments(session, invoice)
152
153 text_template = (
154 f"Статус оплаты счёта: {INVOICE_STATUS[invoice_payments.status]}\n"
155 "Пользователи оплатившие счёт:\n"
156 "{}"
157 )
158 reply_markup = InlineKeyboardMarkup(
159 inline_keyboard=[
160 [
161 InlineKeyboardButton(
162 text="Назад к выбору",
163 callback_data=InvoicePageClb(page=callback_data.page).pack(),
164 )
165 ]
166 ]
167 )
168
169 await clb.message.edit_text(text_template.format("..."), reply_markup=reply_markup)
170 user_status = []
171 for user_id, s in invoice_payments.user_status.items():
172 chat = await bot.get_chat(user_id)
173 user_status.append(f"{PAYMENT_STATUS[s]} - {mention(chat)}")
174 await clb.message.edit_text(
175 text_template.format("\n".join(user_status)),
176 reply_markup=reply_markup,
177 )
178
179 await clb.answer()