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,83 +518,95 @@ 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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#     def test_multi_addr_multi_bill(self):
 | 
					        self.assertEqual(len(self.bill_dates), bill_count)
 | 
				
			||||||
#         """
 | 
					 | 
				
			||||||
#         Ensure multiple bills are created if orders exist with different billing addresses
 | 
					 | 
				
			||||||
#         """
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         username="lotsofplaces"
 | 
					    # def test_multi_addr_multi_bill(self):
 | 
				
			||||||
#         multi_addr_user = get_user_model().objects.create(
 | 
					    #     """
 | 
				
			||||||
#             username=username,
 | 
					    #     Ensure multiple bills are created if orders exist with different billing addresses
 | 
				
			||||||
#             email=f"{username}@example.org")
 | 
					    #     """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         user_addr1 = BillingAddress.objects.create(
 | 
					    #     username="lotsofplaces"
 | 
				
			||||||
#             owner=multi_addr_user,
 | 
					    #     multi_addr_user = get_user_model().objects.create(
 | 
				
			||||||
#             organization = 'Test org',
 | 
					    #         username=username,
 | 
				
			||||||
#             street="unknown",
 | 
					    #         email=f"{username}@example.org")
 | 
				
			||||||
#             city="unknown",
 | 
					 | 
				
			||||||
#             postal_code="unknown",
 | 
					 | 
				
			||||||
#             active=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         order1 = Order.objects.create(
 | 
					    #     user_addr1 = BillingAddress.objects.create(
 | 
				
			||||||
#                 owner=multi_addr_user,
 | 
					    #         owner=multi_addr_user,
 | 
				
			||||||
#                 starting_date=self.order_meta[1]['starting_date'],
 | 
					    #         organization = 'Test org',
 | 
				
			||||||
#                 ending_date=self.order_meta[1]['ending_date'],
 | 
					    #         street="unknown",
 | 
				
			||||||
#                 recurring_period=RecurringPeriod.ONE_TIME,
 | 
					    #         city="unknown",
 | 
				
			||||||
#                 price=self.order_meta[1]['price'],
 | 
					    #         postal_code="unknown",
 | 
				
			||||||
#                 description=self.order_meta[1]['description'],
 | 
					    #         active=True)
 | 
				
			||||||
#                 billing_address=BillingAddress.get_address_for(self.user))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         # Make this address inactive
 | 
					    #     order1 = Order.objects.create(
 | 
				
			||||||
#         user_addr1.active = False
 | 
					    #         owner=multi_addr_user,
 | 
				
			||||||
#         user_addr1.save()
 | 
					    #         recurring_period=RecurringPeriod.ONE_TIME,
 | 
				
			||||||
 | 
					    #         product=self.chocolate,
 | 
				
			||||||
 | 
					    #         billing_address=BillingAddress.get_address_for(self.user),
 | 
				
			||||||
 | 
					    #         starting_date=self.order_meta[1]['starting_date'],
 | 
				
			||||||
 | 
					    #         ending_date=self.order_meta[1]['ending_date'],
 | 
				
			||||||
 | 
					    #         config=chocolate_order_config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         user_addr2 = BillingAddress.objects.create(
 | 
					    #     order1 = Order.objects.create(
 | 
				
			||||||
#             owner=multi_addr_user,
 | 
					    #             owner=multi_addr_user,
 | 
				
			||||||
#             organization = 'Test2 org',
 | 
					    #             starting_date=self.order_meta[1]['starting_date'],
 | 
				
			||||||
#             street="unknown2",
 | 
					    #             ending_date=self.order_meta[1]['ending_date'],
 | 
				
			||||||
#             city="unknown2",
 | 
					    #             recurring_period=RecurringPeriod.ONE_TIME,
 | 
				
			||||||
#             postal_code="unknown2",
 | 
					    #             price=self.order_meta[1]['price'],
 | 
				
			||||||
#             active=True)
 | 
					    #             description=self.order_meta[1]['description'],
 | 
				
			||||||
 | 
					    #             billing_address=BillingAddress.get_address_for(self.user))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         order2 = Order.objects.create(
 | 
					    #     # Make this address inactive
 | 
				
			||||||
#                 owner=multi_addr_user,
 | 
					    #     user_addr1.active = False
 | 
				
			||||||
#                 starting_date=self.order_meta[1]['starting_date'],
 | 
					    #     user_addr1.save()
 | 
				
			||||||
#                 ending_date=self.order_meta[1]['ending_date'],
 | 
					
 | 
				
			||||||
#                 recurring_period=RecurringPeriod.ONE_TIME,
 | 
					    #     user_addr2 = BillingAddress.objects.create(
 | 
				
			||||||
#                 price=self.order_meta[1]['price'],
 | 
					    #         owner=multi_addr_user,
 | 
				
			||||||
#                 description=self.order_meta[1]['description'],
 | 
					    #         organization = 'Test2 org',
 | 
				
			||||||
#                 billing_address=BillingAddress.get_address_for(self.user))
 | 
					    #         street="unknown2",
 | 
				
			||||||
 | 
					    #         city="unknown2",
 | 
				
			||||||
 | 
					    #         postal_code="unknown2",
 | 
				
			||||||
 | 
					    #         active=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #     order2 = Order.objects.create(
 | 
				
			||||||
 | 
					    #             owner=multi_addr_user,
 | 
				
			||||||
 | 
					    #             starting_date=self.order_meta[1]['starting_date'],
 | 
				
			||||||
 | 
					    #             ending_date=self.order_meta[1]['ending_date'],
 | 
				
			||||||
 | 
					    #             recurring_period=RecurringPeriod.ONE_TIME,
 | 
				
			||||||
 | 
					    #             price=self.order_meta[1]['price'],
 | 
				
			||||||
 | 
					    #             description=self.order_meta[1]['description'],
 | 
				
			||||||
 | 
					    #             billing_address=BillingAddress.get_address_for(self.user))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         bills = Bill.create_next_bills_for_user(multi_addr_user)
 | 
					    #     bills = Bill.create_next_bills_for_user(multi_addr_user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#         self.assertEqual(len(bills), 2)
 | 
					    #     self.assertEqual(len(bills), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#     # TO BE IMPLEMENTED -- once orders can be marked as "done" / "inactive" / "not for billing"
 | 
					#     # TO BE IMPLEMENTED -- once orders can be marked as "done" / "inactive" / "not for billing"
 | 
				
			||||||
| 
						 | 
					@ -392,21 +709,21 @@ class BillTestCase(TestCase):
 | 
				
			||||||
    #     # FIXME: where is the assert?
 | 
					    #     # 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue