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: