diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py index 1d95ae1..3f8498d 100644 --- a/uncloud_pay/models.py +++ b/uncloud_pay/models.py @@ -57,6 +57,10 @@ def end_before(a_date): """ Return suitable datetimefield for ending just before a_date """ return a_date - datetime.timedelta(seconds=1) +def start_after(a_date): + """ Return suitable datetimefield for starting just after a_date """ + return a_date + datetime.timedelta(seconds=1) + def default_payment_delay(): return timezone.now() + BILL_PAYMENT_DELAY @@ -333,6 +337,24 @@ class Order(models.Model): return self.starting_date + datetime.timedelta(seconds=self.recurring_period) + @property + def next_ending_date(self): + """ + Return the next proper ending date after n times the + recurring_period, where n is an integer. + """ + + if self.recurring_period > 0: + now = timezone.now() + delta = now - self.starting_date + + num_times = math.ceil(delta.total_seconds() / self.recurring_period) + + next_date = self.starting_date + datetime.timedelta(seconds= num_times * self.recurring_period) + else: + next_date = self.starting_date + + return next_date @property def count_billed(self): @@ -698,7 +720,6 @@ class Product(UncloudModel): def create_or_update_recurring_order(self, when_to_start=None, recurring_period=None): - if not self.recurring_price: return @@ -709,6 +730,13 @@ 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: + when_to_start = start_after(self.last_recurring_order.next_ending_date) + when_to_end = end_before(when_to_start) new_order = Order.objects.create(owner=self.owner, diff --git a/uncloud_pay/tests.py b/uncloud_pay/tests.py index 6586f3a..d9feed0 100644 --- a/uncloud_pay/tests.py +++ b/uncloud_pay/tests.py @@ -416,7 +416,7 @@ class ModifyProductTestCase(TestCase): def test_bill_for_modified_product(self): """ - The bill should NOT contain a partial amount -- this is a BILL TEST :-) + Modify a product, see one pro rata entry """ price = 5 @@ -429,16 +429,32 @@ class ModifyProductTestCase(TestCase): ending_date = timezone.make_aware(datetime.datetime(2019,3,31)) change_date = timezone.make_aware(datetime.datetime(2019,4,17)) - product.create_order(starting_date) + bill_ending_date = timezone.make_aware(datetime.datetime(2019,4,30)) - bills = Bill.create_next_bills_for_user(self.user, - ending_date=ending_date) + product.create_order(starting_date) product.rc_price = 10 product.save() product.create_or_update_recurring_order(when_to_start=change_date) + 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? + + # 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(bills[0].sum, price) + # expeted result: # 1x 5 chf bill record # 1x 5 chf bill record