mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 10:25:29 +03:00
commit
This commit is contained in:
@@ -1,12 +1,16 @@
|
|||||||
from aiogram.types import User
|
from aiogram.types import User, Message, CallbackQuery
|
||||||
|
from aiogram import Bot
|
||||||
|
from aiogram.exceptions import TelegramForbiddenError, TelegramBadRequest
|
||||||
from aiogram_dialog import Dialog, Window, DialogManager
|
from aiogram_dialog import Dialog, Window, DialogManager
|
||||||
from aiogram_dialog.widgets.text import Format, Const
|
from aiogram_dialog.widgets.text import Format, Const
|
||||||
from aiogram_dialog.widgets.kbd import SwitchTo, Back, Start
|
from aiogram_dialog.widgets.kbd import SwitchTo, Button
|
||||||
|
from aiogram_dialog.widgets.input import MessageInput
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
from dishka.integrations.aiogram_dialog import inject
|
from dishka.integrations.aiogram_dialog import inject
|
||||||
|
|
||||||
from dutylog.application.bot.user_dialogs.states import AdminMenuSG
|
from dutylog.application.bot.user_dialogs.states import AdminMenuSG
|
||||||
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
|
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
|
||||||
|
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
||||||
from dutylog.infrastructure.utils.config import Config
|
from dutylog.infrastructure.utils.config import Config
|
||||||
|
|
||||||
|
|
||||||
@@ -42,50 +46,21 @@ async def get_admin_menu_data(
|
|||||||
return {"content": content}
|
return {"content": content}
|
||||||
|
|
||||||
|
|
||||||
@inject
|
|
||||||
async def get_users_list_data(
|
|
||||||
users_repository: FromDishka[UsersRepository],
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
all_users = await users_repository.get_all_users()
|
|
||||||
|
|
||||||
if not all_users:
|
|
||||||
users_text = """
|
|
||||||
<blockquote>👥 <b>Список пользователей</b></blockquote>
|
|
||||||
|
|
||||||
<i>Пользователи не найдены</i>
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
users_lines = []
|
|
||||||
for user in all_users:
|
|
||||||
name = user.first_name or user.username or f"ID: {user.id}"
|
|
||||||
admin_badge = " 👨💼" if user.is_admin else ""
|
|
||||||
users_lines.append(
|
|
||||||
f"• <b>{name}</b>{admin_badge}\n"
|
|
||||||
f" 🟢 <code>{user.active_hours}</code> ч | 🔴 <code>{user.inactive_hours}</code> ч"
|
|
||||||
)
|
|
||||||
|
|
||||||
users_text = f"""
|
|
||||||
<blockquote>👥 <b>Список пользователей</b></blockquote>
|
|
||||||
|
|
||||||
{"".join(f"{line}\n\n" for line in users_lines)}
|
|
||||||
<i>Всего пользователей: {len(all_users)}</i>
|
|
||||||
"""
|
|
||||||
|
|
||||||
return {"users_content": users_text}
|
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
async def get_statistics_data(
|
async def get_statistics_data(
|
||||||
users_repository: FromDishka[UsersRepository],
|
users_repository: FromDishka[UsersRepository],
|
||||||
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
all_users = await users_repository.get_all_users()
|
all_users = await users_repository.get_all_users()
|
||||||
|
all_residents = await residents_repository.get_all_residents()
|
||||||
|
|
||||||
total_users = len(all_users)
|
total_users = len(all_users)
|
||||||
total_active_hours = sum(user.active_hours for user in all_users)
|
total_residents = len(all_residents)
|
||||||
total_inactive_hours = sum(user.inactive_hours for user in all_users)
|
busy_residents = len([r for r in all_residents if r.is_busy])
|
||||||
admins_count = len([user for user in all_users if user.is_admin])
|
total_active_hours = sum(r.active_hours for r in all_residents)
|
||||||
|
total_inactive_hours = sum(r.inactive_hours for r in all_residents)
|
||||||
|
admins_count = len([u for u in all_users if u.is_admin])
|
||||||
|
|
||||||
stats_text = f"""
|
stats_text = f"""
|
||||||
<blockquote>📊 <b>Статистика системы</b></blockquote>
|
<blockquote>📊 <b>Статистика системы</b></blockquote>
|
||||||
@@ -93,6 +68,10 @@ async def get_statistics_data(
|
|||||||
👥 <b>Всего пользователей:</b> <code>{total_users}</code>
|
👥 <b>Всего пользователей:</b> <code>{total_users}</code>
|
||||||
👨💼 <b>Администраторов:</b> <code>{admins_count}</code>
|
👨💼 <b>Администраторов:</b> <code>{admins_count}</code>
|
||||||
|
|
||||||
|
🏠 <b>Всего резидентов:</b> <code>{total_residents}</code>
|
||||||
|
✅ <b>Привязано к пользователям:</b> <code>{busy_residents}</code>
|
||||||
|
❌ <b>Свободных:</b> <code>{total_residents - busy_residents}</code>
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
🟢 <b>Всего активных часов:</b> <code>{total_active_hours}</code> ч
|
🟢 <b>Всего активных часов:</b> <code>{total_active_hours}</code> ч
|
||||||
@@ -103,13 +82,87 @@ async def get_statistics_data(
|
|||||||
return {"stats_content": stats_text}
|
return {"stats_content": stats_text}
|
||||||
|
|
||||||
|
|
||||||
|
async def on_broadcast_message(
|
||||||
|
message: Message,
|
||||||
|
widget: MessageInput,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
):
|
||||||
|
dialog_manager.dialog_data["broadcast_message_id"] = message.message_id
|
||||||
|
dialog_manager.dialog_data["broadcast_chat_id"] = message.chat.id
|
||||||
|
|
||||||
|
await message.copy_to(message.chat.id)
|
||||||
|
await dialog_manager.switch_to(AdminMenuSG.broadcast_confirm)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def on_broadcast_confirm(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
button: Button,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
users_repository: FromDishka[UsersRepository],
|
||||||
|
):
|
||||||
|
assert callback.message is not None
|
||||||
|
|
||||||
|
bot: Bot = dialog_manager.middleware_data["bot"]
|
||||||
|
message_id = dialog_manager.dialog_data["broadcast_message_id"]
|
||||||
|
chat_id = dialog_manager.dialog_data["broadcast_chat_id"]
|
||||||
|
admin_id = callback.from_user.id
|
||||||
|
|
||||||
|
all_users = await users_repository.get_all_users()
|
||||||
|
|
||||||
|
success_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
|
||||||
|
for user in all_users:
|
||||||
|
if user.id == admin_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
await bot.copy_message(
|
||||||
|
chat_id=user.id,
|
||||||
|
from_chat_id=chat_id,
|
||||||
|
message_id=message_id,
|
||||||
|
)
|
||||||
|
success_count += 1
|
||||||
|
except TelegramForbiddenError:
|
||||||
|
failed_count += 1
|
||||||
|
except TelegramBadRequest:
|
||||||
|
failed_count += 1
|
||||||
|
except Exception:
|
||||||
|
failed_count += 1
|
||||||
|
|
||||||
|
result_text = f"""
|
||||||
|
<blockquote>📢 <b>Результаты рассылки</b></blockquote>
|
||||||
|
|
||||||
|
✅ <b>Успешно отправлено:</b> <code>{success_count}</code>
|
||||||
|
❌ <b>Не удалось отправить:</b> <code>{failed_count}</code>
|
||||||
|
📊 <b>Всего пользователей:</b> <code>{len(all_users) - 1}</code>
|
||||||
|
"""
|
||||||
|
|
||||||
|
await callback.message.answer(result_text)
|
||||||
|
await dialog_manager.switch_to(AdminMenuSG.main)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_broadcast_cancel(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
button: Button,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
):
|
||||||
|
await dialog_manager.switch_to(AdminMenuSG.main)
|
||||||
|
|
||||||
|
|
||||||
admin_menu_dialog = Dialog(
|
admin_menu_dialog = Dialog(
|
||||||
Window(
|
Window(
|
||||||
Format("{content}"),
|
Format("{content}"),
|
||||||
|
SwitchTo(
|
||||||
|
Const("🏠 Резиденты"),
|
||||||
|
id="residents_btn",
|
||||||
|
state=AdminMenuSG.residents,
|
||||||
|
),
|
||||||
SwitchTo(
|
SwitchTo(
|
||||||
Const("👥 Пользователи"),
|
Const("👥 Пользователи"),
|
||||||
id="users_btn",
|
id="users_btn",
|
||||||
state=AdminMenuSG.users_list,
|
state=AdminMenuSG.users,
|
||||||
),
|
),
|
||||||
SwitchTo(
|
SwitchTo(
|
||||||
Const("📊 Статистика"),
|
Const("📊 Статистика"),
|
||||||
@@ -125,20 +178,31 @@ admin_menu_dialog = Dialog(
|
|||||||
getter=get_admin_menu_data,
|
getter=get_admin_menu_data,
|
||||||
),
|
),
|
||||||
Window(
|
Window(
|
||||||
Format("{users_content}"),
|
Const("<blockquote>🏠 <b>Резиденты</b></blockquote>\n\n<i>Функционал в разработке</i>"),
|
||||||
Back(Const("◀️ Назад")),
|
SwitchTo(Const("◀️ Назад"), id="back_from_residents", state=AdminMenuSG.main),
|
||||||
state=AdminMenuSG.users_list,
|
state=AdminMenuSG.residents,
|
||||||
getter=get_users_list_data,
|
),
|
||||||
|
Window(
|
||||||
|
Const("<blockquote>👥 <b>Пользователи</b></blockquote>\n\n<i>Функционал в разработке</i>"),
|
||||||
|
SwitchTo(Const("◀️ Назад"), id="back_from_users", state=AdminMenuSG.main),
|
||||||
|
state=AdminMenuSG.users,
|
||||||
),
|
),
|
||||||
Window(
|
Window(
|
||||||
Format("{stats_content}"),
|
Format("{stats_content}"),
|
||||||
Back(Const("◀️ Назад")),
|
SwitchTo(Const("◀️ Назад"), id="back_from_stats", state=AdminMenuSG.main),
|
||||||
state=AdminMenuSG.statistics,
|
state=AdminMenuSG.statistics,
|
||||||
getter=get_statistics_data,
|
getter=get_statistics_data,
|
||||||
),
|
),
|
||||||
Window(
|
Window(
|
||||||
Const("<blockquote>📢 <b>Рассылка</b></blockquote>\n\n<i>Функционал в разработке</i>"),
|
Const("<blockquote>📢 <b>Рассылка</b></blockquote>\n\nОтправьте сообщение, которое хотите разослать всем пользователям:"),
|
||||||
Back(Const("◀️ Назад")),
|
MessageInput(on_broadcast_message),
|
||||||
|
SwitchTo(Const("◀️ Отмена"), id="cancel_broadcast", state=AdminMenuSG.main),
|
||||||
state=AdminMenuSG.broadcast,
|
state=AdminMenuSG.broadcast,
|
||||||
),
|
),
|
||||||
|
Window(
|
||||||
|
Const("<blockquote>📢 <b>Подтверждение рассылки</b></blockquote>\n\n⚠️ Вы уверены, что хотите отправить это сообщение всем пользователям?"),
|
||||||
|
Button(Const("✅ Да, отправить"), id="confirm_broadcast", on_click=on_broadcast_confirm),
|
||||||
|
Button(Const("❌ Нет, отменить"), id="cancel_broadcast_confirm", on_click=on_broadcast_cancel),
|
||||||
|
state=AdminMenuSG.broadcast_confirm,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from aiogram.types import User
|
from aiogram.types import User
|
||||||
from aiogram_dialog import Dialog, Window, DialogManager
|
from aiogram_dialog import Dialog, Window, DialogManager
|
||||||
from aiogram_dialog.widgets.text import Format, Const
|
from aiogram_dialog.widgets.text import Format, Const
|
||||||
from aiogram_dialog.widgets.kbd import SwitchTo, Back, Start
|
from aiogram_dialog.widgets.kbd import SwitchTo, Back
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
from dishka.integrations.aiogram_dialog import inject
|
from dishka.integrations.aiogram_dialog import inject
|
||||||
|
|
||||||
from dutylog.application.bot.user_dialogs.states import MainMenuSG, RegistrationSG
|
from dutylog.application.bot.user_dialogs.states import MainMenuSG
|
||||||
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
|
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
|
||||||
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
||||||
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
|
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
|
||||||
@@ -131,10 +131,10 @@ main_menu_dialog = Dialog(
|
|||||||
state=MainMenuSG.history,
|
state=MainMenuSG.history,
|
||||||
when="has_resident",
|
when="has_resident",
|
||||||
),
|
),
|
||||||
Start(
|
SwitchTo(
|
||||||
Const("🔄 Перерегистрация"),
|
Const("❓ FAQ"),
|
||||||
id="reregister_btn",
|
id="faq_btn",
|
||||||
state=RegistrationSG.select_floor,
|
state=MainMenuSG.faq,
|
||||||
when="is_regular_user",
|
when="is_regular_user",
|
||||||
),
|
),
|
||||||
state=MainMenuSG.main,
|
state=MainMenuSG.main,
|
||||||
@@ -146,5 +146,26 @@ main_menu_dialog = Dialog(
|
|||||||
state=MainMenuSG.history,
|
state=MainMenuSG.history,
|
||||||
getter=get_history_data,
|
getter=get_history_data,
|
||||||
),
|
),
|
||||||
|
Window(
|
||||||
|
Const("""<blockquote>❓ <b>Часто задаваемые вопросы</b></blockquote>
|
||||||
|
|
||||||
|
<b>Что это за система?</b>
|
||||||
|
<blockquote>Это система учета дежурств в общежитии. Здесь отображаются ваши отработанные и неотработанные часы дежурств.</blockquote>
|
||||||
|
|
||||||
|
<b>Что делать, если я зарегистрировался не под собой?</b>
|
||||||
|
<blockquote>⚠️ Перерегистрацию может выполнить только администратор. Обратитесь к администратору для исправления данных.</blockquote>
|
||||||
|
|
||||||
|
<b>Как начисляются часы?</b>
|
||||||
|
<blockquote>Часы начисляются и списываются администраторами системы. Все изменения отображаются в разделе "История".</blockquote>
|
||||||
|
|
||||||
|
<b>Что означают активные и неактивные часы?</b>
|
||||||
|
<blockquote>🟢 <b>Отработанные часы</b> - часы, которые вы уже отработали
|
||||||
|
🔴 <b>Неотработанные часы</b> - часы, которые вам еще предстоит отработать</blockquote>
|
||||||
|
|
||||||
|
<b>Как связаться с администратором?</b>
|
||||||
|
<blockquote>Обратитесь к старосте вашего этажа или в администрацию общежития.</blockquote>"""),
|
||||||
|
SwitchTo(Const("◀️ Назад"), id="back_to_main", state=MainMenuSG.main),
|
||||||
|
state=MainMenuSG.faq,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from aiogram.types import CallbackQuery
|
|||||||
from magic_filter import F
|
from magic_filter import F
|
||||||
from aiogram_dialog import Dialog, Window, DialogManager
|
from aiogram_dialog import Dialog, Window, DialogManager
|
||||||
from aiogram_dialog.widgets.text import Format, Const
|
from aiogram_dialog.widgets.text import Format, Const
|
||||||
from aiogram_dialog.widgets.kbd import Select, Cancel, Group
|
from aiogram_dialog.widgets.kbd import Select, Group, SwitchTo
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
from dishka.integrations.aiogram_dialog import inject
|
from dishka.integrations.aiogram_dialog import inject
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ async def on_resident_selected(
|
|||||||
|
|
||||||
registration_dialog = Dialog(
|
registration_dialog = Dialog(
|
||||||
Window(
|
Window(
|
||||||
Const("<blockquote>🏢 <b>Выбор этажа</b></blockquote>\n\nВыберите этаж, на котором вы живете:", when="has_available"),
|
Const("<blockquote>🏢 <b>Выбор этажа</b></blockquote>\n\n⚠️ <b>Внимание!</b> Перерегистрацию может выполнить только администратор. Выбирайте внимательно!\n\nВыберите этаж, на котором вы живете:", when="has_available"),
|
||||||
Const("<blockquote>⚠️ <b>Нет доступных резидентов</b></blockquote>\n\nВсе резиденты уже заняты.\nОбратитесь к администратору.", when=~F["has_available"]),
|
Const("<blockquote>⚠️ <b>Нет доступных резидентов</b></blockquote>\n\nВсе резиденты уже заняты.\nОбратитесь к администратору.", when=~F["has_available"]),
|
||||||
Group(
|
Group(
|
||||||
Select(
|
Select(
|
||||||
@@ -146,7 +146,7 @@ registration_dialog = Dialog(
|
|||||||
),
|
),
|
||||||
width=3,
|
width=3,
|
||||||
),
|
),
|
||||||
Cancel(Const("◀️ Назад")),
|
SwitchTo(Const("◀️ Назад"), id="back_to_floors", state=RegistrationSG.select_floor),
|
||||||
state=RegistrationSG.select_room,
|
state=RegistrationSG.select_room,
|
||||||
getter=get_rooms_data,
|
getter=get_rooms_data,
|
||||||
),
|
),
|
||||||
@@ -162,7 +162,7 @@ registration_dialog = Dialog(
|
|||||||
),
|
),
|
||||||
width=1,
|
width=1,
|
||||||
),
|
),
|
||||||
Cancel(Const("◀️ Назад")),
|
SwitchTo(Const("◀️ Назад"), id="back_to_rooms", state=RegistrationSG.select_room),
|
||||||
state=RegistrationSG.select_resident,
|
state=RegistrationSG.select_resident,
|
||||||
getter=get_residents_data,
|
getter=get_residents_data,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ from aiogram.fsm.state import State, StatesGroup
|
|||||||
class MainMenuSG(StatesGroup):
|
class MainMenuSG(StatesGroup):
|
||||||
main = State()
|
main = State()
|
||||||
history = State()
|
history = State()
|
||||||
|
faq = State()
|
||||||
|
|
||||||
|
|
||||||
class AdminMenuSG(StatesGroup):
|
class AdminMenuSG(StatesGroup):
|
||||||
main = State()
|
main = State()
|
||||||
users_list = State()
|
residents = State()
|
||||||
|
users = State()
|
||||||
statistics = State()
|
statistics = State()
|
||||||
broadcast = State()
|
broadcast = State()
|
||||||
|
broadcast_confirm = State()
|
||||||
|
|
||||||
|
|
||||||
class RegistrationSG(StatesGroup):
|
class RegistrationSG(StatesGroup):
|
||||||
|
|||||||
Reference in New Issue
Block a user