uncloud pay cleanups

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
This commit is contained in:
Nico Schottelius 2020-08-08 22:20:49 +02:00
parent c9be8cc50b
commit 9bf0a99f6a
5 changed files with 324 additions and 239 deletions

View file

@ -14,7 +14,8 @@ from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress
class BillRecordInline(admin.TabularInline): class BillRecordInline(admin.TabularInline):
model = Bill.bill_records.through # model = Bill.bill_records.through
model = BillRecord
# AT some point in the future: expose REPLACED and orders that depend on us # AT some point in the future: expose REPLACED and orders that depend on us
# class OrderInline(admin.TabularInline): # class OrderInline(admin.TabularInline):
@ -29,6 +30,10 @@ class BillAdmin(admin.ModelAdmin):
# change_list_template = "uncloud_pay/change_list.html" # change_list_template = "uncloud_pay/change_list.html"
def get_urls(self): def get_urls(self):
"""
Create URLs for PDF view
"""
info = "%s_%s" % (self.model._meta.app_label, self.model._meta.model_name) info = "%s_%s" % (self.model._meta.app_label, self.model._meta.model_name)
pat = lambda regex, fn: url(regex, self.admin_site.admin_view(fn), name='%s_%s' % (info, fn.__name__)) pat = lambda regex, fn: url(regex, self.admin_site.admin_view(fn), name='%s_%s' % (info, fn.__name__))
@ -77,7 +82,9 @@ class BillAdmin(admin.ModelAdmin):
admin.site.register(Bill, BillAdmin) admin.site.register(Bill, BillAdmin)
#admin.site.register(Order, OrderAdmin)
admin.site.register(Order) admin.site.register(Order)
admin.site.register(BillRecord) admin.site.register(BillRecord)
admin.site.register(BillingAddress) admin.site.register(BillingAddress)
#admin.site.register(Order, OrderAdmin)

View file

@ -0,0 +1,17 @@
# Generated by Django 3.1 on 2020-08-08 19:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0005_auto_20200808_1954'),
]
operations = [
migrations.RemoveField(
model_name='billrecord',
name='quantity',
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 3.1 on 2020-08-08 20:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0006_remove_billrecord_quantity'),
]
operations = [
migrations.RemoveField(
model_name='bill',
name='bill_records',
),
]

View file

@ -340,6 +340,10 @@ class Order(models.Model):
def is_recurring(self): def is_recurring(self):
return not self.recurring_period == RecurringPeriod.ONE_TIME return not self.recurring_period == RecurringPeriod.ONE_TIME
@property
def is_one_time(self):
return not self.is_recurring
@property @property
def is_terminated(self): def is_terminated(self):
return self.ending_date != None and self.ending_date < timezone.now() return self.ending_date != None and self.ending_date < timezone.now()
@ -382,10 +386,6 @@ class Bill(models.Model):
# what is valid for? should this be "final"? # what is valid for? should this be "final"?
valid = models.BooleanField(default=True) valid = models.BooleanField(default=True)
# Mapping to BillRecords
# https://stackoverflow.com/questions/4443190/djangos-manytomany-relationship-with-additional-fields
bill_records = models.ManyToManyField(Order, through="BillRecord")
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=['owner', models.UniqueConstraint(fields=['owner',
@ -399,13 +399,14 @@ class Bill(models.Model):
@property @property
def sum(self): def sum(self):
pass return 0
# for self.billrecord_set.
@classmethod @classmethod
def create_next_bill_for_user(cls, user): def create_next_bill_for_user(cls, owner):
last_bill = cls.objects.filter(owner=user).order_by('id').last() last_bill = cls.objects.filter(owner=owner).order_by('id').last()
all_orders = Order.objects.filter(owner=user).order_by('id') all_orders = Order.objects.filter(owner=owner).order_by('id')
first_order = all_orders.first() first_order = all_orders.first()
@ -420,16 +421,21 @@ class Bill(models.Model):
ending_date = end_of_month(starting_date) ending_date = end_of_month(starting_date)
bill = cls() bill, created = cls.objects.get_or_create(
owner=owner,
starting_date=starting_date,
ending_date=ending_date)
for order in all_orders: for order in all_orders:
# check if order needs to be billed # check if order needs to be billed
# check if order has previous billing record # check if order has previous billing record
# one time orders if order.is_one_time:
if not order.is_recurring: if order.billrecord_set.count() == 0:
pass br = BillRecord.objects.create(bill=bill,
order=order,
starting_date=starting_date,
ending_date=ending_date)
pass pass
return bill return bill
@ -515,20 +521,13 @@ class BillRecord(models.Model):
bill = models.ForeignKey(Bill, on_delete=models.CASCADE) bill = models.ForeignKey(Bill, on_delete=models.CASCADE)
order = models.ForeignKey(Order, on_delete=models.CASCADE) order = models.ForeignKey(Order, on_delete=models.CASCADE)
# How many times the order has been used in this record
quantity = models.DecimalField(max_digits=19, decimal_places=10)
# quantity can actually be derived from starting/ending date
# The timeframe the bill record is for can (and probably often will) differ
# from the bill time
creation_date = models.DateTimeField(auto_now_add=True) creation_date = models.DateTimeField(auto_now_add=True)
starting_date = models.DateTimeField() starting_date = models.DateTimeField()
ending_date = models.DateTimeField() ending_date = models.DateTimeField()
def quantity2(self): def quantity(self):
if self.order.recurring_period == RecurringPeriod.ONE_TIME: """ Determine the quantity by the duration"""
if self.order.is_recurring:
return 1 return 1
record_delta = self.ending_date - self.starting_date record_delta = self.ending_date - self.starting_date
@ -537,10 +536,7 @@ class BillRecord(models.Model):
def sum(self): def sum(self):
if self.order.recurring_period == RecurringPeriod.ONE_TIME: return self.order.price * self.quantity
return 1
return self.quantity * 1
def __str__(self): def __str__(self):
return f"{self.bill}: {self.quantity} x {self.order}" return f"{self.bill}: {self.quantity} x {self.order}"

View file

@ -5,131 +5,6 @@ from datetime import datetime, date, timedelta
from .models import * from .models import *
from uncloud_service.models import GenericServiceProduct from uncloud_service.models import GenericServiceProduct
class NotABillingTC(TestCase):
#class BillingTestCase(TestCase):
def setUp(self):
self.user = get_user_model().objects.create(
username='jdoe',
email='john.doe@domain.tld')
self.billing_address = BillingAddress.objects.create(
owner=self.user,
street="unknown",
city="unknown",
postal_code="unknown")
def test_basic_monthly_billing(self):
one_time_price = 10
recurring_price = 20
description = "Test Product 1"
# Three months: full, full, partial.
# starting_date = datetime.fromisoformat('2020-03-01')
starting_date = datetime(2020,3,1)
ending_date = datetime(2020,5,8)
# Create order to be billed.
order = Order.objects.create(
owner=self.user,
starting_date=starting_date,
ending_date=ending_date,
recurring_period=RecurringPeriod.PER_30D,
recurring_price=recurring_price,
one_time_price=one_time_price,
description=description,
billing_address=self.billing_address)
# Generate & check bill for first month: full recurring_price + setup.
first_month_bills = order.generate_initial_bill()
self.assertEqual(len(first_month_bills), 1)
self.assertEqual(first_month_bills[0].amount, one_time_price + recurring_price)
# Generate & check bill for second month: full recurring_price.
second_month_bills = Bill.generate_for(2020, 4, self.user)
self.assertEqual(len(second_month_bills), 1)
self.assertEqual(second_month_bills[0].amount, recurring_price)
# Generate & check bill for third and last month: partial recurring_price.
third_month_bills = Bill.generate_for(2020, 5, self.user)
self.assertEqual(len(third_month_bills), 1)
# 31 days in May.
self.assertEqual(float(third_month_bills[0].amount),
round(round((7/31), AMOUNT_DECIMALS) * recurring_price, AMOUNT_DECIMALS))
# Check that running Bill.generate_for() twice does not create duplicates.
self.assertEqual(len(Bill.generate_for(2020, 3, self.user)), 0)
def test_basic_yearly_billing(self):
one_time_price = 10
recurring_price = 150
description = "Test Product 1"
starting_date = datetime.fromisoformat('2020-03-31T08:05:23')
# Create order to be billed.
order = Order.objects.create(
owner=self.user,
starting_date=starting_date,
recurring_period=RecurringPeriod.PER_365D,
recurring_price=recurring_price,
one_time_price=one_time_price,
description=description,
billing_address=self.billing_address)
# Generate & check bill for first year: recurring_price + setup.
first_year_bills = order.generate_initial_bill()
self.assertEqual(len(first_year_bills), 1)
self.assertEqual(first_year_bills[0].starting_date.date(),
date.fromisoformat('2020-03-31'))
self.assertEqual(first_year_bills[0].ending_date.date(),
date.fromisoformat('2021-03-30'))
self.assertEqual(first_year_bills[0].amount,
recurring_price + one_time_price)
# Generate & check bill for second year: recurring_price.
second_year_bills = Bill.generate_for(2021, 3, self.user)
self.assertEqual(len(second_year_bills), 1)
self.assertEqual(second_year_bills[0].starting_date.date(),
date.fromisoformat('2021-03-31'))
self.assertEqual(second_year_bills[0].ending_date.date(),
date.fromisoformat('2022-03-30'))
self.assertEqual(second_year_bills[0].amount, recurring_price)
# Check that running Bill.generate_for() twice does not create duplicates.
self.assertEqual(len(Bill.generate_for(2020, 3, self.user)), 0)
self.assertEqual(len(Bill.generate_for(2020, 4, self.user)), 0)
self.assertEqual(len(Bill.generate_for(2020, 2, self.user)), 0)
self.assertEqual(len(Bill.generate_for(2021, 3, self.user)), 0)
def test_basic_hourly_billing(self):
one_time_price = 10
recurring_price = 1.4
description = "Test Product 1"
starting_date = datetime.fromisoformat('2020-03-31T08:05:23')
ending_date = datetime.fromisoformat('2020-04-01T11:13:32')
# Create order to be billed.
order = Order.objects.create(
owner=self.user,
starting_date=starting_date,
ending_date=ending_date,
recurring_period=RecurringPeriod.PER_HOUR,
recurring_price=recurring_price,
one_time_price=one_time_price,
description=description,
billing_address=self.billing_address)
# Generate & check bill for first month: recurring_price + setup.
first_month_bills = order.generate_initial_bill()
self.assertEqual(len(first_month_bills), 1)
self.assertEqual(float(first_month_bills[0].amount),
round(16 * recurring_price, AMOUNT_DECIMALS) + one_time_price)
# Generate & check bill for first month: recurring_price.
second_month_bills = Bill.generate_for(2020, 4, self.user)
self.assertEqual(len(second_month_bills), 1)
self.assertEqual(float(second_month_bills[0].amount),
round(12 * recurring_price, AMOUNT_DECIMALS))
class ProductActivationTestCase(TestCase): class ProductActivationTestCase(TestCase):
def setUp(self): def setUp(self):
@ -139,102 +14,275 @@ class ProductActivationTestCase(TestCase):
self.billing_address = BillingAddress.objects.create( self.billing_address = BillingAddress.objects.create(
owner=self.user, owner=self.user,
organization = 'Test org',
street="unknown", street="unknown",
city="unknown", city="unknown",
postal_code="unknown") postal_code="unknown")
def test_product_activation(self): self.order_meta = {}
starting_date = datetime.fromisoformat('2020-03-01') self.order_meta[1] = {
one_time_price = 0 'starting_date': timezone.make_aware(datetime.datetime(2020,3,3)),
recurring_price = 1 'ending_date': timezone.make_aware(datetime.datetime(2020,4,17)),
description = "Test Product" 'price': 15,
'description': 'One chocolate bar'
}
def test_bill_one_time_order(self):
one_time_price = 10
recurring_price = 150
description = "Test Product 1"
order = Order.objects.create( order = Order.objects.create(
owner=self.user, owner=self.user,
starting_date=starting_date, starting_date=self.order_meta[1]['starting_date'],
recurring_period=RecurringPeriod.PER_30D, ending_date=self.order_meta[1]['ending_date'],
recurring_price=recurring_price, recurring_period=RecurringPeriod.ONE_TIME,
one_time_price=one_time_price, price=self.order_meta[1]['price'],
description=description, description=self.order_meta[1]['description'],
billing_address=self.billing_address) billing_address=self.billing_address)
product = GenericServiceProduct(
custom_description=description,
custom_one_time_price=one_time_price,
custom_recurring_price=recurring_price,
owner=self.user,
order=order)
product.save()
# Validate initial state: must be awaiting payment. bill = Bill.create_next_bill_for_user(self.user)
self.assertEqual(product.status, UncloudStatus.AWAITING_PAYMENT)
# Pay initial bill, check that product is activated. self.assertEqual(order.billrecord_set.count(), 1)
order.generate_initial_bill()
amount = product.order.bills[0].amount
payment = Payment(owner=self.user, amount=amount)
payment.save()
self.assertEqual(
GenericServiceProduct.objects.get(uuid=product.uuid).status,
UncloudStatus.PENDING
)
class BillingAddressTestCase(TestCase): self.assertEqual(bill.sum, self.order_meta[1]['price'])
def setUp(self):
self.user = get_user_model().objects.create(
username='jdoe',
email='john.doe@domain.tld')
self.billing_address_01 = BillingAddress.objects.create(
owner=self.user,
street="unknown1",
city="unknown1",
postal_code="unknown1",
country="CH")
self.billing_address_02 = BillingAddress.objects.create(
owner=self.user,
street="unknown2",
city="unknown2",
postal_code="unknown2",
country="CH")
def test_billing_with_single_address(self):
# Create new orders somewhere in the past so that we do not encounter
# auto-created initial bills.
starting_date = datetime.fromisoformat('2020-03-01')
order_01 = Order.objects.create( # class NotABillingTC(TestCase):
owner=self.user, # #class BillingTestCase(TestCase):
starting_date=starting_date, # def setUp(self):
recurring_period=RecurringPeriod.PER_30D, # self.user = get_user_model().objects.create(
billing_address=self.billing_address_01) # username='jdoe',
order_02 = Order.objects.create( # email='john.doe@domain.tld')
owner=self.user, # self.billing_address = BillingAddress.objects.create(
starting_date=starting_date, # owner=self.user,
recurring_period=RecurringPeriod.PER_30D, # street="unknown",
billing_address=self.billing_address_01) # city="unknown",
# postal_code="unknown")
# We need a single bill since we work with a single address. # def test_basic_monthly_billing(self):
bills = Bill.generate_for(2020, 4, self.user) # one_time_price = 10
self.assertEqual(len(bills), 1) # recurring_price = 20
# description = "Test Product 1"
def test_billing_with_multiple_addresses(self): # # Three months: full, full, partial.
# Create new orders somewhere in the past so that we do not encounter # # starting_date = datetime.fromisoformat('2020-03-01')
# auto-created initial bills. # starting_date = datetime(2020,3,1)
starting_date = datetime.fromisoformat('2020-03-01') # ending_date = datetime(2020,5,8)
order_01 = Order.objects.create( # # Create order to be billed.
owner=self.user, # order = Order.objects.create(
starting_date=starting_date, # owner=self.user,
recurring_period=RecurringPeriod.PER_30D, # starting_date=starting_date,
billing_address=self.billing_address_01) # ending_date=ending_date,
order_02 = Order.objects.create( # recurring_period=RecurringPeriod.PER_30D,
owner=self.user, # recurring_price=recurring_price,
starting_date=starting_date, # one_time_price=one_time_price,
recurring_period=RecurringPeriod.PER_30D, # description=description,
billing_address=self.billing_address_02) # billing_address=self.billing_address)
# We need different bills since we work with different addresses. # # Generate & check bill for first month: full recurring_price + setup.
bills = Bill.generate_for(2020, 4, self.user) # first_month_bills = order.generate_initial_bill()
self.assertEqual(len(bills), 2) # self.assertEqual(len(first_month_bills), 1)
# self.assertEqual(first_month_bills[0].amount, one_time_price + recurring_price)
# # Generate & check bill for second month: full recurring_price.
# second_month_bills = Bill.generate_for(2020, 4, self.user)
# self.assertEqual(len(second_month_bills), 1)
# self.assertEqual(second_month_bills[0].amount, recurring_price)
# # Generate & check bill for third and last month: partial recurring_price.
# third_month_bills = Bill.generate_for(2020, 5, self.user)
# self.assertEqual(len(third_month_bills), 1)
# # 31 days in May.
# self.assertEqual(float(third_month_bills[0].amount),
# round(round((7/31), AMOUNT_DECIMALS) * recurring_price, AMOUNT_DECIMALS))
# # Check that running Bill.generate_for() twice does not create duplicates.
# self.assertEqual(len(Bill.generate_for(2020, 3, self.user)), 0)
# def test_basic_yearly_billing(self):
# one_time_price = 10
# recurring_price = 150
# description = "Test Product 1"
# starting_date = datetime.fromisoformat('2020-03-31T08:05:23')
# # Create order to be billed.
# order = Order.objects.create(
# owner=self.user,
# starting_date=starting_date,
# recurring_period=RecurringPeriod.PER_365D,
# recurring_price=recurring_price,
# one_time_price=one_time_price,
# description=description,
# billing_address=self.billing_address)
# # Generate & check bill for first year: recurring_price + setup.
# first_year_bills = order.generate_initial_bill()
# self.assertEqual(len(first_year_bills), 1)
# self.assertEqual(first_year_bills[0].starting_date.date(),
# date.fromisoformat('2020-03-31'))
# self.assertEqual(first_year_bills[0].ending_date.date(),
# date.fromisoformat('2021-03-30'))
# self.assertEqual(first_year_bills[0].amount,
# recurring_price + one_time_price)
# # Generate & check bill for second year: recurring_price.
# second_year_bills = Bill.generate_for(2021, 3, self.user)
# self.assertEqual(len(second_year_bills), 1)
# self.assertEqual(second_year_bills[0].starting_date.date(),
# date.fromisoformat('2021-03-31'))
# self.assertEqual(second_year_bills[0].ending_date.date(),
# date.fromisoformat('2022-03-30'))
# self.assertEqual(second_year_bills[0].amount, recurring_price)
# # Check that running Bill.generate_for() twice does not create duplicates.
# self.assertEqual(len(Bill.generate_for(2020, 3, self.user)), 0)
# self.assertEqual(len(Bill.generate_for(2020, 4, self.user)), 0)
# self.assertEqual(len(Bill.generate_for(2020, 2, self.user)), 0)
# self.assertEqual(len(Bill.generate_for(2021, 3, self.user)), 0)
# def test_basic_hourly_billing(self):
# one_time_price = 10
# recurring_price = 1.4
# description = "Test Product 1"
# starting_date = datetime.fromisoformat('2020-03-31T08:05:23')
# ending_date = datetime.fromisoformat('2020-04-01T11:13:32')
# # Create order to be billed.
# order = Order.objects.create(
# owner=self.user,
# starting_date=starting_date,
# ending_date=ending_date,
# recurring_period=RecurringPeriod.PER_HOUR,
# recurring_price=recurring_price,
# one_time_price=one_time_price,
# description=description,
# billing_address=self.billing_address)
# # Generate & check bill for first month: recurring_price + setup.
# first_month_bills = order.generate_initial_bill()
# self.assertEqual(len(first_month_bills), 1)
# self.assertEqual(float(first_month_bills[0].amount),
# round(16 * recurring_price, AMOUNT_DECIMALS) + one_time_price)
# # Generate & check bill for first month: recurring_price.
# second_month_bills = Bill.generate_for(2020, 4, self.user)
# self.assertEqual(len(second_month_bills), 1)
# self.assertEqual(float(second_month_bills[0].amount),
# round(12 * recurring_price, AMOUNT_DECIMALS))
# class ProductActivationTestCase(TestCase):
# def setUp(self):
# self.user = get_user_model().objects.create(
# username='jdoe',
# email='john.doe@domain.tld')
# self.billing_address = BillingAddress.objects.create(
# owner=self.user,
# street="unknown",
# city="unknown",
# postal_code="unknown")
# def test_product_activation(self):
# starting_date = datetime.fromisoformat('2020-03-01')
# one_time_price = 0
# recurring_price = 1
# description = "Test Product"
# order = Order.objects.create(
# owner=self.user,
# starting_date=starting_date,
# recurring_period=RecurringPeriod.PER_30D,
# recurring_price=recurring_price,
# one_time_price=one_time_price,
# description=description,
# billing_address=self.billing_address)
# product = GenericServiceProduct(
# custom_description=description,
# custom_one_time_price=one_time_price,
# custom_recurring_price=recurring_price,
# owner=self.user,
# order=order)
# product.save()
# # Validate initial state: must be awaiting payment.
# self.assertEqual(product.status, UncloudStatus.AWAITING_PAYMENT)
# # Pay initial bill, check that product is activated.
# order.generate_initial_bill()
# amount = product.order.bills[0].amount
# payment = Payment(owner=self.user, amount=amount)
# payment.save()
# self.assertEqual(
# GenericServiceProduct.objects.get(uuid=product.uuid).status,
# UncloudStatus.PENDING
# )
# class BillingAddressTestCase(TestCase):
# def setUp(self):
# self.user = get_user_model().objects.create(
# username='jdoe',
# email='john.doe@domain.tld')
# self.billing_address_01 = BillingAddress.objects.create(
# owner=self.user,
# street="unknown1",
# city="unknown1",
# postal_code="unknown1",
# country="CH")
# self.billing_address_02 = BillingAddress.objects.create(
# owner=self.user,
# street="unknown2",
# city="unknown2",
# postal_code="unknown2",
# country="CH")
# def test_billing_with_single_address(self):
# # Create new orders somewhere in the past so that we do not encounter
# # auto-created initial bills.
# starting_date = datetime.fromisoformat('2020-03-01')
# order_01 = Order.objects.create(
# owner=self.user,
# starting_date=starting_date,
# recurring_period=RecurringPeriod.PER_30D,
# billing_address=self.billing_address_01)
# order_02 = Order.objects.create(
# owner=self.user,
# starting_date=starting_date,
# recurring_period=RecurringPeriod.PER_30D,
# billing_address=self.billing_address_01)
# # We need a single bill since we work with a single address.
# bills = Bill.generate_for(2020, 4, self.user)
# self.assertEqual(len(bills), 1)
# def test_billing_with_multiple_addresses(self):
# # Create new orders somewhere in the past so that we do not encounter
# # auto-created initial bills.
# starting_date = datetime.fromisoformat('2020-03-01')
# order_01 = Order.objects.create(
# owner=self.user,
# starting_date=starting_date,
# recurring_period=RecurringPeriod.PER_30D,
# billing_address=self.billing_address_01)
# order_02 = Order.objects.create(
# owner=self.user,
# starting_date=starting_date,
# recurring_period=RecurringPeriod.PER_30D,
# billing_address=self.billing_address_02)
# # We need different bills since we work with different addresses.
# bills = Bill.generate_for(2020, 4, self.user)
# self.assertEqual(len(bills), 2)