124 lines
4.6 KiB
Python
Executable file
124 lines
4.6 KiB
Python
Executable file
import datetime
|
|
import uuid
|
|
|
|
from django.db import models
|
|
from django.db import transaction
|
|
from django.db.models import F
|
|
from django.utils.translation import gettext_lazy as _
|
|
from model_utils.choices import Choices
|
|
from rest_framework.exceptions import APIException, ValidationError
|
|
|
|
from apps.users.models import User
|
|
|
|
|
|
class TransactionManager(models.Manager):
|
|
def create_transaction(self,
|
|
amount,
|
|
payee: User,
|
|
payer: User = None,
|
|
delay=0,
|
|
detail_id=None,
|
|
application_id=None,
|
|
state=None):
|
|
if not (isinstance(payer, User) or payer is None):
|
|
raise ValidationError('payer type not valid')
|
|
if not isinstance(payee, User):
|
|
raise ValidationError('payee type not valid')
|
|
if not amount > 0:
|
|
raise ValidationError('amount should be greater than zero')
|
|
if not delay >= 0:
|
|
raise ValidationError('delay should be greater than equal zero')
|
|
|
|
transaction = self.model(payer=payer,
|
|
payee=payee,
|
|
amount=amount,
|
|
delay=delay,
|
|
detail_id=detail_id,
|
|
application_id=application_id
|
|
)
|
|
if state:
|
|
transaction.state = state
|
|
|
|
transaction.save()
|
|
return transaction
|
|
|
|
|
|
class Transaction(models.Model):
|
|
STATE_CHOICES = Choices(
|
|
(1, 'created', 'created'),
|
|
(2, 'delayed', 'delayed'),
|
|
(3, 'pending', 'pending'),
|
|
(4, 'failed', 'failed'),
|
|
(5, 'done', 'done'),
|
|
)
|
|
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) # reserve_code
|
|
amount = models.IntegerField(_('amount'), blank=False)
|
|
delay = models.IntegerField(default=0, blank=True) # number of days for delayed payments
|
|
payer = models.ForeignKey(User, on_delete=models.PROTECT, related_name='payments', blank=True, null=True)
|
|
payee = models.ForeignKey(User, on_delete=models.PROTECT, related_name='receipts')
|
|
state = models.IntegerField(choices=STATE_CHOICES, default=STATE_CHOICES.created)
|
|
create = models.DateTimeField(_('create'), blank=True, null=True, auto_now=True)
|
|
last_update = models.DateTimeField(_('last update'), blank=True, null=True, auto_now=True)
|
|
application_id = models.IntegerField("application ID", blank=True, null=True)
|
|
detail_id = models.IntegerField(null=True, blank=True) # or better detail url or res number
|
|
|
|
objects = TransactionManager()
|
|
|
|
class Meta:
|
|
unique_together = ('application_id', 'detail_id',)
|
|
|
|
def pay(self, payer=None):
|
|
if not self.payer and not payer:
|
|
raise APIException('payer identification error')
|
|
|
|
if self.payer and payer and not self.payer == payer:
|
|
raise APIException('payer identification error')
|
|
|
|
if not self.payer:
|
|
self.payer = payer
|
|
|
|
with transaction.atomic():
|
|
# TODO: check bundry conditions
|
|
self.last_update = datetime.datetime.now()
|
|
|
|
if not self.state == self.STATE_CHOICES.created:
|
|
raise APIException('this transaction is not ready to pay')
|
|
|
|
if self.payer.balance < self.amount:
|
|
raise APIException('not enough balance')
|
|
|
|
self.payer.balance = F('balance') - self.amount
|
|
self.payer.save(update_fields=['balance'])
|
|
|
|
self.payer.refresh_from_db()
|
|
if self.payer.balance < 0:
|
|
raise APIException('not enough balance')
|
|
|
|
if self.delay:
|
|
self.state = self.STATE_CHOICES.delayed
|
|
self.save()
|
|
|
|
else:
|
|
self.payee.balance = F('balance') + self.amount
|
|
self.payee.save(update_fields=['balance'])
|
|
|
|
self.state = self.STATE_CHOICES.done
|
|
self.save()
|
|
|
|
self.save()
|
|
return self
|
|
|
|
def receipt(self):
|
|
with transaction.atomic():
|
|
self.last_update = datetime.datetime.now()
|
|
# TODO: check bundry conditions
|
|
if not self.state == self.STATE_CHOICES.delayed:
|
|
raise APIException('this transaction is not delayed')
|
|
|
|
self.payee.balance = F('balance') + self.amount
|
|
self.state = self.STATE_CHOICES.done
|
|
self.payee.save()
|
|
self.save()
|
|
|
|
def __str__(self):
|
|
return f"{self.payer} -> {self.payee} ({self.uuid}): {self.amount}"
|