forked from uncloud/uncloud
Expand recurring period billing logic for DD/MM/hh/month
This commit is contained in:
parent
4ad737ed90
commit
4e51670a90
2 changed files with 66 additions and 5 deletions
|
@ -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(),
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue