Make recurring period a database model

- For easier handling (foreignkeys, many2many)
- For higher flexibility (users can define their own periods)
This commit is contained in:
Nico Schottelius 2020-10-06 15:46:22 +02:00
commit 992c7c551e
11 changed files with 588 additions and 362 deletions

View file

@ -27,7 +27,7 @@ chocolate_order_config = {
chocolate_one_time_price = chocolate_order_config['features']['gramm'] * chocolate_product_config['features']['gramm']['one_time_price']
vm_sample_product_config = {
vm_product_config = {
'features': {
'cores':
{ 'min': 1,
@ -44,13 +44,28 @@ vm_sample_product_config = {
},
}
vm_sample_order_config = {
vm_order_config = {
'features': {
'cores': 2,
'ram_gb': 2
}
}
vm_order_downgrade_config = {
'features': {
'cores': 1,
'ram_gb': 1
}
}
vm_order_upgrade_config = {
'features': {
'cores': 4,
'ram_gb': 4
}
}
class ProductTestCase(TestCase):
"""
@ -77,7 +92,7 @@ class ProductTestCase(TestCase):
p = Product.objects.create(name="Testproduct",
description="Only for testing",
config=vm_sample_product_config)
config=vm_product_config)
class OrderTestCase(TestCase):
@ -105,16 +120,287 @@ class OrderTestCase(TestCase):
p = Product.objects.create(name="Testproduct",
description="Only for testing",
config=vm_sample_product_config)
config=vm_product_config)
o = Order.objects.create(owner=self.user,
billing_address=self.ba,
product=p,
config=vm_sample_order_config)
config=vm_order_config)
self.assertEqual(o.one_time_price, 0)
self.assertEqual(o.recurring_price, 16)
def test_change_order(self):
"""
Change an order and ensure that
- a new order is created
- the price is correct in the new order
"""
p = Product.objects.create(name="Testproduct",
description="Only for testing",
config=vm_product_config)
order1 = Order.objects.create(owner=self.user,
billing_address=self.ba,
product=p,
config=vm_order_config)
self.assertEqual(order1.one_time_price, 0)
self.assertEqual(order1.recurring_price, 16)
class ModifyOrderTestCase(TestCase):
"""
Test typical order flows like
- cancelling
- downgrading
- upgrading
"""
def setUp(self):
self.user = get_user_model().objects.create(
username='random_user',
email='jane.random@domain.tld')
self.ba = BillingAddress.objects.create(
owner=self.user,
organization = 'Test org',
street="unknown",
city="unknown",
postal_code="somewhere else",
active=True)
self.product = Product.objects.create(name="Testproduct",
description="Only for testing",
config=vm_product_config)
def test_change_order(self):
"""
Test changing an order
Expected result:
- Old order should be closed before new order starts
- New order should start at starting data
"""
user = self.user
starting_price = 16
downgrade_price = 8
starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
ending1_date = starting_date + datetime.timedelta(days=15)
change1_date = start_after(ending1_date)
bill_ending_date = change1_date + datetime.timedelta(days=1)
order1 = Order.objects.create(owner=self.user,
billing_address=BillingAddress.get_address_for(self.user),
product=self.product,
config=vm_order_config,
starting_date=starting_date)
order1.update_order(vm_order_downgrade_config, starting_date=change1_date)
bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)
bill = bills[0]
bill_records = BillRecord.objects.filter(bill=bill)
self.assertEqual(len(bill_records), 2)
self.assertEqual(bill_records[0].starting_date, starting_date)
self.assertEqual(bill_records[0].ending_date, ending1_date)
self.assertEqual(bill_records[1].starting_date, change1_date)
# def test_no_pro_rata_first_bill(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(2020,3,3))
# ending_date = timezone.make_aware(datetime.datetime(2020,3,31))
# time_diff = (ending_date - starting_date).total_seconds()
# product.create_order(starting_date)
# bills = Bill.create_next_bills_for_user(self.user,
# ending_date=ending_date)
# # We expect 1 bill for 1 billing address and 1 time frame
# self.assertEqual(len(bills), 1)
# pro_rata_amount = time_diff / product.default_recurring_period.value
# self.assertNotEqual(bills[0].sum, pro_rata_amount * price)
# self.assertEqual(bills[0].sum, price)
def test_downgrade_product(self):
"""
Test downgrading behaviour:
We create a recurring product (recurring time: 30 days) and downgrade after 15 days.
We create the bill right AFTER the end of the first order.
Expected result:
- First bill record for 30 days
- Second bill record starting after 30 days
- Bill contains two bill records
"""
user = self.user
starting_price = 16
downgrade_price = 8
starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
first_order_should_end_at = starting_date + datetime.timedelta(days=30)
change1_date = start_after(starting_date + datetime.timedelta(days=15))
bill_ending_date = change1_date + datetime.timedelta(days=1)
order1 = Order.objects.create(owner=self.user,
billing_address=BillingAddress.get_address_for(self.user),
product=self.product,
config=vm_order_config,
starting_date=starting_date)
order1.update_order(vm_order_downgrade_config, starting_date=change1_date)
# product = Product.objects.create(owner=user, rc_price=starting_price)
# product.create_order(starting_date)
# product.rc_price = downgrade_price
# product.save()
# product.create_or_update_recurring_order(when_to_start=change1_date)
bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)
bill = bills[0]
bill_records = BillRecord.objects.filter(bill=bill)
self.assertEqual(len(bill_records), 2)
self.assertEqual(bill_records[0].starting_date, starting_date)
self.assertEqual(bill_records[0].order.ending_date, first_order_should_end_at)
# self.assertEqual(bill_records[0].ending_date, first_order_should_end_at)
# self.assertEqual(bill_records[0].quantity, 1)
# self.assertEqual(bill_records[1].quantity, 1)
# self.assertEqual(int(bill.sum), 15)
# def test_upgrade_product(self):
# """
# Test upgrading behaviour
# """
# user = self.user
# # Create product
# starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
# starting_price = 10
# product = SampleRecurringProduct.objects.create(owner=user, rc_price=starting_price)
# product.create_order(starting_date)
# change1_date = start_after(starting_date + datetime.timedelta(days=15))
# product.rc_price = 20
# product.save()
# product.create_or_update_recurring_order(when_to_start=change1_date)
# bill_ending_date = change1_date + datetime.timedelta(days=1)
# bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)
# bill = bills[0]
# bill_records = BillRecord.objects.filter(bill=bill)
# self.assertEqual(len(bill_records), 2)
# self.assertEqual(bill_records[0].quantity, .5)
# self.assertEqual(bill_records[0].ending_date, end_before(change1_date))
# self.assertEqual(bill_records[1].quantity, 1)
# self.assertEqual(bill_records[1].starting_date, change1_date)
# self.assertEqual(int(bill.sum), 25)
# 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
class BillTestCase(TestCase):
@ -163,14 +449,11 @@ class BillTestCase(TestCase):
description="Not only for testing, but for joy",
config=chocolate_product_config)
# self.recurring_order = Order.objects.create(
# owner=self.recurring_user,
# starting_date=timezone.make_aware(datetime.datetime(2020,3,3)),
# recurring_period=RecurringPeriod.PER_30D,
# price=15,
# description="A pretty VM",
# billing_address=BillingAddress.get_address_for(self.recurring_user)
# )
self.vm = Product.objects.create(name="Super Fast VM",
description="Zooooom",
config=vm_product_config)
# used for generating multiple bills
self.bill_dates = [
@ -190,6 +473,28 @@ class BillTestCase(TestCase):
ending_date=self.order_meta[1]['ending_date'],
config=chocolate_order_config)
def order_vm(self, owner=None):
if not owner:
owner = self.recurring_user
return Order.objects.create(
owner=owner,
product=self.vm,
config=vm_order_config,
billing_address=BillingAddress.get_address_for(self.recurring_user),
starting_date=timezone.make_aware(datetime.datetime(2020,3,3)),
)
return Order.objects.create(
owner=self.user,
recurring_period=RecurringPeriod.ONE_TIME,
product=self.chocolate,
billing_address=BillingAddress.get_address_for(self.user),
starting_date=self.order_meta[1]['starting_date'],
ending_date=self.order_meta[1]['ending_date'],
config=chocolate_order_config)
def test_bill_one_time_one_bill_record(self):
@ -213,83 +518,95 @@ class BillTestCase(TestCase):
self.assertEqual(bill.sum, chocolate_one_time_price)
# def test_bill_creates_record_for_recurring_order(self):
# """
# Ensure there is only 1 bill record per order
# """
def test_bill_creates_record_for_recurring_order(self):
"""
Ensure there is only 1 bill record per order
"""
# bill = Bill.create_next_bill_for_user_address(self.recurring_user_addr)
order = self.order_vm()
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)
self.assertEqual(order.billrecord_set.count(), 1)
self.assertEqual(bill.billrecord_set.count(), 1)
# def test_new_bill_after_closing(self):
# """
# After closing a bill and the user has a recurring product,
# the next bill run should create e new bill
# """
def test_new_bill_after_closing(self):
"""
After closing a bill and the user has a recurring product,
the next bill run should create e new bill
"""
# for ending_date in self.bill_dates:
# b = Bill.create_next_bill_for_user_address(self.recurring_user_addr, ending_date)
# b.close()
order = self.order_vm()
# bill_count = Bill.objects.filter(owner=self.recurring_user).count()
for ending_date in self.bill_dates:
b = Bill.create_next_bill_for_user_address(self.recurring_user_addr, ending_date)
b.close()
# self.assertEqual(len(self.bill_dates), bill_count)
bill_count = Bill.objects.filter(owner=self.recurring_user).count()
# def test_multi_addr_multi_bill(self):
# """
# Ensure multiple bills are created if orders exist with different billing addresses
# """
self.assertEqual(len(self.bill_dates), bill_count)
# username="lotsofplaces"
# multi_addr_user = get_user_model().objects.create(
# username=username,
# email=f"{username}@example.org")
# def test_multi_addr_multi_bill(self):
# """
# Ensure multiple bills are created if orders exist with different billing addresses
# """
# user_addr1 = BillingAddress.objects.create(
# owner=multi_addr_user,
# organization = 'Test org',
# street="unknown",
# city="unknown",
# postal_code="unknown",
# active=True)
# username="lotsofplaces"
# multi_addr_user = get_user_model().objects.create(
# username=username,
# email=f"{username}@example.org")
# order1 = Order.objects.create(
# owner=multi_addr_user,
# starting_date=self.order_meta[1]['starting_date'],
# ending_date=self.order_meta[1]['ending_date'],
# recurring_period=RecurringPeriod.ONE_TIME,
# price=self.order_meta[1]['price'],
# description=self.order_meta[1]['description'],
# billing_address=BillingAddress.get_address_for(self.user))
# user_addr1 = BillingAddress.objects.create(
# owner=multi_addr_user,
# organization = 'Test org',
# street="unknown",
# city="unknown",
# postal_code="unknown",
# active=True)
# # Make this address inactive
# user_addr1.active = False
# user_addr1.save()
# order1 = Order.objects.create(
# owner=multi_addr_user,
# recurring_period=RecurringPeriod.ONE_TIME,
# product=self.chocolate,
# billing_address=BillingAddress.get_address_for(self.user),
# starting_date=self.order_meta[1]['starting_date'],
# ending_date=self.order_meta[1]['ending_date'],
# config=chocolate_order_config)
# user_addr2 = BillingAddress.objects.create(
# owner=multi_addr_user,
# organization = 'Test2 org',
# street="unknown2",
# city="unknown2",
# postal_code="unknown2",
# active=True)
# order1 = Order.objects.create(
# owner=multi_addr_user,
# starting_date=self.order_meta[1]['starting_date'],
# ending_date=self.order_meta[1]['ending_date'],
# recurring_period=RecurringPeriod.ONE_TIME,
# price=self.order_meta[1]['price'],
# description=self.order_meta[1]['description'],
# billing_address=BillingAddress.get_address_for(self.user))
# order2 = Order.objects.create(
# owner=multi_addr_user,
# starting_date=self.order_meta[1]['starting_date'],
# ending_date=self.order_meta[1]['ending_date'],
# recurring_period=RecurringPeriod.ONE_TIME,
# price=self.order_meta[1]['price'],
# description=self.order_meta[1]['description'],
# billing_address=BillingAddress.get_address_for(self.user))
# # Make this address inactive
# user_addr1.active = False
# user_addr1.save()
# user_addr2 = BillingAddress.objects.create(
# owner=multi_addr_user,
# organization = 'Test2 org',
# street="unknown2",
# city="unknown2",
# postal_code="unknown2",
# active=True)
# order2 = Order.objects.create(
# owner=multi_addr_user,
# starting_date=self.order_meta[1]['starting_date'],
# ending_date=self.order_meta[1]['ending_date'],
# recurring_period=RecurringPeriod.ONE_TIME,
# price=self.order_meta[1]['price'],
# description=self.order_meta[1]['description'],
# billing_address=BillingAddress.get_address_for(self.user))
# bills = Bill.create_next_bills_for_user(multi_addr_user)
# bills = Bill.create_next_bills_for_user(multi_addr_user)
# self.assertEqual(len(bills), 2)
# self.assertEqual(len(bills), 2)
# # TO BE IMPLEMENTED -- once orders can be marked as "done" / "inactive" / "not for billing"
@ -392,21 +709,21 @@ class BillTestCase(TestCase):
# # FIXME: where is the assert?
# class BillingAddressTestCase(TestCase):
# def setUp(self):
# self.user = get_user_model().objects.create(
# username='random_user',
# email='jane.random@domain.tld')
class BillingAddressTestCase(TestCase):
def setUp(self):
self.user = get_user_model().objects.create(
username='random_user',
email='jane.random@domain.tld')
# def test_user_no_address(self):
# """
# Raise an error, when there is no address
# """
def test_user_no_address(self):
"""
Raise an error, when there is no address
"""
# self.assertRaises(uncloud_pay.models.BillingAddress.DoesNotExist,
# BillingAddress.get_address_for,
# self.user)
self.assertRaises(uncloud_pay.models.BillingAddress.DoesNotExist,
BillingAddress.get_address_for,
self.user)
# def test_user_only_inactive_address(self):
# """
@ -497,198 +814,6 @@ class BillTestCase(TestCase):
# class ModifyProductTestCase(TestCase):
# def setUp(self):
# self.user = get_user_model().objects.create(
# username='random_user',
# email='jane.random@domain.tld')
# self.ba = BillingAddress.objects.create(
# owner=self.user,
# organization = 'Test org',
# street="unknown",
# city="unknown",
# postal_code="somewhere else",
# active=True)
# def test_no_pro_rata_first_bill(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(2020,3,3))
# ending_date = timezone.make_aware(datetime.datetime(2020,3,31))
# time_diff = (ending_date - starting_date).total_seconds()
# product.create_order(starting_date)
# bills = Bill.create_next_bills_for_user(self.user,
# ending_date=ending_date)
# # We expect 1 bill for 1 billing address and 1 time frame
# self.assertEqual(len(bills), 1)
# pro_rata_amount = time_diff / product.default_recurring_period.value
# self.assertNotEqual(bills[0].sum, pro_rata_amount * price)
# self.assertEqual(bills[0].sum, price)
# def test_downgrade_product(self):
# """
# Test downgrading behaviour:
# We create a recurring product (recurring time: 30 days) and downgrade after 15 days.
# We create the bill right AFTER the end of the first order.
# Expected result:
# - First bill record for 30 days
# - Second bill record starting after 30 days
# - Bill contains two bill records
# """
# user = self.user
# starting_price = 10
# downgrade_price = 5
# starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
# first_order_should_end_at = starting_date + datetime.timedelta(days=30)
# change1_date = start_after(starting_date + datetime.timedelta(days=15))
# bill_ending_date = change1_date + datetime.timedelta(days=1)
# product = SampleRecurringProduct.objects.create(owner=user, rc_price=starting_price)
# product.create_order(starting_date)
# product.rc_price = downgrade_price
# product.save()
# product.create_or_update_recurring_order(when_to_start=change1_date)
# bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)
# bill = bills[0]
# bill_records = BillRecord.objects.filter(bill=bill)
# self.assertEqual(len(bill_records), 2)
# self.assertEqual(bill_records[0].starting_date, starting_date)
# self.assertEqual(bill_records[0].order.ending_date, first_order_should_end_at)
# # self.assertEqual(bill_records[0].ending_date, first_order_should_end_at)
# # self.assertEqual(bill_records[0].quantity, 1)
# # self.assertEqual(bill_records[1].quantity, 1)
# # self.assertEqual(int(bill.sum), 15)
# def test_upgrade_product(self):
# """
# Test upgrading behaviour
# """
# user = self.user
# # Create product
# starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
# starting_price = 10
# product = SampleRecurringProduct.objects.create(owner=user, rc_price=starting_price)
# product.create_order(starting_date)
# change1_date = start_after(starting_date + datetime.timedelta(days=15))
# product.rc_price = 20
# product.save()
# product.create_or_update_recurring_order(when_to_start=change1_date)
# bill_ending_date = change1_date + datetime.timedelta(days=1)
# bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)
# bill = bills[0]
# bill_records = BillRecord.objects.filter(bill=bill)
# self.assertEqual(len(bill_records), 2)
# self.assertEqual(bill_records[0].quantity, .5)
# self.assertEqual(bill_records[0].ending_date, end_before(change1_date))
# self.assertEqual(bill_records[1].quantity, 1)
# self.assertEqual(bill_records[1].starting_date, change1_date)
# self.assertEqual(int(bill.sum), 25)
# 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
# class NotABillingTC(TestCase):
# #class BillingTestCase(TestCase):