implement basic logic for updating a recurring order

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
This commit is contained in:
Nico Schottelius 2020-08-27 14:45:37 +02:00
parent b8b15704a3
commit 9211894b23
2 changed files with 103 additions and 21 deletions

View file

@ -608,6 +608,20 @@ class BillRecord(models.Model):
# Products # Products
class Product(UncloudModel): class Product(UncloudModel):
"""
A product is something a user orders. To record the pricing, we
create order that define a state in time.
A product can *depend* on other products.
A product can have *one* one_time_order and/or *one*
recurring_order.
If either of them needs to be updated, a new order of the same
type will be created and links to the previous order.
"""
owner = models.ForeignKey(get_user_model(), owner = models.ForeignKey(get_user_model(),
on_delete=models.CASCADE, on_delete=models.CASCADE,
editable=False) editable=False)
@ -623,7 +637,23 @@ class Product(UncloudModel):
# Default period for all products # Default period for all products
default_recurring_period = RecurringPeriod.PER_30D default_recurring_period = RecurringPeriod.PER_30D
def create_order_at(self, when_to_start=None, recurring_period=None): @property
def recurring_orders(self):
return self.orders.order_by('id').exclude(recurring_period=RecurringPeriod.ONE_TIME)
@property
def last_recurring_order(self):
return self.recurring_orders.last()
@property
def one_time_orders(self):
return self.orders.order_by('id').filter(recurring_period=RecurringPeriod.ONE_TIME)
@property
def last_one_time_order(self):
return self.one_time_orders.last()
def create_order(self, when_to_start=None, recurring_period=None):
billing_address = BillingAddress.get_address_for(self.owner) billing_address = BillingAddress.get_address_for(self.owner)
if not billing_address: if not billing_address:
@ -636,7 +666,8 @@ class Product(UncloudModel):
recurring_period = self.default_recurring_period recurring_period = self.default_recurring_period
if self.one_time_price > 0: # Create one time order if we did not create one already
if self.one_time_price > 0 and not self.last_one_time_order:
one_time_order = Order.objects.create(owner=self.owner, one_time_order = Order.objects.create(owner=self.owner,
billing_address=billing_address, billing_address=billing_address,
starting_date=when_to_start, starting_date=when_to_start,
@ -667,32 +698,32 @@ class Product(UncloudModel):
def create_or_update_recurring_order(self, when_to_start=None, recurring_period=None): def create_or_update_recurring_order(self, when_to_start=None, recurring_period=None):
if not self.recurring_price:
return
if not recurring_period:
recurring_period = self.default_recurring_period
if not when_to_start: if not when_to_start:
when_to_start = timezone.now() when_to_start = timezone.now()
# current_recurring_order = Order.objects.filter( if self.last_recurring_order:
# NEXT: find the latest order, use that one...
# Update order = create new order
if self.order:
previous_order = self.order
when_to_end = end_before(when_to_start) when_to_end = end_before(when_to_start)
new_order = Order.objects.create(owner=self.owner, new_order = Order.objects.create(owner=self.owner,
billing_address=self.order.billing_address, billing_address=self.last_recurring_order.billing_address,
starting_date=when_to_start, starting_date=when_to_start,
price=self.recurring_price, price=self.recurring_price,
recurring_period=recurring_period, recurring_period=recurring_period,
description=str(self), description=str(self),
replaces=self.order) replaces=self.last_recurring_order)
print(new_order)
self.order.end_date = when_to_end
self.order.save()
self.order = new_order
self.last_recurring_order.end_date = when_to_end
self.orders.add(new_order)
else: else:
return self.create_order_at(when_to_start, recurring_period) # This might be a bug as it might (re-)create the one time order
self.create_order(when_to_start, recurring_period)
@property @property
def recurring_price(self): def recurring_price(self):

View file

@ -65,26 +65,37 @@ class ProductTestCase(TestCase):
# One order # One order
p = SampleOneTimeProduct.objects.create(owner=self.user) p = SampleOneTimeProduct.objects.create(owner=self.user)
p.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) p.create_order(timezone.make_aware(datetime.datetime(2020,3,3)))
order_count = Order.objects.filter(owner=self.user).count() order_count = Order.objects.filter(owner=self.user).count()
self.assertEqual(order_count, 1) self.assertEqual(order_count, 1)
# One more order # One more order
p = SampleRecurringProduct.objects.create(owner=self.user) p = SampleRecurringProduct.objects.create(owner=self.user)
p.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) p.create_order(timezone.make_aware(datetime.datetime(2020,3,3)))
order_count = Order.objects.filter(owner=self.user).count() order_count = Order.objects.filter(owner=self.user).count()
self.assertEqual(order_count, 2) self.assertEqual(order_count, 2)
# Should create 2 orders # Should create 2 orders
p = SampleRecurringProductOneTimeFee.objects.create(owner=self.user) p = SampleRecurringProductOneTimeFee.objects.create(owner=self.user)
p.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) p.create_order(timezone.make_aware(datetime.datetime(2020,3,3)))
order_count = Order.objects.filter(owner=self.user).count() order_count = Order.objects.filter(owner=self.user).count()
self.assertEqual(order_count, 4) self.assertEqual(order_count, 4)
def test_update_recurring_order(self):
"""
Ensure creating orders from product only creates 1 order
"""
p = SampleRecurringProduct.objects.create(owner=self.user)
p.create_order(timezone.make_aware(datetime.datetime(2020,3,3)))
p.create_or_update_recurring_order(timezone.make_aware(datetime.datetime(2020,3,3)))
class BillingAddressTestCase(TestCase): class BillingAddressTestCase(TestCase):
def setUp(self): def setUp(self):
self.user = get_user_model().objects.create( self.user = get_user_model().objects.create(
@ -351,6 +362,15 @@ class BillTestCase(TestCase):
self.assertEqual(len(bills), 2) self.assertEqual(len(bills), 2)
# TO BE IMPLEMENTED -- once orders can be marked as "done" / "inactive" / "not for billing"
# def test_skip_disabled_orders(self):
# """
# Ensure that a bill only considers "active" orders
# """
# self.assertEqual(1, 2)
class ModifyProductTestCase(TestCase): class ModifyProductTestCase(TestCase):
def setUp(self): def setUp(self):
self.user = get_user_model().objects.create( self.user = get_user_model().objects.create(
@ -367,7 +387,7 @@ class ModifyProductTestCase(TestCase):
def test_no_pro_rata_first_bill(self): def test_no_pro_rata_first_bill(self):
""" """
The bill should NOT contain a partial amount The bill should NOT contain a partial amount -- this is a BILL TEST :-)
""" """
price = 5 price = 5
@ -380,7 +400,7 @@ class ModifyProductTestCase(TestCase):
ending_date = timezone.make_aware(datetime.datetime(2020,3,31)) ending_date = timezone.make_aware(datetime.datetime(2020,3,31))
time_diff = (ending_date - starting_date).total_seconds() time_diff = (ending_date - starting_date).total_seconds()
product.create_order_at(starting_date) product.create_order(starting_date)
bills = Bill.create_next_bills_for_user(self.user, bills = Bill.create_next_bills_for_user(self.user,
ending_date=ending_date) ending_date=ending_date)
@ -394,6 +414,37 @@ 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):
"""
The bill should NOT contain a partial amount -- this is a BILL TEST :-)
"""
price = 5
# Standard 30d recurring product
product = SampleRecurringProduct.objects.create(owner=self.user,
rc_price=price)
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))
product.create_order(starting_date)
bills = Bill.create_next_bills_for_user(self.user,
ending_date=ending_date)
product.rc_price = 10
product.save()
product.create_or_update_recurring_order(when_to_start=change_date)
# expeted result:
# 1x 5 chf bill record
# 1x 5 chf bill record
# 1x 10 partial bill record