get promotion amount from another promotion

This commit is contained in:
Sayyid Hamid Mahdavi 2026-06-25 11:52:04 +03:30
parent 7038ada31e
commit 30ccca8efc
3 changed files with 116 additions and 10 deletions

View file

@ -0,0 +1,23 @@
# Generated by Django 5.2.4 on 2026-06-25 06:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('promotions', '0007_alter_event_options_plan_banner_and_more'),
]
operations = [
migrations.AddField(
model_name='plan',
name='balance_holder',
field=models.UUIDField(blank=True, null=True),
),
migrations.AlterField(
model_name='plan',
name='description_details',
field=models.JSONField(blank=True, default=dict, verbose_name='description details'),
),
]

View file

@ -112,6 +112,8 @@ class Plan(BaseModel):
processor = models.CharField(max_length=64, verbose_name=_('processor'), db_index=True,
choices=ProcessorTypeChoices.choices) # TODO: processor type
data = models.JSONField(default=dict, verbose_name=_('data'), null=True, blank=True)
balance_holder = models.UUIDField(null=True, blank=True)
balance = models.IntegerField(verbose_name=_('balance'), default=0)
objects = PlanQuerySet.as_manager()
@ -142,11 +144,27 @@ class Plan(BaseModel):
yield recipient.promote(plan=self, event=event)
def reserve_promotion_amount(self, amount):
if self.balance_holder:
balance_holder_object = Promotion.objects.get(pk=self.balance_holder)
balance_field_name = 'promotion_amount'
else:
balance_holder_object = self
balance_field_name = 'balance'
updated = False
if amount and self.balance >= amount:
updated = Plan.objects.filter(pk=self.pk, balance__gte=amount).update(
balance=F('balance') - amount
)
select_query_kwargs = {
"pk": balance_holder_object.pk,
f"{balance_field_name}__gte": amount
}
update_query_kwargs = {
balance_field_name: F(balance_field_name) - amount
}
if amount and getattr(balance_holder_object, balance_field_name) >= amount:
updated = balance_holder_object._meta.model.objects.filter(**select_query_kwargs).update(
**update_query_kwargs)
return updated
@ -200,7 +218,6 @@ class Recipient(BaseModel):
if obj_value != value:
return None
model_name, key = recipient_candidate.split(':')
if model_name == 'event':
obj = event
@ -277,7 +294,6 @@ class Recipient(BaseModel):
else:
return base_amount, promotion_amount
# @property
# def handler(self) -> BasePromotionHandler:
# return PromotionTypeChoices(self.promotion_type).handler(self)
@ -287,7 +303,6 @@ class Recipient(BaseModel):
if not recipient:
return None
base_amount, promotion_amount = self.get_promotion_amount(plan=plan, event=event)
promotion, created = Promotion.objects.get_or_create(
promotion_amount=promotion_amount,
@ -306,7 +321,8 @@ class Recipient(BaseModel):
else:
raise Exception('promotion reward not reserved')
except Exception as e:
promotion.change_state(from_states=[promotion.state], to_state=PaymentStateChoices.EXPECTED_FAILURE, same_ok=True)
promotion.change_state(from_states=[promotion.state], to_state=PaymentStateChoices.EXPECTED_FAILURE,
same_ok=True)
promotion.refresh_from_db()
return promotion
@ -382,7 +398,7 @@ class Promotion(BaseModel):
if self.state not in [PaymentStateChoices.CREATED]:
raise Exception(_('Cannot promote. not in correct state'))
if not self.promotion_amount:
if not self.promotion_amount or self.plan.balance_holder:
to_not_payed = self.change_state(
from_states=[PaymentStateChoices.CREATED],
to_state=PaymentStateChoices.SUCCESS,

View file

@ -9,7 +9,8 @@ from rest_framework.test import APITestCase, override_settings, APIClient
from apps.promotions.tasks import analyze_event_task
from apps.users.models import User
from apps.promotions.models import Plan, Promotion, EventSaver, ProcessorTypeChoices, Event, Recipient
from apps.promotions.models import Plan, Promotion, EventSaver, ProcessorTypeChoices, Event, Recipient, \
PaymentStateChoices
AccessToken = get_access_token_model()
Application = get_application_model()
@ -465,3 +466,69 @@ class ApplicationApiFlowsTests(APITestCase):
self.assertEqual(self.serial_promotion_plan.balance, 100000)
self.assertEqual(Event.objects.count(), 3)
def test_plan_balance_from_another_promotion_success(self):
base_plan, _created = Plan.objects.get_or_create(
title='base_plan',
balance=0,
user=self.user_1,
application=self.application,
event_list=['advertising:manual']
)
base_promotion, _created = Promotion.objects.get_or_create(
user_uuid = self.user_1_uuid,
application = self.application,
plan = base_plan,
state = PaymentStateChoices.SUCCESS,
base_amount = 10000,
promotion_amount = 10000
)
promotion_event_saver, _created = EventSaver.objects.get_or_create(
title=self.first_ad_view_reward_label,
event_label=self.first_ad_view_reward_label,
save_once=True,
)
promotion_plan, _created = Plan.objects.get_or_create(
title='promotion_plan',
balance=0,
balance_holder=base_promotion.pk,
user=self.user_1,
application=self.application,
event_list=[self.first_ad_view_reward_label]
)
self.promotion_recipient, _created = Recipient.objects.get_or_create(
label='promotion_recipient',
plan=promotion_plan,
wallet_uuid=self.user_1_uuid,
recipient_uuid_field=self.user_2_uuid,
base_amount_field=1000,
data={}
)
auth = self._create_authorization_header(self.user_access_token.token)
create_data = {
'label': self.first_ad_view_reward_label,
"data": {
'user': self.user_1_uuid,
}
}
self.assertEqual(Event.objects.count(), 0)
response = self.client.post(reverse('promotions:promotion-create', kwargs={'plan': str(promotion_plan.uuid)}),
create_data,
HTTP_AUTHORIZATION=auth, format='json')
print(response.json())
promotion_plan.refresh_from_db()
base_promotion.refresh_from_db()
self.assertEqual(base_promotion.base_amount, 10000)
self.assertEqual(base_promotion.promotion_amount, 9000)
self.assertEqual(Event.objects.count(), 1)
response = self.client.post(reverse('promotions:promotion-create', kwargs={'plan': str(promotion_plan.uuid)}), create_data,
HTTP_AUTHORIZATION=auth, format='json')
print(response.json())