Split bills between orders of the same billing address
This commit is contained in:
parent
db9ff5d18b
commit
3a03717b12
2 changed files with 100 additions and 25 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue