from django.db import models from django.contrib.auth import get_user_model from django.core.validators import MinValueValidator from django.utils.translation import gettext_lazy as _ from django.utils import timezone import uuid AMOUNT_MAX_DIGITS=10 AMOUNT_DECIMALS=2 # See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types class RecurringPeriod(models.TextChoices): ONE_TIME = 'ONCE', _('Onetime') PER_YEAR = 'YEAR', _('Per Year') PER_MONTH = 'MONTH', _('Per Month') PER_MINUTE = 'MINUTE', _('Per Minute') PER_DAY = 'DAY', _('Per Day') PER_HOUR = 'HOUR', _('Per Hour') PER_SECOND = 'SECOND', _('Per Second') class Bill(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) creation_date = models.DateTimeField(auto_now_add=True) starting_date = models.DateTimeField() ending_date = models.DateTimeField() due_date = models.DateField() valid = models.BooleanField(default=True) @property def entries(self): # TODO: return list of Bill entries, extract from linked order # for each related order # for each product # build BillEntry return [] @property def total(self): #return helpers.sum_amounts(self.entries) pass class BillEntry(): start_date = timezone.now() end_date = timezone.now() recurring_period = RecurringPeriod.PER_MONTH recurring_price = 0 amount = 0 description = "" # /!\ BIG FAT WARNING /!\ # # # Order are assumed IMMUTABLE and used as SOURCE OF TRUST for generating # bills. Do **NOT** mutate then! # # Why? We need to store the state somewhere since product are mutable (e.g. # adding RAM to VM, changing price of 1GB of RAM, ...). An alternative could # have been to only store the state in bills but would have been more # confusing: the order is a 'contract' with the customer, were both parts # agree on deal => That's what we want to keep archived. # # SOON: # # We'll need to add some kind of OrderEntry table (each order might have # multiple entries) storing: recurring_price, recurring_period, setup_fee, description # # FOR NOW: # # We dynamically get pricing from linked product, as they are not updated in # this stage of development. # # /!\ BIG FAT WARNING /!\ # class Order(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, editable=False) creation_date = models.DateTimeField(auto_now_add=True) starting_date = models.DateTimeField(auto_now_add=True) ending_date = models.DateTimeField(blank=True, null=True) bill = models.ManyToManyField(Bill, editable=False, blank=True) recurring_period = models.CharField(max_length=32, choices = RecurringPeriod.choices, default = RecurringPeriod.PER_MONTH) # def amount(self): # amount = recurring_price # if recurring and first_month: # amount += one_time_price # return amount # you get the picture class PaymentMethod(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, editable=False) source = models.CharField(max_length=256, choices = ( ('stripe', 'Stripe'), ('unknown', 'Unknown'), ), default='stripe') description = models.TextField() primary = models.BooleanField(default=True) def charge(self, amount): if amount > 0: # Make sure we don't charge negative amount by errors... if self.source == 'stripe': # TODO: wire to strip, see meooow-payv1/strip_utils.py payment = Payment(owner=self.owner, source=self.source, amount=amount) payment.save() # TODO: Check return status return True else: # We do not handle that source yet. return False else: return False class Meta: unique_together = [['owner', 'primary']] class Payment(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) amount = models.DecimalField( default=0.0, max_digits=AMOUNT_MAX_DIGITS, decimal_places=AMOUNT_DECIMALS, validators=[MinValueValidator(0)]) source = models.CharField(max_length=256, choices = ( ('wire', 'Wire Transfer'), ('stripe', 'Stripe'), ('voucher', 'Voucher'), ('referral', 'Referral'), ('unknown', 'Unknown') ), default='unknown') timestamp = models.DateTimeField(editable=False, auto_now_add=True) class Product(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, editable=False) description = "" status = models.CharField(max_length=256, choices = ( ('pending', 'Pending'), ('being_created', 'Being created'), ('active', 'Active'), ('deleted', 'Deleted') ), default='pending' ) order = models.ForeignKey(Order, on_delete=models.CASCADE, editable=False, null=True) @property def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH): pass # To be implemented in child. @property def setup_fee(self): return 0 class Meta: abstract = True