From 29504c55029e968499c1b25b59b1e2c2b5601175 Mon Sep 17 00:00:00 2001 From: kolo Date: Sat, 28 Feb 2026 11:48:31 +0300 Subject: [PATCH] update --- .../admin_dialogs/admin_menu_dialog.py | 229 +++++++++++++++++- .../bot/user_dialogs/main_menu_dialog.py | 9 +- .../application/bot/user_dialogs/states.py | 4 + .../hours_transactions_repository.py | 27 +++ 4 files changed, 255 insertions(+), 14 deletions(-) diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py index c85fd2a..0aa6f33 100644 --- a/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py @@ -18,6 +18,9 @@ from dutylog.infrastructure.database.repositories.residents_repository import ( from dutylog.infrastructure.database.repositories.rooms_repository import ( RoomsRepository, ) +from dutylog.infrastructure.database.repositories.floors_repository import ( + FloorsRepository, +) from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( HoursTransactionsRepository, ) @@ -84,8 +87,8 @@ async def get_statistics_data( ━━━━━━━━━━━━━━━━━━━━ -🟢 Всего активных часов: {total_active_hours} ч -🔴 Всего неактивных часов: {total_inactive_hours} ч +🟢 Всего отработанных часов: {total_inactive_hours} ч +🔴 Всего неотработанных часов: {total_active_hours} ч 📊 Общий итог: {total_active_hours + total_inactive_hours} ч """ @@ -244,9 +247,8 @@ async def get_resident_info_data( ━━━━━━━━━━━━━━━━━━━━ -🟢 Активные часы: {resident.active_hours} ч -🔴 Неактивные часы: {resident.inactive_hours} ч -📊 Всего часов: {resident.active_hours + resident.inactive_hours} ч +🟢 Отработанные часы: {resident.inactive_hours} ч +🔴 Неотработанные часы: {resident.active_hours} ч """ return { @@ -270,7 +272,108 @@ async def on_add_resident( button: Button, dialog_manager: DialogManager, ): - await callback.answer("⚠️ Функционал в разработке", show_alert=True) + await dialog_manager.switch_to(AdminMenuSG.create_resident_name) + + +async def on_resident_name_input( + message: Message, + widget: MessageInput, + dialog_manager: DialogManager, +): + if not message.text or len(message.text.strip()) < 2: + await message.answer("⚠️ Пожалуйста, введите корректное имя и фамилию") + return + + dialog_manager.dialog_data["new_resident_name"] = message.text.strip() + await dialog_manager.switch_to(AdminMenuSG.create_resident_floor) + + +@inject +async def get_create_resident_floors_data( + floors_repository: FromDishka[FloorsRepository], + **kwargs, +): + all_floors = await floors_repository.get_all_floors() + all_floors.sort(key=lambda f: f.number) + + return { + "floors": [(f.id, f"Этаж {f.number}") for f in all_floors], + } + + +async def on_create_resident_floor_selected( + callback: CallbackQuery, + widget: Select, + dialog_manager: DialogManager, + item_id: str, +): + dialog_manager.dialog_data["new_resident_floor_id"] = int(item_id) + await dialog_manager.switch_to(AdminMenuSG.create_resident_room) + + +@inject +async def get_create_resident_rooms_data( + dialog_manager: DialogManager, + rooms_repository: FromDishka[RoomsRepository], + **kwargs, +): + floor_id = dialog_manager.dialog_data.get("new_resident_floor_id") + + if not floor_id: + return {"rooms": []} + + rooms = await rooms_repository.get_rooms_by_floor(floor_id) + rooms.sort(key=lambda r: r.number) + + return { + "rooms": [(r.id, str(r.number)) for r in rooms], + } + + +async def on_create_resident_room_selected( + callback: CallbackQuery, + widget: Select, + dialog_manager: DialogManager, + item_id: str, +): + dialog_manager.dialog_data["new_resident_room_id"] = int(item_id) + await dialog_manager.switch_to(AdminMenuSG.create_resident_confirm) + + +async def get_create_resident_confirm_data( + dialog_manager: DialogManager, + **kwargs, +): + name = dialog_manager.dialog_data.get("new_resident_name", "???") + return {"resident_name": name} + + +@inject +async def on_create_resident_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + residents_repository: FromDishka[ResidentsRepository], +): + name = dialog_manager.dialog_data.get("new_resident_name") + room_id = dialog_manager.dialog_data.get("new_resident_room_id") + + if name and room_id: + await residents_repository.create_resident( + room_id=room_id, + real_name=name, + ) + await callback.answer("✅ Резидент создан!") + + await dialog_manager.switch_to(AdminMenuSG.residents) + + +async def on_create_resident_cancel( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.residents) async def on_filter_residents( @@ -289,6 +392,22 @@ async def on_search_residents( await callback.answer("⚠️ Функционал в разработке", show_alert=True) +async def on_rooms_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await callback.answer("⚠️ Функционал в разработке", show_alert=True) + + +async def on_floors_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await callback.answer("⚠️ Функционал в разработке", show_alert=True) + + @inject async def on_logout_resident_confirm( callback: CallbackQuery, @@ -408,6 +527,7 @@ async def on_add_hours_confirm( admin_id = callback.from_user.id if resident_id and hours: + # Добавляем часы к неотработанным (active_hours) await transactions_repository.add_hours( resident_id=resident_id, amount=hours, @@ -424,17 +544,27 @@ async def on_remove_hours_confirm( button: Button, dialog_manager: DialogManager, transactions_repository: FromDishka[HoursTransactionsRepository], + residents_repository: FromDishka[ResidentsRepository], ): resident_id = dialog_manager.dialog_data.get("selected_resident_id") hours = dialog_manager.dialog_data.get("selected_hours") admin_id = callback.from_user.id if resident_id and hours: - await transactions_repository.remove_hours( + resident = await residents_repository.get_resident_by_id(resident_id) + if resident and resident.active_hours < hours: + await callback.answer( + f"⚠️ Недостаточно часов! У резидента {resident.active_hours} неотработанных ч, а вы пытаетесь отнять {hours} ч", + show_alert=True + ) + await dialog_manager.switch_to(AdminMenuSG.resident_info) + return + + # Перемещаем часы из неотработанных в отработанные + await transactions_repository.move_hours_to_completed( resident_id=resident_id, amount=hours, admin_id=admin_id, - is_active=True, ) await dialog_manager.switch_to(AdminMenuSG.resident_info) @@ -456,6 +586,18 @@ admin_menu_dialog = Dialog( id="residents_btn", state=AdminMenuSG.residents, ), + Row( + Button( + Const("🚪 Комнаты"), + id="rooms_btn", + on_click=on_rooms_click, + ), + Button( + Const("🏢 Этажи"), + id="floors_btn", + on_click=on_floors_click, + ), + ), SwitchTo( Const("📊 Статистика"), id="stats_btn", @@ -512,12 +654,12 @@ admin_menu_dialog = Dialog( Format("{info_content}"), Row( Button( - Const("➕ Добавить часы"), + Const("Добавить часы"), id="add_hours_btn", on_click=on_add_hours_click, ), Button( - Const("➖ Отнять часы"), + Const("Отнять часы"), id="remove_hours_btn", on_click=on_remove_hours_click, ), @@ -658,6 +800,73 @@ admin_menu_dialog = Dialog( state=AdminMenuSG.remove_hours_confirm, getter=get_hours_confirm_data, ), + Window( + Const("
Создание резидента
\n\nВведите имя и фамилию резидента:"), + MessageInput(on_resident_name_input), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_create_resident_name", + state=AdminMenuSG.residents, + ), + state=AdminMenuSG.create_resident_name, + ), + Window( + Const("
🏢 Выбор этажа
\n\nВыберите этаж для нового резидента:"), + Group( + Select( + Format("{item[1]}"), + id="create_resident_floor_select", + item_id_getter=lambda x: x[0], + items="floors", + on_click=on_create_resident_floor_selected, + ), + width=2, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_create_name", + state=AdminMenuSG.create_resident_name, + ), + state=AdminMenuSG.create_resident_floor, + getter=get_create_resident_floors_data, + ), + Window( + Const("
🚪 Выбор комнаты
\n\nВыберите комнату для нового резидента:"), + Group( + Select( + Format("{item[1]}"), + id="create_resident_room_select", + item_id_getter=lambda x: x[0], + items="rooms", + on_click=on_create_resident_room_selected, + ), + width=3, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_create_floor", + state=AdminMenuSG.create_resident_floor, + ), + state=AdminMenuSG.create_resident_room, + getter=get_create_resident_rooms_data, + ), + Window( + Format("
Подтверждение
\n\nСоздать резидента {resident_name}?"), + Row( + Button( + Const("✅ Да"), + id="confirm_create_resident", + on_click=on_create_resident_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_create_resident", + on_click=on_create_resident_cancel, + ), + ), + state=AdminMenuSG.create_resident_confirm, + getter=get_create_resident_confirm_data, + ), Window( Format("{stats_content}"), SwitchTo(Const("◀️ Назад"), id="back_from_stats", state=AdminMenuSG.main), diff --git a/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py b/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py index 7f84eb2..89c6f22 100644 --- a/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py +++ b/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py @@ -81,9 +81,9 @@ async def get_main_menu_data( ⏰ Ваши часы дежурств -
🟢 Отработанные часы: {resident.active_hours} ч +
🟢 Отработанные часы: {resident.inactive_hours} ч ━━━━━━━━━━━━━━━━ -🔴 Неотработанные часы: {resident.inactive_hours} ч
+🔴 Неотработанные часы: {resident.active_hours} ч
""" has_resident = True else: @@ -120,7 +120,8 @@ async def get_history_data( """ else: transactions = await transactions_repository.get_resident_history(resident.id) - last_10 = transactions[:10] + transactions_sorted = sorted(transactions, key=lambda x: x.created_at) + last_10 = transactions_sorted[:10] if not last_10: history_text = """ @@ -131,7 +132,7 @@ async def get_history_data( else: history_lines = [] for tx in last_10: - emoji = "➕" if tx.transaction_type == "increase" else "➖" + emoji = "+" if tx.transaction_type == "increase" else "-" date_str = tx.created_at.strftime("%d.%m.%Y %H:%M") history_lines.append( f"{emoji} {tx.amount} ч • {date_str}" diff --git a/src/dutylog/application/bot/user_dialogs/states.py b/src/dutylog/application/bot/user_dialogs/states.py index 8e54ad2..4e5dce7 100644 --- a/src/dutylog/application/bot/user_dialogs/states.py +++ b/src/dutylog/application/bot/user_dialogs/states.py @@ -18,6 +18,10 @@ class AdminMenuSG(StatesGroup): remove_hours_custom = State() add_hours_confirm = State() remove_hours_confirm = State() + create_resident_name = State() + create_resident_floor = State() + create_resident_room = State() + create_resident_confirm = State() statistics = State() broadcast = State() broadcast_confirm = State() diff --git a/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py b/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py index 13561ec..d9a2abb 100644 --- a/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py +++ b/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py @@ -78,6 +78,33 @@ class HoursTransactionsRepository: return transaction, resident + async def move_hours_to_completed( + self, + resident_id: int, + amount: int, + admin_id: int | None = None, + ) -> tuple[HoursTransaction, Resident | None]: + """Перемещает часы из неотработанных в отработанные""" + transaction = HoursTransaction( + resident_id=resident_id, + transaction_type=TransactionType.DECREASE.value, + amount=amount, + admin_id=admin_id, + ) + transaction = await self.transactions_dao.create(transaction) + + resident = await self.residents_dao.get_by_id(resident_id) + if resident: + new_active = max(0, resident.active_hours - amount) + new_inactive = resident.inactive_hours + amount + resident = await self.residents_dao.update( + resident_id, + active_hours=new_active, + inactive_hours=new_inactive + ) + + return transaction, resident + async def get_resident_history(self, resident_id: int) -> list[HoursTransaction]: return await self.transactions_dao.get_by_resident_id(resident_id)