From d8a7964fed9d081ece0325947d6f744ceb954d46 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Wed, 9 Sep 2020 00:35:55 +0200 Subject: [PATCH] Continue to refactor for shifting logic into the order --- doc/uncloud-manual-2020-08-01.org | 17 ++- uncloud_pay/models.py | 226 +++++++++++++++++++----------- uncloud_pay/tests.py | 9 +- 3 files changed, 161 insertions(+), 91 deletions(-) diff --git a/doc/uncloud-manual-2020-08-01.org b/doc/uncloud-manual-2020-08-01.org index cead06e..4bd2876 100644 --- a/doc/uncloud-manual-2020-08-01.org +++ b/doc/uncloud-manual-2020-08-01.org @@ -95,8 +95,8 @@ python manage.py migrate **** Add it to DNS as vpn-XXX.ungleich.ch **** Route a /40 network to its IPv6 address **** Install wireguard on it -**** TODO Enable wireguard on boot -**** TODO Create a new VPNPool on uncloud with +**** TODO [#C] Enable wireguard on boot +**** TODO [#C] Create a new VPNPool on uncloud with ***** the network address (selecting from our existing pool) ***** the network size (/...) ***** the vpn host that provides the network (selecting the created VM) @@ -210,3 +210,16 @@ VPNNetworks can be managed by all authenticated users. *** Decision We use integers, because they are easy. + +** Milestones :uncloud: +*** 1.1 (cleanup 1) +**** +*** 1.0 (initial release) +**** TODO Initial Generic product support + - Product +***** TODO Recurring product support +****** TODO Support replacing orders for updates +****** TODO [#A] Finish split of bill creation +****** TODO [#A] Test the new functions in the Order class +***** TODO Generating bill for admins/staff + - diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py index d65c7d2..932888d 100644 --- a/uncloud_pay/models.py +++ b/uncloud_pay/models.py @@ -372,24 +372,23 @@ class Order(models.Model): return next_date - def get_ending_date_for_bill(bill): + def get_ending_date_for_bill(self, bill): """ Determine the ending date given a specific bill """ # If the order is quit, charge the final amount (?) - if order.ending_date: - this_ending_date = order.ending_date + if self.ending_date: + this_ending_date = self.ending_date else: - if order.earliest_ending_date > bill.ending_date: - this_ending_date = order.earliest_ending_date + if self.earliest_ending_date > bill.ending_date: + this_ending_date = self.earliest_ending_date else: this_ending_date = bill.ending_date return this_ending_date - @property def count_billed(self): """ @@ -458,6 +457,74 @@ class Order(models.Model): super().save(*args, **kwargs) + + def create_bill_record(self, bill): + br = None + + if self.is_one_time: + if self.billrecord_set.count() == 0: + br = BillRecord.objects.create(bill=bill, + order=self, + starting_date=self.starting_date, + ending_date=self.ending_date) + else: + br = BillRecord.objects.filter(bill=bill, order=self).first() + if br: + self.update_bill_record_for_recurring_order(br, bill) + + else: + br = self.create_new_bill_record_for_recurring_order(bill) + + return br + + def update_bill_record_for_recurring_order(self, + bill_record, + bill): + """ + Possibly update a bill record according to the information in the bill + """ + + # If the order has an ending date set, we might need to adjust the bill_record + if self.ending_date: + if bill_record_for_this_bill.ending_date != self.ending_date: + bill_record_for_this_bill.ending_date = self.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() + + def create_new_bill_record_for_recurring_order(self, bill): + """ + Create a new bill record + """ + + last_bill_record = BillRecord.objects.filter(order=self).order_by('id').last() + + starting_date=self.starting_date + + if last_bill_record: + # We already charged beyond the end of this bill's period + if last_bill_record.ending_date >= bill.ending_date: + return + + # This order is terminated or replaced + if self.ending_date: + # And the last bill record already covered us -> nothing to be done anymore + if last_bill_record.ending_date == self.ending_date: + return + + starting_date = start_after(last_bill_record.ending_date) + + ending_date = self.get_ending_date_for_bill(bill) + + return BillRecord.objects.create(bill=bill, + order=self, + starting_date=starting_date, + ending_date=ending_date) + def __str__(self): return f"{self.description} (order={self.id})" @@ -509,34 +576,12 @@ class Bill(models.Model): @classmethod def create_bills_for_all_users(cls): """ - Create bills for all users + Create next bill for each user """ for owner in get_user_model().objects.all(): cls.create_next_bills_for_user(owner) - @classmethod - def create_next_bill_for_user_address(cls, - owner, - billing_address, - ending_date=None): - - """ - Check the existing orders of the billing address - and generate a bill if there is at least one active order - """ - - all_orders = Order.objects.filter(owner=owner, - billing_address=billing_address).order_by('id') - - - # a bill. If there - # for order in all_orders: - # if order.is_one_time: - # cls.create_one_time_record - - # pass - @classmethod def create_next_bills_for_user(cls, owner, ending_date=None): """ @@ -547,80 +592,93 @@ class Bill(models.Model): bills = [] for billing_address in BillingAddress.objects.filter(owner=owner): - bills.append(cls.create_next_bill_for_user_address(owner, billing_address, ending_date)) + bills.append(cls.create_next_bill_for_user_address(billing_address, ending_date)) return bills + @classmethod + def get_or_create_bill(cls, billing_address): + last_bill = cls.objects.filter(billing_address=billing_address).order_by('id').last() + + all_orders = Order.objects.filter(billing_address=billing_address).order_by('id') + first_order = all_orders.first() + + bill = None + ending_date = None + + # Get date & bill from previous bill, if it exists + if last_bill: + if not last_bill.is_final: + bill = last_bill + starting_date = last_bill.starting_date + ending_date = bill.ending_date + else: + starting_date = last_bill.ending_date + datetime.timedelta(seconds=1) + else: + # Might be an idea to make this the start of the month, too + if first_order: + starting_date = first_order.starting_date + else: + starting_date = timezone.now() + + if not ending_date: + ending_date = end_of_month(starting_date) + + if not bill: + bill = cls.objects.create( + owner=billing_address.owner, + starting_date=starting_date, + ending_date=ending_date, + billing_address=billing_address) + + + return bill @classmethod - def create_bill_records_for_recurring_orders(cls, bill): + def create_next_bill_for_user_address(cls, + billing_address, + ending_date=None): + """ - Create or update bill records for recurring orders for the - given bill + Create the next bill for a specific billing address of a user """ - owner = bill.owner - billing_address = bill.billing_address + owner = billing_address.owner all_orders = Order.objects.filter(owner=owner, - billing_address=billing_address).order_by('id').exclude(recurring_period=RecurringPeriod.ONE_TIME) + billing_address=billing_address).order_by('id') + + bill = cls.get_or_create_bill(billing_address) 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) + order.create_bill_record(bill) - else: - cls.create_new_bill_record_for_this_bill(order, bill) + return bill + # @classmethod + # def create_bill_records_for_recurring_orders(cls, bill): + # """ + # Create or update bill records for recurring orders for the + # given bill + # """ - @staticmethod - def create_new_bill_record_for_this_bill(order, bill): - last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last() + # owner = bill.owner + # billing_address = bill.billing_address - this_starting_date=order.starting_date + # all_orders = Order.objects.filter(owner=owner, + # billing_address=billing_address).order_by('id').exclude(recurring_period=RecurringPeriod.ONE_TIME) - if last_bill_record: - if last_bill_record.ending_date >= bill.ending_date: - return + # 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) - if order.ending_date: - if last_bill_record.ending_date == order.ending_date: - return + # else: + # cls.create_new_bill_record_for_this_bill(order, bill) - this_starting_date = start_after(last_bill_record.ending_date) - - - ending_date = order.get_ending_date_for_bill(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() @classmethod def create_next_bill_for_user_address_old(cls, diff --git a/uncloud_pay/tests.py b/uncloud_pay/tests.py index 3d805b2..3099ed3 100644 --- a/uncloud_pay/tests.py +++ b/uncloud_pay/tests.py @@ -269,7 +269,7 @@ class BillTestCase(TestCase): Ensure there is only 1 bill record per order """ - bill = Bill.create_next_bill_for_user_address(self.user, self.user_addr) + bill = Bill.create_next_bill_for_user_address(self.user_addr) self.assertEqual(self.one_time_order.billrecord_set.count(), 1) @@ -278,7 +278,7 @@ class BillTestCase(TestCase): Check the bill sum for a single one time order """ - bill = Bill.create_next_bill_for_user_address(self.user, self.user_addr) + bill = Bill.create_next_bill_for_user_address(self.user_addr) self.assertEqual(bill.sum, self.order_meta[1]['price']) @@ -287,8 +287,7 @@ class BillTestCase(TestCase): Ensure there is only 1 bill record per order """ - bill = Bill.create_next_bill_for_user_address(self.recurring_user, - self.recurring_user_addr) + bill = Bill.create_next_bill_for_user_address(self.recurring_user_addr) self.assertEqual(self.recurring_order.billrecord_set.count(), 1) self.assertEqual(bill.billrecord_set.count(), 1) @@ -301,7 +300,7 @@ class BillTestCase(TestCase): """ for ending_date in self.bill_dates: - b = Bill.create_next_bill_for_user_address(self.recurring_user, self.recurring_user_addr, ending_date) + b = Bill.create_next_bill_for_user_address(self.recurring_user_addr, ending_date) b.close() bill_count = Bill.objects.filter(owner=self.recurring_user).count()