mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 10:25:29 +03:00
update
This commit is contained in:
@@ -7,6 +7,7 @@ from dutylog.application.bot.admin_dialogs.main_menu import (
|
||||
from dutylog.application.bot.admin_dialogs.residents_management import (
|
||||
residents_list_window,
|
||||
resident_info_window,
|
||||
resident_history_window,
|
||||
resident_logout_confirm_window,
|
||||
resident_delete_confirm_window,
|
||||
resident_rebind_floor_window,
|
||||
@@ -34,6 +35,7 @@ from dutylog.application.bot.admin_dialogs.rooms_management import (
|
||||
rooms_select_floor_window,
|
||||
rooms_list_window,
|
||||
room_info_window,
|
||||
room_history_window,
|
||||
room_delete_confirm_window,
|
||||
room_add_hours_select_window,
|
||||
room_remove_hours_select_window,
|
||||
@@ -76,6 +78,7 @@ admin_menu_dialog = Dialog(
|
||||
main_menu_window,
|
||||
residents_list_window,
|
||||
resident_info_window,
|
||||
resident_history_window,
|
||||
resident_logout_confirm_window,
|
||||
resident_delete_confirm_window,
|
||||
resident_rebind_floor_window,
|
||||
@@ -100,6 +103,7 @@ admin_menu_dialog = Dialog(
|
||||
rooms_select_floor_window,
|
||||
rooms_list_window,
|
||||
room_info_window,
|
||||
room_history_window,
|
||||
room_delete_confirm_window,
|
||||
room_add_hours_select_window,
|
||||
room_remove_hours_select_window,
|
||||
|
||||
@@ -7,73 +7,48 @@ from dutylog.application.bot.user_dialogs.states import AdminMenuSG
|
||||
|
||||
async def get_admin_faq_data(**kwargs) -> dict[str, str]:
|
||||
content = """
|
||||
<blockquote>❓ <b>Гайд по админке</b></blockquote>
|
||||
<blockquote>❓ <b>О боте DutyLog</b></blockquote>
|
||||
|
||||
Привет! Ты теперь админ, и это круто 😎
|
||||
Давай разберёмся, что тут к чему.
|
||||
<b>DutyLog</b> — система учёта дежурных часов для общежития.
|
||||
|
||||
<blockquote><b>🏠 Резиденты — твоя главная тусовка</b>
|
||||
<b>Основные возможности:</b>
|
||||
|
||||
Здесь живут все жители общаги. Можешь:
|
||||
• Искать кого угодно — по имени, комнате или нику в телеге
|
||||
• Фильтровать по часам (кто должник, а кто молодец)
|
||||
• Начислять или снимать часы (не забывай писать за что!)
|
||||
• Добавлять новых людей или удалять старых
|
||||
• Отвязывать юзеров от резидентов (если кто-то съехал)</blockquote>
|
||||
<blockquote><b>👥 Управление резидентами</b>
|
||||
• Регистрация жителей общежития
|
||||
• Привязка к комнатам и пользователям Telegram
|
||||
• Учёт отработанных и неотработанных часов
|
||||
• Поиск по имени, комнате или username
|
||||
• Фильтрация по количеству часов</blockquote>
|
||||
|
||||
<blockquote><b>🚪 Комнаты и 🏢 Этажи</b>
|
||||
<blockquote><b>🏢 Структура общежития</b>
|
||||
• Управление этажами и комнатами
|
||||
• Учёт часов как для резидентов, так и для комнат
|
||||
• Просмотр жителей каждой комнаты</blockquote>
|
||||
|
||||
Тут всё просто — структура общежития.
|
||||
Добавляй этажи, создавай комнаты, привязывай их друг к другу.
|
||||
Без этого резидентов не создать!</blockquote>
|
||||
<blockquote><b>⏰ Учёт часов</b>
|
||||
• Начисление неотработанных часов
|
||||
• Списание часов (перевод в отработанные)
|
||||
• История всех операций с примечаниями
|
||||
• Уведомления резидентам об изменениях</blockquote>
|
||||
|
||||
<blockquote><b>📅 Отчётный период — важная штука!</b>
|
||||
<blockquote><b>📅 Отчётные периоды</b>
|
||||
• Создание периодов учёта (обычно месяц)
|
||||
• Автоматическое закрытие предыдущего периода
|
||||
• Генерация Excel-отчётов за период
|
||||
• 4 листа в отчёте: начисления/списания для резидентов и комнат</blockquote>
|
||||
|
||||
Система работает по месяцам. Один период = один месяц учёта.
|
||||
<blockquote><b>📊 Статистика и отчёты</b>
|
||||
• Общая статистика по резидентам и комнатам
|
||||
• Количество пользователей и админов
|
||||
• Детальные отчёты в формате Excel
|
||||
• История операций для каждого резидента и комнаты</blockquote>
|
||||
|
||||
Как это работает:
|
||||
• Создаёшь новый период → старый автоматом закрывается
|
||||
• Дата начала = когда создал
|
||||
• Дата конца = когда создал следующий
|
||||
<blockquote><b>📢 Дополнительно</b>
|
||||
• Рассылка сообщений всем пользователям
|
||||
• Разграничение прав (админы/пользователи)
|
||||
• Админы не участвуют в учёте часов</blockquote>
|
||||
|
||||
Зачем это нужно? Чтобы потом сделать красивый отчёт за месяц и показать всем, кто сколько отработал.</blockquote>
|
||||
|
||||
<blockquote><b>📊 Отчёты — твоя суперсила</b>
|
||||
|
||||
После закрытия периода можешь сгенерить Excel-файл.
|
||||
В нём будет:
|
||||
• Все начисления и списания за месяц
|
||||
• Кто и когда начислил/снял часы
|
||||
• Примечания (поэтому их важно писать!)
|
||||
• Автоматический подсчёт итогов
|
||||
|
||||
Отчёт можно скинуть старосте или куратору — всё наглядно.</blockquote>
|
||||
|
||||
<blockquote><b>📊 Статистика</b>
|
||||
|
||||
Быстрый взгляд на цифры:
|
||||
• Сколько всего людей в системе
|
||||
• Сколько админов (ты не один!)
|
||||
• Общая сумма часов по всем резидентам</blockquote>
|
||||
|
||||
<blockquote><b>📢 Рассылка</b>
|
||||
|
||||
Нужно всем что-то сообщить? Жми сюда.
|
||||
Сообщение улетит всем пользователям бота.
|
||||
Можно использовать HTML для красоты.</blockquote>
|
||||
|
||||
<blockquote><b>💡 Лайфхаки:</b>
|
||||
|
||||
• <b>Всегда пиши примечание</b> при начислении/списании часов
|
||||
Через месяц никто не вспомнит, за что было
|
||||
|
||||
• <b>Делай отчёты регулярно</b>
|
||||
Это твоя страховка и архив данных
|
||||
|
||||
• <b>Проверяй статистику</b> перед закрытием периода
|
||||
Вдруг что-то забыл начислить?</blockquote>
|
||||
|
||||
Вопросы? Пиши создателю бота 👑
|
||||
<b>Для вопросов и предложений обращайтесь к создателю бота.</b>
|
||||
"""
|
||||
|
||||
return {"content": content}
|
||||
|
||||
@@ -20,6 +20,10 @@ from dutylog.infrastructure.database.repositories.floors_repository import (
|
||||
from dutylog.infrastructure.database.repositories.users_repository import (
|
||||
UsersRepository,
|
||||
)
|
||||
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
|
||||
HoursTransactionsRepository,
|
||||
)
|
||||
from dutylog.infrastructure.utils.datetime import msk_now
|
||||
|
||||
|
||||
@inject
|
||||
@@ -546,6 +550,58 @@ async def on_search_resident_selected(
|
||||
await dialog_manager.switch_to(AdminMenuSG.resident_info)
|
||||
|
||||
|
||||
@inject
|
||||
async def get_resident_history_data(
|
||||
dialog_manager: DialogManager,
|
||||
residents_repository: FromDishka[ResidentsRepository],
|
||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||
**kwargs,
|
||||
):
|
||||
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
|
||||
|
||||
if not resident_id:
|
||||
return {"history_content": "Ошибка: резидент не выбран"}
|
||||
|
||||
resident = await residents_repository.get_resident_by_id(resident_id)
|
||||
|
||||
if not resident:
|
||||
return {"history_content": "Ошибка: резидент не найден"}
|
||||
|
||||
transactions = await transactions_repository.get_resident_history(resident_id)
|
||||
transactions_sorted = sorted(transactions, key=lambda x: x.created_at)
|
||||
last_10 = transactions_sorted[-10:]
|
||||
|
||||
resident_name = resident.real_name if resident.real_name else "Без имени"
|
||||
|
||||
if not last_10:
|
||||
history_text = f"""
|
||||
<blockquote>📜 <b>История операций</b></blockquote>
|
||||
|
||||
<b>Резидент:</b> {resident_name}
|
||||
|
||||
<i>История операций пуста</i>
|
||||
"""
|
||||
else:
|
||||
history_text = f"""
|
||||
<blockquote>📜 <b>История операций</b></blockquote>
|
||||
|
||||
<b>Резидент:</b> {resident_name}
|
||||
|
||||
"""
|
||||
for tx in last_10:
|
||||
operation = "Начислено" if tx.transaction_type == "increase" else "Списано"
|
||||
emoji = "+" if tx.transaction_type == "increase" else "−"
|
||||
|
||||
msk_time = tx.created_at.astimezone(msk_now().tzinfo).replace(tzinfo=None)
|
||||
date_str = msk_time.strftime("%d.%m.%Y %H:%M")
|
||||
|
||||
remark_text = f"\n💬 <i>{tx.remark}</i>" if tx.remark else ""
|
||||
|
||||
history_text += f"<blockquote><b>{operation}</b> {emoji}<code>{tx.amount}</code> ч\n📅 {date_str}{remark_text}</blockquote>\n"
|
||||
|
||||
return {"history_content": history_text}
|
||||
|
||||
|
||||
residents_list_window = Window(
|
||||
Format("{content}"),
|
||||
Row(
|
||||
@@ -602,6 +658,12 @@ resident_info_window = Window(
|
||||
when=~F["is_admin"],
|
||||
),
|
||||
),
|
||||
Button(
|
||||
Const("📜 История"),
|
||||
id="resident_history_btn",
|
||||
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_history),
|
||||
when=~F["is_admin"],
|
||||
),
|
||||
Button(
|
||||
Const("🔄 Перепривязать к комнате"),
|
||||
id="rebind_resident_btn",
|
||||
@@ -850,3 +912,14 @@ resident_rebind_confirm_window = Window(
|
||||
state=AdminMenuSG.resident_rebind_confirm,
|
||||
getter=get_rebind_confirm_data,
|
||||
)
|
||||
|
||||
resident_history_window = Window(
|
||||
Format("{history_content}"),
|
||||
SwitchTo(
|
||||
Const("◀️ Назад"),
|
||||
id="back_to_resident_info",
|
||||
state=AdminMenuSG.resident_info,
|
||||
),
|
||||
state=AdminMenuSG.resident_history,
|
||||
getter=get_resident_history_data,
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ from dutylog.infrastructure.database.repositories.room_hours_transactions_reposi
|
||||
from dutylog.infrastructure.database.repositories.users_repository import (
|
||||
UsersRepository,
|
||||
)
|
||||
from dutylog.infrastructure.utils.datetime import msk_now
|
||||
|
||||
|
||||
async def on_rooms_click(
|
||||
@@ -533,6 +534,56 @@ async def on_delete_room_cancel(
|
||||
await dialog_manager.switch_to(AdminMenuSG.room_info)
|
||||
|
||||
|
||||
@inject
|
||||
async def get_room_history_data(
|
||||
dialog_manager: DialogManager,
|
||||
rooms_repository: FromDishka[RoomsRepository],
|
||||
room_transactions_repository: FromDishka[RoomHoursTransactionsRepository],
|
||||
**kwargs,
|
||||
):
|
||||
room_id = dialog_manager.dialog_data.get("selected_room_id")
|
||||
|
||||
if not room_id:
|
||||
return {"history_content": "Ошибка: комната не выбрана"}
|
||||
|
||||
room = await rooms_repository.get_room_by_id(room_id)
|
||||
|
||||
if not room:
|
||||
return {"history_content": "Ошибка: комната не найдена"}
|
||||
|
||||
transactions = await room_transactions_repository.get_room_history(room_id)
|
||||
transactions_sorted = sorted(transactions, key=lambda x: x.created_at)
|
||||
last_10 = transactions_sorted[-10:]
|
||||
|
||||
if not last_10:
|
||||
history_text = f"""
|
||||
<blockquote>📜 <b>История операций</b></blockquote>
|
||||
|
||||
<b>Комната:</b> {room.number}
|
||||
|
||||
<i>История операций пуста</i>
|
||||
"""
|
||||
else:
|
||||
history_text = f"""
|
||||
<blockquote>📜 <b>История операций</b></blockquote>
|
||||
|
||||
<b>Комната:</b> {room.number}
|
||||
|
||||
"""
|
||||
for tx in last_10:
|
||||
operation = "Начислено" if tx.transaction_type == "increase" else "Списано"
|
||||
emoji = "+" if tx.transaction_type == "increase" else "−"
|
||||
|
||||
msk_time = tx.created_at.astimezone(msk_now().tzinfo).replace(tzinfo=None)
|
||||
date_str = msk_time.strftime("%d.%m.%Y %H:%M")
|
||||
|
||||
remark_text = f"\n💬 <i>{tx.remark}</i>" if tx.remark else ""
|
||||
|
||||
history_text += f"<blockquote><b>{operation}</b> {emoji}<code>{tx.amount}</code> ч\n📅 {date_str}{remark_text}</blockquote>\n"
|
||||
|
||||
return {"history_content": history_text}
|
||||
|
||||
|
||||
rooms_select_floor_window = Window(
|
||||
Format("{content}"),
|
||||
Group(
|
||||
@@ -596,6 +647,11 @@ room_info_window = Window(
|
||||
on_click=on_room_remove_hours_click,
|
||||
),
|
||||
),
|
||||
Button(
|
||||
Const("📜 История"),
|
||||
id="room_history_btn",
|
||||
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.room_history),
|
||||
),
|
||||
Button(
|
||||
Const("🗑 Удалить комнату"),
|
||||
id="delete_room_btn",
|
||||
@@ -819,3 +875,14 @@ create_room_confirm_window = Window(
|
||||
state=AdminMenuSG.create_room_confirm,
|
||||
getter=get_create_room_confirm_data,
|
||||
)
|
||||
|
||||
room_history_window = Window(
|
||||
Format("{history_content}"),
|
||||
SwitchTo(
|
||||
Const("◀️ Назад"),
|
||||
id="back_to_room_info",
|
||||
state=AdminMenuSG.room_info,
|
||||
),
|
||||
state=AdminMenuSG.room_history,
|
||||
getter=get_room_history_data,
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ class AdminMenuSG(StatesGroup):
|
||||
residents_filter_hours_input = State()
|
||||
residents_filtered_results = State()
|
||||
resident_info = State()
|
||||
resident_history = State()
|
||||
resident_logout_confirm = State()
|
||||
resident_delete_confirm = State()
|
||||
resident_rebind_floor = State()
|
||||
@@ -41,6 +42,7 @@ class AdminMenuSG(StatesGroup):
|
||||
rooms_select_floor = State()
|
||||
rooms_list = State()
|
||||
room_info = State()
|
||||
room_history = State()
|
||||
room_delete_confirm = State()
|
||||
room_add_hours_select = State()
|
||||
room_remove_hours_select = State()
|
||||
|
||||
Reference in New Issue
Block a user