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:
parent
58883765d7
commit
992c7c551e
11 changed files with 588 additions and 362 deletions
11
uncloud/management/commands/db-add-defaults.py
Normal file
11
uncloud/management/commands/db-add-defaults.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from uncloud_pay.models import RecurringPeriod
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Add standard uncloud values'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
RecurringPeriod.populate_db_defaults()
|
|
@ -173,7 +173,7 @@ class VPNNetwork(models.Model):
|
||||||
|
|
||||||
wireguard_public_key = models.CharField(max_length=48)
|
wireguard_public_key = models.CharField(max_length=48)
|
||||||
|
|
||||||
default_recurring_period = RecurringPeriod.PER_365D
|
# default_recurring_period = RecurringPeriod.PER_365D
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def recurring_price(self):
|
def recurring_price(self):
|
||||||
|
|
|
@ -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, Product
|
from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress, Product, RecurringPeriod
|
||||||
|
|
||||||
|
|
||||||
class BillRecordInline(admin.TabularInline):
|
class BillRecordInline(admin.TabularInline):
|
||||||
|
@ -90,9 +90,8 @@ admin.site.register(Bill, BillAdmin)
|
||||||
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(RecurringPeriod)
|
||||||
#for m in [ SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee ]:
|
|
||||||
|
|
||||||
admin.site.register(Product)
|
admin.site.register(Product)
|
||||||
|
|
||||||
#admin.site.register(Order, OrderAdmin)
|
#admin.site.register(Order, OrderAdmin)
|
||||||
|
#for m in [ SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee ]:
|
||||||
|
|
41
uncloud_pay/migrations/0027_auto_20201006_1319.py
Normal file
41
uncloud_pay/migrations/0027_auto_20201006_1319.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 3.1 on 2020-10-06 13:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_pay', '0026_order_should_be_billed'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RecurringPeriod',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('duration_seconds', models.IntegerField(unique=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='SampleOneTimeProduct',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='SampleRecurringProduct',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='SampleRecurringProductOneTimeFee',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='recurring_period',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.recurringperiod'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='product',
|
||||||
|
name='default_recurring_period',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.recurringperiod'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -64,21 +64,6 @@ def start_after(a_date):
|
||||||
def default_payment_delay():
|
def default_payment_delay():
|
||||||
return timezone.now() + BILL_PAYMENT_DELAY
|
return timezone.now() + BILL_PAYMENT_DELAY
|
||||||
|
|
||||||
# See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types
|
|
||||||
class RecurringPeriod(models.IntegerChoices):
|
|
||||||
"""
|
|
||||||
We don't support months are years, because they vary in length.
|
|
||||||
This is not only complicated, but also unfair to the user, as the user pays the same
|
|
||||||
amount for different durations.
|
|
||||||
"""
|
|
||||||
PER_365D = 365*24*3600, _('Per 365 days')
|
|
||||||
PER_30D = 30*24*3600, _('Per 30 days')
|
|
||||||
PER_WEEK = 7*24*3600, _('Per Week')
|
|
||||||
PER_DAY = 24*3600, _('Per Day')
|
|
||||||
PER_HOUR = 3600, _('Per Hour')
|
|
||||||
PER_MINUTE = 60, _('Per Minute')
|
|
||||||
PER_SECOND = 1, _('Per Second')
|
|
||||||
ONE_TIME = 0, _('Onetime')
|
|
||||||
|
|
||||||
class Currency(models.TextChoices):
|
class Currency(models.TextChoices):
|
||||||
"""
|
"""
|
||||||
|
@ -236,6 +221,41 @@ class PaymentMethod(models.Model):
|
||||||
# non-primary method.
|
# non-primary method.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types
|
||||||
|
class RecurringPeriodChoices(models.IntegerChoices):
|
||||||
|
"""
|
||||||
|
This is an old class and being superseeded by the database model below
|
||||||
|
"""
|
||||||
|
PER_365D = 365*24*3600, _('Per 365 days')
|
||||||
|
PER_30D = 30*24*3600, _('Per 30 days')
|
||||||
|
PER_WEEK = 7*24*3600, _('Per Week')
|
||||||
|
PER_DAY = 24*3600, _('Per Day')
|
||||||
|
PER_HOUR = 3600, _('Per Hour')
|
||||||
|
PER_MINUTE = 60, _('Per Minute')
|
||||||
|
PER_SECOND = 1, _('Per Second')
|
||||||
|
ONE_TIME = 0, _('Onetime')
|
||||||
|
|
||||||
|
# RecurringPeriods
|
||||||
|
class RecurringPeriod(models.Model):
|
||||||
|
"""
|
||||||
|
Available recurring periods.
|
||||||
|
By default seeded from RecurringPeriodChoices
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = models.CharField(max_length=100, unique=True)
|
||||||
|
duration_seconds = models.IntegerField(unique=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def populate_db_defaults(cls):
|
||||||
|
for (seconds, name) in RecurringPeriodChoices.choices:
|
||||||
|
obj, created = cls.objects.get_or_create(name=name,
|
||||||
|
defaults={ 'duration_seconds': seconds })
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.duration_seconds})"
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Bills.
|
# Bills.
|
||||||
|
|
||||||
|
@ -313,7 +333,11 @@ class Product(UncloudModel):
|
||||||
description = models.CharField(max_length=1024)
|
description = models.CharField(max_length=1024)
|
||||||
|
|
||||||
config = models.JSONField()
|
config = models.JSONField()
|
||||||
default_recurring_period = models.IntegerField(choices=RecurringPeriod.choices, default=RecurringPeriod.PER_30D)
|
|
||||||
|
# default_recurring_period = models.IntegerField(choices=RecurringPeriod.choices, default=RecurringPeriod.PER_30D)
|
||||||
|
default_recurring_period = models.ForeignKey(RecurringPeriod, on_delete=models.CASCADE, editable=True)
|
||||||
|
|
||||||
|
|
||||||
currency = models.CharField(max_length=32, choices=Currency.choices, default=Currency.CHF)
|
currency = models.CharField(max_length=32, choices=Currency.choices, default=Currency.CHF)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -409,7 +433,6 @@ class Product(UncloudModel):
|
||||||
self.create_order(when_to_start, recurring_period)
|
self.create_order(when_to_start, recurring_period)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def recurring_price(self):
|
def recurring_price(self):
|
||||||
""" implement correct values in the child class """
|
""" implement correct values in the child class """
|
||||||
|
@ -564,8 +587,9 @@ class Order(models.Model):
|
||||||
ending_date = models.DateTimeField(blank=True, null=True)
|
ending_date = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
# FIXME: ensure the period is defined in the product
|
# FIXME: ensure the period is defined in the product
|
||||||
recurring_period = models.IntegerField(choices = RecurringPeriod.choices,
|
# recurring_period = models.IntegerField(choices = RecurringPeriod.choices,
|
||||||
default = RecurringPeriod.PER_30D)
|
# default = RecurringPeriod.PER_30D)
|
||||||
|
recurring_period = models.ForeignKey(RecurringPeriod, on_delete=models.CASCADE, editable=True)
|
||||||
|
|
||||||
one_time_price = models.DecimalField(default=0.0,
|
one_time_price = models.DecimalField(default=0.0,
|
||||||
max_digits=AMOUNT_MAX_DIGITS,
|
max_digits=AMOUNT_MAX_DIGITS,
|
||||||
|
@ -591,7 +615,6 @@ class Order(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
|
|
||||||
|
|
||||||
should_be_billed = models.BooleanField(default=True)
|
should_be_billed = models.BooleanField(default=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -700,6 +723,29 @@ class Order(models.Model):
|
||||||
self.ending_date = end_before(new_order.starting_date)
|
self.ending_date = end_before(new_order.starting_date)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def update_order(self, config, starting_date=None):
|
||||||
|
"""
|
||||||
|
Updating an order means creating a new order and reference the previous order
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not starting_date:
|
||||||
|
starting_date = timezone.now()
|
||||||
|
|
||||||
|
new_order = self.__class__(owner=self.owner,
|
||||||
|
billing_address=self.billing_address,
|
||||||
|
product=self.product,
|
||||||
|
starting_date=starting_date,
|
||||||
|
config=config)
|
||||||
|
|
||||||
|
(new_order_one_time_price, new_order_recurring_price) = new_order.prices
|
||||||
|
|
||||||
|
new_order.replaces = self
|
||||||
|
new_order.save()
|
||||||
|
|
||||||
|
self.ending_date = end_before(new_order.starting_date)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.ending_date and self.ending_date < self.starting_date:
|
if self.ending_date and self.ending_date < self.starting_date:
|
||||||
raise ValidationError("End date cannot be before starting date")
|
raise ValidationError("End date cannot be before starting date")
|
||||||
|
@ -778,12 +824,14 @@ class Order(models.Model):
|
||||||
is_recurring_record=True)
|
is_recurring_record=True)
|
||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
@property
|
||||||
|
def prices(self):
|
||||||
one_time_price = 0
|
one_time_price = 0
|
||||||
recurring_price = 0
|
recurring_price = 0
|
||||||
|
|
||||||
# FIXME: support amount independent one time prices
|
# FIXME: support amount independent one time prices
|
||||||
# FIXME: support a base price
|
# FIXME: support a base price
|
||||||
|
# FIXME: adjust to the selected recurring_period
|
||||||
|
|
||||||
if 'features' in self.product.config:
|
if 'features' in self.product.config:
|
||||||
for feature in self.product.config['features']:
|
for feature in self.product.config['features']:
|
||||||
|
@ -794,12 +842,14 @@ class Order(models.Model):
|
||||||
one_time_price += self.product.config['features'][feature]['one_time_price'] * self.config['features'][feature]
|
one_time_price += self.product.config['features'][feature]['one_time_price'] * self.config['features'][feature]
|
||||||
recurring_price += self.product.config['features'][feature]['recurring_price'] * self.config['features'][feature]
|
recurring_price += self.product.config['features'][feature]['recurring_price'] * self.config['features'][feature]
|
||||||
|
|
||||||
|
return (one_time_price, recurring_price)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
# Calculate the price of the order when we create it
|
||||||
# IMMUTABLE fields -- need to create new order to modify them
|
# IMMUTABLE fields -- need to create new order to modify them
|
||||||
# However this is not enforced here...
|
# However this is not enforced here...
|
||||||
if self._state.adding:
|
if self._state.adding:
|
||||||
self.one_time_price = one_time_price
|
(self.one_time_price, self.recurring_price) = self.prices
|
||||||
self.recurring_price = recurring_price
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -1137,50 +1187,50 @@ class BillRecord(models.Model):
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Sample products included into uncloud
|
# # Sample products included into uncloud
|
||||||
class SampleOneTimeProduct(models.Model):
|
# class SampleOneTimeProduct(models.Model):
|
||||||
"""
|
# """
|
||||||
Products are usually more complex, but this product shows how easy
|
# Products are usually more complex, but this product shows how easy
|
||||||
it can be to create your own one time product.
|
# it can be to create your own one time product.
|
||||||
"""
|
# """
|
||||||
|
|
||||||
default_recurring_period = RecurringPeriod.ONE_TIME
|
# default_recurring_period = RecurringPeriod.ONE_TIME
|
||||||
|
|
||||||
ot_price = models.IntegerField(default=5)
|
# ot_price = models.IntegerField(default=5)
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def one_time_price(self):
|
# def one_time_price(self):
|
||||||
return self.ot_price
|
# return self.ot_price
|
||||||
|
|
||||||
class SampleRecurringProduct(models.Model):
|
# class SampleRecurringProduct(models.Model):
|
||||||
"""
|
# """
|
||||||
Products are usually more complex, but this product shows how easy
|
# Products are usually more complex, but this product shows how easy
|
||||||
it can be to create your own recurring fee product.
|
# it can be to create your own recurring fee product.
|
||||||
"""
|
# """
|
||||||
|
|
||||||
default_recurring_period = RecurringPeriod.PER_30D
|
# default_recurring_period = RecurringPeriod.PER_30D
|
||||||
|
|
||||||
rc_price = models.IntegerField(default=10)
|
# rc_price = models.IntegerField(default=10)
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def recurring_price(self):
|
# def recurring_price(self):
|
||||||
return self.rc_price
|
# return self.rc_price
|
||||||
|
|
||||||
class SampleRecurringProductOneTimeFee(models.Model):
|
# class SampleRecurringProductOneTimeFee(models.Model):
|
||||||
"""
|
# """
|
||||||
Products are usually more complex, but this product shows how easy
|
# Products are usually more complex, but this product shows how easy
|
||||||
it can be to create your own one time + recurring fee product.
|
# it can be to create your own one time + recurring fee product.
|
||||||
"""
|
# """
|
||||||
|
|
||||||
default_recurring_period = RecurringPeriod.PER_30D
|
# default_recurring_period = RecurringPeriod.PER_30D
|
||||||
|
|
||||||
ot_price = models.IntegerField(default=5)
|
# ot_price = models.IntegerField(default=5)
|
||||||
rc_price = models.IntegerField(default=10)
|
# rc_price = models.IntegerField(default=10)
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def one_time_price(self):
|
# def one_time_price(self):
|
||||||
return self.ot_price
|
# return self.ot_price
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def recurring_price(self):
|
# def recurring_price(self):
|
||||||
return self.rc_price
|
# return self.rc_price
|
||||||
|
|
|
@ -82,7 +82,7 @@ class BillRecordSerializer(serializers.Serializer):
|
||||||
description = serializers.CharField()
|
description = serializers.CharField()
|
||||||
one_time_price = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
one_time_price = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
||||||
recurring_price = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
recurring_price = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
||||||
recurring_period = serializers.ChoiceField(choices=RecurringPeriod.choices)
|
# recurring_period = serializers.ChoiceField()
|
||||||
recurring_count = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
recurring_count = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
||||||
vat_rate = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
vat_rate = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
||||||
vat_amount = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
vat_amount = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS)
|
||||||
|
|
|
@ -27,7 +27,7 @@ chocolate_order_config = {
|
||||||
|
|
||||||
chocolate_one_time_price = chocolate_order_config['features']['gramm'] * chocolate_product_config['features']['gramm']['one_time_price']
|
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': {
|
'features': {
|
||||||
'cores':
|
'cores':
|
||||||
{ 'min': 1,
|
{ 'min': 1,
|
||||||
|
@ -44,13 +44,28 @@ vm_sample_product_config = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
vm_sample_order_config = {
|
vm_order_config = {
|
||||||
'features': {
|
'features': {
|
||||||
'cores': 2,
|
'cores': 2,
|
||||||
'ram_gb': 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):
|
class ProductTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -77,7 +92,7 @@ class ProductTestCase(TestCase):
|
||||||
|
|
||||||
p = Product.objects.create(name="Testproduct",
|
p = Product.objects.create(name="Testproduct",
|
||||||
description="Only for testing",
|
description="Only for testing",
|
||||||
config=vm_sample_product_config)
|
config=vm_product_config)
|
||||||
|
|
||||||
|
|
||||||
class OrderTestCase(TestCase):
|
class OrderTestCase(TestCase):
|
||||||
|
@ -105,16 +120,287 @@ class OrderTestCase(TestCase):
|
||||||
|
|
||||||
p = Product.objects.create(name="Testproduct",
|
p = Product.objects.create(name="Testproduct",
|
||||||
description="Only for testing",
|
description="Only for testing",
|
||||||
config=vm_sample_product_config)
|
config=vm_product_config)
|
||||||
|
|
||||||
o = Order.objects.create(owner=self.user,
|
o = Order.objects.create(owner=self.user,
|
||||||
billing_address=self.ba,
|
billing_address=self.ba,
|
||||||
product=p,
|
product=p,
|
||||||
config=vm_sample_order_config)
|
config=vm_order_config)
|
||||||
|
|
||||||
self.assertEqual(o.one_time_price, 0)
|
self.assertEqual(o.one_time_price, 0)
|
||||||
self.assertEqual(o.recurring_price, 16)
|
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):
|
class BillTestCase(TestCase):
|
||||||
|
@ -163,14 +449,11 @@ class BillTestCase(TestCase):
|
||||||
description="Not only for testing, but for joy",
|
description="Not only for testing, but for joy",
|
||||||
config=chocolate_product_config)
|
config=chocolate_product_config)
|
||||||
|
|
||||||
# self.recurring_order = Order.objects.create(
|
|
||||||
# owner=self.recurring_user,
|
self.vm = Product.objects.create(name="Super Fast VM",
|
||||||
# starting_date=timezone.make_aware(datetime.datetime(2020,3,3)),
|
description="Zooooom",
|
||||||
# recurring_period=RecurringPeriod.PER_30D,
|
config=vm_product_config)
|
||||||
# price=15,
|
|
||||||
# description="A pretty VM",
|
|
||||||
# billing_address=BillingAddress.get_address_for(self.recurring_user)
|
|
||||||
# )
|
|
||||||
|
|
||||||
# used for generating multiple bills
|
# used for generating multiple bills
|
||||||
self.bill_dates = [
|
self.bill_dates = [
|
||||||
|
@ -190,6 +473,28 @@ class BillTestCase(TestCase):
|
||||||
ending_date=self.order_meta[1]['ending_date'],
|
ending_date=self.order_meta[1]['ending_date'],
|
||||||
config=chocolate_order_config)
|
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):
|
def test_bill_one_time_one_bill_record(self):
|
||||||
|
@ -213,30 +518,33 @@ class BillTestCase(TestCase):
|
||||||
self.assertEqual(bill.sum, chocolate_one_time_price)
|
self.assertEqual(bill.sum, chocolate_one_time_price)
|
||||||
|
|
||||||
|
|
||||||
# def test_bill_creates_record_for_recurring_order(self):
|
def test_bill_creates_record_for_recurring_order(self):
|
||||||
# """
|
"""
|
||||||
# Ensure there is only 1 bill record per order
|
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(order.billrecord_set.count(), 1)
|
||||||
# self.assertEqual(bill.billrecord_set.count(), 1)
|
self.assertEqual(bill.billrecord_set.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
# def test_new_bill_after_closing(self):
|
def test_new_bill_after_closing(self):
|
||||||
# """
|
"""
|
||||||
# After closing a bill and the user has a recurring product,
|
After closing a bill and the user has a recurring product,
|
||||||
# the next bill run should create e new bill
|
the next bill run should create e new bill
|
||||||
# """
|
"""
|
||||||
|
|
||||||
# for ending_date in self.bill_dates:
|
order = self.order_vm()
|
||||||
# b = Bill.create_next_bill_for_user_address(self.recurring_user_addr, ending_date)
|
|
||||||
# b.close()
|
|
||||||
|
|
||||||
# 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()
|
||||||
|
|
||||||
|
self.assertEqual(len(self.bill_dates), bill_count)
|
||||||
|
|
||||||
# def test_multi_addr_multi_bill(self):
|
# def test_multi_addr_multi_bill(self):
|
||||||
# """
|
# """
|
||||||
|
@ -256,6 +564,15 @@ class BillTestCase(TestCase):
|
||||||
# postal_code="unknown",
|
# postal_code="unknown",
|
||||||
# active=True)
|
# active=True)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
# order1 = Order.objects.create(
|
# order1 = Order.objects.create(
|
||||||
# owner=multi_addr_user,
|
# owner=multi_addr_user,
|
||||||
# starting_date=self.order_meta[1]['starting_date'],
|
# starting_date=self.order_meta[1]['starting_date'],
|
||||||
|
@ -392,21 +709,21 @@ class BillTestCase(TestCase):
|
||||||
# # FIXME: where is the assert?
|
# # FIXME: where is the assert?
|
||||||
|
|
||||||
|
|
||||||
# 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(
|
||||||
# username='random_user',
|
username='random_user',
|
||||||
# email='jane.random@domain.tld')
|
email='jane.random@domain.tld')
|
||||||
|
|
||||||
|
|
||||||
# def test_user_no_address(self):
|
def test_user_no_address(self):
|
||||||
# """
|
"""
|
||||||
# Raise an error, when there is no address
|
Raise an error, when there is no address
|
||||||
# """
|
"""
|
||||||
|
|
||||||
# self.assertRaises(uncloud_pay.models.BillingAddress.DoesNotExist,
|
self.assertRaises(uncloud_pay.models.BillingAddress.DoesNotExist,
|
||||||
# BillingAddress.get_address_for,
|
BillingAddress.get_address_for,
|
||||||
# self.user)
|
self.user)
|
||||||
|
|
||||||
# def test_user_only_inactive_address(self):
|
# 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 NotABillingTC(TestCase):
|
||||||
# #class BillingTestCase(TestCase):
|
# #class BillingTestCase(TestCase):
|
||||||
|
|
|
@ -15,8 +15,8 @@ class MatrixServiceProduct(models.Model):
|
||||||
domain = models.CharField(max_length=255, default='domain.tld')
|
domain = models.CharField(max_length=255, default='domain.tld')
|
||||||
|
|
||||||
# Default recurring price is PER_MONT, see Product class.
|
# Default recurring price is PER_MONT, see Product class.
|
||||||
def recurring_price(self, recurring_period=RecurringPeriod.PER_30D):
|
# def recurring_price(self, recurring_period=RecurringPeriod.PER_30D):
|
||||||
return self.monthly_managment_fee
|
# return self.monthly_managment_fee
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def base_image():
|
def base_image():
|
||||||
|
@ -24,11 +24,11 @@ class MatrixServiceProduct(models.Model):
|
||||||
#e return VMDiskImageProduct.objects.get(uuid="93e564c5-adb3-4741-941f-718f76075f02")
|
#e return VMDiskImageProduct.objects.get(uuid="93e564c5-adb3-4741-941f-718f76075f02")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
# @staticmethod
|
||||||
def allowed_recurring_periods():
|
# def allowed_recurring_periods():
|
||||||
return list(filter(
|
# return list(filter(
|
||||||
lambda pair: pair[0] in [RecurringPeriod.PER_30D],
|
# lambda pair: pair[0] in [RecurringPeriod.PER_30D],
|
||||||
RecurringPeriod.choices))
|
# RecurringPeriod.choices))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def one_time_price(self):
|
def one_time_price(self):
|
||||||
|
|
|
@ -17,8 +17,8 @@ class MatrixServiceProductSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = ['order', 'owner', 'status']
|
read_only_fields = ['order', 'owner', 'status']
|
||||||
|
|
||||||
class OrderMatrixServiceProductSerializer(MatrixServiceProductSerializer):
|
class OrderMatrixServiceProductSerializer(MatrixServiceProductSerializer):
|
||||||
recurring_period = serializers.ChoiceField(
|
# recurring_period = serializers.ChoiceField(
|
||||||
choices=MatrixServiceProduct.allowed_recurring_periods())
|
# choices=MatrixServiceProduct.allowed_recurring_periods())
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(OrderMatrixServiceProductSerializer, self).__init__(*args, **kwargs)
|
super(OrderMatrixServiceProductSerializer, self).__init__(*args, **kwargs)
|
||||||
|
@ -42,8 +42,8 @@ class GenericServiceProductSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = [ 'owner', 'status']
|
read_only_fields = [ 'owner', 'status']
|
||||||
|
|
||||||
class OrderGenericServiceProductSerializer(GenericServiceProductSerializer):
|
class OrderGenericServiceProductSerializer(GenericServiceProductSerializer):
|
||||||
recurring_period = serializers.ChoiceField(
|
# recurring_period = serializers.ChoiceField(
|
||||||
choices=GenericServiceProduct.allowed_recurring_periods())
|
# choices=GenericServiceProduct.allowed_recurring_periods())
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(OrderGenericServiceProductSerializer, self).__init__(*args, **kwargs)
|
super(OrderGenericServiceProductSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|
|
@ -71,12 +71,12 @@ class VMProduct(models.Model):
|
||||||
return "Virtual machine '{}': {} core(s), {}GB memory".format(
|
return "Virtual machine '{}': {} core(s), {}GB memory".format(
|
||||||
self.name, self.cores, self.ram_in_gb)
|
self.name, self.cores, self.ram_in_gb)
|
||||||
|
|
||||||
@staticmethod
|
# @staticmethod
|
||||||
def allowed_recurring_periods():
|
# def allowed_recurring_periods():
|
||||||
return list(filter(
|
# return list(filter(
|
||||||
lambda pair: pair[0] in [RecurringPeriod.PER_365D,
|
# lambda pair: pair[0] in [RecurringPeriod.PER_365D,
|
||||||
RecurringPeriod.PER_30D, RecurringPeriod.PER_HOUR],
|
# RecurringPeriod.PER_30D, RecurringPeriod.PER_HOUR],
|
||||||
RecurringPeriod.choices))
|
# RecurringPeriod.choices))
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -101,8 +101,8 @@ class VMProductSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = ['order', 'owner', 'status']
|
read_only_fields = ['order', 'owner', 'status']
|
||||||
|
|
||||||
class OrderVMProductSerializer(VMProductSerializer):
|
class OrderVMProductSerializer(VMProductSerializer):
|
||||||
recurring_period = serializers.ChoiceField(
|
# recurring_period = serializers.ChoiceField(
|
||||||
choices=VMWithOSProduct.allowed_recurring_periods())
|
# choices=VMWithOSProduct.allowed_recurring_periods())
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(VMProductSerializer, self).__init__(*args, **kwargs)
|
super(VMProductSerializer, self).__init__(*args, **kwargs)
|
||||||
|
@ -133,8 +133,8 @@ class DCLVMProductSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Custom field used at creation (= ordering) only.
|
# Custom field used at creation (= ordering) only.
|
||||||
recurring_period = serializers.ChoiceField(
|
# recurring_period = serializers.ChoiceField(
|
||||||
choices=VMProduct.allowed_recurring_periods())
|
# choices=VMProduct.allowed_recurring_periods())
|
||||||
|
|
||||||
os_disk_uuid = serializers.UUIDField()
|
os_disk_uuid = serializers.UUIDField()
|
||||||
# os_disk_size =
|
# os_disk_size =
|
||||||
|
|
Loading…
Reference in a new issue