accounts/apps/transactions/models.py
2020-04-04 14:14:01 +04:30

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_client_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_client_id=application_client_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_client_id = models.CharField(max_length=100, db_index=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_client_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}"