From 4e51670a901ac28bf2d1d0691caa984afb293b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Tue, 3 Mar 2020 08:53:19 +0100 Subject: [PATCH] Expand recurring period billing logic for DD/MM/hh/month --- uncloud/uncloud_pay/models.py | 69 ++++++++++++++++++++++++++++-- uncloud/uncloud_pay/serializers.py | 2 +- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index a29dc3c..3be3c2c 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -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(), diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index 6c6c04e..d523b7a 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -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: