mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 10:25:29 +03:00
update
This commit is contained in:
@@ -18,6 +18,9 @@ from dutylog.infrastructure.database.repositories.residents_repository import (
|
|||||||
from dutylog.infrastructure.database.repositories.rooms_repository import (
|
from dutylog.infrastructure.database.repositories.rooms_repository import (
|
||||||
RoomsRepository,
|
RoomsRepository,
|
||||||
)
|
)
|
||||||
|
from dutylog.infrastructure.database.repositories.floors_repository import (
|
||||||
|
FloorsRepository,
|
||||||
|
)
|
||||||
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
|
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
|
||||||
HoursTransactionsRepository,
|
HoursTransactionsRepository,
|
||||||
)
|
)
|
||||||
@@ -84,8 +87,8 @@ async def get_statistics_data(
|
|||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
🟢 <b>Всего активных часов:</b> <code>{total_active_hours}</code> ч
|
🟢 <b>Всего отработанных часов:</b> <code>{total_inactive_hours}</code> ч
|
||||||
🔴 <b>Всего неактивных часов:</b> <code>{total_inactive_hours}</code> ч
|
🔴 <b>Всего неотработанных часов:</b> <code>{total_active_hours}</code> ч
|
||||||
📊 <b>Общий итог:</b> <code>{total_active_hours + total_inactive_hours}</code> ч
|
📊 <b>Общий итог:</b> <code>{total_active_hours + total_inactive_hours}</code> ч
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -244,9 +247,8 @@ async def get_resident_info_data(
|
|||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
🟢 <b>Активные часы:</b> <code>{resident.active_hours}</code> ч
|
🟢 <b>Отработанные часы:</b> <code>{resident.inactive_hours}</code> ч
|
||||||
🔴 <b>Неактивные часы:</b> <code>{resident.inactive_hours}</code> ч
|
🔴 <b>Неотработанные часы:</b> <code>{resident.active_hours}</code> ч
|
||||||
📊 <b>Всего часов:</b> <code>{resident.active_hours + resident.inactive_hours}</code> ч
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -270,7 +272,108 @@ async def on_add_resident(
|
|||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
):
|
):
|
||||||
await callback.answer("⚠️ Функционал в разработке", show_alert=True)
|
await dialog_manager.switch_to(AdminMenuSG.create_resident_name)
|
||||||
|
|
||||||
|
|
||||||
|
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_filter_residents(
|
async def on_filter_residents(
|
||||||
@@ -289,6 +392,22 @@ async def on_search_residents(
|
|||||||
await callback.answer("⚠️ Функционал в разработке", show_alert=True)
|
await callback.answer("⚠️ Функционал в разработке", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_rooms_click(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
button: Button,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
):
|
||||||
|
await callback.answer("⚠️ Функционал в разработке", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_floors_click(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
button: Button,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
):
|
||||||
|
await callback.answer("⚠️ Функционал в разработке", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
async def on_logout_resident_confirm(
|
async def on_logout_resident_confirm(
|
||||||
callback: CallbackQuery,
|
callback: CallbackQuery,
|
||||||
@@ -408,6 +527,7 @@ async def on_add_hours_confirm(
|
|||||||
admin_id = callback.from_user.id
|
admin_id = callback.from_user.id
|
||||||
|
|
||||||
if resident_id and hours:
|
if resident_id and hours:
|
||||||
|
# Добавляем часы к неотработанным (active_hours)
|
||||||
await transactions_repository.add_hours(
|
await transactions_repository.add_hours(
|
||||||
resident_id=resident_id,
|
resident_id=resident_id,
|
||||||
amount=hours,
|
amount=hours,
|
||||||
@@ -424,17 +544,27 @@ async def on_remove_hours_confirm(
|
|||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||||
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
):
|
):
|
||||||
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
|
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
|
||||||
hours = dialog_manager.dialog_data.get("selected_hours")
|
hours = dialog_manager.dialog_data.get("selected_hours")
|
||||||
admin_id = callback.from_user.id
|
admin_id = callback.from_user.id
|
||||||
|
|
||||||
if resident_id and hours:
|
if resident_id and hours:
|
||||||
await transactions_repository.remove_hours(
|
resident = await residents_repository.get_resident_by_id(resident_id)
|
||||||
|
if resident and resident.active_hours < hours:
|
||||||
|
await callback.answer(
|
||||||
|
f"⚠️ Недостаточно часов! У резидента {resident.active_hours} неотработанных ч, а вы пытаетесь отнять {hours} ч",
|
||||||
|
show_alert=True
|
||||||
|
)
|
||||||
|
await dialog_manager.switch_to(AdminMenuSG.resident_info)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Перемещаем часы из неотработанных в отработанные
|
||||||
|
await transactions_repository.move_hours_to_completed(
|
||||||
resident_id=resident_id,
|
resident_id=resident_id,
|
||||||
amount=hours,
|
amount=hours,
|
||||||
admin_id=admin_id,
|
admin_id=admin_id,
|
||||||
is_active=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await dialog_manager.switch_to(AdminMenuSG.resident_info)
|
await dialog_manager.switch_to(AdminMenuSG.resident_info)
|
||||||
@@ -456,6 +586,18 @@ admin_menu_dialog = Dialog(
|
|||||||
id="residents_btn",
|
id="residents_btn",
|
||||||
state=AdminMenuSG.residents,
|
state=AdminMenuSG.residents,
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
Button(
|
||||||
|
Const("🚪 Комнаты"),
|
||||||
|
id="rooms_btn",
|
||||||
|
on_click=on_rooms_click,
|
||||||
|
),
|
||||||
|
Button(
|
||||||
|
Const("🏢 Этажи"),
|
||||||
|
id="floors_btn",
|
||||||
|
on_click=on_floors_click,
|
||||||
|
),
|
||||||
|
),
|
||||||
SwitchTo(
|
SwitchTo(
|
||||||
Const("📊 Статистика"),
|
Const("📊 Статистика"),
|
||||||
id="stats_btn",
|
id="stats_btn",
|
||||||
@@ -512,12 +654,12 @@ admin_menu_dialog = Dialog(
|
|||||||
Format("{info_content}"),
|
Format("{info_content}"),
|
||||||
Row(
|
Row(
|
||||||
Button(
|
Button(
|
||||||
Const("➕ Добавить часы"),
|
Const("Добавить часы"),
|
||||||
id="add_hours_btn",
|
id="add_hours_btn",
|
||||||
on_click=on_add_hours_click,
|
on_click=on_add_hours_click,
|
||||||
),
|
),
|
||||||
Button(
|
Button(
|
||||||
Const("➖ Отнять часы"),
|
Const("Отнять часы"),
|
||||||
id="remove_hours_btn",
|
id="remove_hours_btn",
|
||||||
on_click=on_remove_hours_click,
|
on_click=on_remove_hours_click,
|
||||||
),
|
),
|
||||||
@@ -658,6 +800,73 @@ admin_menu_dialog = Dialog(
|
|||||||
state=AdminMenuSG.remove_hours_confirm,
|
state=AdminMenuSG.remove_hours_confirm,
|
||||||
getter=get_hours_confirm_data,
|
getter=get_hours_confirm_data,
|
||||||
),
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
Window(
|
Window(
|
||||||
Format("{stats_content}"),
|
Format("{stats_content}"),
|
||||||
SwitchTo(Const("◀️ Назад"), id="back_from_stats", state=AdminMenuSG.main),
|
SwitchTo(Const("◀️ Назад"), id="back_from_stats", state=AdminMenuSG.main),
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ async def get_main_menu_data(
|
|||||||
|
|
||||||
⏰ <b>Ваши часы дежурств</b>
|
⏰ <b>Ваши часы дежурств</b>
|
||||||
|
|
||||||
<blockquote>🟢 <b>Отработанные часы:</b> <code>{resident.active_hours}</code> ч
|
<blockquote>🟢 <b>Отработанные часы:</b> <code>{resident.inactive_hours}</code> ч
|
||||||
━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━
|
||||||
🔴 Неотработанные часы: <code>{resident.inactive_hours}</code> ч</blockquote>
|
🔴 Неотработанные часы: <code>{resident.active_hours}</code> ч</blockquote>
|
||||||
"""
|
"""
|
||||||
has_resident = True
|
has_resident = True
|
||||||
else:
|
else:
|
||||||
@@ -120,7 +120,8 @@ async def get_history_data(
|
|||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
transactions = await transactions_repository.get_resident_history(resident.id)
|
transactions = await transactions_repository.get_resident_history(resident.id)
|
||||||
last_10 = transactions[:10]
|
transactions_sorted = sorted(transactions, key=lambda x: x.created_at)
|
||||||
|
last_10 = transactions_sorted[:10]
|
||||||
|
|
||||||
if not last_10:
|
if not last_10:
|
||||||
history_text = """
|
history_text = """
|
||||||
@@ -131,7 +132,7 @@ async def get_history_data(
|
|||||||
else:
|
else:
|
||||||
history_lines = []
|
history_lines = []
|
||||||
for tx in last_10:
|
for tx in last_10:
|
||||||
emoji = "➕" if tx.transaction_type == "increase" else "➖"
|
emoji = "+" if tx.transaction_type == "increase" else "-"
|
||||||
date_str = tx.created_at.strftime("%d.%m.%Y %H:%M")
|
date_str = tx.created_at.strftime("%d.%m.%Y %H:%M")
|
||||||
history_lines.append(
|
history_lines.append(
|
||||||
f"{emoji} <code>{tx.amount}</code> ч • <i>{date_str}</i>"
|
f"{emoji} <code>{tx.amount}</code> ч • <i>{date_str}</i>"
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ class AdminMenuSG(StatesGroup):
|
|||||||
remove_hours_custom = State()
|
remove_hours_custom = State()
|
||||||
add_hours_confirm = State()
|
add_hours_confirm = State()
|
||||||
remove_hours_confirm = State()
|
remove_hours_confirm = State()
|
||||||
|
create_resident_name = State()
|
||||||
|
create_resident_floor = State()
|
||||||
|
create_resident_room = State()
|
||||||
|
create_resident_confirm = State()
|
||||||
statistics = State()
|
statistics = State()
|
||||||
broadcast = State()
|
broadcast = State()
|
||||||
broadcast_confirm = State()
|
broadcast_confirm = State()
|
||||||
|
|||||||
@@ -78,6 +78,33 @@ class HoursTransactionsRepository:
|
|||||||
|
|
||||||
return transaction, resident
|
return transaction, resident
|
||||||
|
|
||||||
|
async def move_hours_to_completed(
|
||||||
|
self,
|
||||||
|
resident_id: int,
|
||||||
|
amount: int,
|
||||||
|
admin_id: int | None = None,
|
||||||
|
) -> tuple[HoursTransaction, Resident | None]:
|
||||||
|
"""Перемещает часы из неотработанных в отработанные"""
|
||||||
|
transaction = HoursTransaction(
|
||||||
|
resident_id=resident_id,
|
||||||
|
transaction_type=TransactionType.DECREASE.value,
|
||||||
|
amount=amount,
|
||||||
|
admin_id=admin_id,
|
||||||
|
)
|
||||||
|
transaction = await self.transactions_dao.create(transaction)
|
||||||
|
|
||||||
|
resident = await self.residents_dao.get_by_id(resident_id)
|
||||||
|
if resident:
|
||||||
|
new_active = max(0, resident.active_hours - amount)
|
||||||
|
new_inactive = resident.inactive_hours + amount
|
||||||
|
resident = await self.residents_dao.update(
|
||||||
|
resident_id,
|
||||||
|
active_hours=new_active,
|
||||||
|
inactive_hours=new_inactive
|
||||||
|
)
|
||||||
|
|
||||||
|
return transaction, resident
|
||||||
|
|
||||||
async def get_resident_history(self, resident_id: int) -> list[HoursTransaction]:
|
async def get_resident_history(self, resident_id: int) -> list[HoursTransaction]:
|
||||||
return await self.transactions_dao.get_by_resident_id(resident_id)
|
return await self.transactions_dao.get_by_resident_id(resident_id)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user