forked from uncloud/uncloud
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 uuid
|
||||||
import logging
|
import logging
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
import itertools
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
import uncloud_pay.stripe
|
import uncloud_pay.stripe
|
||||||
|
@ -525,10 +525,12 @@ class Bill(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def billing_address(self):
|
def billing_address(self):
|
||||||
# FIXME: make sure all the orders of a bill match the same billing address.
|
|
||||||
orders = Order.objects.filter(bill=self)
|
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
|
return orders[0].billing_address
|
||||||
|
|
||||||
|
# TODO: split this huuuge method!
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_for(year, month, user):
|
def generate_for(year, month, user):
|
||||||
# /!\ We exclusively work on the specified year and month.
|
# /!\ 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
|
prepaid_due_date = min(creation_date, starting_date) + BILL_PAYMENT_DELAY
|
||||||
postpaid_due_date = max(creation_date, ending_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,
|
next_monthly_bill = Bill.objects.create(owner=user,
|
||||||
creation_date=creation_date,
|
creation_date=creation_date,
|
||||||
starting_date=starting_date, # FIXME: this is a hack!
|
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
|
# It is not possible to register many-to-many relationship before
|
||||||
# the two end-objects are saved in database.
|
# 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)
|
order.bill.add(next_monthly_bill)
|
||||||
|
|
||||||
logger.info("Generated monthly bill {} (amount: {}) for user {}."
|
logger.info("Generated monthly bill {} (amount: {}) for user {}."
|
||||||
|
@ -614,6 +623,13 @@ class Bill(models.Model):
|
||||||
ending_date = next_yearly_bill_start_on.replace(
|
ending_date = next_yearly_bill_start_on.replace(
|
||||||
year=next_yearly_bill_start_on.year+1) - timedelta(days=1)
|
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,
|
next_yearly_bill = Bill.objects.create(owner=user,
|
||||||
creation_date=creation_date,
|
creation_date=creation_date,
|
||||||
starting_date=next_yearly_bill_start_on,
|
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
|
# It is not possible to register many-to-many relationship before
|
||||||
# the two end-objects are saved in database.
|
# 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)
|
order.bill.add(next_yearly_bill)
|
||||||
|
|
||||||
logger.info("Generated yearly bill {} (amount: {}) for user {}."
|
logger.info("Generated yearly bill {} (amount: {}) for user {}."
|
||||||
|
|
|
@ -143,7 +143,6 @@ class ProductActivationTestCase(TestCase):
|
||||||
starting_date=starting_date,
|
starting_date=starting_date,
|
||||||
recurring_period=RecurringPeriod.PER_MONTH,
|
recurring_period=RecurringPeriod.PER_MONTH,
|
||||||
billing_address=self.billing_address)
|
billing_address=self.billing_address)
|
||||||
order.save()
|
|
||||||
|
|
||||||
product = GenericServiceProduct(
|
product = GenericServiceProduct(
|
||||||
custom_description="Test product",
|
custom_description="Test product",
|
||||||
|
@ -154,7 +153,7 @@ class ProductActivationTestCase(TestCase):
|
||||||
product.save()
|
product.save()
|
||||||
|
|
||||||
# XXX: to be automated.
|
# 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.
|
# Validate initial state: must be awaiting payment.
|
||||||
self.assertEqual(product.status, UncloudStatus.AWAITING_PAYMENT)
|
self.assertEqual(product.status, UncloudStatus.AWAITING_PAYMENT)
|
||||||
|
@ -167,3 +166,63 @@ class ProductActivationTestCase(TestCase):
|
||||||
GenericServiceProduct.objects.get(uuid=product.uuid).status,
|
GenericServiceProduct.objects.get(uuid=product.uuid).status,
|
||||||
UncloudStatus.PENDING
|
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