Split bills between orders of the same billing address

This commit is contained in:
fnux 2020-04-18 11:21:11 +02:00
parent db9ff5d18b
commit 3a03717b12
2 changed files with 100 additions and 25 deletions

View file

@ -9,10 +9,10 @@ from django.core.exceptions import ObjectDoesNotExist
import uuid
import logging
from functools import reduce
import itertools
from math import ceil
from datetime import timedelta
from calendar import monthrange
from decimal import Decimal
import uncloud_pay.stripe
@ -525,10 +525,12 @@ class Bill(models.Model):
@property
def billing_address(self):
# FIXME: make sure all the orders of a bill match the same billing address.
orders = Order.objects.filter(bill=self)
# The genrate_for method makes sure all the orders of a bill share the
# same billing address. TODO: It would be nice to enforce that somehow...
return orders[0].billing_address
# TODO: split this huuuge method!
@staticmethod
def generate_for(year, month, user):
# /!\ We exclusively work on the specified year and month.
@ -587,22 +589,29 @@ class Bill(models.Model):
prepaid_due_date = min(creation_date, starting_date) + BILL_PAYMENT_DELAY
postpaid_due_date = max(creation_date, ending_date) + BILL_PAYMENT_DELAY
next_monthly_bill = Bill.objects.create(owner=user,
# There should not be any bill linked to orders with different
# billing addresses.
per_address_orders = itertools.groupby(
unpaid_orders['monthly_or_less'],
lambda o: o.billing_address)
for addr, bill_orders in per_address_orders:
next_monthly_bill = Bill.objects.create(owner=user,
creation_date=creation_date,
starting_date=starting_date, # FIXME: this is a hack!
ending_date=ending_date,
due_date=postpaid_due_date)
# It is not possible to register many-to-many relationship before
# the two end-objects are saved in database.
for order in unpaid_orders['monthly_or_less']:
order.bill.add(next_monthly_bill)
# It is not possible to register many-to-many relationship before
# the two end-objects are saved in database.
for order in bill_orders:
order.bill.add(next_monthly_bill)
logger.info("Generated monthly bill {} (amount: {}) for user {}."
logger.info("Generated monthly bill {} (amount: {}) for user {}."
.format(next_monthly_bill.uuid, next_monthly_bill.total, user))
# Add to output.
generated_bills.append(next_monthly_bill)
# Add to output.
generated_bills.append(next_monthly_bill)
# Handle yearly bills starting on working month.
if len(unpaid_orders['yearly']) > 0:
@ -614,22 +623,29 @@ class Bill(models.Model):
ending_date = next_yearly_bill_start_on.replace(
year=next_yearly_bill_start_on.year+1) - timedelta(days=1)
next_yearly_bill = Bill.objects.create(owner=user,
creation_date=creation_date,
starting_date=next_yearly_bill_start_on,
ending_date=ending_date,
due_date=prepaid_due_date)
# There should not be any bill linked to orders with different
# billing addresses.
per_address_orders = itertools.groupby(
unpaid_orders['yearly'][next_yearly_bill_start_on],
lambda o: o.billing_address)
# It is not possible to register many-to-many relationship before
# the two end-objects are saved in database.
for order in unpaid_orders['yearly'][next_yearly_bill_start_on]:
order.bill.add(next_yearly_bill)
for addr, bill_orders in per_address_orders:
next_yearly_bill = Bill.objects.create(owner=user,
creation_date=creation_date,
starting_date=next_yearly_bill_start_on,
ending_date=ending_date,
due_date=prepaid_due_date)
logger.info("Generated yearly bill {} (amount: {}) for user {}."
.format(next_yearly_bill.uuid, next_yearly_bill.total, user))
# It is not possible to register many-to-many relationship before
# the two end-objects are saved in database.
for order in bill_orders:
order.bill.add(next_yearly_bill)
# Add to output.
generated_bills.append(next_yearly_bill)
logger.info("Generated yearly bill {} (amount: {}) for user {}."
.format(next_yearly_bill.uuid, next_yearly_bill.total, user))
# Add to output.
generated_bills.append(next_yearly_bill)
# Return generated (monthly + yearly) bills.
return generated_bills

View file

@ -143,7 +143,6 @@ class ProductActivationTestCase(TestCase):
starting_date=starting_date,
recurring_period=RecurringPeriod.PER_MONTH,
billing_address=self.billing_address)
order.save()
product = GenericServiceProduct(
custom_description="Test product",
@ -154,7 +153,7 @@ class ProductActivationTestCase(TestCase):
product.save()
# XXX: to be automated.
order.add_record(product.one_time_price, product.recurring_price(), product.description)
order.add_record(product.one_time_price, product.recurring_price, product.description)
# Validate initial state: must be awaiting payment.
self.assertEqual(product.status, UncloudStatus.AWAITING_PAYMENT)
@ -167,3 +166,63 @@ class ProductActivationTestCase(TestCase):
GenericServiceProduct.objects.get(uuid=product.uuid).status,
UncloudStatus.PENDING
)
class BillingAddressTestCase(TestCase):
def setUp(self):
self.user = get_user_model().objects.create(
username='jdoe',
email='john.doe@domain.tld')
self.billing_address_01 = BillingAddress.objects.create(
owner=self.user,
street="unknown1",
city="unknown1",
postal_code="unknown1",
country="CH")
self.billing_address_02 = BillingAddress.objects.create(
owner=self.user,
street="unknown2",
city="unknown2",
postal_code="unknown2",
country="CH")
def test_billing_with_single_address(self):
# Create new orders somewhere in the past so that we do not encounter
# auto-created initial bills.
starting_date = datetime.fromisoformat('2020-03-01')
order_01 = Order.objects.create(
owner=self.user,
starting_date=starting_date,
recurring_period=RecurringPeriod.PER_MONTH,
billing_address=self.billing_address_01)
order_02 = Order.objects.create(
owner=self.user,
starting_date=starting_date,
recurring_period=RecurringPeriod.PER_MONTH,
billing_address=self.billing_address_01)
# We need a single bill since we work with a single address.
bills = Bill.generate_for(2020, 4, self.user)
self.assertEqual(len(bills), 1)
def test_billing_with_multiple_addresses(self):
# Create new orders somewhere in the past so that we do not encounter
# auto-created initial bills.
starting_date = datetime.fromisoformat('2020-03-01')
order_01 = Order.objects.create(
owner=self.user,
starting_date=starting_date,
recurring_period=RecurringPeriod.PER_MONTH,
billing_address=self.billing_address_01)
order_02 = Order.objects.create(
owner=self.user,
starting_date=starting_date,
recurring_period=RecurringPeriod.PER_MONTH,
billing_address=self.billing_address_02)
# We need different bills since we work with different addresses.
bills = Bill.generate_for(2020, 4, self.user)
self.assertEqual(len(bills), 2)