This commit is contained in:
2026-03-01 15:32:25 +03:00
parent 8b05a2d512
commit 1110d89bb0
17 changed files with 150 additions and 59 deletions
@@ -0,0 +1,645 @@
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 Row, SwitchTo, Button, ScrollingGroup, Select, Group
from aiogram_dialog.widgets.input import MessageInput
from magic_filter import F
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.residents_repository import (
ResidentsRepository,
)
from dutylog.infrastructure.database.repositories.rooms_repository import (
RoomsRepository,
)
from dutylog.infrastructure.database.repositories.floors_repository import (
FloorsRepository,
)
from dutylog.infrastructure.database.repositories.users_repository import (
UsersRepository,
)
@inject
async def get_residents_list_data(
residents_repository: FromDishka[ResidentsRepository],
rooms_repository: FromDishka[RoomsRepository],
floors_repository: FromDishka[FloorsRepository],
**kwargs,
):
all_residents = await residents_repository.get_all_residents()
residents_with_rooms = []
for resident in all_residents:
room = await rooms_repository.get_room_by_id(resident.room)
if room:
floor = await floors_repository.get_floor_by_id(room.on_floor)
floor_number = floor.number if floor else 999999
room_number = room.number
else:
floor_number = 999999
room_number = 999999
residents_with_rooms.append((resident, floor_number, room_number))
residents_with_rooms.sort(key=lambda x: (x[1], x[2]))
residents_data = []
for resident, floor_number, room_number in residents_with_rooms:
status = "🟢" if resident.is_busy else "⚪️"
name = resident.real_name if resident.real_name else "Без имени"
residents_data.append(
(f"{name} | Комната {room_number} | {status}", resident.id)
)
content = f"""
<blockquote>🏠 <b>Резиденты</b></blockquote>
<b>Всего резидентов:</b> <code>{len(all_residents)}</code>
Выберите резидента для просмотра информации:
"""
return {
"content": content,
"residents": residents_data,
}
@inject
async def get_resident_info_data(
dialog_manager: DialogManager,
residents_repository: FromDishka[ResidentsRepository],
rooms_repository: FromDishka[RoomsRepository],
users_repository: FromDishka[UsersRepository],
**kwargs,
):
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
if not resident_id:
return {"info_content": "Ошибка: резидент не выбран", "is_busy": False, "from_search": False, "from_filter": False}
resident = await residents_repository.get_resident_by_id(resident_id)
if not resident:
return {"info_content": "Ошибка: резидент не найден", "is_busy": False, "from_search": False, "from_filter": False}
room = await rooms_repository.get_room_by_id(resident.room)
room_number = room.number if room else "???"
name = resident.real_name if resident.real_name else "Без имени"
status = "🟢 Занят" if resident.is_busy else "⚪️ Свободен"
user_info = "Не привязан"
if resident.user_entity:
user = await users_repository.get_user_by_id(resident.user_entity)
if user:
if user.username:
username = f"@{user.username}"
else:
username = f"ID: {user.id}"
user_info = f"{user.first_name} ({username})"
info_content = f"""
<blockquote>👤 <b>Информация о резиденте</b></blockquote>
<b>ID:</b> <code>{resident.id}</code>
<b>Имя:</b> {name}
<b>Комната:</b> <code>{room_number}</code>
<b>Статус:</b> {status}
<b>Пользователь:</b> {user_info}
━━━━━━━━━━━━━━━━━━━━
🟢 <b>Отработанные часы:</b> <code>{resident.inactive_hours}</code> ч
🔴 <b>Неотработанные часы:</b> <code>{resident.active_hours}</code> ч
"""
from_search = dialog_manager.dialog_data.get("from_search", False)
from_filter = dialog_manager.dialog_data.get("from_filter", False)
return {
"info_content": info_content,
"is_busy": resident.is_busy,
"from_search": from_search,
"from_filter": from_filter,
}
async def on_resident_selected(
callback: CallbackQuery,
widget: Select,
dialog_manager: DialogManager,
item_id: str,
):
dialog_manager.dialog_data["selected_resident_id"] = int(item_id)
dialog_manager.dialog_data["from_search"] = False
dialog_manager.dialog_data["from_filter"] = False
await dialog_manager.switch_to(AdminMenuSG.resident_info)
async def on_add_resident(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
await dialog_manager.switch_to(AdminMenuSG.create_resident_name)
async def on_filter_residents(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
await dialog_manager.switch_to(AdminMenuSG.residents_filter_select)
async def on_search_residents(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
await dialog_manager.switch_to(AdminMenuSG.residents_search_input)
@inject
async def on_logout_resident_confirm(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
residents_repository: FromDishka[ResidentsRepository],
):
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
if resident_id:
await residents_repository.unbind_user_from_resident(resident_id)
await dialog_manager.switch_to(AdminMenuSG.resident_info)
async def on_logout_resident_cancel(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
await dialog_manager.switch_to(AdminMenuSG.resident_info)
@inject
async def on_delete_resident_confirm(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
residents_repository: FromDishka[ResidentsRepository],
):
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
if resident_id:
await residents_repository.delete_resident(resident_id)
await callback.answer("✅ Резидент удален!")
await dialog_manager.switch_to(AdminMenuSG.residents)
async def on_delete_resident_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,
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_search_input(
message: Message,
widget: MessageInput,
dialog_manager: DialogManager,
):
if not message.text or len(message.text.strip()) < 1:
await message.answer("⚠️ Пожалуйста, введите поисковый запрос")
return
query = message.text.strip()
if query.startswith("@"):
query = query[1:]
dialog_manager.dialog_data["search_query"] = query
dialog_manager.dialog_data["is_search_active"] = True
await dialog_manager.switch_to(AdminMenuSG.residents_search_results)
@inject
async def get_search_results_data(
dialog_manager: DialogManager,
residents_repository: FromDishka[ResidentsRepository],
rooms_repository: FromDishka[RoomsRepository],
floors_repository: FromDishka[FloorsRepository],
users_repository: FromDishka[UsersRepository],
**kwargs,
):
query = dialog_manager.dialog_data.get("search_query", "")
residents, search_type = await residents_repository.search_residents(
query, users_repository
)
if not residents:
return {
"content": f"""
<blockquote>🔍 <b>Результаты поиска</b></blockquote>
<b>Запрос:</b> <code>{query}</code>
❌ Ничего не найдено
""",
"residents": [],
"has_results": False,
}
residents_with_rooms = []
for resident in residents:
room = await rooms_repository.get_room_by_id(resident.room)
if room:
floor = await floors_repository.get_floor_by_id(room.on_floor)
floor_number = floor.number if floor else 999999
room_number = room.number
else:
floor_number = 999999
room_number = 999999
residents_with_rooms.append((resident, floor_number, room_number))
residents_with_rooms.sort(key=lambda x: (x[1], x[2]))
residents_data = []
for resident, floor_number, room_number in residents_with_rooms:
status = "🟢" if resident.is_busy else "⚪️"
name = resident.real_name if resident.real_name else "Без имени"
residents_data.append(
(f"{name} | Комната {room_number} | {status}", resident.id)
)
content = f"""
<blockquote>🔍 <b>Результаты поиска по {search_type}</b></blockquote>
<b>Найдено резидентов:</b> <code>{len(residents)}</code>
Выберите резидента для просмотра информации:
"""
return {
"content": content,
"residents": residents_data,
"has_results": True,
}
async def on_search_resident_selected(
callback: CallbackQuery,
widget: Select,
dialog_manager: DialogManager,
item_id: str,
):
dialog_manager.dialog_data["selected_resident_id"] = int(item_id)
dialog_manager.dialog_data["from_search"] = True
dialog_manager.dialog_data["from_filter"] = False
await dialog_manager.switch_to(AdminMenuSG.resident_info)
residents_list_window = Window(
Format("{content}"),
Row(
Button(
Const("🔍 Поиск"),
id="search_residents_btn",
on_click=on_search_residents,
),
Button(
Const("🔽 Фильтр"),
id="filter_residents_btn",
on_click=on_filter_residents,
),
),
ScrollingGroup(
Select(
Format("{item[0]}"),
id="residents_select",
item_id_getter=lambda x: x[1],
items="residents",
on_click=on_resident_selected,
),
id="residents_scroll",
width=1,
height=7,
),
Button(
Const("➕ Добавить резидента"),
id="add_resident_btn",
on_click=on_add_resident,
),
SwitchTo(
Const("◀️ Назад"),
id="back_to_admin_menu",
state=AdminMenuSG.main,
),
state=AdminMenuSG.residents,
getter=get_residents_list_data,
)
resident_info_window = Window(
Format("{info_content}"),
Row(
Button(
Const("Добавить часы"),
id="add_hours_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.add_hours_select),
),
Button(
Const("Отнять часы"),
id="remove_hours_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.remove_hours_select),
),
),
Button(
Const("🚪 Разлогинить"),
id="logout_resident_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_logout_confirm),
when="is_busy",
),
Button(
Const("🗑 Удалить резидента"),
id="delete_resident_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_delete_confirm),
),
SwitchTo(
Const("◀️ Назад к результатам поиска"),
id="back_to_search_results",
state=AdminMenuSG.residents_search_results,
when="from_search",
),
SwitchTo(
Const("◀️ Назад к результатам фильтра"),
id="back_to_filter_results",
state=AdminMenuSG.residents_filtered_results,
when="from_filter",
),
SwitchTo(
Const("◀️ Назад к списку"),
id="back_to_residents_list",
state=AdminMenuSG.residents,
when=~F["from_search"] & ~F["from_filter"],
),
state=AdminMenuSG.resident_info,
getter=get_resident_info_data,
)
resident_logout_confirm_window = Window(
Const(
"<blockquote>⚠️ <b>Подтверждение</b></blockquote>\n\nВы уверены, что хотите разлогинить этого резидента?"
),
Row(
Button(
Const("✅ Да"),
id="confirm_logout",
on_click=on_logout_resident_confirm,
),
Button(
Const("❌ Нет"),
id="cancel_logout",
on_click=on_logout_resident_cancel,
),
),
state=AdminMenuSG.resident_logout_confirm,
)
create_resident_name_window = Window(
Const("<blockquote> <b>Создание резидента</b></blockquote>\n\nВведите имя и фамилию резидента:"),
MessageInput(on_resident_name_input),
SwitchTo(
Const("◀️ Отмена"),
id="cancel_create_resident_name",
state=AdminMenuSG.residents,
),
state=AdminMenuSG.create_resident_name,
)
create_resident_floor_window = Window(
Const("<blockquote>🏢 <b>Выбор этажа</b></blockquote>\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,
)
create_resident_room_window = Window(
Const("<blockquote>🚪 <b>Выбор комнаты</b></blockquote>\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,
)
create_resident_confirm_window = Window(
Format("<blockquote>✅ <b>Подтверждение</b></blockquote>\n\nСоздать резидента <b>{resident_name}</b>?"),
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,
)
search_input_window = Window(
Const("<blockquote>🔍 <b>Поиск резидентов</b></blockquote>\n\n<blockquote>Введите номер комнаты, имя резидента или username пользователя. Можно указать только часть имени или username для поиска.</blockquote>"),
MessageInput(on_search_input),
SwitchTo(
Const("◀️ Отмена"),
id="cancel_search",
state=AdminMenuSG.residents,
),
state=AdminMenuSG.residents_search_input,
)
search_results_window = Window(
Format("{content}"),
ScrollingGroup(
Select(
Format("{item[0]}"),
id="search_residents_select",
item_id_getter=lambda x: x[1],
items="residents",
on_click=on_search_resident_selected,
),
id="search_residents_scroll",
width=1,
height=7,
when="has_results",
),
SwitchTo(
Const("🔍 Новый поиск"),
id="new_search",
state=AdminMenuSG.residents_search_input,
),
SwitchTo(
Const("◀️ К списку резидентов"),
id="back_to_residents_from_search",
state=AdminMenuSG.residents,
),
state=AdminMenuSG.residents_search_results,
getter=get_search_results_data,
)
resident_delete_confirm_window = Window(
Const(
"<blockquote>⚠️ <b>Подтверждение удаления</b></blockquote>\n\nВы уверены, что хотите удалить этого резидента? Это действие необратимо!"
),
Row(
Button(
Const("✅ Да, удалить"),
id="confirm_delete",
on_click=on_delete_resident_confirm,
),
Button(
Const("❌ Отмена"),
id="cancel_delete",
on_click=on_delete_resident_cancel,
),
),
state=AdminMenuSG.resident_delete_confirm,
)