Expand recurring period billing logic for DD/MM/hh/month

This commit is contained in:
fnux 2020-03-03 08:53:19 +01:00
parent 4ad737ed90
commit 4e51670a90
2 changed files with 66 additions and 5 deletions

View File

@ -4,6 +4,9 @@ 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
from math import ceil
from datetime import timedelta
from calendar import monthrange
import uuid
@ -38,7 +41,7 @@ class Bill(models.Model):
orders = Order.objects.filter(bill=self)
for order in orders:
for order_record in order.records:
bill_record = BillRecord(order_record)
bill_record = BillRecord(self, order_record)
bill_records.append(bill_record)
return bill_records
@ -47,18 +50,66 @@ class Bill(models.Model):
def total(self):
return reduce(lambda acc, record: acc + record.amount(), self.records, 0)
@property
def final(self):
# A bill is final when its ending date is passed.
return self.ending_date < timezone.now()
class BillRecord():
def __init__(self, order_record):
self.order = order_record.order.uuid
def __init__(self, bill, order_record):
self.bill = bill
self.order = order_record.order
self.setup_fee = order_record.setup_fee
self.recurring_price = order_record.recurring_price
self.recurring_period = order_record.recurring_period
self.description = order_record.description
def amount(self):
# TODO: Billing logic here!
# Compute billing delta.
billed_until = self.bill.ending_date
if self.order.ending_date != None and self.order.ending_date < self.order.ending_date:
billed_until = self.order.ending_date
billed_from = self.bill.starting_date
if self.order.starting_date > self.bill.starting_date:
billed_from = self.order.starting_date
if billed_from > billed_until:
# TODO: think about and check edges cases. This should not be
# possible.
raise Exception('Impossible billing delta!')
billed_delta = billed_until - billed_from
# TODO: refactor this thing?
# TODO: weekly
# TODO: yearly
if self.recurring_period == RecurringPeriod.PER_MONTH:
days = ceil(billed_delta / timedelta(days=1))
# XXX: we assume monthly bills for now.
if (self.bill.starting_date.year != self.bill.starting_date.year or
self.bill.starting_date.month != self.bill.ending_date.month):
raise Exception('Bill {} covers more than one month. Cannot bill PER_MONTH.'.
format(self.bill.uuid))
# XXX: minumal length of monthly order is to be enforced somewhere else.
(_, days_in_month) = monthrange(
self.bill.starting_date.year,
self.bill.starting_date.month)
adjusted_recurring_price = self.recurring_price / days_in_month
recurring_price = adjusted_recurring_price * days
return self.recurring_price # TODO
elif self.recurring_period == RecurringPeriod.PER_DAY:
days = ceil(billed_delta / timedelta(days=1))
return self.recurring_price * days
elif self.recurring_period == RecurringPeriod.PER_HOUR:
hours = ceil(billed_delta / timedelta(hours=1))
return self.recurring_price * hours
elif self.recurring_period == RecurringPeriod.PER_SECOND:
seconds = ceil(billed_delta / timedelta(seconds=1))
return self.recurring_price * seconds
else:
raise Exception('Unsupported recurring period: {}.'.
format(record.recurring_period))
@ -75,12 +126,14 @@ class BillRecord():
# agree on deal => That's what we want to keep archived.
#
# /!\ 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)
# TODO: enforce ending_date - starting_date to be larger than recurring_period.
creation_date = models.DateTimeField(auto_now_add=True)
starting_date = models.DateTimeField(auto_now_add=True)
ending_date = models.DateTimeField(blank=True,
@ -129,6 +182,14 @@ class OrderRecord(models.Model):
def recurring_period(self):
return self.order.recurring_period
@property
def starting_date(self):
return self.order.starting_date
@property
def ending_date(self):
return self.order.ending_date
class PaymentMethod(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey(get_user_model(),

View File

@ -22,7 +22,7 @@ class BillSerializer(serializers.ModelSerializer):
class Meta:
model = Bill
fields = ['owner', 'total', 'due_date', 'creation_date',
'starting_date', 'ending_date', 'records']
'starting_date', 'ending_date', 'records', 'final']
class PaymentSerializer(serializers.ModelSerializer):
class Meta: