From 1c7d81762d13fae5d3fef76fee29bacb32380acc Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Wed, 2 Sep 2020 16:02:28 +0200 Subject: [PATCH] begin splitting bill record creation function --- uncloud_pay/admin.py | 4 +- uncloud_pay/models.py | 127 +++++++++++++++++++++++++++++++++++------- uncloud_pay/tests.py | 106 ++++++++++++++++++++++++----------- 3 files changed, 185 insertions(+), 52 deletions(-) diff --git a/uncloud_pay/admin.py b/uncloud_pay/admin.py index df2cdde..45714d6 100644 --- a/uncloud_pay/admin.py +++ b/uncloud_pay/admin.py @@ -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) diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py index 3f8498d..e8e7ac2 100644 --- a/uncloud_pay/models.py +++ b/uncloud_pay/models.py @@ -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: diff --git a/uncloud_pay/tests.py b/uncloud_pay/tests.py index d9feed0..3d805b2 100644 --- a/uncloud_pay/tests.py +++ b/uncloud_pay/tests.py @@ -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