From 9b19b82cafde117696856d07f05fb6f186738863 Mon Sep 17 00:00:00 2001 From: Trevor1985 Date: Mon, 14 Sep 2026 21:09:36 +0900 Subject: [PATCH] Use PostgreSQL-compatible winner menu queries --- docs/OPERATOR_SCENARIO_AUDIT.md | 1 + src/handlers/admin_panel.py | 6 ++---- tests/test_operator_scenarios.py | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/OPERATOR_SCENARIO_AUDIT.md b/docs/OPERATOR_SCENARIO_AUDIT.md index 63f704c..be41714 100644 --- a/docs/OPERATOR_SCENARIO_AUDIT.md +++ b/docs/OPERATOR_SCENARIO_AUDIT.md @@ -11,6 +11,7 @@ - При отклонённой операции `rollback()` инвалидировал загруженные ORM-объекты. Итоговый ответ обращался к `lottery.title` после закрытия сессии. Теперь необходимые значения сохраняются до операции; аналогичные обращения исправлены в одиночных и массовых операциях по пользователям. - Быстрый ввод `КАРТА СЧЁТ` не создавал отсутствующий счёт, хотя предварительный экран находил владельца карты. Теперь известная карта позволяет создать счёт и участие одной транзакцией. Уникальность счёта защищает работу кассиров в разных розыгрышах; владелец существующего счёта не меняется. - Широкие фильтры кнопок перехватывали отчёты, подтверждения переигровки и удаления, редактирование победителя. Фильтры теперь проверяют полный формат и допустимый числовой ID. Нераспознанная кнопка получает понятный ответ. +- PostgreSQL-прогон выявил несовместимость `SELECT DISTINCT` по розыгрышам с JSON-полями. Меню редактирования и удаления победителей используют `EXISTS`: розыгрыш выводится один раз без сравнения JSON. - Удалённые розыгрыши и истёкшие данные диалога обрабатываются без `NoneType` и `IndexError`. - Добавление и удаление счёта из детального меню используют общие транзакционные проверки открытого розыгрыша и владельца. - Экраны победителей поддерживают участие без Telegram-профиля. При ручном назначении по счёту сохраняется именно выбранный билет; один счёт нельзя назначить на разные призовые места. diff --git a/src/handlers/admin_panel.py b/src/handlers/admin_panel.py index 62f40a6..a3340fb 100644 --- a/src/handlers/admin_panel.py +++ b/src/handlers/admin_panel.py @@ -3069,8 +3069,7 @@ async def edit_winner_start(callback: CallbackQuery): from sqlalchemy import select result = await session.execute( select(Lottery) - .join(Winner) - .distinct() + .where(select(Winner.id).where(Winner.lottery_id == Lottery.id).exists()) .order_by(Lottery.created_at.desc()) .limit(20) ) @@ -3196,8 +3195,7 @@ async def remove_winner_start(callback: CallbackQuery): from sqlalchemy import select result = await session.execute( select(Lottery) - .join(Winner) - .distinct() + .where(select(Winner.id).where(Winner.lottery_id == Lottery.id).exists()) .order_by(Lottery.created_at.desc()) .limit(20) ) diff --git a/tests/test_operator_scenarios.py b/tests/test_operator_scenarios.py index 9f210f6..7fb7781 100644 --- a/tests/test_operator_scenarios.py +++ b/tests/test_operator_scenarios.py @@ -119,7 +119,7 @@ async def test_duplicate_account_reports_and_finishes_dialog(route, caplog): @pytest.mark.parametrize("callback", [ - "admin_participants_by_lottery", "admin_participants_report", "admin_edit_winner", + "admin_participants_by_lottery", "admin_participants_report", "admin_edit_winner", "admin_remove_winner", "admin_edit_winner_lottery_{id}", "admin_edit_lottery_select_{id}", "admin_lottery_detail_{id}", "admin_participants_{id}", "admin_stats", "admin_list_all_participants", "admin_users_list:1", "admin_winners", @@ -244,7 +244,8 @@ async def test_anonymous_winner_admin_pages(caplog): session.add(winner) await session.commit() winner_id = winner.id - for callback in (f"admin_edit_winner_lottery_{lottery_id}", f"admin_edit_winner_id_{winner_id}", + for callback in ("admin_edit_winner", "admin_remove_winner", + f"admin_edit_winner_lottery_{lottery_id}", f"admin_edit_winner_id_{winner_id}", f"admin_remove_winner_lottery_{lottery_id}", f"admin_confirm_remove_winner_{winner_id}", f"admin_do_remove_winner_{winner_id}"): await send(callback=callback)