from aiogram.types import Message, CallbackQuery from aiogram_dialog import Window, DialogManager from aiogram_dialog.widgets.text import Format, Const from aiogram_dialog.widgets.kbd import SwitchTo, Button, ScrollingGroup, Select, Row, Group from aiogram_dialog.widgets.input import MessageInput from dishka import FromDishka from dishka.integrations.aiogram_dialog import inject from dutylog.application.bot.user_dialogs.states import AdminMenuSG from dutylog.infrastructure.database.repositories.rooms_repository import ( RoomsRepository, ) 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, ) from dutylog.infrastructure.utils.datetime import msk_now async def on_rooms_click( callback: CallbackQuery, button: Button, dialog_manager: DialogManager, ): await dialog_manager.switch_to(AdminMenuSG.rooms_select_floor) @inject async def get_rooms_floors_data( floors_repository: FromDishka[FloorsRepository], **kwargs, ): all_floors = await floors_repository.get_all_floors() all_floors.sort(key=lambda f: f.number) floors_data = [(f"🏢 Этаж {f.number}", f.id) for f in all_floors] content = """
🚪 Комнаты
Выберите этаж для просмотра комнат: """ return { "content": content, "floors": floors_data, } async def on_rooms_floor_selected( callback: CallbackQuery, widget: Select, dialog_manager: DialogManager, item_id: str, ): dialog_manager.dialog_data["selected_floor_id"] = int(item_id) await dialog_manager.switch_to(AdminMenuSG.rooms_list) @inject async def get_rooms_list_data( dialog_manager: DialogManager, rooms_repository: FromDishka[RoomsRepository], floors_repository: FromDishka[FloorsRepository], **kwargs, ): floor_id = dialog_manager.dialog_data.get("selected_floor_id") if not floor_id: return { "content": "Ошибка: этаж не выбран", "rooms": [], "floor_number": "???", } floor = await floors_repository.get_floor_by_id(floor_id) floor_number = floor.number if floor else "???" rooms = await rooms_repository.get_rooms_by_floor(floor_id) rooms.sort(key=lambda r: r.number) rooms_data = [(f"🚪 Комната {r.number}", r.id) for r in rooms] content = f"""
🚪 Комнаты на этаже {floor_number}
Всего комнат: {len(rooms)} Выберите комнату для просмотра: """ return { "content": content, "rooms": rooms_data, "floor_number": floor_number, } async def on_room_selected( callback: CallbackQuery, widget: Select, dialog_manager: DialogManager, item_id: str, ): dialog_manager.dialog_data["selected_room_id"] = int(item_id) await dialog_manager.switch_to(AdminMenuSG.room_info) async def on_add_room_click( callback: CallbackQuery, button: Button, dialog_manager: DialogManager, ): await dialog_manager.switch_to(AdminMenuSG.create_room_select_floor) @inject async def get_create_room_floors_data( floors_repository: FromDishka[FloorsRepository], **kwargs, ): all_floors = await floors_repository.get_all_floors() all_floors.sort(key=lambda f: f.number) floors_data = [(f"🏢 Этаж {f.number}", f.id) for f in all_floors] content = """
Создание комнаты
Выберите этаж для новой комнаты: """ return { "content": content, "floors": floors_data, } async def on_create_room_floor_selected( callback: CallbackQuery, widget: Select, dialog_manager: DialogManager, item_id: str, ): dialog_manager.dialog_data["new_room_floor_id"] = int(item_id) await dialog_manager.switch_to(AdminMenuSG.create_room_input) async def on_room_number_input( message: Message, widget: MessageInput, dialog_manager: DialogManager, ): if not message.text: await message.answer("⚠️ Пожалуйста, введите номер комнаты") return try: room_number = int(message.text) if room_number <= 0: await message.answer("⚠️ Номер комнаты должен быть положительным числом") return dialog_manager.dialog_data["new_room_number"] = room_number await dialog_manager.switch_to(AdminMenuSG.create_room_confirm) except ValueError: await message.answer("⚠️ Пожалуйста, введите корректное число") async def get_create_room_confirm_data( dialog_manager: DialogManager, **kwargs, ): room_number = dialog_manager.dialog_data.get("new_room_number", "???") return {"room_number": room_number} @inject async def on_create_room_confirm( callback: CallbackQuery, button: Button, dialog_manager: DialogManager, rooms_repository: FromDishka[RoomsRepository], ): room_number = dialog_manager.dialog_data.get("new_room_number") floor_id = dialog_manager.dialog_data.get("new_room_floor_id") if room_number and floor_id: existing_room = await rooms_repository.get_room_by_number(room_number) if existing_room: await callback.answer( f"⚠️ Комната {room_number} уже существует!", show_alert=True ) await dialog_manager.switch_to(AdminMenuSG.rooms_select_floor) return await rooms_repository.create_room(room_number, floor_id) await callback.answer("✅ Комната создана!") await dialog_manager.switch_to(AdminMenuSG.rooms_select_floor) async def on_create_room_cancel( callback: CallbackQuery, button: Button, dialog_manager: DialogManager, ): 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, rooms_repository: FromDishka[RoomsRepository], **kwargs, ): room_id = dialog_manager.dialog_data.get("selected_room_id") if not room_id: return {"room_number": "???"} room = await rooms_repository.get_room_by_id(room_id) room_number = room.number if room else "???" return {"room_number": room_number} @inject async def on_delete_room_confirm( callback: CallbackQuery, button: Button, dialog_manager: DialogManager, rooms_repository: FromDishka[RoomsRepository], ): room_id = dialog_manager.dialog_data.get("selected_room_id") if room_id: await rooms_repository.delete_room(room_id) await callback.answer("✅ Комната удалена!") await dialog_manager.switch_to(AdminMenuSG.rooms_list) async def on_delete_room_cancel( callback: CallbackQuery, button: Button, dialog_manager: DialogManager, ): await dialog_manager.switch_to(AdminMenuSG.room_info) @inject async def get_room_history_data( dialog_manager: DialogManager, rooms_repository: FromDishka[RoomsRepository], room_transactions_repository: FromDishka[RoomHoursTransactionsRepository], **kwargs, ): room_id = dialog_manager.dialog_data.get("selected_room_id") if not room_id: return {"history_content": "Ошибка: комната не выбрана"} room = await rooms_repository.get_room_by_id(room_id) if not room: return {"history_content": "Ошибка: комната не найдена"} transactions = await room_transactions_repository.get_room_history(room_id) transactions_sorted = sorted(transactions, key=lambda x: x.created_at) last_10 = transactions_sorted[-10:] if not last_10: history_text = f"""
📜 История операций
Комната: {room.number} История операций пуста """ else: history_text = f"""
📜 История операций
Комната: {room.number} """ for tx in last_10: operation = "Начислено" if tx.transaction_type == "increase" else "Списано" emoji = "+" if tx.transaction_type == "increase" else "−" msk_time = tx.created_at.astimezone(msk_now().tzinfo).replace(tzinfo=None) date_str = msk_time.strftime("%d.%m.%Y %H:%M") remark_text = f"\n💬 {tx.remark}" if tx.remark else "" history_text += f"
{operation} {emoji}{tx.amount} ч\n📅 {date_str}{remark_text}
\n" return {"history_content": history_text} rooms_select_floor_window = Window( Format("{content}"), Group( Select( Format("{item[0]}"), id="rooms_floors_select", item_id_getter=lambda x: x[1], items="floors", on_click=on_rooms_floor_selected, ), width=2, ), Button( Const("➕ Добавить комнату"), id="add_room_btn", on_click=on_add_room_click, ), SwitchTo( Const("◀️ Назад"), id="back_to_admin_menu_from_rooms", state=AdminMenuSG.main, ), state=AdminMenuSG.rooms_select_floor, getter=get_rooms_floors_data, ) rooms_list_window = Window( Format("{content}"), ScrollingGroup( Select( Format("{item[0]}"), id="rooms_select", item_id_getter=lambda x: x[1], items="rooms", on_click=on_room_selected, ), id="rooms_scroll", width=1, height=7, ), SwitchTo( Const("◀️ Назад к этажам"), id="back_to_rooms_floors", state=AdminMenuSG.rooms_select_floor, ), state=AdminMenuSG.rooms_list, 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="room_history_btn", on_click=lambda c, b, m: m.switch_to(AdminMenuSG.room_history), ), 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( Button( Const("✅ Да, удалить"), id="confirm_delete_room", on_click=on_delete_room_confirm, ), Button( Const("❌ Отмена"), id="cancel_delete_room", on_click=on_delete_room_cancel, ), ), state=AdminMenuSG.room_delete_confirm, 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( Select( Format("{item[0]}"), id="create_room_floors_select", item_id_getter=lambda x: x[1], items="floors", on_click=on_create_room_floor_selected, ), width=2, ), SwitchTo( Const("◀️ Отмена"), id="cancel_create_room_floor", state=AdminMenuSG.rooms_select_floor, ), state=AdminMenuSG.create_room_select_floor, getter=get_create_room_floors_data, ) create_room_input_window = Window( Const("
Создание комнаты
\n\nВведите номер комнаты:"), MessageInput(on_room_number_input), SwitchTo( Const("◀️ Назад"), id="back_to_create_room_floor", state=AdminMenuSG.create_room_select_floor, ), state=AdminMenuSG.create_room_input, ) create_room_confirm_window = Window( Format("
Подтверждение
\n\nСоздать комнату {room_number}?"), Row( Button( Const("✅ Да"), id="confirm_create_room", on_click=on_create_room_confirm, ), Button( Const("❌ Нет"), id="cancel_create_room", on_click=on_create_room_cancel, ), ), state=AdminMenuSG.create_room_confirm, getter=get_create_room_confirm_data, ) room_history_window = Window( Format("{history_content}"), SwitchTo( Const("◀️ Назад"), id="back_to_room_info", state=AdminMenuSG.room_info, ), state=AdminMenuSG.room_history, getter=get_room_history_data, )