From ac37f15bea290881e6d4b674e8015ddeccf8eb76 Mon Sep 17 00:00:00 2001 From: Ali Asadi Date: Mon, 3 Aug 2026 17:33:59 +0330 Subject: [PATCH 1/3] FIX(promotions): default event status promotion_amount to 0 get_event_status_for_user returned None whenever the event was already processed, no plan matched, or the user lacked access to the recipient. Callers now always get a concrete amount: the real payout if accessible, 0 otherwise. Co-Authored-By: Claude Sonnet 5 --- apps/promotions/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/promotions/models.py b/apps/promotions/models.py index 269eda1..ae8c384 100644 --- a/apps/promotions/models.py +++ b/apps/promotions/models.py @@ -393,7 +393,7 @@ class PromotionQuerySet(models.QuerySet): def get_event_status_for_user(user_uuid, event_label): processed = Promotion.objects.is_processed_for_user(user_uuid, event_label) - promotion_amount = None + promotion_amount = 0 if not processed: plan = Plan.objects.filter(event_list__icontains=event_label).first() if plan: -- 2.45.3 From 33aa0ed1b483c486bf4020d498c0658efe2cc714 Mon Sep 17 00:00:00 2001 From: Ali Asadi Date: Mon, 3 Aug 2026 17:55:42 +0330 Subject: [PATCH 2/3] PERF(promotions): batch allowed-user lookup in get_configured_promotion_amount Checking each recipient via Recipient.is_user_allowed() inside the loop issued one AllowedUser query per restricted recipient (N+1). Fetch all matching AllowedUser rows for the plan's recipients in a single query up front instead. Co-Authored-By: Claude Sonnet 5 --- apps/promotions/models.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/promotions/models.py b/apps/promotions/models.py index ae8c384..25d107f 100644 --- a/apps/promotions/models.py +++ b/apps/promotions/models.py @@ -136,9 +136,16 @@ class Plan(BaseModel): return self.title def get_configured_promotion_amount(self, user_uuid=None): + recipients = list(self.recipients.filter(recipient_uuid_field='->event:user')) #TODO: instead of hard code use better soloution + restricted_ids = [r.pk for r in recipients if r.access_type == RecipientTypeChoices.RESTRICTED] + allowed_ids = set( + AllowedUser.objects.filter(recipient_id__in=restricted_ids, user__uuid=user_uuid) + .values_list('recipient_id', flat=True) + ) if restricted_ids and user_uuid else set() + amount = 0 - for recipient in self.recipients.filter(recipient_uuid_field='->event:user'): #TODO: instead of hard code use better soloution - if not recipient.is_user_allowed(user_uuid): + for recipient in recipients: + if recipient.access_type == RecipientTypeChoices.RESTRICTED and recipient.pk not in allowed_ids: continue base_amount, promotion_amount = recipient.get_promotion_amount() amount += promotion_amount -- 2.45.3 From 54de7fdb480d42e2bac1c597ba6533775da60f6d Mon Sep 17 00:00:00 2001 From: Ali Asadi Date: Mon, 3 Aug 2026 18:21:23 +0330 Subject: [PATCH 3/3] PERF(promotions): collapse allowed-user check into a single query Fold the restricted-recipient access check into the recipients queryset itself via Q(public OR restricted-and-allowed), joining against AllowedUser instead of running a separate lookup query. Co-Authored-By: Claude Sonnet 5 --- apps/promotions/models.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/promotions/models.py b/apps/promotions/models.py index 25d107f..6f9a661 100644 --- a/apps/promotions/models.py +++ b/apps/promotions/models.py @@ -3,7 +3,7 @@ import uuid from django.conf import settings from django.contrib.postgres.indexes import GinIndex from django.db import models, transaction -from django.db.models import F +from django.db.models import F, Q from django.utils import timezone from django_minio_backend import MinioBackend, iso_date_prefix @@ -136,17 +136,16 @@ class Plan(BaseModel): return self.title def get_configured_promotion_amount(self, user_uuid=None): - recipients = list(self.recipients.filter(recipient_uuid_field='->event:user')) #TODO: instead of hard code use better soloution - restricted_ids = [r.pk for r in recipients if r.access_type == RecipientTypeChoices.RESTRICTED] - allowed_ids = set( - AllowedUser.objects.filter(recipient_id__in=restricted_ids, user__uuid=user_uuid) - .values_list('recipient_id', flat=True) - ) if restricted_ids and user_uuid else set() + access_filter = Q(access_type=RecipientTypeChoices.PUBLIC) + if user_uuid: + access_filter |= Q(access_type=RecipientTypeChoices.RESTRICTED, allowed_users__user__uuid=user_uuid) + + recipients = self.recipients.filter( + recipient_uuid_field='->event:user' #TODO: instead of hard code use better soloution + ).filter(access_filter).distinct() amount = 0 for recipient in recipients: - if recipient.access_type == RecipientTypeChoices.RESTRICTED and recipient.pk not in allowed_ids: - continue base_amount, promotion_amount = recipient.get_promotion_amount() amount += promotion_amount return amount -- 2.45.3