bot prime refactor. Notification events & messages
This commit is contained in:
@@ -1,23 +1,25 @@
|
||||
import random
|
||||
import logging
|
||||
from asgiref.sync import async_to_sync
|
||||
from datetime import datetime
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin, messages
|
||||
from django.urls import path, reverse
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.utils.dateparse import parse_date
|
||||
from django.utils.html import format_html
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
|
||||
from .models import Lottery, Prize, LotteryParticipant, DrawResult
|
||||
from .forms import AddParticipantsForm
|
||||
from webapp.models import Invoice, Client, BindingRequest
|
||||
from bot.notifications import NotificationService
|
||||
from bot.utils import create_bot_instance
|
||||
from .views import view_draw_results
|
||||
from django import forms
|
||||
from django.utils.dateparse import parse_datetime, parse_date
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
# Настройка логгера
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
if not logger.handlers:
|
||||
@@ -34,7 +36,6 @@ def add_participants_view(request):
|
||||
return HttpResponse("Не указан параметр lottery_id", status=400)
|
||||
lottery = get_object_or_404(Lottery, id=lottery_id)
|
||||
|
||||
# Только счета, у которых used=False, и не добавленные в данную лотерею
|
||||
used_invoice_ids = LotteryParticipant.objects.filter(lottery=lottery).values_list("invoice_id", flat=True)
|
||||
qs = Invoice.objects.filter(used=False).exclude(id__in=used_invoice_ids)
|
||||
|
||||
@@ -47,7 +48,6 @@ def add_participants_view(request):
|
||||
qs = qs.filter(deposit_sum__gte=deposit_min)
|
||||
if deposit_max:
|
||||
qs = qs.filter(deposit_sum__lte=deposit_max)
|
||||
|
||||
if created_after:
|
||||
try:
|
||||
dt = parse_date(created_after)
|
||||
@@ -55,7 +55,6 @@ def add_participants_view(request):
|
||||
qs = qs.filter(created_at__gte=dt)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if created_before:
|
||||
try:
|
||||
dt = parse_date(created_before)
|
||||
@@ -63,14 +62,13 @@ def add_participants_view(request):
|
||||
qs = qs.filter(created_at__lte=dt)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
form = AddParticipantsForm(request.POST)
|
||||
form.fields["invoices"].queryset = qs
|
||||
if form.is_valid():
|
||||
selected_invoices = form.cleaned_data["invoices"]
|
||||
for invoice in selected_invoices:
|
||||
# При добавлении участника отмечаем счет как использованный
|
||||
invoice.used = True
|
||||
invoice.save()
|
||||
LotteryParticipant.objects.create(lottery=lottery, invoice=invoice)
|
||||
@@ -85,16 +83,90 @@ def add_participants_view(request):
|
||||
|
||||
|
||||
def get_client_by_invoice(invoice):
|
||||
"""
|
||||
Возвращает клиента, используя значение поля client_club_card_number у счета.
|
||||
Предполагается, что у модели Invoice есть поле client_club_card_number.
|
||||
"""
|
||||
try:
|
||||
return Client.objects.get(club_card_number=invoice.client_club_card_number)
|
||||
except Client.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
# def start_draw(request, lottery_id):
|
||||
# lottery = get_object_or_404(Lottery, id=lottery_id)
|
||||
# logger.info("Запуск розыгрыша для лотереи: %s", lottery.name)
|
||||
|
||||
# if lottery.finished:
|
||||
# messages.warning(request, "Розыгрыш уже завершён.")
|
||||
# return redirect("..")
|
||||
|
||||
# notifier = NotificationService(bot=create_bot_instance())
|
||||
# async_to_sync(notifier.notify_draw_start)(lottery)
|
||||
|
||||
# manually_assigned_invoice_ids = set()
|
||||
# for prize in lottery.prizes.all():
|
||||
# if prize.winner and prize.winner.invoice:
|
||||
# manually_assigned_invoice_ids.add(prize.winner.invoice_id)
|
||||
# prize.winner.used = True
|
||||
# prize.winner.save()
|
||||
|
||||
# for prize in lottery.prizes.all():
|
||||
# logger.info("Обработка приза: %s", prize.prize_place)
|
||||
|
||||
# if prize.winner:
|
||||
# try:
|
||||
# draw_result = lottery.draw_results.get(prize=prize)
|
||||
# draw_result.participant = prize.winner
|
||||
# draw_result.drawn_at = timezone.now()
|
||||
# draw_result.confirmed = False
|
||||
# draw_result.save()
|
||||
# except DrawResult.DoesNotExist:
|
||||
# DrawResult.objects.create(
|
||||
# lottery=lottery,
|
||||
# prize=prize,
|
||||
# participant=prize.winner,
|
||||
# confirmed=False,
|
||||
# drawn_at=timezone.now()
|
||||
# )
|
||||
# continue
|
||||
|
||||
# try:
|
||||
# draw_result = lottery.draw_results.get(prize=prize)
|
||||
# if draw_result.confirmed:
|
||||
# continue
|
||||
# except DrawResult.DoesNotExist:
|
||||
# draw_result = None
|
||||
|
||||
# participants = list(
|
||||
# lottery.participants.filter(used=False).exclude(invoice_id__in=manually_assigned_invoice_ids)
|
||||
# )
|
||||
# if not participants:
|
||||
# continue
|
||||
|
||||
# winner_participant = random.choice(participants)
|
||||
# winner_participant.used = True
|
||||
# winner_participant.save()
|
||||
|
||||
# if draw_result:
|
||||
# draw_result.participant = winner_participant
|
||||
# draw_result.drawn_at = timezone.now()
|
||||
# draw_result.confirmed = False
|
||||
# draw_result.save()
|
||||
# else:
|
||||
# DrawResult.objects.create(
|
||||
# lottery=lottery,
|
||||
# prize=prize,
|
||||
# participant=winner_participant,
|
||||
# confirmed=False,
|
||||
# drawn_at=timezone.now()
|
||||
# )
|
||||
|
||||
# draw_results = lottery.draw_results.all()
|
||||
# async_to_sync(notifier.notify_draw_results)(lottery, draw_results)
|
||||
|
||||
# if not lottery.prizes.filter(winner__isnull=True).exists():
|
||||
# lottery.finished = True
|
||||
# lottery.save()
|
||||
|
||||
# return render(request, "admin/draw_result.html", {"lottery": lottery, "draw_results": draw_results})
|
||||
|
||||
def start_draw(request, lottery_id):
|
||||
lottery = get_object_or_404(Lottery, id=lottery_id)
|
||||
logger.info("Запуск розыгрыша для лотереи: %s", lottery.name)
|
||||
@@ -104,23 +176,18 @@ def start_draw(request, lottery_id):
|
||||
return redirect("..")
|
||||
|
||||
notifier = NotificationService(bot=create_bot_instance())
|
||||
async_to_sync(notifier.notify_draw_start)(lottery)
|
||||
|
||||
# Собираем ID счетов вручную назначенных победителей
|
||||
manually_assigned_invoice_ids = set()
|
||||
for prize in lottery.prizes.all():
|
||||
if prize.winner and prize.winner.invoice:
|
||||
manually_assigned_invoice_ids.add(prize.winner.invoice_id)
|
||||
# Помечаем вручную выбранного участника как использованного
|
||||
prize.winner.used = True
|
||||
prize.winner.save()
|
||||
|
||||
for prize in lottery.prizes.all():
|
||||
logger.info("Обработка приза: %s", prize.prize_place)
|
||||
|
||||
# Если у приза уже установлен победитель вручную — сохраняем его в таблицу результатов
|
||||
if prize.winner:
|
||||
logger.info("Приз '%s' имеет установленного вручную победителя. Сохраняем в таблице результатов.", prize.prize_place)
|
||||
try:
|
||||
draw_result = lottery.draw_results.get(prize=prize)
|
||||
draw_result.participant = prize.winner
|
||||
@@ -140,31 +207,26 @@ def start_draw(request, lottery_id):
|
||||
try:
|
||||
draw_result = lottery.draw_results.get(prize=prize)
|
||||
if draw_result.confirmed:
|
||||
logger.info("Приз '%s' уже подтвержден.", prize.prize_place)
|
||||
continue
|
||||
except DrawResult.DoesNotExist:
|
||||
draw_result = None
|
||||
|
||||
# Получаем всех неиспользованных участников, исключая вручную выбранных
|
||||
participants = list(
|
||||
lottery.participants.filter(used=False).exclude(invoice_id__in=manually_assigned_invoice_ids)
|
||||
)
|
||||
logger.info("Найдено свободных участников для приза '%s': %d", prize.prize_place, len(participants))
|
||||
logger.info(f"Найдено участников: {len(participants)} для приза {prize.prize_place}")
|
||||
if not participants:
|
||||
logger.warning("Нет свободных участников для приза '%s'.", prize.prize_place)
|
||||
continue
|
||||
|
||||
winner_participant = random.choice(participants)
|
||||
winner_participant.used = True
|
||||
winner_participant.save()
|
||||
logger.info("Выбран участник с счетом '%s' для приза '%s'.", winner_participant.invoice.api_id, prize.prize_place)
|
||||
|
||||
if draw_result:
|
||||
draw_result.participant = winner_participant
|
||||
draw_result.drawn_at = timezone.now()
|
||||
draw_result.confirmed = False
|
||||
draw_result.save()
|
||||
logger.info("Обновлен результат розыгрыша для приза '%s'.", prize.prize_place)
|
||||
else:
|
||||
DrawResult.objects.create(
|
||||
lottery=lottery,
|
||||
@@ -173,10 +235,30 @@ def start_draw(request, lottery_id):
|
||||
confirmed=False,
|
||||
drawn_at=timezone.now()
|
||||
)
|
||||
logger.info("Создан результат розыгрыша для приза '%s'.", prize.prize_place)
|
||||
|
||||
draw_results = lottery.draw_results.all()
|
||||
async_to_sync(notifier.notify_draw_results)(lottery, draw_results)
|
||||
draw_results = lottery.draw_results.select_related('prize', 'participant__invoice')
|
||||
|
||||
# 🧠 Считаем сумму призов и формируем список победителей
|
||||
total_reward = sum([r.prize.reward for r in draw_results if r.prize and r.prize.reward])
|
||||
winners_list = ""
|
||||
for r in draw_results:
|
||||
try:
|
||||
winners_list += f"• Счёт {r.participant.invoice.id} — {r.prize.reward}₩\\n"
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
async def notify_all():
|
||||
await notifier.notify_draw_start(lottery)
|
||||
await notifier.notify_draw_results(
|
||||
lottery,
|
||||
results=draw_results,
|
||||
context_vars={
|
||||
"total_reward": f"{total_reward:,}",
|
||||
"winners_list": winners_list.strip()
|
||||
}
|
||||
)
|
||||
|
||||
async_to_sync(notify_all)()
|
||||
|
||||
if not lottery.prizes.filter(winner__isnull=True).exists():
|
||||
lottery.finished = True
|
||||
@@ -185,15 +267,14 @@ def start_draw(request, lottery_id):
|
||||
return render(request, "admin/draw_result.html", {"lottery": lottery, "draw_results": draw_results})
|
||||
|
||||
|
||||
|
||||
def confirm_draw_result(request, result_id):
|
||||
from django.http import HttpResponseRedirect
|
||||
result = get_object_or_404(DrawResult, id=result_id)
|
||||
|
||||
# Проверяем, что для результата задан участник и его счет
|
||||
|
||||
if not result.participant or not result.participant.invoice:
|
||||
messages.error(request, "Невозможно подтвердить результат: отсутствует участник или его счет.")
|
||||
return HttpResponseRedirect(reverse("admin:start_draw", args=[result.lottery.id]))
|
||||
|
||||
|
||||
result.confirmed = True
|
||||
result.save()
|
||||
|
||||
@@ -201,18 +282,14 @@ def confirm_draw_result(request, result_id):
|
||||
prize.winner = result.participant
|
||||
prize.save()
|
||||
|
||||
logger.info("Подтвержден результат розыгрыша для приза '%s'.", prize.prize_place)
|
||||
messages.success(request, f"Результат для приза '{prize.prize_place}' подтвержден.")
|
||||
|
||||
# Получаем клиента по счету участника
|
||||
client = get_client_by_invoice(result.participant.invoice)
|
||||
if client:
|
||||
try:
|
||||
# Можно попробовать запуск уведомления в отдельном потоке или отключить его временно:
|
||||
async_to_sync(NotificationService(bot=create_bot_instance()).notify_prize_status_update)(client, result)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка отправки уведомления о статусе приза пользователю {client.telegram_id}: {e}")
|
||||
|
||||
messages.success(request, f"Результат для приза '{prize.prize_place}' подтвержден.")
|
||||
return HttpResponseRedirect(reverse("admin:view_draw_results", args=[result.lottery.id]))
|
||||
|
||||
|
||||
@@ -227,16 +304,15 @@ class LotteryAdmin(admin.ModelAdmin):
|
||||
path('<int:lottery_id>/start-draw/', self.admin_site.admin_view(start_draw), name='start_draw'),
|
||||
path('confirm-draw-result/<int:result_id>/', self.admin_site.admin_view(confirm_draw_result), name='confirm_draw_result'),
|
||||
path('<int:lottery_id>/view-draw-results/', self.admin_site.admin_view(view_draw_results), name='view_draw_results'),
|
||||
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def start_draw_button(self, obj):
|
||||
# Если лотерея завершена, кнопку скрываем
|
||||
if obj.finished:
|
||||
return ""
|
||||
url = reverse("admin:start_draw", args=[obj.pk])
|
||||
return format_html('<a class="btn btn-primary" href="{}">Запуск розыгрыша</a>', url)
|
||||
|
||||
start_draw_button.short_description = "Розыгрыш"
|
||||
|
||||
|
||||
@@ -248,7 +324,6 @@ class PrizeAdmin(admin.ModelAdmin):
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super().get_form(request, obj, **kwargs)
|
||||
# При создании нового приза поле winner скрываем, так как участников ещё нет
|
||||
if obj is None:
|
||||
form.base_fields.pop('winner', None)
|
||||
return form
|
||||
@@ -258,13 +333,12 @@ class PrizeAdmin(admin.ModelAdmin):
|
||||
obj_id = request.resolver_match.kwargs.get('object_id')
|
||||
try:
|
||||
prize_obj = self.model.objects.get(pk=obj_id)
|
||||
# Ограничиваем выбор участниками данной лотереи
|
||||
kwargs["queryset"] = prize_obj.lottery.participants.all()
|
||||
except self.model.DoesNotExist:
|
||||
kwargs["queryset"] = self.model.objects.none()
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
@admin.register(LotteryParticipant)
|
||||
class LotteryParticipantAdmin(admin.ModelAdmin):
|
||||
list_display = ("lottery", "invoice", "added_at", "used")
|
||||
@@ -274,13 +348,11 @@ class LotteryParticipantAdmin(admin.ModelAdmin):
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
from draw.models import Lottery
|
||||
active_lotteries = Lottery.objects.filter(prizes__winner__isnull=True).distinct()
|
||||
extra_context['active_lotteries'] = active_lotteries
|
||||
return super().changelist_view(request, extra_context=extra_context)
|
||||
|
||||
def get_urls(self):
|
||||
from django.urls import path
|
||||
urls = super().get_urls()
|
||||
custom_urls = [
|
||||
path('add-participants/', self.admin_site.admin_view(add_participants_view), name="add_participants"),
|
||||
|
||||
Reference in New Issue
Block a user