Move bill generation logic to Bill class, initial work for prepaid

This commit is contained in:
fnux 2020-03-04 10:55:12 +01:00
parent 9aabc66e57
commit 9e8149135b
2 changed files with 56 additions and 31 deletions

View file

@ -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,26 +15,7 @@ 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:
for bill in Bill.get_overdue_for(user):
print("/!\ Overdue bill for {}, {} with amount {}"
.format(user.username, bill.uuid, bill.amount))
# TODO: take action?

View file

@ -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,