From 9e8149135bdad0b53beff72d74d4a74271ed4140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Wed, 4 Mar 2020 10:55:12 +0100 Subject: [PATCH] Move bill generation logic to Bill class, initial work for prepaid --- .../commands/handle-overdue-bills.py | 31 ++-------- uncloud/uncloud_pay/models.py | 56 +++++++++++++++++-- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/uncloud/uncloud_pay/management/commands/handle-overdue-bills.py b/uncloud/uncloud_pay/management/commands/handle-overdue-bills.py index 40468ba..595fbc2 100644 --- a/uncloud/uncloud_pay/management/commands/handle-overdue-bills.py +++ b/uncloud/uncloud_pay/management/commands/handle-overdue-bills.py @@ -1,12 +1,12 @@ from django.core.management.base import BaseCommand from uncloud_auth.models import User -from uncloud_pay.models import Order, Bill, get_balance_for +from uncloud_pay.models import Bill from datetime import timedelta from django.utils import timezone class Command(BaseCommand): - help = 'Generate bills and charge customers if necessary.' + help = 'Take action on overdue bills.' def add_arguments(self, parser): pass @@ -15,28 +15,9 @@ class Command(BaseCommand): users = User.objects.all() print("Processing {} users.".format(users.count())) for user in users: - balance = get_balance_for(user) - if balance < 0: - print("User {} has negative balance ({}), checking for overdue bills." - .format(user.username, balance)) - - # Get bills DESCENDING by creation date (= latest at top). - bills = Bill.objects.filter( - owner=user, - due_date__lt=timezone.now() - ).order_by('-creation_date') - overdue_balance = abs(balance) - overdue_bills = [] - for bill in bills: - if overdue_balance < 0: - break # XXX: I'm (fnux) not fond of breaks! - - overdue_balance -= bill.amount - overdue_bills.append(bill) - - for bill in overdue_bills: - print("/!\ Overdue bill for {}, {} with amount {}" - .format(user.username, bill.uuid, bill.amount)) - # TODO: take action? + for bill in Bill.get_overdue_for(user): + print("/!\ Overdue bill for {}, {} with amount {}" + .format(user.username, bill.uuid, bill.amount)) + # TODO: take action? print("=> Done.") diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index 6f18931..43064e4 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -18,10 +18,14 @@ import uncloud_pay.stripe from uncloud_pay.helpers import beginning_of_month, end_of_month from decimal import Decimal + # Define DecimalField properties, used to represent amounts of money. AMOUNT_MAX_DIGITS=10 AMOUNT_DECIMALS=2 +# Used to generate bill due dates. +BILL_PAYMENT_DELAY=timedelta(days=10) + # See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types class RecurringPeriod(models.TextChoices): ONE_TIME = 'ONCE', _('Onetime') @@ -86,6 +90,20 @@ class Payment(models.Model): default='unknown') timestamp = models.DateTimeField(editable=False, auto_now_add=True) + # WIP prepaid and service activation logic by fnux. + ## We override save() in order to active products awaiting payment. + #def save(self, *args, **kwargs): + # # TODO: only run activation logic on creation, not on update. + # unpaid_bills_before_payment = Bill.get_unpaid_for(self.owner) + # super(Payment, self).save(*args, **kwargs) # Save payment in DB. + # unpaid_bills_after_payment = Bill.get_unpaid_for(self.owner) + + # newly_paid_bills = list( + # set(unpaid_bills_before_payment) - set(unpaid_bills_after_payment)) + # for bill in newly_paid_bills: + # bill.activate_orders() + + class PaymentMethod(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) owner = models.ForeignKey(get_user_model(), @@ -183,7 +201,7 @@ class Bill(models.Model): return self.ending_date < timezone.now() @staticmethod - def generate_for(year, month, user, allowed_delay): + def generate_for(year, month, user): # /!\ We exclusively work on the specified year and month. # Default values for next bill (if any). Only saved at the end of @@ -192,7 +210,7 @@ class Bill(models.Model): starting_date=beginning_of_month(year, month), ending_date=end_of_month(year, month), creation_date=timezone.now(), - due_date=timezone.now() + allowed_delay) + due_date=timezone.now() + BILL_PAYMENT_DELAY) # Select all orders active on the request period. orders = Order.objects.filter( @@ -231,6 +249,35 @@ class Bill(models.Model): # Return None if no bill was created. return None + @staticmethod + def get_unpaid_for(user): + balance = get_balance_for(user) + unpaid_bills = [] + # No unpaid bill if balance is positive. + if balance >= 0: + return [] + else: + bills = Bill.objects.filter( + owner=user, + due_date__lt=timezone.now() + ).order_by('-creation_date') + + # Amount to be paid by the customer. + unpaid_balance = abs(balance) + for bill in bills: + if unpaid_balance < 0: + break + + unpaid_balance -= bill.amount + unpaid_bills.append(bill) + + return unpaid_bills + + @staticmethod + def get_overdue_for(user): + unpaid_bills = Bill.get_unpaid_for(user) + return list(filter(lambda bill: bill.due_date > timezone.now(), unpaid_bills)) + class BillRecord(): """ Entry of a bill, dynamically generated from order records. @@ -345,9 +392,6 @@ class Order(models.Model): recurring_price=recurring_price, description=description) - def generate_bill(self): - pass - class OrderRecord(models.Model): """ @@ -398,7 +442,7 @@ class Product(models.Model): status = models.CharField(max_length=32, choices=ProductStatus.choices, - default=ProductStatus.AWAITING_PAYMENT) + default=ProductStatus.PENDING) order = models.ForeignKey(Order, on_delete=models.CASCADE,