forked from uncloud/uncloud
implement basic logic for updating a recurring order
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
This commit is contained in:
parent
b8b15704a3
commit
9211894b23
2 changed files with 103 additions and 21 deletions
|
@ -608,6 +608,20 @@ class BillRecord(models.Model):
|
|||
# Products
|
||||
|
||||
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(),
|
||||
on_delete=models.CASCADE,
|
||||
editable=False)
|
||||
|
@ -623,7 +637,23 @@ class Product(UncloudModel):
|
|||
# Default period for all products
|
||||
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)
|
||||
|
||||
if not billing_address:
|
||||
|
@ -636,7 +666,8 @@ class Product(UncloudModel):
|
|||
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,
|
||||
billing_address=billing_address,
|
||||
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):
|
||||
|
||||
if not self.recurring_price:
|
||||
return
|
||||
|
||||
if not recurring_period:
|
||||
recurring_period = self.default_recurring_period
|
||||
|
||||
if not when_to_start:
|
||||
when_to_start = timezone.now()
|
||||
|
||||
# current_recurring_order = Order.objects.filter(
|
||||
# NEXT: find the latest order, use that one...
|
||||
# Update order = create new order
|
||||
if self.order:
|
||||
previous_order = self.order
|
||||
if self.last_recurring_order:
|
||||
when_to_end = end_before(when_to_start)
|
||||
|
||||
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,
|
||||
price=self.recurring_price,
|
||||
recurring_period=recurring_period,
|
||||
description=str(self),
|
||||
replaces=self.order)
|
||||
|
||||
print(new_order)
|
||||
self.order.end_date = when_to_end
|
||||
self.order.save()
|
||||
|
||||
self.order = new_order
|
||||
replaces=self.last_recurring_order)
|
||||
|
||||
self.last_recurring_order.end_date = when_to_end
|
||||
self.orders.add(new_order)
|
||||
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
|
||||
def recurring_price(self):
|
||||
|
|
|
@ -65,26 +65,37 @@ class ProductTestCase(TestCase):
|
|||
|
||||
# One order
|
||||
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()
|
||||
self.assertEqual(order_count, 1)
|
||||
|
||||
# One more order
|
||||
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()
|
||||
self.assertEqual(order_count, 2)
|
||||
|
||||
# Should create 2 orders
|
||||
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()
|
||||
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):
|
||||
def setUp(self):
|
||||
self.user = get_user_model().objects.create(
|
||||
|
@ -351,6 +362,15 @@ class BillTestCase(TestCase):
|
|||
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):
|
||||
def setUp(self):
|
||||
self.user = get_user_model().objects.create(
|
||||
|
@ -367,7 +387,7 @@ class ModifyProductTestCase(TestCase):
|
|||
|
||||
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
|
||||
|
@ -380,7 +400,7 @@ class ModifyProductTestCase(TestCase):
|
|||
ending_date = timezone.make_aware(datetime.datetime(2020,3,31))
|
||||
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,
|
||||
ending_date=ending_date)
|
||||
|
@ -394,6 +414,37 @@ 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):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue