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 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): class BillRecordInline(admin.TabularInline):
@ -91,5 +91,7 @@ admin.site.register(Order)
admin.site.register(BillRecord) admin.site.register(BillRecord)
admin.site.register(BillingAddress) admin.site.register(BillingAddress)
for m in [ SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee ]:
admin.site.register(m)
#admin.site.register(Order, OrderAdmin) #admin.site.register(Order, OrderAdmin)

View file

@ -459,19 +459,121 @@ class Bill(models.Model):
return bills return bills
@classmethod @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() 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 # it is important to sort orders here, as bill records will be
# created (and listed) in this order # 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() first_order = all_orders.first()
bill = None bill = None
ending_date = None ending_date = None
# Get date & bill from previous bill # Get date & bill from previous bill, if it exists
if last_bill: if last_bill:
if not last_bill.is_final: if not last_bill.is_final:
bill = last_bill bill = last_bill
@ -504,12 +606,10 @@ class Bill(models.Model):
starting_date=order.starting_date, starting_date=order.starting_date,
ending_date=order.ending_date) ending_date=order.ending_date)
else: else: # recurring orders
# Bill all recurring orders
bill_record_for_this_bill = BillRecord.objects.filter(bill=bill, bill_record_for_this_bill = BillRecord.objects.filter(bill=bill,
order=order).first() order=order).first()
# This bill already has a bill record for the order # This bill already has a bill record for the order
# We potentially need to update the ending_date if the ending_date # We potentially need to update the ending_date if the ending_date
# of the bill changed. # of the bill changed.
@ -529,11 +629,8 @@ class Bill(models.Model):
bill_record_for_this_bill.save() bill_record_for_this_bill.save()
else: else: # No bill record in this bill for this order yet
# No bill record in this bill for the order yet # Find out when billed last time
# Find out whether it was already billed for the billing period of
# this bill
last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last() last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last()
# Default starting date # 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 return bill
@ -730,8 +821,6 @@ class Product(UncloudModel):
when_to_start = timezone.now() when_to_start = timezone.now()
if self.last_recurring_order: 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 self.recurring_price < self.last_recurring_order.price:
if when_to_start < self.last_recurring_order.next_ending_date: 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.assertNotEqual(bills[0].sum, pro_rata_amount * price)
self.assertEqual(bills[0].sum, 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 # Create product
# Standard 30d recurring product
product = SampleRecurringProduct.objects.create(owner=self.user,
rc_price=price)
starting_date = timezone.make_aware(datetime.datetime(2019,3,3)) starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
ending_date = timezone.make_aware(datetime.datetime(2019,3,31)) starting_price = 10
change_date = timezone.make_aware(datetime.datetime(2019,4,17)) product = SampleRecurringProduct.objects.create(owner=self.user,
rc_price=starting_price)
bill_ending_date = timezone.make_aware(datetime.datetime(2019,4,30))
product.create_order(starting_date) 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.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, bills = Bill.create_next_bills_for_user(self.user,
ending_date=bill_ending_date) ending_date=bill_ending_date)
# Sum: bill = bills[0]
# recurring1 = 5 CHF -> for 30days bill_records = BillRecord.objects.filter(bill=bill)
# recurring2 = 5 CHF -> from 3rd of April to 3rd of May
# recurring3 = 10 CHF -> from 17th of April to ... 17th of May?
# If replacing lower order with higher: self.assertEqual(len(bill_records), 3)
# - close the lower order NOW self.assertEqual(int(bill.sum), 35)
# - 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(bills[0].sum, price) # Expected bill sum & records:
# 2019-03-03 - 2019-04-02 +30d: 10
# expeted result: # 2019-04-02 - 2019-04-17: +15d: 5
# 1x 5 chf bill record # 2019-04-17 - 2019-05-17: +30d: 20
# 1x 5 chf bill record # total: 35
# 1x 10 partial bill record
# 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