From cf0b0309fcdc102bed3ded336b4b6ebf6eeb6e27 Mon Sep 17 00:00:00 2001 From: kolo Date: Sun, 1 Mar 2026 15:58:47 +0300 Subject: [PATCH] update --- src/dutylog/application/__main__.py | 2 + .../bot/admin_dialogs/main_menu.py | 14 +- .../reporting_period_management.py | 4 - .../bot/creator_dialogs/__init__.py | 5 + .../bot/creator_dialogs/admins_management.py | 374 ++++++++++++++++++ .../creator_dialogs/creator_menu_dialog.py | 18 + .../bot/user_dialogs/registration_dialog.py | 10 +- .../application/bot/user_dialogs/states.py | 8 + src/dutylog/application/bot/user_handlers.py | 8 +- .../database/repositories/users_repository.py | 3 + test_report_2026-03-01_2026-03-01.xlsx | Bin 5881 -> 0 bytes test_report_generation.py | 79 ---- 12 files changed, 430 insertions(+), 95 deletions(-) create mode 100644 src/dutylog/application/bot/creator_dialogs/__init__.py create mode 100644 src/dutylog/application/bot/creator_dialogs/admins_management.py create mode 100644 src/dutylog/application/bot/creator_dialogs/creator_menu_dialog.py delete mode 100644 test_report_2026-03-01_2026-03-01.xlsx delete mode 100644 test_report_generation.py diff --git a/src/dutylog/application/__main__.py b/src/dutylog/application/__main__.py index 606b0dd..b05e501 100644 --- a/src/dutylog/application/__main__.py +++ b/src/dutylog/application/__main__.py @@ -11,6 +11,7 @@ from dishka.integrations.aiogram import setup_dishka from dutylog.application.bot.user_handlers import router as user_router from dutylog.application.bot.user_dialogs import main_menu_dialog from dutylog.application.bot.admin_dialogs import admin_menu_dialog +from dutylog.application.bot.creator_dialogs import creator_menu_dialog from dutylog.application.bot.user_dialogs.registration_dialog import registration_dialog from dutylog.infrastructure.ioc import ( ConfigProvider, @@ -46,6 +47,7 @@ async def main(): dp.include_router(user_router) dp.include_router(main_menu_dialog) dp.include_router(admin_menu_dialog) + dp.include_router(creator_menu_dialog) dp.include_router(registration_dialog) setup_dialogs(dp) diff --git a/src/dutylog/application/bot/admin_dialogs/main_menu.py b/src/dutylog/application/bot/admin_dialogs/main_menu.py index 4c75de1..79e9212 100644 --- a/src/dutylog/application/bot/admin_dialogs/main_menu.py +++ b/src/dutylog/application/bot/admin_dialogs/main_menu.py @@ -6,7 +6,7 @@ from dishka import FromDishka from dishka.integrations.aiogram_dialog import inject from datetime import datetime, timedelta -from dutylog.application.bot.user_dialogs.states import AdminMenuSG +from dutylog.application.bot.user_dialogs.states import AdminMenuSG, CreatorMenuSG from dutylog.infrastructure.database.repositories.users_repository import ( UsersRepository, ) @@ -86,7 +86,7 @@ async def get_admin_menu_data( Выберите действие: """ - return {"content": content} + return {"content": content, "is_creator": is_creator} @inject @@ -137,6 +137,10 @@ async def on_reporting_period_click(callback, button, dialog_manager): await dialog_manager.switch_to(AdminMenuSG.reporting_period) +async def on_admins_click(callback, button, dialog_manager): + await dialog_manager.start(CreatorMenuSG.admins_list) + + main_menu_window = Window( Format("{content}"), SwitchTo( @@ -161,6 +165,12 @@ main_menu_window = Window( id="reporting_period_btn", on_click=on_reporting_period_click, ), + Button( + Const("👨‍💼 Админы"), + id="admins_btn", + on_click=on_admins_click, + when="is_creator", + ), SwitchTo( Const("📊 Статистика"), id="stats_btn", diff --git a/src/dutylog/application/bot/admin_dialogs/reporting_period_management.py b/src/dutylog/application/bot/admin_dialogs/reporting_period_management.py index e287736..f6f62fc 100644 --- a/src/dutylog/application/bot/admin_dialogs/reporting_period_management.py +++ b/src/dutylog/application/bot/admin_dialogs/reporting_period_management.py @@ -63,8 +63,6 @@ async def get_reporting_period_data( Дата начала: {start_date.strftime('%d.%m.%Y')} Прошло дней: {days_passed} -━━━━━━━━━━━━━━━━━━━━ - Используйте кнопки ниже для управления периодом. """ has_active = True @@ -74,8 +72,6 @@ async def get_reporting_period_data( Статус: ⚪️ Нет активного периода -━━━━━━━━━━━━━━━━━━━━ - Создайте новый отчётный период, чтобы начать учёт дежурств. """ has_active = False diff --git a/src/dutylog/application/bot/creator_dialogs/__init__.py b/src/dutylog/application/bot/creator_dialogs/__init__.py new file mode 100644 index 0000000..4fa302d --- /dev/null +++ b/src/dutylog/application/bot/creator_dialogs/__init__.py @@ -0,0 +1,5 @@ +from dutylog.application.bot.creator_dialogs.creator_menu_dialog import ( + creator_menu_dialog, +) + +__all__ = ["creator_menu_dialog"] diff --git a/src/dutylog/application/bot/creator_dialogs/admins_management.py b/src/dutylog/application/bot/creator_dialogs/admins_management.py new file mode 100644 index 0000000..893a050 --- /dev/null +++ b/src/dutylog/application/bot/creator_dialogs/admins_management.py @@ -0,0 +1,374 @@ +from aiogram.types import CallbackQuery +from aiogram_dialog import Window, DialogManager +from aiogram_dialog.widgets.text import Format, Const +from aiogram_dialog.widgets.kbd import SwitchTo, Button, Select, ScrollingGroup, Row +from dishka import FromDishka +from dishka.integrations.aiogram_dialog import inject + +from dutylog.application.bot.user_dialogs.states import CreatorMenuSG, AdminMenuSG +from dutylog.infrastructure.database.repositories.users_repository import UsersRepository + + +@inject +async def get_admins_list_data( + users_repository: FromDishka[UsersRepository], + **kwargs, +) -> dict[str, str | list[tuple[str, int]]]: + all_users = await users_repository.get_all_users() + admins = [u for u in all_users if u.is_admin] + + if not admins: + content = """ +
👨‍💼 Администраторы
+ +⚠️ Нет администраторов в системе. +""" + admins_list = [] + else: + content = f""" +
👨‍💼 Администраторы
+ +Всего администраторов: {len(admins)} + +Выберите администратора для просмотра информации: +""" + admins_list = [] + for admin in admins: + display_name = f"@{admin.username}" if admin.username else f"ID: {admin.id}" + if admin.first_name: + display_name = f"{admin.first_name} ({display_name})" + admins_list.append((display_name, admin.id)) + + return { + "content": content, + "admins": admins_list, + } + + +async def on_admin_selected( + callback: CallbackQuery, + widget, + dialog_manager: DialogManager, + item_id: str, +) -> None: + dialog_manager.dialog_data["selected_admin_id"] = int(item_id) + await dialog_manager.switch_to(CreatorMenuSG.admin_info) + + +async def on_add_admin_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +) -> None: + await dialog_manager.switch_to(CreatorMenuSG.add_admin_select_user) + + +@inject +async def get_admin_info_data( + users_repository: FromDishka[UsersRepository], + dialog_manager: DialogManager, + **kwargs, +) -> dict[str, str]: + admin_id = dialog_manager.dialog_data.get("selected_admin_id") + if not admin_id: + return {"content": "⚠️ Администратор не выбран"} + + admin = await users_repository.get_user_by_id(int(admin_id)) + + if not admin: + return {"content": "⚠️ Администратор не найден"} + + username = f"@{admin.username}" if admin.username else "—" + first_name = admin.first_name or "—" + last_name = admin.last_name or "—" + + content = f""" +
👨‍💼 Информация об администраторе
+ +ID: {admin.id} +Username: {username} +Имя: {first_name} +Фамилия: {last_name} + +Дата добавления: {admin.created_at.strftime('%d.%m.%Y %H:%M')} +""" + + return {"content": content} + + +async def on_remove_admin_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +) -> None: + await dialog_manager.switch_to(CreatorMenuSG.remove_admin_confirm) + + +@inject +async def get_remove_admin_confirm_data( + users_repository: FromDishka[UsersRepository], + dialog_manager: DialogManager, + **kwargs, +) -> dict[str, str]: + admin_id = dialog_manager.dialog_data.get("selected_admin_id") + if not admin_id: + return {"content": "⚠️ Администратор не выбран"} + + admin = await users_repository.get_user_by_id(int(admin_id)) + + if not admin: + return {"content": "⚠️ Администратор не найден"} + + username = f"@{admin.username}" if admin.username else f"ID: {admin.id}" + display_name = admin.first_name or username + + content = f""" +
⚠️ Подтверждение удаления
+ +Вы уверены, что хотите удалить администратора {display_name}? + +Пользователь потеряет права администратора. +""" + + return {"content": content} + + +@inject +async def on_remove_admin_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + users_repository: FromDishka[UsersRepository], +) -> None: + admin_id = dialog_manager.dialog_data.get("selected_admin_id") + if not admin_id: + await callback.answer("⚠️ Администратор не выбран", show_alert=True) + return + + await users_repository.update_user(int(admin_id), is_admin=False) + + await callback.answer("✅ Администратор удалён!") + await dialog_manager.switch_to(CreatorMenuSG.admins_list) + + +async def on_remove_admin_cancel( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +) -> None: + await dialog_manager.switch_to(CreatorMenuSG.admin_info) + + +@inject +async def get_add_admin_select_user_data( + users_repository: FromDishka[UsersRepository], + **kwargs, +) -> dict[str, str | list[tuple[str, int]]]: + all_users = await users_repository.get_all_users() + non_admin_users = [u for u in all_users if not u.is_admin] + + if not non_admin_users: + content = """ +
Добавить администратора
+ +⚠️ Нет пользователей, которых можно сделать администраторами. + +Все пользователи уже являются администраторами. +""" + users_list = [] + else: + content = f""" +
Добавить администратора
+ +Всего пользователей: {len(non_admin_users)} + +Выберите пользователя для назначения администратором: +""" + users_list = [] + for user in non_admin_users: + display_name = f"@{user.username}" if user.username else f"ID: {user.id}" + if user.first_name: + display_name = f"{user.first_name} ({display_name})" + users_list.append((display_name, user.id)) + + return { + "content": content, + "users": users_list, + } + + +async def on_user_selected( + callback: CallbackQuery, + widget, + dialog_manager: DialogManager, + item_id: str, +) -> None: + dialog_manager.dialog_data["selected_user_id"] = int(item_id) + await dialog_manager.switch_to(CreatorMenuSG.add_admin_confirm) + + +@inject +async def get_add_admin_confirm_data( + users_repository: FromDishka[UsersRepository], + dialog_manager: DialogManager, + **kwargs, +) -> dict[str, str]: + user_id = dialog_manager.dialog_data.get("selected_user_id") + if not user_id: + return {"content": "⚠️ Пользователь не выбран"} + + user = await users_repository.get_user_by_id(int(user_id)) + + if not user: + return {"content": "⚠️ Пользователь не найден"} + + username = f"@{user.username}" if user.username else f"ID: {user.id}" + display_name = user.first_name or username + + content = f""" +
⚠️ Подтверждение
+ +Вы уверены, что хотите назначить {display_name} администратором? + +Пользователь получит доступ к панели управления. +""" + + return {"content": content} + + +@inject +async def on_add_admin_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + users_repository: FromDishka[UsersRepository], +) -> None: + user_id = dialog_manager.dialog_data.get("selected_user_id") + if not user_id: + await callback.answer("⚠️ Пользователь не выбран", show_alert=True) + return + + await users_repository.update_user(int(user_id), is_admin=True) + + await callback.answer("✅ Администратор добавлен!") + await dialog_manager.switch_to(CreatorMenuSG.admins_list) + + +async def on_add_admin_cancel( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +) -> None: + await dialog_manager.switch_to(CreatorMenuSG.add_admin_select_user) + + +async def on_back_to_main( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +) -> None: + await dialog_manager.done() + + +admins_list_window = Window( + Format("{content}"), + ScrollingGroup( + Select( + Format("{item[0]}"), + id="admin_select", + item_id_getter=lambda x: x[1], + items="admins", + on_click=on_admin_selected, + ), + id="admins_scroll", + width=1, + height=8, + ), + Button( + Const("➕ Добавить администратора"), + id="add_admin_btn", + on_click=on_add_admin_click, + ), + Button( + Const("◀️ Назад"), + id="back_to_main_from_admins", + on_click=on_back_to_main, + ), + state=CreatorMenuSG.admins_list, + getter=get_admins_list_data, +) + +admin_info_window = Window( + Format("{content}"), + Button( + Const("🗑 Удалить администратора"), + id="remove_admin_btn", + on_click=on_remove_admin_click, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_admins_list", + state=CreatorMenuSG.admins_list, + ), + state=CreatorMenuSG.admin_info, + getter=get_admin_info_data, +) + +remove_admin_confirm_window = Window( + Format("{content}"), + Row( + Button( + Const("✅ Да"), + id="confirm_remove_admin", + on_click=on_remove_admin_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_remove_admin", + on_click=on_remove_admin_cancel, + ), + ), + state=CreatorMenuSG.remove_admin_confirm, + getter=get_remove_admin_confirm_data, +) + +add_admin_select_user_window = Window( + Format("{content}"), + ScrollingGroup( + Select( + Format("{item[0]}"), + id="user_select", + item_id_getter=lambda x: x[1], + items="users", + on_click=on_user_selected, + ), + id="users_scroll", + width=1, + height=8, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_admins_from_add", + state=CreatorMenuSG.admins_list, + ), + state=CreatorMenuSG.add_admin_select_user, + getter=get_add_admin_select_user_data, +) + +add_admin_confirm_window = Window( + Format("{content}"), + Row( + Button( + Const("✅ Да"), + id="confirm_add_admin", + on_click=on_add_admin_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_add_admin", + on_click=on_add_admin_cancel, + ), + ), + state=CreatorMenuSG.add_admin_confirm, + getter=get_add_admin_confirm_data, +) diff --git a/src/dutylog/application/bot/creator_dialogs/creator_menu_dialog.py b/src/dutylog/application/bot/creator_dialogs/creator_menu_dialog.py new file mode 100644 index 0000000..cedc4a1 --- /dev/null +++ b/src/dutylog/application/bot/creator_dialogs/creator_menu_dialog.py @@ -0,0 +1,18 @@ +from aiogram_dialog import Dialog + +from dutylog.application.bot.creator_dialogs.admins_management import ( + admins_list_window, + admin_info_window, + remove_admin_confirm_window, + add_admin_select_user_window, + add_admin_confirm_window, +) + + +creator_menu_dialog = Dialog( + admins_list_window, + admin_info_window, + remove_admin_confirm_window, + add_admin_select_user_window, + add_admin_confirm_window, +) diff --git a/src/dutylog/application/bot/user_dialogs/registration_dialog.py b/src/dutylog/application/bot/user_dialogs/registration_dialog.py index f8056d5..edee3dd 100644 --- a/src/dutylog/application/bot/user_dialogs/registration_dialog.py +++ b/src/dutylog/application/bot/user_dialogs/registration_dialog.py @@ -116,19 +116,11 @@ async def on_resident_selected( widget: Select, dialog_manager: DialogManager, item_id: str, - residents_repository: FromDishka[ResidentsRepository], - users_repository: FromDishka[UsersRepository], + residents_repository: FromDishka[ResidentsRepository] ): user_id = callback.from_user.id resident_id = int(item_id) - await users_repository.get_or_create_user( - user_id=user_id, - username=callback.from_user.username, - first_name=callback.from_user.first_name, - last_name=callback.from_user.last_name, - ) - await residents_repository.bind_user_to_resident(resident_id, user_id) await callback.answer("✅ Регистрация успешна!") diff --git a/src/dutylog/application/bot/user_dialogs/states.py b/src/dutylog/application/bot/user_dialogs/states.py index a1563bc..bb84ef6 100644 --- a/src/dutylog/application/bot/user_dialogs/states.py +++ b/src/dutylog/application/bot/user_dialogs/states.py @@ -49,6 +49,14 @@ class AdminMenuSG(StatesGroup): broadcast_confirm = State() +class CreatorMenuSG(StatesGroup): + admins_list = State() + admin_info = State() + remove_admin_confirm = State() + add_admin_select_user = State() + add_admin_confirm = State() + + class RegistrationSG(StatesGroup): select_floor = State() select_room = State() diff --git a/src/dutylog/application/bot/user_handlers.py b/src/dutylog/application/bot/user_handlers.py index b54807e..a69d764 100644 --- a/src/dutylog/application/bot/user_handlers.py +++ b/src/dutylog/application/bot/user_handlers.py @@ -29,7 +29,13 @@ async def start_handler( config: FromDishka[Config], ): assert message.from_user is not None - user = await users_repository.get_user_by_id(message.from_user.id) + + user = await users_repository.get_or_create_user( + user_id=message.from_user.id, + username=message.from_user.username, + first_name=message.from_user.first_name, + last_name=message.from_user.last_name, + ) is_creator = message.from_user.id == config.bot.creator_id is_admin = user.is_admin if user else False diff --git a/src/dutylog/infrastructure/database/repositories/users_repository.py b/src/dutylog/infrastructure/database/repositories/users_repository.py index f79e154..fefa6de 100644 --- a/src/dutylog/infrastructure/database/repositories/users_repository.py +++ b/src/dutylog/infrastructure/database/repositories/users_repository.py @@ -50,3 +50,6 @@ class UsersRepository: async def get_all_users(self) -> list[User]: return await self.users_dao.get_all() + + async def update_user(self, user_id: int, **kwargs) -> User | None: + return await self.users_dao.update(user_id, **kwargs) diff --git a/test_report_2026-03-01_2026-03-01.xlsx b/test_report_2026-03-01_2026-03-01.xlsx deleted file mode 100644 index 47a9a97a7dd0e4e279045ccf4e57233514008e46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5881 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 S69B-yx}~qEzu?zG0{kDCcUbHI diff --git a/test_report_generation.py b/test_report_generation.py deleted file mode 100644 index fbd57c8..0000000 --- a/test_report_generation.py +++ /dev/null @@ -1,79 +0,0 @@ -import asyncio -from datetime import date - -from dutylog.infrastructure.database.config import create_engine, create_session_maker -from dutylog.infrastructure.database.dao.hours_transactions_dao import HoursTransactionsDAO -from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO -from dutylog.infrastructure.database.dao.rooms_dao import RoomsDAO -from dutylog.infrastructure.database.dao.floors_dao import FloorsDAO -from dutylog.infrastructure.database.dao.reporting_periods_dao import ReportingPeriodsDAO -from dutylog.infrastructure.database.dao.users_dao import UsersDAO -from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( - HoursTransactionsRepository, -) -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.reporting_periods_repository import ( - ReportingPeriodsRepository, -) -from dutylog.infrastructure.database.repositories.users_repository import UsersRepository -from dutylog.infrastructure.utils.config import load_config -from dutylog.services.report_service import ReportService - - -async def test_report_generation() -> None: - config = load_config() - engine = create_engine(config.database.url) - session_maker = create_session_maker(engine) - - async with session_maker() as session: - hours_transactions_dao = HoursTransactionsDAO(session) - residents_dao = ResidentsDAO(session) - rooms_dao = RoomsDAO(session) - floors_dao = FloorsDAO(session) - reporting_periods_dao = ReportingPeriodsDAO(session) - users_dao = UsersDAO(session) - - hours_transactions_repository = HoursTransactionsRepository( - hours_transactions_dao, residents_dao - ) - residents_repository = ResidentsRepository(residents_dao) - rooms_repository = RoomsRepository(rooms_dao) - floors_repository = FloorsRepository(floors_dao) - reporting_periods_repository = ReportingPeriodsRepository(reporting_periods_dao) - users_repository = UsersRepository(users_dao) - - report_service = ReportService( - hours_transactions_repository, - residents_repository, - rooms_repository, - floors_repository, - users_repository, - ) - - active_period = await reporting_periods_repository.get_active_period() - - if not active_period: - print("⚠️ Нет активного периода") - return - - print(f"📅 Активный период: с {active_period.start_date}") - - end_date = active_period.end_date if active_period.end_date else date.today() - - print(f"📊 Генерирую отчёт за период {active_period.start_date} - {end_date}") - - report_file = await report_service.generate_period_report( - active_period.start_date, end_date - ) - - filename = f"test_report_{active_period.start_date}_{end_date}.xlsx" - with open(filename, "wb") as f: - f.write(report_file.read()) - - print(f"✅ Отчёт сохранён в файл: {filename}") - - -if __name__ == "__main__": - asyncio.run(test_report_generation())