Move bill generation logic to Bill class, initial work for prepaid
This commit is contained in:
parent
9aabc66e57
commit
9e8149135b
2 changed files with 56 additions and 31 deletions
|
@ -1,12 +1,12 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from uncloud_auth.models import User
|
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 datetime import timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Generate bills and charge customers if necessary.'
|
help = 'Take action on overdue bills.'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
pass
|
pass
|
||||||
|
@ -15,28 +15,9 @@ class Command(BaseCommand):
|
||||||
users = User.objects.all()
|
users = User.objects.all()
|
||||||
print("Processing {} users.".format(users.count()))
|
print("Processing {} users.".format(users.count()))
|
||||||
for user in users:
|
for user in users:
|
||||||
balance = get_balance_for(user)
|
for bill in Bill.get_overdue_for(user):
|
||||||
if balance < 0:
|
print("/!\ Overdue bill for {}, {} with amount {}"
|
||||||
print("User {} has negative balance ({}), checking for overdue bills."
|
.format(user.username, bill.uuid, bill.amount))
|
||||||
.format(user.username, balance))
|
# TODO: take action?
|
||||||
|
|
||||||
# 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?
|
|
||||||
|
|
||||||
print("=> Done.")
|
print("=> Done.")
|
||||||
|
|
|
@ -18,10 +18,14 @@ import uncloud_pay.stripe
|
||||||
from uncloud_pay.helpers import beginning_of_month, end_of_month
|
from uncloud_pay.helpers import beginning_of_month, end_of_month
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
# Define DecimalField properties, used to represent amounts of money.
|
# Define DecimalField properties, used to represent amounts of money.
|
||||||
AMOUNT_MAX_DIGITS=10
|
AMOUNT_MAX_DIGITS=10
|
||||||
AMOUNT_DECIMALS=2
|
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
|
# See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types
|
||||||
class RecurringPeriod(models.TextChoices):
|
class RecurringPeriod(models.TextChoices):
|
||||||
ONE_TIME = 'ONCE', _('Onetime')
|
ONE_TIME = 'ONCE', _('Onetime')
|
||||||
|
@ -86,6 +90,20 @@ class Payment(models.Model):
|
||||||
default='unknown')
|
default='unknown')
|
||||||
timestamp = models.DateTimeField(editable=False, auto_now_add=True)
|
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):
|
class PaymentMethod(models.Model):
|
||||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
owner = models.ForeignKey(get_user_model(),
|
owner = models.ForeignKey(get_user_model(),
|
||||||
|
@ -183,7 +201,7 @@ class Bill(models.Model):
|
||||||
return self.ending_date < timezone.now()
|
return self.ending_date < timezone.now()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_for(year, month, user, allowed_delay):
|
def generate_for(year, month, user):
|
||||||
# /!\ We exclusively work on the specified year and month.
|
# /!\ We exclusively work on the specified year and month.
|
||||||
|
|
||||||
# Default values for next bill (if any). Only saved at the end of
|
# 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),
|
starting_date=beginning_of_month(year, month),
|
||||||
ending_date=end_of_month(year, month),
|
ending_date=end_of_month(year, month),
|
||||||
creation_date=timezone.now(),
|
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.
|
# Select all orders active on the request period.
|
||||||
orders = Order.objects.filter(
|
orders = Order.objects.filter(
|
||||||
|
@ -231,6 +249,35 @@ class Bill(models.Model):
|
||||||
# Return None if no bill was created.
|
# Return None if no bill was created.
|
||||||
return None
|
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():
|
class BillRecord():
|
||||||
"""
|
"""
|
||||||
Entry of a bill, dynamically generated from order records.
|
Entry of a bill, dynamically generated from order records.
|
||||||
|
@ -345,9 +392,6 @@ class Order(models.Model):
|
||||||
recurring_price=recurring_price,
|
recurring_price=recurring_price,
|
||||||
description=description)
|
description=description)
|
||||||
|
|
||||||
def generate_bill(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OrderRecord(models.Model):
|
class OrderRecord(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -398,7 +442,7 @@ class Product(models.Model):
|
||||||
|
|
||||||
status = models.CharField(max_length=32,
|
status = models.CharField(max_length=32,
|
||||||
choices=ProductStatus.choices,
|
choices=ProductStatus.choices,
|
||||||
default=ProductStatus.AWAITING_PAYMENT)
|
default=ProductStatus.PENDING)
|
||||||
|
|
||||||
order = models.ForeignKey(Order,
|
order = models.ForeignKey(Order,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|
Loading…
Reference in a new issue