mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-08-08 18:11:14 +03:00
fix: media group photos saving, add photos to user notifications, admin info in transaction details, remove deep link fallback
- Fix media group collection: use module-level dicts instead of dialog_data to avoid race conditions between concurrent handler calls - Add photo/media group sending in user notifications (add/remove hours) - Add admin username with emoji to all transaction detail windows - Add emoji prefixes to detail content fields - Support multiple photos via JSON array in photo_file_id column - Change photo_file_id column from String(512) to Text - Add get_photo_file_ids() helper with backward compat for single file_id - Add 'All photos' button for media groups in detail views - Remove /start photo_ deep link fallback handler - Add Alembic migration for column type change
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
from aiogram import Bot
|
||||
from aiogram.enums import ContentType
|
||||
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.types import Message, CallbackQuery, InputMediaPhoto
|
||||
from aiogram_dialog import Window, DialogManager
|
||||
from aiogram_dialog.widgets.text import Format, Const
|
||||
from aiogram_dialog.widgets.kbd import SwitchTo, Button, ScrollingGroup, Select, Row, Group
|
||||
from aiogram_dialog.widgets.input import MessageInput
|
||||
from aiogram_dialog.widgets.media import DynamicMedia
|
||||
from aiogram_dialog.api.entities.media import MediaAttachment, MediaId
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
|
||||
@@ -444,12 +447,12 @@ async def get_room_history_data(
|
||||
room_id = dialog_manager.dialog_data.get("selected_room_id")
|
||||
|
||||
if not room_id:
|
||||
return {"history_content": "Ошибка: комната не выбрана"}
|
||||
return {"history_content": "Ошибка: комната не выбрана", "transactions": [], "has_transactions": False}
|
||||
|
||||
room = await rooms_repository.get_room_by_id(room_id)
|
||||
|
||||
if not room:
|
||||
return {"history_content": "Ошибка: комната не найдена"}
|
||||
return {"history_content": "Ошибка: комната не найдена", "transactions": [], "has_transactions": False}
|
||||
|
||||
transactions = await transactions_repository.get_room_transactions(room_id)
|
||||
last_10 = transactions[:10]
|
||||
@@ -458,18 +461,115 @@ async def get_room_history_data(
|
||||
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>\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 "−"
|
||||
|
||||
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}<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"
|
||||
|
||||
return {"history_content": history_text}
|
||||
tx_items = []
|
||||
for tx in last_10:
|
||||
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")
|
||||
photo_mark = " 📷" if tx.photo_file_id else ""
|
||||
label = f"{emoji}{tx.amount} ч | {date_str}{photo_mark}"
|
||||
tx_items.append((label, str(tx.id)))
|
||||
|
||||
return {
|
||||
"history_content": history_text,
|
||||
"transactions": tx_items,
|
||||
"has_transactions": len(tx_items) > 0,
|
||||
}
|
||||
|
||||
|
||||
@inject
|
||||
async def on_room_transaction_selected(
|
||||
callback: CallbackQuery,
|
||||
widget: Select,
|
||||
dialog_manager: DialogManager,
|
||||
item_id: str,
|
||||
):
|
||||
dialog_manager.dialog_data["selected_transaction_id"] = int(item_id)
|
||||
await dialog_manager.switch_to(AdminMenuSG.room_history_detail)
|
||||
|
||||
|
||||
@inject
|
||||
async def on_show_room_all_photos(
|
||||
callback: CallbackQuery,
|
||||
button: Button,
|
||||
dialog_manager: DialogManager,
|
||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||
**kwargs,
|
||||
):
|
||||
tx_id = dialog_manager.dialog_data.get("selected_transaction_id")
|
||||
if not tx_id:
|
||||
return
|
||||
|
||||
tx = await transactions_repository.get_transaction_by_id(int(tx_id))
|
||||
if not tx:
|
||||
return
|
||||
|
||||
photo_file_ids = HoursTransactionsRepository.get_photo_file_ids(tx)
|
||||
if len(photo_file_ids) <= 1:
|
||||
return
|
||||
|
||||
bot: Bot = kwargs["bot"]
|
||||
media = [InputMediaPhoto(media=file_id) for file_id in photo_file_ids]
|
||||
try:
|
||||
await bot.send_media_group(callback.message.chat.id, media=media)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@inject
|
||||
async def get_room_transaction_detail_data(
|
||||
dialog_manager: DialogManager,
|
||||
transactions_repository: FromDishka[HoursTransactionsRepository],
|
||||
users_repository: FromDishka[UsersRepository],
|
||||
**kwargs,
|
||||
):
|
||||
tx_id = dialog_manager.dialog_data.get("selected_transaction_id")
|
||||
|
||||
if not tx_id:
|
||||
return {"detail_content": "Ошибка: транзакция не выбрана", "photo_media": None, "has_multiple_photos": False, "photo_count": 0}
|
||||
|
||||
tx = await transactions_repository.get_transaction_by_id(int(tx_id))
|
||||
|
||||
if not tx:
|
||||
return {"detail_content": "Ошибка: транзакция не найдена", "photo_media": None, "has_multiple_photos": False, "photo_count": 0}
|
||||
|
||||
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 ""
|
||||
|
||||
admin_info = ""
|
||||
if tx.admin_id:
|
||||
admin = await users_repository.get_user_by_id(tx.admin_id)
|
||||
if admin:
|
||||
admin_name = f"@{admin.username}" if admin.username else admin.first_name or f"ID: {admin.id}"
|
||||
admin_info = f"\n👨💼 <b>Администратор:</b> {admin_name}"
|
||||
|
||||
content = f"""
|
||||
<blockquote>📋 <b>Детали транзакции</b></blockquote>
|
||||
|
||||
⚙️ <b>Операция:</b> {operation}
|
||||
🔢 <b>Количество:</b> {emoji}<code>{tx.amount}</code> ч
|
||||
📅 <b>Дата:</b> {date_str}{remark_text}{admin_info}
|
||||
"""
|
||||
|
||||
photo_file_ids = HoursTransactionsRepository.get_photo_file_ids(tx)
|
||||
photo_media = None
|
||||
if photo_file_ids:
|
||||
photo_media = MediaAttachment(
|
||||
type=ContentType.PHOTO,
|
||||
file_id=MediaId(file_id=photo_file_ids[0]),
|
||||
)
|
||||
|
||||
return {
|
||||
"detail_content": content,
|
||||
"photo_media": photo_media,
|
||||
"has_multiple_photos": len(photo_file_ids) > 1,
|
||||
"photo_count": len(photo_file_ids),
|
||||
}
|
||||
|
||||
|
||||
rooms_select_floor_window = Window(
|
||||
@@ -688,6 +788,19 @@ create_room_confirm_window = Window(
|
||||
|
||||
room_history_window = Window(
|
||||
Format("{history_content}"),
|
||||
ScrollingGroup(
|
||||
Select(
|
||||
Format("{item[0]}"),
|
||||
id="room_transactions_select",
|
||||
item_id_getter=lambda x: x[1],
|
||||
items="transactions",
|
||||
on_click=on_room_transaction_selected,
|
||||
),
|
||||
id="room_transactions_scroll",
|
||||
width=1,
|
||||
height=8,
|
||||
when="has_transactions",
|
||||
),
|
||||
SwitchTo(
|
||||
Const("◀️ Назад"),
|
||||
id="back_to_room_info",
|
||||
@@ -696,3 +809,21 @@ room_history_window = Window(
|
||||
state=AdminMenuSG.room_history,
|
||||
getter=get_room_history_data,
|
||||
)
|
||||
|
||||
room_history_detail_window = Window(
|
||||
DynamicMedia("photo_media"),
|
||||
Format("{detail_content}"),
|
||||
Button(
|
||||
Format("📷 Все фото ({photo_count})"),
|
||||
id="show_room_all_photos_btn",
|
||||
on_click=on_show_room_all_photos,
|
||||
when="has_multiple_photos",
|
||||
),
|
||||
SwitchTo(
|
||||
Const("◀️ К списку"),
|
||||
id="back_to_room_history_from_detail",
|
||||
state=AdminMenuSG.room_history,
|
||||
),
|
||||
state=AdminMenuSG.room_history_detail,
|
||||
getter=get_room_transaction_detail_data,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user