begin splitting bill record creation function

This commit is contained in:
Nico Schottelius 2020-09-02 16:02:28 +02:00
parent 18f9a3848a
commit 1c7d81762d
3 changed files with 185 additions and 52 deletions

View file

@ -11,7 +11,7 @@ from django.http import FileResponse
from django.template.loader import render_to_string
from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress
from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress, SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee
class BillRecordInline(admin.TabularInline):
@ -91,5 +91,7 @@ admin.site.register(Order)
admin.site.register(BillRecord)
admin.site.register(BillingAddress)
for m in [ SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee ]:
admin.site.register(m)
#admin.site.register(Order, OrderAdmin)

View file

@ -459,19 +459,121 @@ class Bill(models.Model):
return bills
@classmethod
def create_next_bill_for_user_address(cls, owner, billing_address, ending_date=None):
def create_bill_records_for_recurring_orders(cls, bill):
"""
Create or update bill records for recurring orders for the
given bill
"""
owner = bill.owner
billing_address = bill.billing_address
all_orders = Order.objects.filter(owner=owner,
billing_address=billing_address).order_by('id').exclude(recurring_period=RecurringPeriod.ONE_TIME)
for order in all_orders:
bill_record_for_this_bill = BillRecord.objects.filter(bill=bill,
order=order).first()
if bill_record_for_this_bill:
cls.update_bill_record_for_this_bill(bill_record_for_this_bill,
order,
bill)
else:
cls.create_new_bill_record_for_this_bill(order, bill)
@staticmethod
def create_new_bill_record_for_this_bill(order, bill):
last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last()
this_starting_date=order.starting_date
if last_bill_record:
if last_bill_record.ending_date >= bill.ending_date:
return
if order.ending_date:
if last_bill_record.ending_date == order.ending_date:
return
this_starting_date = start_after(last_bill_record.ending_date)
ending_date = cls.get_bill_record_ending_date(order, bill)
return BillRecord.objects.create(bill=bill,
order=order,
starting_date=this_starting_date,
ending_date=this_ending_date)
@staticmethod
def update_bill_record_for_this_bill(bill_record,
order,
bill):
# we may need to adjust it, but let's do this logic another time
# If the order has an ending date set, we might need to adjust the bill_record
if order.ending_date:
if bill_record_for_this_bill.ending_date != order.ending_date:
bill_record_for_this_bill.ending_date = order.ending_date
else:
# recurring, not terminated, should go until at least end of bill
if bill_record_for_this_bill.ending_date < bill.ending_date:
bill_record_for_this_bill.ending_date = bill.ending_date
bill_record_for_this_bill.save()
@staticmethod
def get_bill_record_ending_date(order, bill):
"""
Determine the ending date of the billing record
"""
# If the order is quit, charge the final amount (?)
if order.ending_date:
this_ending_date = order.ending_date
else:
if order.earliest_ending_date > bill.ending_date:
this_ending_date = order.earliest_ending_date
else:
this_ending_date = bill.ending_date
return this_ending_date
@classmethod
def create_next_bill_for_user_address(cls,
owner,
billing_address,
ending_date=None):
"""
Filtering ideas (TBD):
If order is replaced, it should not be added anymore if it has been billed "last time"
If order has ended and finally charged, do not charge anymore
Find out the last billrecord for the order, if there is none, bill from the starting date
"""
last_bill = cls.objects.filter(owner=owner, billing_address=billing_address).order_by('id').last()
# it is important to sort orders here, as bill records will be
# created (and listed) in this order
all_orders = Order.objects.filter(owner=owner, billing_address=billing_address).order_by('id')
all_orders = Order.objects.filter(owner=owner,
billing_address=billing_address).order_by('id')
first_order = all_orders.first()
bill = None
ending_date = None
# Get date & bill from previous bill
# Get date & bill from previous bill, if it exists
if last_bill:
if not last_bill.is_final:
bill = last_bill
@ -504,12 +606,10 @@ class Bill(models.Model):
starting_date=order.starting_date,
ending_date=order.ending_date)
else:
# Bill all recurring orders
else: # recurring orders
bill_record_for_this_bill = BillRecord.objects.filter(bill=bill,
order=order).first()
# This bill already has a bill record for the order
# We potentially need to update the ending_date if the ending_date
# of the bill changed.
@ -529,11 +629,8 @@ class Bill(models.Model):
bill_record_for_this_bill.save()
else:
# No bill record in this bill for the order yet
# Find out whether it was already billed for the billing period of
# this bill
else: # No bill record in this bill for this order yet
# Find out when billed last time
last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last()
# Default starting date
@ -580,12 +677,6 @@ class Bill(models.Model):
# Filtering ideas:
# If order is replaced, it should not be added anymore if it has been billed "last time"
# If order has ended and finally charged, do not charge anymore
# Find out the last billrecord for the order, if there is none, bill from the starting date
return bill
@ -730,8 +821,6 @@ class Product(UncloudModel):
when_to_start = timezone.now()
if self.last_recurring_order:
# If the new order is less in value than the previous
# order, the previous order needs to be finished first
if self.recurring_price < self.last_recurring_order.price:
if when_to_start < self.last_recurring_order.next_ending_date:

View file

@ -414,53 +414,95 @@ class ModifyProductTestCase(TestCase):
self.assertNotEqual(bills[0].sum, pro_rata_amount * price)
self.assertEqual(bills[0].sum, price)
def test_bill_for_modified_product(self):
def test_bill_for_increasing_product_easy(self):
"""
Modify a product, see one pro rata entry
Modify product, check general logi
"""
price = 5
# Standard 30d recurring product
product = SampleRecurringProduct.objects.create(owner=self.user,
rc_price=price)
# Create product
starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
ending_date = timezone.make_aware(datetime.datetime(2019,3,31))
change_date = timezone.make_aware(datetime.datetime(2019,4,17))
bill_ending_date = timezone.make_aware(datetime.datetime(2019,4,30))
starting_price = 10
product = SampleRecurringProduct.objects.create(owner=self.user,
rc_price=starting_price)
product.create_order(starting_date)
product.rc_price = 10
change1_date = timezone.make_aware(datetime.datetime(2019,4,17))
product.rc_price = 20
product.save()
product.create_or_update_recurring_order(when_to_start=change1_date)
product.create_or_update_recurring_order(when_to_start=change_date)
bill_ending_date = timezone.make_aware(datetime.datetime(2019,6,30))
bills = Bill.create_next_bills_for_user(self.user,
ending_date=bill_ending_date)
# Sum:
# recurring1 = 5 CHF -> for 30days
# recurring2 = 5 CHF -> from 3rd of April to 3rd of May
# recurring3 = 10 CHF -> from 17th of April to ... 17th of May?
bill = bills[0]
bill_records = BillRecord.objects.filter(bill=bill)
# If replacing lower order with higher:
# - close the lower order NOW
# - start higher order NOW+1s
# If replacing higher order with lower order:
# higher order continues until its end
# lower order starts after higher order+1s
self.assertEqual(len(bill_records), 3)
self.assertEqual(int(bill.sum), 35)
self.assertEqual(bills[0].sum, price)
# expeted result:
# 1x 5 chf bill record
# 1x 5 chf bill record
# 1x 10 partial bill record
# Expected bill sum & records:
# 2019-03-03 - 2019-04-02 +30d: 10
# 2019-04-02 - 2019-04-17: +15d: 5
# 2019-04-17 - 2019-05-17: +30d: 20
# total: 35
# def test_bill_for_increasing_product(self):
# """
# Modify a product, see one pro rata entry
# """
# # Create product
# starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
# starting_price = 30.5
# product = SampleRecurringProduct.objects.create(owner=self.user,
# rc_price=starting_price)
# product.create_order(starting_date)
# recurring_period = product.default_recurring_period.value
# # First change
# change1_date = timezone.make_aware(datetime.datetime(2019,4,17))
# product.rc_price = 49.5
# product.save()
# product.create_or_update_recurring_order(when_to_start=change1_date)
# # Second change
# change2_date = timezone.make_aware(datetime.datetime(2019,5,8))
# product.rc_price = 56.5
# product.save()
# product.create_or_update_recurring_order(when_to_start=change2_date)
# # Create bill one month after 2nd change
# bill_ending_date = timezone.make_aware(datetime.datetime(2019,6,30))
# bills = Bill.create_next_bills_for_user(self.user,
# ending_date=bill_ending_date)
# # only one bill in this test case
# bill = bills[0]
# expected_amount = starting_price
# d2 = starting_date + recurring_period
# duration2 = change1_date - d2
# expected_amount = 0
# # Expected bill sum & records:
# # 2019-03-03 - 2019-04-02 +30d: 30.5
# # 2019-04-02 - 2019-04-17: +15d: 15.25
# # 2019-04-17 - 2019-05-08: +21d: (21/30) * 49.5
# # 2019-05-08 - 2019-06-07: +30d: 56.5
# # 2019-06-07 - 2019-07-07: +30d: 56.5
# self.assertEqual(bills[0].sum, price)
# # expeted result:
# # 1x 5 chf bill record
# # 1x 5 chf bill record
# # 1x 10 partial bill record