forked from uncloud/uncloud
begin splitting bill record creation function
This commit is contained in:
parent
18f9a3848a
commit
1c7d81762d
3 changed files with 185 additions and 52 deletions
|
@ -11,7 +11,7 @@ from django.http import FileResponse
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
|
||||||
from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress
|
from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress, SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee
|
||||||
|
|
||||||
|
|
||||||
class BillRecordInline(admin.TabularInline):
|
class BillRecordInline(admin.TabularInline):
|
||||||
|
@ -91,5 +91,7 @@ admin.site.register(Order)
|
||||||
admin.site.register(BillRecord)
|
admin.site.register(BillRecord)
|
||||||
admin.site.register(BillingAddress)
|
admin.site.register(BillingAddress)
|
||||||
|
|
||||||
|
for m in [ SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee ]:
|
||||||
|
admin.site.register(m)
|
||||||
|
|
||||||
#admin.site.register(Order, OrderAdmin)
|
#admin.site.register(Order, OrderAdmin)
|
||||||
|
|
|
@ -459,19 +459,121 @@ class Bill(models.Model):
|
||||||
|
|
||||||
return bills
|
return bills
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_next_bill_for_user_address(cls, owner, billing_address, ending_date=None):
|
def create_bill_records_for_recurring_orders(cls, bill):
|
||||||
|
"""
|
||||||
|
Create or update bill records for recurring orders for the
|
||||||
|
given bill
|
||||||
|
"""
|
||||||
|
|
||||||
|
owner = bill.owner
|
||||||
|
billing_address = bill.billing_address
|
||||||
|
|
||||||
|
all_orders = Order.objects.filter(owner=owner,
|
||||||
|
billing_address=billing_address).order_by('id').exclude(recurring_period=RecurringPeriod.ONE_TIME)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
else:
|
||||||
|
cls.create_new_bill_record_for_this_bill(order, bill)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_new_bill_record_for_this_bill(order, bill):
|
||||||
|
last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last()
|
||||||
|
|
||||||
|
this_starting_date=order.starting_date
|
||||||
|
|
||||||
|
if last_bill_record:
|
||||||
|
if last_bill_record.ending_date >= bill.ending_date:
|
||||||
|
return
|
||||||
|
|
||||||
|
if order.ending_date:
|
||||||
|
if last_bill_record.ending_date == order.ending_date:
|
||||||
|
return
|
||||||
|
|
||||||
|
this_starting_date = start_after(last_bill_record.ending_date)
|
||||||
|
|
||||||
|
|
||||||
|
ending_date = cls.get_bill_record_ending_date(order, 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()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_bill_record_ending_date(order, bill):
|
||||||
|
"""
|
||||||
|
Determine the ending date of the billing record
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If the order is quit, charge the final amount (?)
|
||||||
|
if order.ending_date:
|
||||||
|
this_ending_date = order.ending_date
|
||||||
|
else:
|
||||||
|
if order.earliest_ending_date > bill.ending_date:
|
||||||
|
this_ending_date = order.earliest_ending_date
|
||||||
|
else:
|
||||||
|
this_ending_date = bill.ending_date
|
||||||
|
|
||||||
|
return this_ending_date
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_next_bill_for_user_address(cls,
|
||||||
|
owner,
|
||||||
|
billing_address,
|
||||||
|
ending_date=None):
|
||||||
|
"""
|
||||||
|
Filtering ideas (TBD):
|
||||||
|
If order is replaced, it should not be added anymore if it has been billed "last time"
|
||||||
|
If order has ended and finally charged, do not charge anymore
|
||||||
|
|
||||||
|
Find out the last billrecord for the order, if there is none, bill from the starting date
|
||||||
|
"""
|
||||||
|
|
||||||
last_bill = cls.objects.filter(owner=owner, billing_address=billing_address).order_by('id').last()
|
last_bill = cls.objects.filter(owner=owner, billing_address=billing_address).order_by('id').last()
|
||||||
|
|
||||||
# it is important to sort orders here, as bill records will be
|
# it is important to sort orders here, as bill records will be
|
||||||
# created (and listed) in this order
|
# created (and listed) in this order
|
||||||
all_orders = Order.objects.filter(owner=owner, billing_address=billing_address).order_by('id')
|
all_orders = Order.objects.filter(owner=owner,
|
||||||
|
billing_address=billing_address).order_by('id')
|
||||||
first_order = all_orders.first()
|
first_order = all_orders.first()
|
||||||
|
|
||||||
bill = None
|
bill = None
|
||||||
ending_date = None
|
ending_date = None
|
||||||
|
|
||||||
# Get date & bill from previous bill
|
# Get date & bill from previous bill, if it exists
|
||||||
if last_bill:
|
if last_bill:
|
||||||
if not last_bill.is_final:
|
if not last_bill.is_final:
|
||||||
bill = last_bill
|
bill = last_bill
|
||||||
|
@ -504,12 +606,10 @@ class Bill(models.Model):
|
||||||
starting_date=order.starting_date,
|
starting_date=order.starting_date,
|
||||||
ending_date=order.ending_date)
|
ending_date=order.ending_date)
|
||||||
|
|
||||||
else:
|
else: # recurring orders
|
||||||
# Bill all recurring orders
|
|
||||||
bill_record_for_this_bill = BillRecord.objects.filter(bill=bill,
|
bill_record_for_this_bill = BillRecord.objects.filter(bill=bill,
|
||||||
order=order).first()
|
order=order).first()
|
||||||
|
|
||||||
|
|
||||||
# This bill already has a bill record for the order
|
# This bill already has a bill record for the order
|
||||||
# We potentially need to update the ending_date if the ending_date
|
# We potentially need to update the ending_date if the ending_date
|
||||||
# of the bill changed.
|
# of the bill changed.
|
||||||
|
@ -529,11 +629,8 @@ class Bill(models.Model):
|
||||||
|
|
||||||
bill_record_for_this_bill.save()
|
bill_record_for_this_bill.save()
|
||||||
|
|
||||||
else:
|
else: # No bill record in this bill for this order yet
|
||||||
# No bill record in this bill for the order yet
|
# Find out when billed last time
|
||||||
|
|
||||||
# Find out whether it was already billed for the billing period of
|
|
||||||
# this bill
|
|
||||||
last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last()
|
last_bill_record = BillRecord.objects.filter(order=order).order_by('id').last()
|
||||||
|
|
||||||
# Default starting date
|
# Default starting date
|
||||||
|
@ -580,12 +677,6 @@ class Bill(models.Model):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Filtering ideas:
|
|
||||||
# If order is replaced, it should not be added anymore if it has been billed "last time"
|
|
||||||
# If order has ended and finally charged, do not charge anymore
|
|
||||||
|
|
||||||
# Find out the last billrecord for the order, if there is none, bill from the starting date
|
|
||||||
|
|
||||||
|
|
||||||
return bill
|
return bill
|
||||||
|
|
||||||
|
@ -730,8 +821,6 @@ class Product(UncloudModel):
|
||||||
when_to_start = timezone.now()
|
when_to_start = timezone.now()
|
||||||
|
|
||||||
if self.last_recurring_order:
|
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 self.recurring_price < self.last_recurring_order.price:
|
||||||
|
|
||||||
if when_to_start < self.last_recurring_order.next_ending_date:
|
if when_to_start < self.last_recurring_order.next_ending_date:
|
||||||
|
|
|
@ -414,53 +414,95 @@ 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):
|
def test_bill_for_increasing_product_easy(self):
|
||||||
"""
|
"""
|
||||||
Modify a product, see one pro rata entry
|
Modify product, check general logi
|
||||||
"""
|
"""
|
||||||
|
|
||||||
price = 5
|
# Create product
|
||||||
|
|
||||||
# Standard 30d recurring product
|
|
||||||
product = SampleRecurringProduct.objects.create(owner=self.user,
|
|
||||||
rc_price=price)
|
|
||||||
|
|
||||||
starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
|
starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
|
||||||
ending_date = timezone.make_aware(datetime.datetime(2019,3,31))
|
starting_price = 10
|
||||||
change_date = timezone.make_aware(datetime.datetime(2019,4,17))
|
product = SampleRecurringProduct.objects.create(owner=self.user,
|
||||||
|
rc_price=starting_price)
|
||||||
bill_ending_date = timezone.make_aware(datetime.datetime(2019,4,30))
|
|
||||||
|
|
||||||
product.create_order(starting_date)
|
product.create_order(starting_date)
|
||||||
|
|
||||||
product.rc_price = 10
|
change1_date = timezone.make_aware(datetime.datetime(2019,4,17))
|
||||||
|
product.rc_price = 20
|
||||||
product.save()
|
product.save()
|
||||||
|
product.create_or_update_recurring_order(when_to_start=change1_date)
|
||||||
|
|
||||||
product.create_or_update_recurring_order(when_to_start=change_date)
|
bill_ending_date = timezone.make_aware(datetime.datetime(2019,6,30))
|
||||||
|
|
||||||
bills = Bill.create_next_bills_for_user(self.user,
|
bills = Bill.create_next_bills_for_user(self.user,
|
||||||
ending_date=bill_ending_date)
|
ending_date=bill_ending_date)
|
||||||
|
|
||||||
# Sum:
|
bill = bills[0]
|
||||||
# recurring1 = 5 CHF -> for 30days
|
bill_records = BillRecord.objects.filter(bill=bill)
|
||||||
# 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:
|
self.assertEqual(len(bill_records), 3)
|
||||||
# - close the lower order NOW
|
self.assertEqual(int(bill.sum), 35)
|
||||||
# - 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)
|
# Expected bill sum & records:
|
||||||
|
# 2019-03-03 - 2019-04-02 +30d: 10
|
||||||
# expeted result:
|
# 2019-04-02 - 2019-04-17: +15d: 5
|
||||||
# 1x 5 chf bill record
|
# 2019-04-17 - 2019-05-17: +30d: 20
|
||||||
# 1x 5 chf bill record
|
# total: 35
|
||||||
# 1x 10 partial bill record
|
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue