Can order a generic product now
This commit is contained in:
parent
c32499199a
commit
8d8c4d660c
4 changed files with 135 additions and 46 deletions
18
uncloud_pay/migrations/0023_auto_20200928_1944.py
Normal file
18
uncloud_pay/migrations/0023_auto_20200928_1944.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.1 on 2020-09-28 19:44
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_pay', '0022_auto_20200928_1932'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='order',
|
||||||
|
old_name='price',
|
||||||
|
new_name='one_time_price',
|
||||||
|
),
|
||||||
|
]
|
24
uncloud_pay/migrations/0024_auto_20200928_1945.py
Normal file
24
uncloud_pay/migrations/0024_auto_20200928_1945.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.1 on 2020-09-28 19:45
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_pay', '0023_auto_20200928_1944'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='currency',
|
||||||
|
field=models.CharField(choices=[('CHF', 'Swiss Franc'), ('EUR', 'Euro'), ('USD', 'US Dollar')], default='CHF', max_length=32),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='recurring_price',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10, validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -563,14 +563,22 @@ class Order(models.Model):
|
||||||
starting_date = models.DateTimeField(default=timezone.now)
|
starting_date = models.DateTimeField(default=timezone.now)
|
||||||
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
|
||||||
recurring_period = models.IntegerField(choices = RecurringPeriod.choices,
|
recurring_period = models.IntegerField(choices = RecurringPeriod.choices,
|
||||||
default = RecurringPeriod.PER_30D)
|
default = RecurringPeriod.PER_30D)
|
||||||
|
|
||||||
price = models.DecimalField(default=0.0,
|
one_time_price = models.DecimalField(default=0.0,
|
||||||
max_digits=AMOUNT_MAX_DIGITS,
|
max_digits=AMOUNT_MAX_DIGITS,
|
||||||
decimal_places=AMOUNT_DECIMALS,
|
decimal_places=AMOUNT_DECIMALS,
|
||||||
validators=[MinValueValidator(0)])
|
validators=[MinValueValidator(0)])
|
||||||
|
|
||||||
|
recurring_price = models.DecimalField(default=0.0,
|
||||||
|
max_digits=AMOUNT_MAX_DIGITS,
|
||||||
|
decimal_places=AMOUNT_DECIMALS,
|
||||||
|
validators=[MinValueValidator(0)])
|
||||||
|
|
||||||
|
currency = models.CharField(max_length=32, choices=Currency.choices, default=Currency.CHF)
|
||||||
|
|
||||||
replaces = models.ForeignKey('self',
|
replaces = models.ForeignKey('self',
|
||||||
related_name='replaced_by',
|
related_name='replaced_by',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
@ -768,6 +776,33 @@ class Order(models.Model):
|
||||||
starting_date=starting_date,
|
starting_date=starting_date,
|
||||||
ending_date=ending_date)
|
ending_date=ending_date)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
one_time_price = 0
|
||||||
|
recurring_price = 0
|
||||||
|
|
||||||
|
# FIXME: support amount independent one time prices
|
||||||
|
# FIXME: support a base price
|
||||||
|
|
||||||
|
if 'features' in self.product.config:
|
||||||
|
for feature in self.product.config['features']:
|
||||||
|
# FIXME: support optional features (?)
|
||||||
|
if not feature in self.config['features']:
|
||||||
|
raise ValidationError(f"Configuration is missing feature {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]
|
||||||
|
|
||||||
|
|
||||||
|
# IMMUTABLE fields -- need to create new order to modify them
|
||||||
|
# However this is not enforced here...
|
||||||
|
if self._state.adding:
|
||||||
|
self.one_time_price = one_time_price
|
||||||
|
self.recurring_price = recurring_price
|
||||||
|
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.description} (order={self.id})"
|
return f"{self.description} (order={self.id})"
|
||||||
|
|
||||||
|
|
|
@ -8,33 +8,29 @@ from uncloud_service.models import GenericServiceProduct
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# class OrderTestCase(TestCase):
|
vm_sample_product_config = {
|
||||||
# """
|
'features': {
|
||||||
# The heart of ordering products
|
'cores':
|
||||||
# """
|
{ 'min': 1,
|
||||||
|
'max': 48,
|
||||||
|
'one_time_price': 0,
|
||||||
|
'recurring_price': 4
|
||||||
|
},
|
||||||
|
'ram_gb':
|
||||||
|
{ 'min': 1,
|
||||||
|
'max': 256,
|
||||||
|
'one_time_price': 0,
|
||||||
|
'recurring_price': 4
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
# def setUp(self):
|
vm_sample_order_config = {
|
||||||
# self.user = get_user_model().objects.create(
|
'features': {
|
||||||
# username='random_user',
|
'cores': 2,
|
||||||
# email='jane.random@domain.tld')
|
'ram_gb': 2
|
||||||
|
}
|
||||||
# self.ba = BillingAddress.objects.create(
|
}
|
||||||
# owner=self.user,
|
|
||||||
# organization = 'Test org',
|
|
||||||
# street="unknown",
|
|
||||||
# city="unknown",
|
|
||||||
# postal_code="somewhere else",
|
|
||||||
# active=True)
|
|
||||||
|
|
||||||
# def test_create_one_time_product(self):
|
|
||||||
# """
|
|
||||||
# One time payment products cannot be updated - can they?
|
|
||||||
# """
|
|
||||||
|
|
||||||
# p = SampleOneTimeProduct.objects.create(owner=self.user)
|
|
||||||
|
|
||||||
# self.assertEqual(p.one_time_price, 5)
|
|
||||||
# self.assertEqual(p.recurring_price, 0)
|
|
||||||
|
|
||||||
|
|
||||||
class ProductTestCase(TestCase):
|
class ProductTestCase(TestCase):
|
||||||
|
@ -60,29 +56,45 @@ class ProductTestCase(TestCase):
|
||||||
Create a sample product
|
Create a sample product
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config = {
|
p = Product.objects.create(name="Testproduct",
|
||||||
'features': {
|
description="Only for testing",
|
||||||
'cores':
|
config=vm_sample_product_config)
|
||||||
{ 'min': 1,
|
|
||||||
'max': 48,
|
|
||||||
'one_time_price': 0,
|
class OrderTestCase(TestCase):
|
||||||
'recurring_price': 4
|
"""
|
||||||
},
|
The heart of ordering products
|
||||||
'ram_gb':
|
"""
|
||||||
{ 'min': 1,
|
|
||||||
'max': 256,
|
def setUp(self):
|
||||||
'one_time_price': 0,
|
self.user = get_user_model().objects.create(
|
||||||
'recurring_price': 3
|
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_order_product(self):
|
||||||
|
"""
|
||||||
|
Order a product, ensure the order has correct price setup
|
||||||
|
"""
|
||||||
|
|
||||||
p = Product.objects.create(name="Testproduct",
|
p = Product.objects.create(name="Testproduct",
|
||||||
description="Only for testing",
|
description="Only for testing",
|
||||||
config=config)
|
config=vm_sample_product_config)
|
||||||
|
|
||||||
# self.assertEqual(p.one_time_price, 5)
|
o = Order.objects.create(owner=self.user,
|
||||||
# self.assertEqual(p.recurring_price, 0)
|
billing_address=self.ba,
|
||||||
|
product=p,
|
||||||
|
config=vm_sample_order_config)
|
||||||
|
|
||||||
|
self.assertEqual(o.one_time_price, 0)
|
||||||
|
self.assertEqual(o.recurring_price, 16)
|
||||||
|
|
||||||
|
|
||||||
# class ProductTestCase(TestCase):
|
# class ProductTestCase(TestCase):
|
||||||
|
|
Loading…
Reference in a new issue