diff --git a/src/dutylog/application/bot/admin_dialogs/admin_menu_dialog.py b/src/dutylog/application/bot/admin_dialogs/admin_menu_dialog.py index 5a3494f..c7872a8 100644 --- a/src/dutylog/application/bot/admin_dialogs/admin_menu_dialog.py +++ b/src/dutylog/application/bot/admin_dialogs/admin_menu_dialog.py @@ -33,7 +33,16 @@ from dutylog.application.bot.admin_dialogs.floors_management import ( from dutylog.application.bot.admin_dialogs.rooms_management import ( rooms_select_floor_window, rooms_list_window, + room_info_window, room_delete_confirm_window, + room_add_hours_select_window, + room_remove_hours_select_window, + room_add_hours_custom_window, + room_remove_hours_custom_window, + room_add_hours_remark_window, + room_remove_hours_remark_window, + room_add_hours_confirm_window, + room_remove_hours_confirm_window, create_room_select_floor_window, create_room_input_window, create_room_confirm_window, @@ -90,7 +99,16 @@ admin_menu_dialog = Dialog( create_floor_confirm_window, rooms_select_floor_window, rooms_list_window, + room_info_window, room_delete_confirm_window, + room_add_hours_select_window, + room_remove_hours_select_window, + room_add_hours_custom_window, + room_remove_hours_custom_window, + room_add_hours_remark_window, + room_remove_hours_remark_window, + room_add_hours_confirm_window, + room_remove_hours_confirm_window, create_room_select_floor_window, create_room_input_window, create_room_confirm_window, diff --git a/src/dutylog/application/bot/admin_dialogs/residents_management.py b/src/dutylog/application/bot/admin_dialogs/residents_management.py index 86e7039..e7d8b7d 100644 --- a/src/dutylog/application/bot/admin_dialogs/residents_management.py +++ b/src/dutylog/application/bot/admin_dialogs/residents_management.py @@ -111,8 +111,6 @@ async def get_resident_info_data( Статус: {status} Пользователь: {user_info} -━━━━━━━━━━━━━━━━━━━━ - 🟢 Отработанные часы: {resident.inactive_hours} ч 🔴 Неотработанные часы: {resident.active_hours} ч """ diff --git a/src/dutylog/application/bot/admin_dialogs/rooms_management.py b/src/dutylog/application/bot/admin_dialogs/rooms_management.py index 9d514d9..10bbf07 100644 --- a/src/dutylog/application/bot/admin_dialogs/rooms_management.py +++ b/src/dutylog/application/bot/admin_dialogs/rooms_management.py @@ -13,6 +13,15 @@ from dutylog.infrastructure.database.repositories.rooms_repository import ( from dutylog.infrastructure.database.repositories.floors_repository import ( FloorsRepository, ) +from dutylog.infrastructure.database.repositories.residents_repository import ( + ResidentsRepository, +) +from dutylog.infrastructure.database.repositories.room_hours_transactions_repository import ( + RoomHoursTransactionsRepository, +) +from dutylog.infrastructure.database.repositories.users_repository import ( + UsersRepository, +) async def on_rooms_click( @@ -84,7 +93,7 @@ async def get_rooms_list_data( Всего комнат: {len(rooms)} -Выберите комнату для удаления: +Выберите комнату для просмотра: """ return { @@ -101,7 +110,7 @@ async def on_room_selected( item_id: str, ): dialog_manager.dialog_data["selected_room_id"] = int(item_id) - await dialog_manager.switch_to(AdminMenuSG.room_delete_confirm) + await dialog_manager.switch_to(AdminMenuSG.room_info) async def on_add_room_click( @@ -207,6 +216,282 @@ async def on_create_room_cancel( await dialog_manager.switch_to(AdminMenuSG.rooms_select_floor) +@inject +async def get_room_info_data( + dialog_manager: DialogManager, + rooms_repository: FromDishka[RoomsRepository], + floors_repository: FromDishka[FloorsRepository], + residents_repository: FromDishka[ResidentsRepository], + **kwargs, +): + room_id = dialog_manager.dialog_data.get("selected_room_id") + + if not room_id: + return {"info_content": "Ошибка: комната не выбрана"} + + room = await rooms_repository.get_room_by_id(room_id) + + if not room: + return {"info_content": "Ошибка: комната не найдена"} + + floor = await floors_repository.get_floor_by_id(room.on_floor) + floor_number = floor.number if floor else "???" + + # Получаем резидентов комнаты + residents = await residents_repository.get_residents_by_room(room_id) + + residents_info = "" + if residents: + residents_info = "\nПроживающие:\n" + for resident in residents: + status = "🟢" if resident.is_busy else "⚪️" + name = resident.real_name if resident.real_name else "Без имени" + residents_info += f"{status} {name}\n" + else: + residents_info = "\nНет проживающих\n" + + info_content = f""" +
🚪 Информация о комнате
+ +Номер: {room.number} +Этаж: {floor_number} +{residents_info} +🟢 Отработанные часы: {room.inactive_hours} ч +🔴 Неотработанные часы: {room.active_hours} ч +""" + + return { + "info_content": info_content, + "has_residents": len(residents) > 0, + } + + +async def on_room_add_hours_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.room_add_hours_select) + + +async def on_room_remove_hours_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.room_remove_hours_select) + + +async def get_room_hours_select_data(**kwargs): + hours_options = [ + (5, "5"), (10, "10"), (15, "15"), (20, "20"), + (25, "25"), (30, "30"), (35, "35"), (40, "40"), + (45, "45"), (50, "50"), (55, "55"), (60, "60"), + (65, "65"), (70, "70"), (75, "75"), (80, "80"), + ] + return {"hours_options": hours_options} + + +async def on_room_hours_selected( + callback: CallbackQuery, + widget: Select, + dialog_manager: DialogManager, + item_id: str, +): + dialog_manager.dialog_data["selected_hours"] = int(item_id) + + if dialog_manager.current_context().state == AdminMenuSG.room_add_hours_select: + await dialog_manager.switch_to(AdminMenuSG.room_add_hours_remark) + else: + await dialog_manager.switch_to(AdminMenuSG.room_remove_hours_remark) + + +async def on_room_custom_hours_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + if dialog_manager.current_context().state == AdminMenuSG.room_add_hours_select: + await dialog_manager.switch_to(AdminMenuSG.room_add_hours_custom) + else: + await dialog_manager.switch_to(AdminMenuSG.room_remove_hours_custom) + + +async def on_room_custom_hours_input( + message: Message, + widget: MessageInput, + dialog_manager: DialogManager, +): + if not message.text: + await message.answer("⚠️ Пожалуйста, введите число") + return + + try: + hours = int(message.text) + if hours <= 0: + await message.answer("⚠️ Количество часов должно быть положительным числом") + return + + dialog_manager.dialog_data["selected_hours"] = hours + + if dialog_manager.current_context().state == AdminMenuSG.room_add_hours_custom: + await dialog_manager.switch_to(AdminMenuSG.room_add_hours_remark) + else: + await dialog_manager.switch_to(AdminMenuSG.room_remove_hours_remark) + except ValueError: + await message.answer("⚠️ Пожалуйста, введите корректное число") + + +async def on_room_remark_input( + message: Message, + widget: MessageInput, + dialog_manager: DialogManager, +): + if message.text and message.text.strip(): + dialog_manager.dialog_data["remark"] = message.text.strip() + else: + dialog_manager.dialog_data["remark"] = None + + if dialog_manager.current_context().state == AdminMenuSG.room_add_hours_remark: + await dialog_manager.switch_to(AdminMenuSG.room_add_hours_confirm) + else: + await dialog_manager.switch_to(AdminMenuSG.room_remove_hours_confirm) + + +async def on_room_skip_remark( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + dialog_manager.dialog_data["remark"] = None + + if dialog_manager.current_context().state == AdminMenuSG.room_add_hours_remark: + await dialog_manager.switch_to(AdminMenuSG.room_add_hours_confirm) + else: + await dialog_manager.switch_to(AdminMenuSG.room_remove_hours_confirm) + + +async def get_room_hours_confirm_data( + dialog_manager: DialogManager, + **kwargs, +): + hours = dialog_manager.dialog_data.get("selected_hours", 0) + remark = dialog_manager.dialog_data.get("remark", "") + remark_text = f"\n\nПримечание: {remark}" if remark else "" + + return { + "hours": hours, + "remark_text": remark_text, + } + + +@inject +async def on_room_add_hours_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + room_transactions_repository: FromDishka[RoomHoursTransactionsRepository], + residents_repository: FromDishka[ResidentsRepository], + users_repository: FromDishka[UsersRepository], + **kwargs, +): + from aiogram import Bot + bot: Bot = dialog_manager.middleware_data.get("bot") + + room_id = dialog_manager.dialog_data.get("selected_room_id") + hours = dialog_manager.dialog_data.get("selected_hours") + remark = dialog_manager.dialog_data.get("remark") + admin_id = callback.from_user.id + + if room_id and hours: + transaction, _ = await room_transactions_repository.add_hours( + room_id=room_id, + amount=hours, + admin_id=admin_id, + is_active=True, + ) + + # Отправляем уведомления всем проживающим + residents = await residents_repository.get_residents_by_room(room_id) + for resident in residents: + if resident.user_entity: + user = await users_repository.get_user_by_id(resident.user_entity) + if user: + try: + remark_text = f"\n💬 {remark}" if remark else "" + await bot.send_message( + user.id, + f"
📢 Уведомление
\n\n" + f"Вашей комнате начислено +{hours} ч{remark_text}" + ) + except Exception: + pass + + await dialog_manager.switch_to(AdminMenuSG.room_info) + + +@inject +async def on_room_remove_hours_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + room_transactions_repository: FromDishka[RoomHoursTransactionsRepository], + rooms_repository: FromDishka[RoomsRepository], + residents_repository: FromDishka[ResidentsRepository], + users_repository: FromDishka[UsersRepository], + **kwargs, +): + from aiogram import Bot + bot: Bot = dialog_manager.middleware_data.get("bot") + + room_id = dialog_manager.dialog_data.get("selected_room_id") + hours = dialog_manager.dialog_data.get("selected_hours") + remark = dialog_manager.dialog_data.get("remark") + admin_id = callback.from_user.id + + if room_id and hours: + room = await rooms_repository.get_room_by_id(room_id) + if room and room.active_hours < hours: + await callback.answer( + f"⚠️ Недостаточно часов! У комнаты {room.active_hours} неотработанных ч, а вы пытаетесь отнять {hours} ч", + show_alert=True + ) + await dialog_manager.switch_to(AdminMenuSG.room_info) + return + + await room_transactions_repository.move_hours_to_completed( + room_id=room_id, + amount=hours, + admin_id=admin_id, + ) + + # Отправляем уведомления всем проживающим + residents = await residents_repository.get_residents_by_room(room_id) + for resident in residents: + if resident.user_entity: + user = await users_repository.get_user_by_id(resident.user_entity) + if user: + try: + remark_text = f"\n💬 {remark}" if remark else "" + await bot.send_message( + user.id, + f"
📢 Уведомление
\n\n" + f"С вашей комнаты списано -{hours} ч{remark_text}" + ) + except Exception: + pass + + await dialog_manager.switch_to(AdminMenuSG.room_info) + + +async def on_room_hours_cancel( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.room_info) + + @inject async def get_room_delete_confirm_data( dialog_manager: DialogManager, @@ -245,7 +530,7 @@ async def on_delete_room_cancel( button: Button, dialog_manager: DialogManager, ): - await dialog_manager.switch_to(AdminMenuSG.rooms_list) + await dialog_manager.switch_to(AdminMenuSG.room_info) rooms_select_floor_window = Window( @@ -297,6 +582,34 @@ rooms_list_window = Window( getter=get_rooms_list_data, ) +room_info_window = Window( + Format("{info_content}"), + Row( + Button( + Const("Добавить часы"), + id="room_add_hours_btn", + on_click=on_room_add_hours_click, + ), + Button( + Const("Отнять часы"), + id="room_remove_hours_btn", + on_click=on_room_remove_hours_click, + ), + ), + Button( + Const("🗑 Удалить комнату"), + id="delete_room_btn", + on_click=lambda c, b, m: m.switch_to(AdminMenuSG.room_delete_confirm), + ), + SwitchTo( + Const("◀️ Назад к списку"), + id="back_to_rooms_list", + state=AdminMenuSG.rooms_list, + ), + state=AdminMenuSG.room_info, + getter=get_room_info_data, +) + room_delete_confirm_window = Window( Format("
⚠️ Подтверждение удаления
\n\nВы точно хотите удалить комнату {room_number}?\nЭто действие необратимо и удалит всех резидентов в этой комнате!"), Row( @@ -315,6 +628,148 @@ room_delete_confirm_window = Window( getter=get_room_delete_confirm_data, ) +room_add_hours_select_window = Window( + Const("
Добавить часы комнате
\n\nВыберите количество часов:"), + Group( + Select( + Format("{item[1]} ч"), + id="room_hours_select_add", + item_id_getter=lambda x: x[0], + items="hours_options", + on_click=on_room_hours_selected, + ), + width=4, + ), + Button( + Const("✏️ Ввести свое количество"), + id="room_custom_hours_add_btn", + on_click=on_room_custom_hours_click, + ), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_room_add_hours", + state=AdminMenuSG.room_info, + ), + state=AdminMenuSG.room_add_hours_select, + getter=get_room_hours_select_data, +) + +room_remove_hours_select_window = Window( + Const("
Отнять часы у комнаты
\n\nВыберите количество часов:"), + Group( + Select( + Format("{item[1]} ч"), + id="room_hours_select_remove", + item_id_getter=lambda x: x[0], + items="hours_options", + on_click=on_room_hours_selected, + ), + width=4, + ), + Button( + Const("✏️ Ввести свое количество"), + id="room_custom_hours_remove_btn", + on_click=on_room_custom_hours_click, + ), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_room_remove_hours", + state=AdminMenuSG.room_info, + ), + state=AdminMenuSG.room_remove_hours_select, + getter=get_room_hours_select_data, +) + +room_add_hours_custom_window = Window( + Const("
✏️ Добавить часы
\n\nВведите количество часов:"), + MessageInput(on_room_custom_hours_input), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_room_custom_add", + state=AdminMenuSG.room_add_hours_select, + ), + state=AdminMenuSG.room_add_hours_custom, +) + +room_remove_hours_custom_window = Window( + Const("
✏️ Отнять часы
\n\nВведите количество часов:"), + MessageInput(on_room_custom_hours_input), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_room_custom_remove", + state=AdminMenuSG.room_remove_hours_select, + ), + state=AdminMenuSG.room_remove_hours_custom, +) + +room_add_hours_remark_window = Window( + Const("
💬 Примечание
\n\nВведите примечание к операции (или пропустите):"), + MessageInput(on_room_remark_input), + Button( + Const("⏭ Пропустить"), + id="skip_room_add_remark", + on_click=on_room_skip_remark, + ), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_room_add_remark", + state=AdminMenuSG.room_info, + ), + state=AdminMenuSG.room_add_hours_remark, +) + +room_remove_hours_remark_window = Window( + Const("
💬 Примечание
\n\nВведите примечание к операции (или пропустите):"), + MessageInput(on_room_remark_input), + Button( + Const("⏭ Пропустить"), + id="skip_room_remove_remark", + on_click=on_room_skip_remark, + ), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_room_remove_remark", + state=AdminMenuSG.room_info, + ), + state=AdminMenuSG.room_remove_hours_remark, +) + +room_add_hours_confirm_window = Window( + Format("
Подтверждение
\n\nВы уверены, что хотите добавить {hours} часов?{remark_text}"), + Row( + Button( + Const("✅ Да"), + id="confirm_room_add_hours", + on_click=on_room_add_hours_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_room_add_hours_confirm", + on_click=on_room_hours_cancel, + ), + ), + state=AdminMenuSG.room_add_hours_confirm, + getter=get_room_hours_confirm_data, +) + +room_remove_hours_confirm_window = Window( + Format("
Подтверждение
\n\nВы уверены, что хотите отнять {hours} часов?{remark_text}"), + Row( + Button( + Const("✅ Да"), + id="confirm_room_remove_hours", + on_click=on_room_remove_hours_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_room_remove_hours_confirm", + on_click=on_room_hours_cancel, + ), + ), + state=AdminMenuSG.room_remove_hours_confirm, + getter=get_room_hours_confirm_data, +) + create_room_select_floor_window = Window( Format("{content}"), Group( diff --git a/src/dutylog/application/bot/user_dialogs/states.py b/src/dutylog/application/bot/user_dialogs/states.py index 882be5f..0e60b90 100644 --- a/src/dutylog/application/bot/user_dialogs/states.py +++ b/src/dutylog/application/bot/user_dialogs/states.py @@ -40,7 +40,16 @@ class AdminMenuSG(StatesGroup): create_floor_confirm = State() rooms_select_floor = State() rooms_list = State() + room_info = State() room_delete_confirm = State() + room_add_hours_select = State() + room_remove_hours_select = State() + room_add_hours_custom = State() + room_remove_hours_custom = State() + room_add_hours_remark = State() + room_add_hours_confirm = State() + room_remove_hours_remark = State() + room_remove_hours_confirm = State() create_room_select_floor = State() create_room_input = State() create_room_confirm = State()