mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 18:35:29 +03:00
update
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from aiogram import Bot
|
||||
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.exceptions import TelegramForbiddenError
|
||||
from aiogram_dialog import Window, DialogManager
|
||||
from aiogram_dialog.widgets.text import Format, Const
|
||||
from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button, Select, Group
|
||||
@@ -173,17 +173,11 @@ async def on_add_hours_confirm(
|
||||
admin = await users_repository.get_user_by_id(admin_id)
|
||||
admin_username = f"@{admin.username}" if admin and admin.username else "Администратор"
|
||||
|
||||
notification_text = (
|
||||
f"<blockquote>➕ <b>Начислены часы</b></blockquote>\n\n"
|
||||
f"<b>Количество:</b> <code>{hours}</code> ч\n"
|
||||
f"<b>Причина:</b> {remark}\n"
|
||||
f"<b>Администратор:</b> {admin_username}\n\n"
|
||||
f"<b>Всего неотработанных часов:</b> <code>{resident.active_hours}</code> ч"
|
||||
)
|
||||
notification_text = f"<blockquote>➕ <b>Начислены часы</b></blockquote>\n\n<b>Количество:</b> <code>{hours}</code> ч\n<b>Причина:</b> {remark}\n<b>Администратор:</b> {admin_username}\n\n<b>Всего неотработанных часов:</b> <code>{resident.active_hours}</code> ч"
|
||||
|
||||
try:
|
||||
await bot.send_message(resident.user_entity, notification_text)
|
||||
except TelegramForbiddenError:
|
||||
except (TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter):
|
||||
pass
|
||||
|
||||
await dialog_manager.switch_to(AdminMenuSG.resident_info)
|
||||
@@ -227,29 +221,19 @@ async def on_remove_hours_confirm(
|
||||
admin_username = f"@{admin.username}" if admin and admin.username else "Администратор"
|
||||
|
||||
if resident.active_hours == 0:
|
||||
notification_text = (
|
||||
f"<blockquote>🎉 <b>Поздравляем!</b></blockquote>\n\n"
|
||||
f"Вы отработали все часы! Теперь у вас <code>0</code> неотработанных часов.\n\n"
|
||||
f"<b>Списано:</b> <code>{hours}</code> ч\n"
|
||||
)
|
||||
notification_text = f"<blockquote>🎉 <b>Поздравляем!</b></blockquote>\n\nВы отработали все часы! Теперь у вас <code>0</code> неотработанных часов.\n\n<b>Списано:</b> <code>{hours}</code> ч\n"
|
||||
if remark:
|
||||
notification_text += f"<b>Причина:</b> {remark}\n"
|
||||
notification_text += f"<b>Администратор:</b> {admin_username}"
|
||||
else:
|
||||
notification_text = (
|
||||
f"<blockquote>➖ <b>Списаны часы</b></blockquote>\n\n"
|
||||
f"<b>Количество:</b> <code>{hours}</code> ч\n"
|
||||
)
|
||||
notification_text = f"<blockquote>➖ <b>Списаны часы</b></blockquote>\n\n<b>Количество:</b> <code>{hours}</code> ч\n"
|
||||
if remark:
|
||||
notification_text += f"<b>Причина:</b> {remark}\n"
|
||||
notification_text += (
|
||||
f"<b>Администратор:</b> {admin_username}\n\n"
|
||||
f"<b>Осталось неотработанных часов:</b> <code>{resident.active_hours}</code> ч"
|
||||
)
|
||||
notification_text += f"<b>Администратор:</b> {admin_username}\n\n<b>Осталось неотработанных часов:</b> <code>{resident.active_hours}</code> ч"
|
||||
|
||||
try:
|
||||
await bot.send_message(resident.user_entity, notification_text)
|
||||
except TelegramForbiddenError:
|
||||
except (TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter):
|
||||
pass
|
||||
|
||||
await dialog_manager.switch_to(AdminMenuSG.resident_info)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from aiogram import Bot
|
||||
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram_dialog import Window, DialogManager
|
||||
from aiogram_dialog.widgets.text import Format, Const
|
||||
@@ -16,8 +18,8 @@ from dutylog.infrastructure.database.repositories.floors_repository import (
|
||||
from dutylog.infrastructure.database.repositories.residents_repository import (
|
||||
ResidentsRepository,
|
||||
)
|
||||
from dutylog.infrastructure.database.repositories.room_hours_transactions_repository import (
|
||||
RoomHoursTransactionsRepository,
|
||||
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
|
||||
HoursTransactionsRepository,
|
||||
)
|
||||
from dutylog.infrastructure.database.repositories.users_repository import (
|
||||
UsersRepository,
|
||||
@@ -43,11 +45,7 @@ async def get_rooms_floors_data(
|
||||
|
||||
floors_data = [(f"🏢 Этаж {f.number}", f.id) for f in all_floors]
|
||||
|
||||
content = """
|
||||
<blockquote>🚪 <b>Комнаты</b></blockquote>
|
||||
|
||||
Выберите этаж для просмотра комнат:
|
||||
"""
|
||||
content = "<blockquote>🚪 <b>Комнаты</b></blockquote>\n\nВыберите этаж для просмотра комнат:"
|
||||
|
||||
return {
|
||||
"content": content,
|
||||
@@ -89,13 +87,7 @@ async def get_rooms_list_data(
|
||||
|
||||
rooms_data = [(f"🚪 Комната {r.number}", r.id) for r in rooms]
|
||||
|
||||
content = f"""
|
||||
<blockquote>🚪 <b>Комнаты на этаже {floor_number}</b></blockquote>
|
||||
|
||||
<b>Всего комнат:</b> <code>{len(rooms)}</code>
|
||||
|
||||
Выберите комнату для просмотра:
|
||||
"""
|
||||
content = f"<blockquote>🚪 <b>Комнаты на этаже {floor_number}</b></blockquote>\n\n<b>Всего комнат:</b> <code>{len(rooms)}</code>\n\nВыберите комнату для просмотра:"
|
||||
|
||||
return {
|
||||
"content": content,
|
||||
@@ -132,11 +124,7 @@ async def get_create_room_floors_data(
|
||||
|
||||
floors_data = [(f"🏢 Этаж {f.number}", f.id) for f in all_floors]
|
||||
|
||||
content = """
|
||||
<blockquote>➕ <b>Создание комнаты</b></blockquote>
|
||||
|
||||
Выберите этаж для новой комнаты:
|
||||
"""
|
||||
content = "<blockquote>➕ <b>Создание комнаты</b></blockquote>\n\nВыберите этаж для новой комнаты:"
|
||||
|
||||
return {
|
||||
"content": content,
|
||||
@@ -238,28 +226,22 @@ async def get_room_info_data(
|
||||
floor = await floors_repository.get_floor_by_id(room.on_floor)
|
||||
floor_number = floor.number if floor else "???"
|
||||
|
||||
# Получаем резидентов комнаты
|
||||
residents = await residents_repository.get_residents_by_room(room_id)
|
||||
|
||||
total_active_hours = sum(r.active_hours for r in residents)
|
||||
total_inactive_hours = sum(r.inactive_hours for r in residents)
|
||||
|
||||
residents_info = ""
|
||||
if residents:
|
||||
residents_info = "\n<b>Проживающие:</b>\n"
|
||||
for resident in residents:
|
||||
status = "🟢" if resident.is_busy else "⚪️"
|
||||
name = resident.real_name if resident.real_name else "Без имени"
|
||||
residents_info += f"{status} {name}\n"
|
||||
residents_info += f"{status} {name} (<code>{resident.active_hours}</code> ч)\n"
|
||||
else:
|
||||
residents_info = "\n<i>Нет проживающих</i>\n"
|
||||
|
||||
info_content = f"""
|
||||
<blockquote>🚪 <b>Информация о комнате</b></blockquote>
|
||||
|
||||
<b>Номер:</b> <code>{room.number}</code>
|
||||
<b>Этаж:</b> <code>{floor_number}</code>
|
||||
{residents_info}
|
||||
🟢 <b>Отработанные часы:</b> <code>{room.inactive_hours}</code> ч
|
||||
🔴 <b>Неотработанные часы:</b> <code>{room.active_hours}</code> ч
|
||||
"""
|
||||
info_content = f"<blockquote>🚪 <b>Информация о комнате</b></blockquote>\n\n<b>Номер:</b> <code>{room.number}</code>\n<b>Этаж:</b> <code>{floor_number}</code>\n{residents_info}\n🟢 <b>Отработанные часы (всего):</b> <code>{total_inactive_hours}</code> ч\n🔴 <b>Неотработанные часы (всего):</b> <code>{total_active_hours}</code> ч"
|
||||
|
||||
return {
|
||||
"info_content": info_content,
|
||||
@@ -391,12 +373,11 @@ async def on_room_add_hours_confirm(
|
||||
callback: CallbackQuery,
|
||||
button: Button,
|
||||
dialog_manager: DialogManager,
|
||||
room_transactions_repository: FromDishka[RoomHoursTransactionsRepository],
|
||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||
residents_repository: FromDishka[ResidentsRepository],
|
||||
users_repository: FromDishka[UsersRepository],
|
||||
**kwargs,
|
||||
):
|
||||
from aiogram import Bot
|
||||
bot: Bot = dialog_manager.middleware_data.get("bot")
|
||||
|
||||
room_id = dialog_manager.dialog_data.get("selected_room_id")
|
||||
@@ -405,27 +386,26 @@ async def on_room_add_hours_confirm(
|
||||
admin_id = callback.from_user.id
|
||||
|
||||
if room_id and hours:
|
||||
transaction, _ = await room_transactions_repository.add_hours(
|
||||
results = await transactions_repository.add_hours_to_room(
|
||||
room_id=room_id,
|
||||
amount=hours,
|
||||
admin_id=admin_id,
|
||||
is_active=True,
|
||||
remark=remark,
|
||||
)
|
||||
|
||||
# Отправляем уведомления всем проживающим
|
||||
residents = await residents_repository.get_residents_by_room(room_id)
|
||||
for resident in residents:
|
||||
if resident.user_entity:
|
||||
for transaction, resident in results:
|
||||
if resident and resident.user_entity:
|
||||
user = await users_repository.get_user_by_id(resident.user_entity)
|
||||
if user:
|
||||
try:
|
||||
remark_text = f"\n💬 <i>{remark}</i>" if remark else ""
|
||||
await bot.send_message(
|
||||
user.id,
|
||||
f"<blockquote>📢 <b>Уведомление</b></blockquote>\n\n"
|
||||
f"<blockquote>� <b>Уведомление</b></blockq uote>\n\n"
|
||||
f"Вашей комнате начислено <b>+{hours}</b> ч{remark_text}"
|
||||
)
|
||||
except Exception:
|
||||
except (TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter):
|
||||
pass
|
||||
|
||||
await dialog_manager.switch_to(AdminMenuSG.room_info)
|
||||
@@ -436,13 +416,12 @@ async def on_room_remove_hours_confirm(
|
||||
callback: CallbackQuery,
|
||||
button: Button,
|
||||
dialog_manager: DialogManager,
|
||||
room_transactions_repository: FromDishka[RoomHoursTransactionsRepository],
|
||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||
rooms_repository: FromDishka[RoomsRepository],
|
||||
residents_repository: FromDishka[ResidentsRepository],
|
||||
users_repository: FromDishka[UsersRepository],
|
||||
**kwargs,
|
||||
):
|
||||
from aiogram import Bot
|
||||
bot: Bot = dialog_manager.middleware_data.get("bot")
|
||||
|
||||
room_id = dialog_manager.dialog_data.get("selected_room_id")
|
||||
@@ -451,25 +430,26 @@ async def on_room_remove_hours_confirm(
|
||||
admin_id = callback.from_user.id
|
||||
|
||||
if room_id and hours:
|
||||
room = await rooms_repository.get_room_by_id(room_id)
|
||||
if room and room.active_hours < hours:
|
||||
await callback.answer(
|
||||
f"⚠️ Недостаточно часов! У комнаты {room.active_hours} неотработанных ч, а вы пытаетесь отнять {hours} ч",
|
||||
show_alert=True
|
||||
)
|
||||
await dialog_manager.switch_to(AdminMenuSG.room_info)
|
||||
return
|
||||
residents = await residents_repository.get_residents_by_room(room_id)
|
||||
|
||||
await room_transactions_repository.move_hours_to_completed(
|
||||
for resident in residents:
|
||||
if resident.active_hours < hours:
|
||||
await callback.answer(
|
||||
f"⚠️ Недостаточно часов! У резидента {resident.real_name or 'без имени'} только {resident.active_hours} неотработанных ч, а вы пытаетесь отнять {hours} ч",
|
||||
show_alert=True
|
||||
)
|
||||
await dialog_manager.switch_to(AdminMenuSG.room_info)
|
||||
return
|
||||
|
||||
results = await transactions_repository.remove_hours_from_room(
|
||||
room_id=room_id,
|
||||
amount=hours,
|
||||
admin_id=admin_id,
|
||||
remark=remark,
|
||||
)
|
||||
|
||||
# Отправляем уведомления всем проживающим
|
||||
residents = await residents_repository.get_residents_by_room(room_id)
|
||||
for resident in residents:
|
||||
if resident.user_entity:
|
||||
for transaction, resident in results:
|
||||
if resident and resident.user_entity:
|
||||
user = await users_repository.get_user_by_id(resident.user_entity)
|
||||
if user:
|
||||
try:
|
||||
@@ -479,7 +459,7 @@ async def on_room_remove_hours_confirm(
|
||||
f"<blockquote>📢 <b>Уведомление</b></blockquote>\n\n"
|
||||
f"С вашей комнаты списано <b>-{hours}</b> ч{remark_text}"
|
||||
)
|
||||
except Exception:
|
||||
except (TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter):
|
||||
pass
|
||||
|
||||
await dialog_manager.switch_to(AdminMenuSG.room_info)
|
||||
@@ -538,7 +518,7 @@ async def on_delete_room_cancel(
|
||||
async def get_room_history_data(
|
||||
dialog_manager: DialogManager,
|
||||
rooms_repository: FromDishka[RoomsRepository],
|
||||
room_transactions_repository: FromDishka[RoomHoursTransactionsRepository],
|
||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||
**kwargs,
|
||||
):
|
||||
room_id = dialog_manager.dialog_data.get("selected_room_id")
|
||||
@@ -551,25 +531,13 @@ async def get_room_history_data(
|
||||
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:]
|
||||
transactions = await transactions_repository.get_room_transactions(room_id)
|
||||
last_10 = transactions[:10]
|
||||
|
||||
if not last_10:
|
||||
history_text = f"""
|
||||
<blockquote>📜 <b>История операций</b></blockquote>
|
||||
|
||||
<b>Комната:</b> {room.number}
|
||||
|
||||
<i>История операций пуста</i>
|
||||
"""
|
||||
history_text = f"<blockquote>📜 <b>История операций</b></blockquote>\n\n<b>Комната:</b> {room.number}\n\n<i>История операций пуста</i>"
|
||||
else:
|
||||
history_text = f"""
|
||||
<blockquote>📜 <b>История операций</b></blockquote>
|
||||
|
||||
<b>Комната:</b> {room.number}
|
||||
|
||||
"""
|
||||
history_text = f"<blockquote>📜 <b>История операций</b></blockquote>\n\n<b>Комната:</b> {room.number}\n\n"
|
||||
for tx in last_10:
|
||||
operation = "Начислено" if tx.transaction_type == "increase" else "Списано"
|
||||
emoji = "+" if tx.transaction_type == "increase" else "−"
|
||||
@@ -577,7 +545,7 @@ async def get_room_history_data(
|
||||
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 ""
|
||||
remark_text = f"\n💬 <i>{tx.remark}<t/i>" if tx.remark else ""
|
||||
|
||||
history_text += f"<blockquote><b>{operation}</b> {emoji}<code>{tx.amount}</code> ч\n📅 {date_str}{remark_text}</blockquote>\n"
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ from dutylog.infrastructure.database.repositories.rooms_repository import (
|
||||
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
|
||||
HoursTransactionsRepository,
|
||||
)
|
||||
from dutylog.infrastructure.database.repositories.room_hours_transactions_repository import (
|
||||
RoomHoursTransactionsRepository,
|
||||
)
|
||||
from dutylog.infrastructure.utils.datetime import msk_now
|
||||
|
||||
|
||||
@@ -27,38 +24,21 @@ async def get_history_data(
|
||||
residents_repository: FromDishka[ResidentsRepository],
|
||||
rooms_repository: FromDishka[RoomsRepository],
|
||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||
room_transactions_repository: FromDishka[RoomHoursTransactionsRepository],
|
||||
**kwargs,
|
||||
) -> dict[str, str]:
|
||||
resident = await residents_repository.get_resident_by_user_id(event_from_user.id)
|
||||
|
||||
if not resident:
|
||||
history_text = """
|
||||
<blockquote>📜 <b>История операций</b></blockquote>
|
||||
|
||||
⚠️ <i>Профиль не найден</i>
|
||||
"""
|
||||
history_text = "<blockquote>📜 <b>История операций</b></blockquote>\n\n⚠️ <i>Профиль не найден</i>"
|
||||
else:
|
||||
# История резидента
|
||||
transactions = await transactions_repository.get_resident_history(resident.id)
|
||||
transactions_sorted = sorted(transactions, key=lambda x: x.created_at)
|
||||
last_10 = transactions_sorted[-10:]
|
||||
|
||||
if not last_10:
|
||||
history_text = """
|
||||
📜 <b>История операций</b>
|
||||
|
||||
<b>👤 Ваши операции:</b>
|
||||
<i>История операций пуста</i>
|
||||
|
||||
"""
|
||||
history_text = "📜 <b>История операций</b>\n\n<b>👤 Ваши операции:</b>\n<i>История операций пуста</i>\n\n"
|
||||
else:
|
||||
history_text = """
|
||||
📜 <b>История операций</b>
|
||||
|
||||
<b>👤 Ваши операции:</b>
|
||||
|
||||
"""
|
||||
history_text = "📜 <b>История операций</b>\n\n<b>👤 Ваши операции:</b>\n\n"
|
||||
for tx in last_10:
|
||||
operation = "Начислено" if tx.transaction_type == "increase" else "Списано"
|
||||
emoji = "+" if tx.transaction_type == "increase" else "−"
|
||||
@@ -68,35 +48,9 @@ async def get_history_data(
|
||||
|
||||
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"
|
||||
|
||||
# История комнаты
|
||||
room = await rooms_repository.get_room_by_id(resident.room)
|
||||
if room:
|
||||
room_transactions = await room_transactions_repository.get_room_history(room.id)
|
||||
room_transactions_sorted = sorted(room_transactions, key=lambda x: x.created_at)
|
||||
last_10_room = room_transactions_sorted[-10:]
|
||||
|
||||
if not last_10_room:
|
||||
history_text += """
|
||||
<b>🚪 Операции комнаты:</b>
|
||||
<i>История операций пуста</i>
|
||||
"""
|
||||
else:
|
||||
history_text += """
|
||||
<b>🚪 Операции комнаты:</b>
|
||||
|
||||
"""
|
||||
for tx in last_10_room:
|
||||
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"
|
||||
room_mark = " 🚪" if tx.per_room else ""
|
||||
|
||||
history_text += f"<blockquote><b>{operation}</b> {emoji}<code>{tx.amount}</code> ч{room_mark}\n📅 {date_str}{remark_text}</blockquote>\n"
|
||||
|
||||
return {"history_content": history_text}
|
||||
|
||||
|
||||
@@ -63,44 +63,20 @@ async def get_main_menu_data(
|
||||
)
|
||||
|
||||
if not resident:
|
||||
content = f"""
|
||||
{greeting}
|
||||
|
||||
<blockquote>⚠️ <b>Профиль не найден</b></blockquote>
|
||||
|
||||
Вы еще не привязаны к резиденту.
|
||||
Обратитесь к администратору для регистрации.
|
||||
"""
|
||||
content = f"{greeting}\n\n<blockquote>⚠️ <b>Профиль не найден</b></blockquote>\n\nВы еще не привязаны к резиденту.\nОбратитесь к администратору для регистрации."
|
||||
has_resident = False
|
||||
else:
|
||||
room = await rooms_repository.get_room_by_id(resident.room)
|
||||
room_active = room.active_hours if room else 0
|
||||
room_inactive = room.inactive_hours if room else 0
|
||||
room_number = room.number if room else "???"
|
||||
|
||||
content = f"""
|
||||
{greeting}
|
||||
|
||||
📊 <i>Статус отработки:</i>
|
||||
|
||||
<b>👤 Ваши часы:</b>
|
||||
<blockquote>✅ Выполнено: <b>{resident.inactive_hours}</b> ч.
|
||||
⏳ Осталось: <b>{resident.active_hours}</b> ч.</blockquote>
|
||||
|
||||
<b>🚪 Часы комнаты:</b>
|
||||
<blockquote>✅ Выполнено: <b>{room_inactive}</b> ч.
|
||||
⏳ Осталось: <b>{room_active}</b> ч.</blockquote>
|
||||
|
||||
<code>made by kolo</code>
|
||||
"""
|
||||
room_residents = await residents_repository.get_residents_by_room(resident.room)
|
||||
room_active = sum(r.active_hours for r in room_residents)
|
||||
room_inactive = sum(r.inactive_hours for r in room_residents)
|
||||
|
||||
content = f"{greeting}\n\n📊 <i>Статус отработки:</i>\n\n<b>👤 Ваши часы:</b>\n<blockquote>✅ Выполнено: <b>{resident.inactive_hours}</b> ч.\n⏳ Осталось: <b>{resident.active_hours}</b> ч.</blockquote>\n\n<b>🚪 Часы комнаты:</b>\n<blockquote>✅ Выполнено: <b>{room_inactive}</b> ч.\n⏳ Осталось: <b>{room_active}</b> ч.</blockquote>\n\n<code>made by kolo</code>"
|
||||
has_resident = True
|
||||
else:
|
||||
content = f"""
|
||||
{greeting}
|
||||
|
||||
<blockquote>📋 <b>Панель управления</b></blockquote>
|
||||
|
||||
Добро пожаловать в систему учета дежурств!
|
||||
"""
|
||||
content = f"{greeting}\n\n<blockquote>📋 <b>Панель управления</b></blockquote>\n\nДобро пожаловать в систему учета дежурств!"
|
||||
has_resident = False
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user