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 19eea16..5a3494f 100644 --- a/src/dutylog/application/bot/admin_dialogs/admin_menu_dialog.py +++ b/src/dutylog/application/bot/admin_dialogs/admin_menu_dialog.py @@ -9,6 +9,9 @@ from dutylog.application.bot.admin_dialogs.residents_management import ( resident_info_window, resident_logout_confirm_window, resident_delete_confirm_window, + resident_rebind_floor_window, + resident_rebind_room_window, + resident_rebind_confirm_window, create_resident_name_window, create_resident_floor_window, create_resident_room_window, @@ -66,6 +69,9 @@ admin_menu_dialog = Dialog( resident_info_window, resident_logout_confirm_window, resident_delete_confirm_window, + resident_rebind_floor_window, + resident_rebind_room_window, + resident_rebind_confirm_window, add_hours_select_window, remove_hours_select_window, add_hours_custom_window, diff --git a/src/dutylog/application/bot/admin_dialogs/residents_management.py b/src/dutylog/application/bot/admin_dialogs/residents_management.py index a17641a..86e7039 100644 --- a/src/dutylog/application/bot/admin_dialogs/residents_management.py +++ b/src/dutylog/application/bot/admin_dialogs/residents_management.py @@ -211,6 +211,118 @@ async def on_delete_resident_cancel( await dialog_manager.switch_to(AdminMenuSG.resident_info) +async def on_rebind_resident( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.resident_rebind_floor) + + +@inject +async def get_rebind_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_rebind_floor_selected( + callback: CallbackQuery, + widget: Select, + dialog_manager: DialogManager, + item_id: str, +): + dialog_manager.dialog_data["rebind_floor_id"] = int(item_id) + await dialog_manager.switch_to(AdminMenuSG.resident_rebind_room) + + +@inject +async def get_rebind_rooms_data( + dialog_manager: DialogManager, + rooms_repository: FromDishka[RoomsRepository], + **kwargs, +): + floor_id = dialog_manager.dialog_data.get("rebind_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_rebind_room_selected( + callback: CallbackQuery, + widget: Select, + dialog_manager: DialogManager, + item_id: str, +): + dialog_manager.dialog_data["rebind_room_id"] = int(item_id) + await dialog_manager.switch_to(AdminMenuSG.resident_rebind_confirm) + + +@inject +async def get_rebind_confirm_data( + dialog_manager: DialogManager, + residents_repository: FromDishka[ResidentsRepository], + rooms_repository: FromDishka[RoomsRepository], + **kwargs, +): + resident_id = dialog_manager.dialog_data.get("selected_resident_id") + new_room_id = dialog_manager.dialog_data.get("rebind_room_id") + + resident = await residents_repository.get_resident_by_id(resident_id) if resident_id else None + new_room = await rooms_repository.get_room_by_id(new_room_id) if new_room_id else None + old_room = await rooms_repository.get_room_by_id(resident.room) if resident else None + + resident_name = resident.real_name if resident and resident.real_name else "???" + old_room_number = old_room.number if old_room else "???" + new_room_number = new_room.number if new_room else "???" + + return { + "resident_name": resident_name, + "old_room_number": old_room_number, + "new_room_number": new_room_number, + } + + +@inject +async def on_rebind_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + residents_repository: FromDishka[ResidentsRepository], +): + resident_id = dialog_manager.dialog_data.get("selected_resident_id") + new_room_id = dialog_manager.dialog_data.get("rebind_room_id") + + if resident_id and new_room_id: + resident = await residents_repository.get_resident_by_id(resident_id) + if resident: + await residents_repository.update_resident_room(resident_id, new_room_id) + await callback.answer("✅ Резидент перепривязан к новой комнате!") + + await dialog_manager.switch_to(AdminMenuSG.resident_info) + + +async def on_rebind_cancel( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.resident_info) + + async def on_resident_name_input( message: Message, widget: MessageInput, @@ -462,6 +574,11 @@ resident_info_window = Window( on_click=lambda c, b, m: m.switch_to(AdminMenuSG.remove_hours_select), ), ), + Button( + Const("🔄 Перепривязать к комнате"), + id="rebind_resident_btn", + on_click=on_rebind_resident, + ), Button( Const("🚪 Разлогинить"), id="logout_resident_btn", @@ -643,3 +760,63 @@ resident_delete_confirm_window = Window( ), state=AdminMenuSG.resident_delete_confirm, ) + +resident_rebind_floor_window = Window( + Const("
🏢 Перепривязка резидента
\n\nВыберите новый этаж:"), + Group( + Select( + Format("{item[1]}"), + id="rebind_floor_select", + item_id_getter=lambda x: x[0], + items="floors", + on_click=on_rebind_floor_selected, + ), + width=2, + ), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_rebind_floor", + state=AdminMenuSG.resident_info, + ), + state=AdminMenuSG.resident_rebind_floor, + getter=get_rebind_floors_data, +) + +resident_rebind_room_window = Window( + Const("
🚪 Перепривязка резидента
\n\nВыберите новую комнату:"), + Group( + Select( + Format("{item[1]}"), + id="rebind_room_select", + item_id_getter=lambda x: x[0], + items="rooms", + on_click=on_rebind_room_selected, + ), + width=3, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_rebind_floor", + state=AdminMenuSG.resident_rebind_floor, + ), + state=AdminMenuSG.resident_rebind_room, + getter=get_rebind_rooms_data, +) + +resident_rebind_confirm_window = Window( + Format("
Подтверждение перепривязки
\n\nПерепривязать резидента {resident_name} из комнаты {old_room_number} в комнату {new_room_number}?"), + Row( + Button( + Const("✅ Да"), + id="confirm_rebind", + on_click=on_rebind_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_rebind", + on_click=on_rebind_cancel, + ), + ), + state=AdminMenuSG.resident_rebind_confirm, + getter=get_rebind_confirm_data, +) diff --git a/src/dutylog/application/bot/user_dialogs/states.py b/src/dutylog/application/bot/user_dialogs/states.py index df6d19f..882be5f 100644 --- a/src/dutylog/application/bot/user_dialogs/states.py +++ b/src/dutylog/application/bot/user_dialogs/states.py @@ -19,6 +19,9 @@ class AdminMenuSG(StatesGroup): resident_info = State() resident_logout_confirm = State() resident_delete_confirm = State() + resident_rebind_floor = State() + resident_rebind_room = State() + resident_rebind_confirm = State() add_hours_select = State() remove_hours_select = State() add_hours_custom = State() diff --git a/src/dutylog/infrastructure/database/repositories/residents_repository.py b/src/dutylog/infrastructure/database/repositories/residents_repository.py index e7ebf26..398e4aa 100644 --- a/src/dutylog/infrastructure/database/repositories/residents_repository.py +++ b/src/dutylog/infrastructure/database/repositories/residents_repository.py @@ -43,6 +43,16 @@ class ResidentsRepository: real_name=real_name, ) + async def update_resident_room( + self, + resident_id: int, + room_id: int, + ) -> Resident | None: + return await self.residents_dao.update( + resident_id, + room=room_id, + ) + async def add_active_hours(self, resident_id: int, hours: int) -> Resident | None: resident = await self.residents_dao.get_by_id(resident_id) if resident: