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,6 +589,13 @@ 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
# 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!
@ -595,7 +604,7 @@ class Bill(models.Model):
# 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']:
for order in bill_orders:
order.bill.add(next_monthly_bill)
logger.info("Generated monthly bill {} (amount: {}) for user {}."
@ -614,6 +623,13 @@ class Bill(models.Model):
ending_date = next_yearly_bill_start_on.replace(
year=next_yearly_bill_start_on.year+1) - timedelta(days=1)
# 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)
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,
@ -622,7 +638,7 @@ class Bill(models.Model):
# 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]:
for order in bill_orders:
order.bill.add(next_yearly_bill)
logger.info("Generated yearly bill {} (amount: {}) for user {}."

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)