promotion with new method works
This commit is contained in:
parent
e30005021d
commit
26c7e765a3
2 changed files with 151 additions and 42 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.2.4 on 2025-08-02 16:18
|
||||
# Generated by Django 5.1.7 on 2025-10-16 15:00
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
|
@ -16,18 +16,67 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventSaver',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||
('event_label', models.CharField(db_index=True, max_length=255, unique=True, verbose_name='label')),
|
||||
('save_once', models.BooleanField(default=False, verbose_name='save once')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('label', models.CharField(db_index=True, max_length=255)),
|
||||
('data', models.JSONField()),
|
||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Plan',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('event_list', models.JSONField(default=list)),
|
||||
('title', models.CharField(db_index=True, max_length=255, verbose_name='title')),
|
||||
('description', models.TextField(blank=True, verbose_name='description')),
|
||||
('promotion_type', models.CharField(choices=[('wallet percentage', 'Wallet percentage'), ('others', 'others')], db_index=True, max_length=64, verbose_name='promotion type')),
|
||||
('policy', models.JSONField(blank=True, default=dict, null=True, verbose_name='policy')),
|
||||
('balance', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='balance')),
|
||||
('promotion_type', models.CharField(choices=[('percentage', 'percentage'), ('referral', 'referral'), ('others', 'others')], db_index=True, max_length=64, verbose_name='promotion type')),
|
||||
('data', models.JSONField(blank=True, default=dict, null=True, verbose_name='data')),
|
||||
('balance', models.IntegerField(default=0, verbose_name='balance')),
|
||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Recipient',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('label', models.CharField(db_index=True, max_length=255)),
|
||||
('wallet_uuid', models.UUIDField(blank=True, null=True)),
|
||||
('promotion_type', models.CharField(choices=[('percentage', 'percentage'), ('referral', 'referral'), ('others', 'others')], db_index=True, max_length=64, verbose_name='promotion type')),
|
||||
('recipient_uuid_field', models.CharField(db_index=True, max_length=255)),
|
||||
('base_amount_field', models.CharField(db_index=True, max_length=255)),
|
||||
('data', models.JSONField(blank=True, default=dict, null=True)),
|
||||
('plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='recipients', to='promotions.plan')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
|
|
@ -39,11 +88,14 @@ class Migration(migrations.Migration):
|
|||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('state', models.IntegerField(choices=[(0, 'Created'), (1, 'Assigned'), (10, 'Expired')], default=0)),
|
||||
('state', models.IntegerField(choices=[(0, 'Created'), (1, 'Assigned'), (2, 'Promoted'), (10, 'Expired')], default=0)),
|
||||
('base_amount', models.IntegerField(blank=True, null=True)),
|
||||
('promotion_amount', models.IntegerField(blank=True, null=True)),
|
||||
('data', models.JSONField(blank=True, null=True)),
|
||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||
('plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.plan')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.recipient')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
from django.db.models import F
|
||||
|
|
@ -5,17 +7,17 @@ from django.utils import timezone
|
|||
|
||||
from oauth2_provider.settings import oauth2_settings
|
||||
|
||||
|
||||
from apps.promotions import handlers
|
||||
|
||||
import logging
|
||||
from apps.promotions.handlers import PromotionTypeChoices, BasePromotionHandler
|
||||
|
||||
|
||||
from apps.users.models import User
|
||||
from utils.clients.wallet_client import deposit_to_user_wallet_submit, deposit_to_user_wallet_verify
|
||||
from utils.models import BaseModel
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EventSaverQuerySet(models.QuerySet):
|
||||
pass
|
||||
|
|
@ -28,6 +30,9 @@ class EventSaver(BaseModel):
|
|||
|
||||
objects = EventSaverQuerySet.as_manager()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def save_event(self, **kwargs):
|
||||
user = kwargs.get('user')
|
||||
application = kwargs.get('application')
|
||||
|
|
@ -35,7 +40,8 @@ class EventSaver(BaseModel):
|
|||
data = kwargs.get('data')
|
||||
|
||||
if self.save_once:
|
||||
event, created = Event.objects.get_or_create(user=user, application=application, label=label, defaults={"data": data})
|
||||
event, created = Event.objects.get_or_create(user=user, application=application, label=label,
|
||||
defaults={"data": data})
|
||||
if created:
|
||||
return event
|
||||
else:
|
||||
|
|
@ -87,7 +93,7 @@ class Plan(BaseModel):
|
|||
title = models.CharField(max_length=255, verbose_name=_('title'), db_index=True)
|
||||
description = models.TextField(verbose_name=_('description'), blank=True)
|
||||
promotion_type = models.CharField(max_length=64, verbose_name=_('promotion type'), db_index=True,
|
||||
choices=PromotionTypeChoices.choices)
|
||||
choices=PromotionTypeChoices.choices) # TODO: processor type
|
||||
data = models.JSONField(default=dict, verbose_name=_('data'), null=True, blank=True)
|
||||
balance = models.IntegerField(verbose_name=_('balance'), default=0)
|
||||
|
||||
|
|
@ -97,6 +103,7 @@ class Plan(BaseModel):
|
|||
return self.title
|
||||
|
||||
def process_event(self, event=None):
|
||||
# TODO: processor
|
||||
self.promote(event=event)
|
||||
|
||||
def promote(self, event=None):
|
||||
|
|
@ -115,40 +122,59 @@ class Plan(BaseModel):
|
|||
class Recipient(BaseModel):
|
||||
label = models.CharField(max_length=255, db_index=True)
|
||||
plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='recipients', null=True, blank=True)
|
||||
recipient_uuid_field = models.CharField(max_length=255, db_index=True)
|
||||
# wallet_uuid = models.UUIDField(null=True, blank=True)
|
||||
wallet_uuid = models.UUIDField(null=True, blank=True)
|
||||
promotion_type = models.CharField(max_length=64, verbose_name=_('promotion type'), db_index=True,
|
||||
choices=PromotionTypeChoices.choices)
|
||||
|
||||
base_amount = models.FloatField(null=True, blank=True)
|
||||
recipient_uuid_field = models.CharField(max_length=255, db_index=True)
|
||||
base_amount_field = models.CharField(max_length=255, db_index=True)
|
||||
|
||||
data = models.JSONField(null=True, blank=True, default=dict)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.label} --> {self.plan}"
|
||||
|
||||
def get_wallet_category_uuid(self):
|
||||
return settings.WALLET_PROMOTION_CATEGORY_UUID
|
||||
return self.wallet_uuid or settings.WALLET_PROMOTION_CATEGORY_UUID
|
||||
|
||||
def get_recipient_uuid(self, event=None):
|
||||
model_name, key = self.recipient_uuid_field.split('::')
|
||||
if model_name == 'event':
|
||||
obj = event
|
||||
elif model_name == 'plan':
|
||||
obj = self.plan
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
def get_recipient_uuid(self, plan=None, event=None):
|
||||
try:
|
||||
model_name, key = self.recipient_uuid_field.split('::')
|
||||
if model_name == 'event':
|
||||
obj = event
|
||||
elif model_name == 'plan':
|
||||
obj = plan
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
return obj.data.get(key)
|
||||
return obj.data.get(key)
|
||||
except Exception as e:
|
||||
try:
|
||||
uuid.UUID(self.recipient_uuid_field)
|
||||
return self.recipient_uuid_field
|
||||
except:
|
||||
pass
|
||||
|
||||
def calculate(self, plan=None, event=None):
|
||||
base_amount = self.base_amount
|
||||
def get_promotion_amount(self, plan=None, event=None):
|
||||
# TODO: get base amount from policy_choice
|
||||
base_amount = 0
|
||||
try:
|
||||
model_name, key = self.base_amount_field.split('::')
|
||||
if model_name == 'event':
|
||||
obj = event
|
||||
elif model_name == 'plan':
|
||||
obj = plan
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
if base_amount <= base_amount and self.plan.balance >= base_amount:
|
||||
promotion_amount = base_amount
|
||||
base_amount = obj.data.get(key)
|
||||
except Exception as e:
|
||||
try:
|
||||
base_amount = int(self.base_amount_field) # todo: to float
|
||||
except:
|
||||
pass
|
||||
|
||||
elif self.plan.balance >= base_amount:
|
||||
promotion_amount = base_amount
|
||||
|
||||
else:
|
||||
raise
|
||||
return promotion_amount
|
||||
return base_amount
|
||||
|
||||
# @property
|
||||
# def handler(self) -> BasePromotionHandler:
|
||||
|
|
@ -156,15 +182,15 @@ class Recipient(BaseModel):
|
|||
|
||||
def promote(self, plan=None, event=None):
|
||||
with transaction.atomic():
|
||||
promotion_amount = self.calculate(plan=plan, event=event)
|
||||
promotion_amount = self.get_promotion_amount(plan=plan, event=event)
|
||||
reserved = self.plan.reserve_promotion_amount(promotion_amount)
|
||||
if reserved:
|
||||
promotion, created = Promotion.objects.get_or_create(
|
||||
promotion_amount=promotion_amount,
|
||||
user_id=self.get_recipient_uuid(event),
|
||||
user_id=self.get_recipient_uuid(plan, event),
|
||||
plan=plan,
|
||||
recipient=self,
|
||||
base_amount=self.base_amount,
|
||||
base_amount=promotion_amount,
|
||||
)
|
||||
if created:
|
||||
promotion.promote()
|
||||
|
|
@ -186,7 +212,7 @@ class Promotion(BaseModel):
|
|||
PROMOTED = 2, _('Promoted')
|
||||
EXPIRED = 10, _('Expired')
|
||||
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
user_id = models.UUIDField() # user_uuid
|
||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||
related_name='+', null=True, blank=True)
|
||||
plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
||||
|
|
@ -223,8 +249,39 @@ class Promotion(BaseModel):
|
|||
self.change_state(from_states=[Promotion.StateChoices.CREATED], to_state=Promotion.StateChoices.ASSIGNED,
|
||||
same_ok=False, raise_exception=True)
|
||||
|
||||
handler = self.plan.handler
|
||||
if handler.promote(promotion=self, **kwargs):
|
||||
self.change_state(from_states=[Promotion.StateChoices.ASSIGNED], to_state=Promotion.StateChoices.PROMOTED)
|
||||
return True
|
||||
return False
|
||||
payee_uuid = self.user_id
|
||||
|
||||
data = {
|
||||
"uuid": str(self.uuid),
|
||||
"payee_id": str(payee_uuid),
|
||||
"payee_type": 1,
|
||||
"amount": self.promotion_amount,
|
||||
"details": {
|
||||
'description': _("for promotion"),
|
||||
'reference_id': str(self.uuid),
|
||||
'application_details_url': ''
|
||||
},
|
||||
}
|
||||
|
||||
submit_response = deposit_to_user_wallet_submit(data)
|
||||
try:
|
||||
submit_response.uuid
|
||||
except Exception as e:
|
||||
logger.exception('Failed to submit payment')
|
||||
raise Exception('Failed to submit payment')
|
||||
|
||||
verify_response = deposit_to_user_wallet_verify(str(self.uuid))
|
||||
logger.debug(f'verify response: {verify_response}')
|
||||
try:
|
||||
if verify_response.state != 5:
|
||||
self.change_state(from_states=[Promotion.StateChoices.ASSIGNED],
|
||||
to_state=Promotion.StateChoices.PROMOTED)
|
||||
else:
|
||||
logger.debug('Failed to verify payment')
|
||||
raise Exception('Failed to verify payment')
|
||||
|
||||
except Exception as e:
|
||||
logger.exception('Failed to verify payment')
|
||||
raise Exception('Failed to verify payment')
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue