in the middle of restructering
This commit is contained in:
		
					parent
					
						
							
								5d1eaaf0af
							
						
					
				
			
			
				commit
				
					
						a3f3ca8cf9
					
				
			
		
					 2 changed files with 103 additions and 386 deletions
				
			
		| 
						 | 
					@ -31,6 +31,11 @@ logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types
 | 
					# See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types
 | 
				
			||||||
class RecurringPeriod(models.IntegerChoices):
 | 
					class RecurringPeriod(models.IntegerChoices):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    We don't support months are years, because the 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_365D   = 365*24*3600, _('Per 365 days')
 | 
				
			||||||
    PER_30D    = 30*24*3600, _('Per 30 days')
 | 
					    PER_30D    = 30*24*3600, _('Per 30 days')
 | 
				
			||||||
    PER_WEEK   = 7*24*3600, _('Per Week')
 | 
					    PER_WEEK   = 7*24*3600, _('Per Week')
 | 
				
			||||||
| 
						 | 
					@ -40,14 +45,13 @@ class RecurringPeriod(models.IntegerChoices):
 | 
				
			||||||
    PER_SECOND = 1, _('Per Second')
 | 
					    PER_SECOND = 1, _('Per Second')
 | 
				
			||||||
    ONE_TIME   = 0, _('Onetime')
 | 
					    ONE_TIME   = 0, _('Onetime')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class CountryField(models.CharField):
 | 
					class CountryField(models.CharField):
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
        kwargs.setdefault('choices', COUNTRIES)
 | 
					        kwargs.setdefault('choices', COUNTRIES)
 | 
				
			||||||
        kwargs.setdefault('default', 'CH')
 | 
					        kwargs.setdefault('default', 'CH')
 | 
				
			||||||
        kwargs.setdefault('max_length', 2)
 | 
					        kwargs.setdefault('max_length', 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super(CountryField, self).__init__(*args, **kwargs)
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_internal_type(self):
 | 
					    def get_internal_type(self):
 | 
				
			||||||
        return "CharField"
 | 
					        return "CharField"
 | 
				
			||||||
| 
						 | 
					@ -246,7 +250,23 @@ class VATRate(models.Model):
 | 
				
			||||||
            logger.debug("Did not find VAT rate for %s, returning 0" % country_code)
 | 
					            logger.debug("Did not find VAT rate for %s, returning 0" % country_code)
 | 
				
			||||||
            return 0
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BillNico(models.Model):
 | 
					class BillRecord(models.Model):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Entry of a bill, dynamically generated from an order.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bill = models.ForeignKey(Bill, on_delete=models.CASCADE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # How many times the order has been used in this record
 | 
				
			||||||
 | 
					    usage_count = models.IntegerField(default=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The timeframe the bill record is for can (and probably often will) differ
 | 
				
			||||||
 | 
					    # from the bill time
 | 
				
			||||||
 | 
					    creation_date = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
 | 
					    starting_date = models.DateTimeField()
 | 
				
			||||||
 | 
					    ending_date = models.DateTimeField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Bill(models.Model):
 | 
				
			||||||
    """ FIXME:
 | 
					    """ FIXME:
 | 
				
			||||||
    Bill needs to be unique in the triple (owner, year, month)
 | 
					    Bill needs to be unique in the triple (owner, year, month)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
| 
						 | 
					@ -262,12 +282,20 @@ class BillNico(models.Model):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    valid = models.BooleanField(default=True)
 | 
					    valid = models.BooleanField(default=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    # billing address and vat rate is the same for the whole bill
 | 
				
			||||||
    def create_all_bills():
 | 
					    # @property
 | 
				
			||||||
 | 
					    # def vat_rate(self):
 | 
				
			||||||
 | 
					    #     return Decimal(VATRate.get_for_country(self.bill.billing_address.country))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def create_all_bills(cls):
 | 
				
			||||||
        for owner in get_user_model().objects.all():
 | 
					        for owner in get_user_model().objects.all():
 | 
				
			||||||
            # mintime = time of first order
 | 
					            # mintime = time of first order
 | 
				
			||||||
            # maxtime = time of last order
 | 
					            # maxtime = time of last order
 | 
				
			||||||
            # iterate month based through it
 | 
					            # iterate month based through it
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cls.assign_orders_to_bill(owner, year, month)
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def assign_orders_to_bill(self, owner, year, month):
 | 
					    def assign_orders_to_bill(self, owner, year, month):
 | 
				
			||||||
| 
						 | 
					@ -291,359 +319,55 @@ class BillNico(models.Model):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # FIXME: add something to check  whether the order should be billed at all - i.e. a marker that
 | 
					        # FIXME: add something to check  whether the order should be billed at all - i.e. a marker that
 | 
				
			||||||
        # disables searching -> optimization for later
 | 
					        # disables searching -> optimization for later
 | 
				
			||||||
        for order in Order.objects.filter(Q(starting_date__gte=self.starting_date),
 | 
					        # Create the initial bill record
 | 
				
			||||||
                                          Q(starting_date__lte=self.ending_date),
 | 
					        # FIXME: maybe limit not even to starting/ending date, but to empty_bill record -- to be fixed in the future
 | 
				
			||||||
                                          owner=owner):
 | 
					        # for order in Order.objects.filter(Q(starting_date__gte=self.starting_date),
 | 
				
			||||||
 | 
					        #                                   Q(starting_date__lte=self.ending_date),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#            order.bill.add(this_bill)
 | 
					        # FIXME below: only check for active orders
 | 
				
			||||||
            pass
 | 
					
 | 
				
			||||||
 | 
					        # Ensure all orders of that owner have at least one bill record
 | 
				
			||||||
 | 
					        for order in Order.objects.filter(owner=owner,
 | 
				
			||||||
 | 
					                                          bill_records=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bill_record = BillRecord.objects.create(bill=self,
 | 
				
			||||||
 | 
					                                                    usage_count=1,
 | 
				
			||||||
 | 
					                                                    starting_date=order.starting_date,
 | 
				
			||||||
 | 
					                                                    ending_date=order.starting_date + timedelta(seconds=order.recurring_period))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        # For each recurring order get the usage and bill it
 | 
				
			||||||
        Find all recurring orders that did not start in this time frame, but need
 | 
					 | 
				
			||||||
        to be billed in this time frame.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        This is:
 | 
					 | 
				
			||||||
        - order starting time before our starting time
 | 
					 | 
				
			||||||
        - order start time + (x * (the_period)) is inside our time frame, x must be integer
 | 
					 | 
				
			||||||
        test cases:
 | 
					 | 
				
			||||||
        + 365days:
 | 
					 | 
				
			||||||
          time_since_last_billed = self.starting_or_ending_date - order.last_bill_date
 | 
					 | 
				
			||||||
          periods =
 | 
					 | 
				
			||||||
        [ we could in theory add this as a property to the order: next
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        for order in Order.objects.filter(~Q(recurring_period=RecurringPeriod.ONE_TIME),
 | 
					        for order in Order.objects.filter(~Q(recurring_period=RecurringPeriod.ONE_TIME),
 | 
				
			||||||
                                          Q(starting_date__lt=self.starting_date),
 | 
					                                          Q(starting_date__lt=self.starting_date),
 | 
				
			||||||
                                          owner=owner):
 | 
					                                          owner=owner):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if order.recurring_period > 0: # avoid div/0 - these are one time payments
 | 
					            if order.recurring_period > 0: # avoid div/0 - these are one time payments
 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # How much time will have passed by the end of the billing cycle
 | 
				
			||||||
 | 
					                td = self.ending_date - order.starting_date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # How MANY times it will have been used by then
 | 
				
			||||||
 | 
					                used_times = ceil(td / timedelta(seconds=order.recurring_period))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Bill(models.Model):
 | 
					                billed_times = len(order.bills)
 | 
				
			||||||
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
 | 
					 | 
				
			||||||
    owner = models.ForeignKey(get_user_model(),
 | 
					 | 
				
			||||||
            on_delete=models.CASCADE)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    creation_date = models.DateTimeField(auto_now_add=True)
 | 
					                # How many times it WAS billed -- can also be inferred from the bills that link to it!
 | 
				
			||||||
    starting_date = models.DateTimeField()
 | 
					                if used_times > billed_times:
 | 
				
			||||||
    ending_date = models.DateTimeField()
 | 
					                    billing_times = used_times - billed_times
 | 
				
			||||||
    due_date = models.DateField()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    valid = models.BooleanField(default=True)
 | 
					                    # ALSO REGISTER THE TIME PERIOD!
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Trigger product activation if bill paid at creation (from balance).
 | 
					 | 
				
			||||||
    def save(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        super(Bill, self).save(*args, **kwargs)
 | 
					 | 
				
			||||||
        if not self in Bill.get_unpaid_for(self.owner):
 | 
					 | 
				
			||||||
            self.activate_products()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def reference(self):
 | 
					 | 
				
			||||||
        return "{}-{}".format(
 | 
					 | 
				
			||||||
                self.owner.username,
 | 
					 | 
				
			||||||
                self.creation_date.strftime("%Y-%m-%d-%H%M"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def records(self):
 | 
					 | 
				
			||||||
        bill_records = []
 | 
					 | 
				
			||||||
        orders = Order.objects.filter(bill=self)
 | 
					 | 
				
			||||||
        for order in orders:
 | 
					 | 
				
			||||||
            bill_record = BillRecord(self, order)
 | 
					 | 
				
			||||||
            bill_records.append(bill_record)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return bill_records
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def amount(self):
 | 
					 | 
				
			||||||
        return reduce(lambda acc, record: acc + record.amount, self.records, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def vat_amount(self):
 | 
					 | 
				
			||||||
        return reduce(lambda acc, record: acc + record.vat_amount, self.records, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def total(self):
 | 
					 | 
				
			||||||
        return self.amount + self.vat_amount
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def final(self):
 | 
					 | 
				
			||||||
        # A bill is final when its ending date is passed, or when all of its
 | 
					 | 
				
			||||||
        # orders have been terminated.
 | 
					 | 
				
			||||||
        every_order_terminated = True
 | 
					 | 
				
			||||||
        billing_period_is_over = self.ending_date < timezone.now()
 | 
					 | 
				
			||||||
        for order in self.order_set.all():
 | 
					 | 
				
			||||||
            every_order_terminated = every_order_terminated and order.is_terminated
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return billing_period_is_over or every_order_terminated
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def activate_products(self):
 | 
					 | 
				
			||||||
        for order in self.order_set.all():
 | 
					 | 
				
			||||||
            # FIXME: using __something might not be a good idea.
 | 
					 | 
				
			||||||
            for product_class in Product.__subclasses__():
 | 
					 | 
				
			||||||
                for product in product_class.objects.filter(order=order):
 | 
					 | 
				
			||||||
                    if product.status == UncloudStatus.AWAITING_PAYMENT:
 | 
					 | 
				
			||||||
                        product.status = UncloudStatus.PENDING
 | 
					 | 
				
			||||||
                        product.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def billing_address(self):
 | 
					 | 
				
			||||||
        orders = Order.objects.filter(bill=self)
 | 
					 | 
				
			||||||
        # The genrate_for method makes sure all the orders of a bill share the
 | 
					 | 
				
			||||||
        # same billing address. TODO: It would be nice to enforce that somehow...
 | 
					 | 
				
			||||||
        if orders:
 | 
					 | 
				
			||||||
            return orders[0].billing_address
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # TODO: split this huuuge method!
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def generate_for(year, month, user):
 | 
					 | 
				
			||||||
        # /!\ We exclusively work on the specified year and month.
 | 
					 | 
				
			||||||
        generated_bills = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Default values for next bill (if any).
 | 
					 | 
				
			||||||
        starting_date=beginning_of_month(year, month)
 | 
					 | 
				
			||||||
        ending_date=end_of_month(year, month)
 | 
					 | 
				
			||||||
        creation_date=timezone.now()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Select all orders active on the request period (i.e. starting on or after starting_date).
 | 
					 | 
				
			||||||
        orders = Order.objects.filter(
 | 
					 | 
				
			||||||
                Q(ending_date__gte=starting_date) | Q(ending_date__isnull=True),
 | 
					 | 
				
			||||||
                owner=user)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Check if there is already a bill covering the order and period pair:
 | 
					 | 
				
			||||||
        #   * Get latest bill by ending_date: previous_bill.ending_date
 | 
					 | 
				
			||||||
        #   * For monthly bills: if previous_bill.ending_date is before
 | 
					 | 
				
			||||||
        #     (next_bill) ending_date, a new bill has to be generated.
 | 
					 | 
				
			||||||
        #   * For yearly bill: if previous_bill.ending_date is on working
 | 
					 | 
				
			||||||
        #     month, generate new bill.
 | 
					 | 
				
			||||||
        unpaid_orders = { 'monthly_or_less': [], 'yearly': {} }
 | 
					 | 
				
			||||||
        for order in orders:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                previous_bill = order.bill.latest('ending_date')
 | 
					 | 
				
			||||||
            except ObjectDoesNotExist:
 | 
					 | 
				
			||||||
                previous_bill = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # FIXME: control flow is confusing in this block.
 | 
					 | 
				
			||||||
            # if order.recurring_period == RecurringPeriod.PER_YEAR:
 | 
					 | 
				
			||||||
            #     # We ignore anything smaller than a day in here.
 | 
					 | 
				
			||||||
            #     next_yearly_bill_start_on = None
 | 
					 | 
				
			||||||
            #     if previous_bill == None:
 | 
					 | 
				
			||||||
            #         next_yearly_bill_start_on = order.starting_date
 | 
					 | 
				
			||||||
            #     elif previous_bill.ending_date <= ending_date:
 | 
					 | 
				
			||||||
            #         next_yearly_bill_start_on = (previous_bill.ending_date + timedelta(days=1))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            #     # Store for bill generation. One bucket per day of month with a starting bill.
 | 
					 | 
				
			||||||
            #     # bucket is a reference here, no need to reassign.
 | 
					 | 
				
			||||||
            #     if next_yearly_bill_start_on:
 | 
					 | 
				
			||||||
            #         # We want to group orders by date but keep using datetimes.
 | 
					 | 
				
			||||||
            #         next_yearly_bill_start_on = next_yearly_bill_start_on.replace(
 | 
					 | 
				
			||||||
            #                 minute=0, hour=0, second=0, microsecond=0)
 | 
					 | 
				
			||||||
            #         bucket = unpaid_orders['yearly'].get(next_yearly_bill_start_on)
 | 
					 | 
				
			||||||
            #         if bucket == None:
 | 
					 | 
				
			||||||
            #             unpaid_orders['yearly'][next_yearly_bill_start_on] = [order]
 | 
					 | 
				
			||||||
            #         else:
 | 
					 | 
				
			||||||
            #             unpaid_orders['yearly'][next_yearly_bill_start_on] = bucket + [order]
 | 
					 | 
				
			||||||
            # else:
 | 
					 | 
				
			||||||
            #     if previous_bill == None or previous_bill.ending_date < ending_date:
 | 
					 | 
				
			||||||
            #         unpaid_orders['monthly_or_less'].append(order)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Handle working month's billing.
 | 
					 | 
				
			||||||
        if len(unpaid_orders['monthly_or_less']) > 0:
 | 
					 | 
				
			||||||
            # TODO: PREPAID billing is not supported yet.
 | 
					 | 
				
			||||||
            prepaid_due_date = min(creation_date, starting_date) + BILL_PAYMENT_DELAY
 | 
					 | 
				
			||||||
            postpaid_due_date = max(creation_date, ending_date) + BILL_PAYMENT_DELAY
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # There should not be any bill linked to orders with different
 | 
					 | 
				
			||||||
            # billing addresses.
 | 
					 | 
				
			||||||
            per_address_orders = itertools.groupby(
 | 
					 | 
				
			||||||
                    unpaid_orders['monthly_or_less'],
 | 
					 | 
				
			||||||
                    lambda o: o.billing_address)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for addr, bill_orders in per_address_orders:
 | 
					 | 
				
			||||||
                next_monthly_bill = Bill.objects.create(owner=user,
 | 
					 | 
				
			||||||
                    creation_date=creation_date,
 | 
					 | 
				
			||||||
                    starting_date=starting_date, # FIXME: this is a hack!
 | 
					 | 
				
			||||||
                    ending_date=ending_date,
 | 
					 | 
				
			||||||
                    due_date=postpaid_due_date)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # It is not possible to register many-to-many relationship before
 | 
					 | 
				
			||||||
                # the two end-objects are saved in database.
 | 
					 | 
				
			||||||
                for order in bill_orders:
 | 
					 | 
				
			||||||
                    order.bill.add(next_monthly_bill)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                logger.info("Generated monthly bill {} (amount: {}) for user {}."
 | 
					 | 
				
			||||||
                    .format(next_monthly_bill.uuid, next_monthly_bill.total, user))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # Add to output.
 | 
					 | 
				
			||||||
                generated_bills.append(next_monthly_bill)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Handle yearly bills starting on working month.
 | 
					 | 
				
			||||||
        if len(unpaid_orders['yearly']) > 0:
 | 
					 | 
				
			||||||
            # For every starting date, generate new bill.
 | 
					 | 
				
			||||||
            for next_yearly_bill_start_on in unpaid_orders['yearly']:
 | 
					 | 
				
			||||||
                # No postpaid for yearly payments.
 | 
					 | 
				
			||||||
                prepaid_due_date = min(creation_date, next_yearly_bill_start_on) + BILL_PAYMENT_DELAY
 | 
					 | 
				
			||||||
                # Bump by one year, remove one day.
 | 
					 | 
				
			||||||
                ending_date = next_yearly_bill_start_on.replace(
 | 
					 | 
				
			||||||
                        year=next_yearly_bill_start_on.year+1) - timedelta(days=1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # There should not be any bill linked to orders with different
 | 
					 | 
				
			||||||
                # billing addresses.
 | 
					 | 
				
			||||||
                per_address_orders = itertools.groupby(
 | 
					 | 
				
			||||||
                        unpaid_orders['yearly'][next_yearly_bill_start_on],
 | 
					 | 
				
			||||||
                        lambda o: o.billing_address)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for addr, bill_orders in per_address_orders:
 | 
					 | 
				
			||||||
                    next_yearly_bill = Bill.objects.create(owner=user,
 | 
					 | 
				
			||||||
                            creation_date=creation_date,
 | 
					 | 
				
			||||||
                            starting_date=next_yearly_bill_start_on,
 | 
					 | 
				
			||||||
                            ending_date=ending_date,
 | 
					 | 
				
			||||||
                            due_date=prepaid_due_date)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # It is not possible to register many-to-many relationship before
 | 
					 | 
				
			||||||
                    # the two end-objects are saved in database.
 | 
					 | 
				
			||||||
                    for order in bill_orders:
 | 
					 | 
				
			||||||
                        order.bill.add(next_yearly_bill)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    logger.info("Generated yearly bill {} (amount: {}) for user {}."
 | 
					 | 
				
			||||||
                            .format(next_yearly_bill.uuid, next_yearly_bill.total, user))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # Add to output.
 | 
					 | 
				
			||||||
                    generated_bills.append(next_yearly_bill)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Return generated (monthly + yearly) bills.
 | 
					 | 
				
			||||||
        return generated_bills
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def get_unpaid_for(user):
 | 
					 | 
				
			||||||
        balance = get_balance_for_user(user)
 | 
					 | 
				
			||||||
        unpaid_bills = []
 | 
					 | 
				
			||||||
        # No unpaid bill if balance is positive.
 | 
					 | 
				
			||||||
        if balance >= 0:
 | 
					 | 
				
			||||||
            return unpaid_bills
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            bills = Bill.objects.filter(
 | 
					 | 
				
			||||||
                    owner=user,
 | 
					 | 
				
			||||||
                    ).order_by('-creation_date')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Amount to be paid by the customer.
 | 
					 | 
				
			||||||
            unpaid_balance = abs(balance)
 | 
					 | 
				
			||||||
            for bill in bills:
 | 
					 | 
				
			||||||
                if unpaid_balance <= 0:
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                unpaid_balance -= bill.total
 | 
					 | 
				
			||||||
                unpaid_bills.append(bill)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return unpaid_bills
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def get_overdue_for(user):
 | 
					 | 
				
			||||||
        unpaid_bills = Bill.get_unpaid_for(user)
 | 
					 | 
				
			||||||
        return list(filter(lambda bill: bill.due_date > timezone.now(), unpaid_bills))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BillRecord():
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Entry of a bill, dynamically generated from an order.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, bill, order):
 | 
					 | 
				
			||||||
        self.bill = bill
 | 
					 | 
				
			||||||
        self.order = order
 | 
					 | 
				
			||||||
        self.recurring_price = order.recurring_price
 | 
					 | 
				
			||||||
        self.recurring_period = order.recurring_period
 | 
					 | 
				
			||||||
        self.description = order.description
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self.order.starting_date >= self.bill.starting_date:
 | 
					 | 
				
			||||||
            self.one_time_price = order.one_time_price
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.one_time_price = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Set decimal context for amount computations.
 | 
					 | 
				
			||||||
        # XXX: understand why we need +1 here.
 | 
					 | 
				
			||||||
        decimal.getcontext().prec = AMOUNT_DECIMALS + 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def recurring_count(self):
 | 
					 | 
				
			||||||
        # Compute billing delta.
 | 
					 | 
				
			||||||
        billed_until = self.bill.ending_date
 | 
					 | 
				
			||||||
        if self.order.ending_date != None and self.order.ending_date <= self.bill.ending_date:
 | 
					 | 
				
			||||||
            billed_until = self.order.ending_date
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        billed_from = self.bill.starting_date
 | 
					 | 
				
			||||||
        if self.order.starting_date > self.bill.starting_date:
 | 
					 | 
				
			||||||
            billed_from = self.order.starting_date
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if billed_from > billed_until:
 | 
					 | 
				
			||||||
            # TODO: think about and check edge cases. This should not be
 | 
					 | 
				
			||||||
            # possible.
 | 
					 | 
				
			||||||
            raise Exception('Impossible billing delta!')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        billed_delta = billed_until - billed_from
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # TODO: refactor this thing?
 | 
					 | 
				
			||||||
        # TODO: weekly
 | 
					 | 
				
			||||||
        # if self.recurring_period == RecurringPeriod.PER_YEAR:
 | 
					 | 
				
			||||||
        #     # XXX: Should always be one => we do not bill for more than one year.
 | 
					 | 
				
			||||||
        #     # TODO: check billed_delta is ~365 days.
 | 
					 | 
				
			||||||
        #     return 1
 | 
					 | 
				
			||||||
        # elif self.recurring_period == RecurringPeriod.PER_MONTH:
 | 
					 | 
				
			||||||
        #     days = ceil(billed_delta / timedelta(days=1))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #     # Monthly bills always cover one single month.
 | 
					 | 
				
			||||||
        #     if (self.bill.starting_date.year != self.bill.starting_date.year or
 | 
					 | 
				
			||||||
        #         self.bill.starting_date.month != self.bill.ending_date.month):
 | 
					 | 
				
			||||||
        #             raise Exception('Bill {} covers more than one month. Cannot bill PER_MONTH.'.
 | 
					 | 
				
			||||||
        #                 format(self.bill.uuid))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #     # XXX: minumal length of monthly order is to be enforced somewhere else.
 | 
					 | 
				
			||||||
        #     (_, days_in_month) = monthrange(
 | 
					 | 
				
			||||||
        #             self.bill.starting_date.year,
 | 
					 | 
				
			||||||
        #             self.bill.starting_date.month)
 | 
					 | 
				
			||||||
        #     return round(days / days_in_month, AMOUNT_DECIMALS)
 | 
					 | 
				
			||||||
        if self.recurring_period == RecurringPeriod.PER_WEEK:
 | 
					 | 
				
			||||||
            weeks = ceil(billed_delta / timedelta(week=1))
 | 
					 | 
				
			||||||
            return weeks
 | 
					 | 
				
			||||||
        elif self.recurring_period == RecurringPeriod.PER_DAY:
 | 
					 | 
				
			||||||
            days = ceil(billed_delta / timedelta(days=1))
 | 
					 | 
				
			||||||
            return days
 | 
					 | 
				
			||||||
        elif self.recurring_period == RecurringPeriod.PER_HOUR:
 | 
					 | 
				
			||||||
            hours = ceil(billed_delta / timedelta(hours=1))
 | 
					 | 
				
			||||||
            return hours
 | 
					 | 
				
			||||||
        elif self.recurring_period == RecurringPeriod.PER_SECOND:
 | 
					 | 
				
			||||||
            seconds = ceil(billed_delta / timedelta(seconds=1))
 | 
					 | 
				
			||||||
            return seconds
 | 
					 | 
				
			||||||
        elif self.recurring_period == RecurringPeriod.ONE_TIME:
 | 
					 | 
				
			||||||
            return 0
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise Exception('Unsupported recurring period: {}.'.
 | 
					 | 
				
			||||||
                    format(self.order.recurring_period))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def vat_rate(self):
 | 
					 | 
				
			||||||
        return Decimal(VATRate.get_for_country(self.bill.billing_address.country))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def vat_amount(self):
 | 
					 | 
				
			||||||
        return self.amount * self.vat_rate
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def amount(self):
 | 
					 | 
				
			||||||
        return Decimal(float(self.recurring_price) * self.recurring_count) + self.one_time_price
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def total(self):
 | 
					 | 
				
			||||||
        return self.amount + self.vat_amount
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
###
 | 
					###
 | 
				
			||||||
# Orders.
 | 
					# Orders.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Order are assumed IMMUTABLE and used as SOURCE OF TRUST for generating
 | 
					 | 
				
			||||||
# bills. Do **NOT** mutate then!
 | 
					 | 
				
			||||||
class Order(models.Model):
 | 
					class Order(models.Model):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Order are assumed IMMUTABLE and used as SOURCE OF TRUST for generating
 | 
				
			||||||
 | 
					    bills. Do **NOT** mutate then!
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
 | 
					    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
 | 
				
			||||||
    owner = models.ForeignKey(get_user_model(),
 | 
					    owner = models.ForeignKey(get_user_model(),
 | 
				
			||||||
                              on_delete=models.CASCADE,
 | 
					                              on_delete=models.CASCADE,
 | 
				
			||||||
| 
						 | 
					@ -658,11 +382,22 @@ class Order(models.Model):
 | 
				
			||||||
    ending_date = models.DateTimeField(blank=True,
 | 
					    ending_date = models.DateTimeField(blank=True,
 | 
				
			||||||
                                       null=True)
 | 
					                                       null=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bill = models.ManyToManyField(Bill,
 | 
					    bill_records = models.ManyToManyField(BillRecord,
 | 
				
			||||||
                                  editable=False,
 | 
					                                          editable=False,
 | 
				
			||||||
                                  blank=True)
 | 
					                                          blank=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    recurring_period = models.IntegerField(choices = RecurringPeriod.choices, default = RecurringPeriod.PER_30D)
 | 
					    @property
 | 
				
			||||||
 | 
					    def count_billed(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        How many times this order was billed so far.
 | 
				
			||||||
 | 
					        This logic is mainly thought to be for recurring bills, but also works for one time bills
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sum([ br.usage_count for br in self.bill_records.all() ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    recurring_period = models.IntegerField(choices = RecurringPeriod.choices,
 | 
				
			||||||
 | 
					                                           default = RecurringPeriod.PER_30D)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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,
 | 
				
			||||||
| 
						 | 
					@ -696,7 +431,6 @@ class Order(models.Model):
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_recurring(self):
 | 
					    def is_recurring(self):
 | 
				
			||||||
        return not self.recurring_period == RecurringPeriod.ONE_TIME
 | 
					        return not self.recurring_period == RecurringPeriod.ONE_TIME
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_terminated(self):
 | 
					    def is_terminated(self):
 | 
				
			||||||
        return self.ending_date != None and self.ending_date < timezone.now()
 | 
					        return self.ending_date != None and self.ending_date < timezone.now()
 | 
				
			||||||
| 
						 | 
					@ -709,9 +443,6 @@ class Order(models.Model):
 | 
				
			||||||
            self.ending_date = timezone.now()
 | 
					            self.ending_date = timezone.now()
 | 
				
			||||||
            self.save()
 | 
					            self.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_to_be_charged_in(year, month):
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Trigger initial bill generation at order creation.
 | 
					    # Trigger initial bill generation at order creation.
 | 
				
			||||||
    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:
 | 
				
			||||||
| 
						 | 
					@ -749,35 +480,6 @@ class Order(models.Model):
 | 
				
			||||||
            self.one_time_price,
 | 
					            self.one_time_price,
 | 
				
			||||||
            self.recurring_price)
 | 
					            self.recurring_price)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrderTimothee(models.Model):
 | 
					 | 
				
			||||||
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
 | 
					 | 
				
			||||||
    owner = models.ForeignKey(get_user_model(),
 | 
					 | 
				
			||||||
                              on_delete=models.CASCADE,
 | 
					 | 
				
			||||||
                              editable=False)
 | 
					 | 
				
			||||||
    billing_address = models.ForeignKey(BillingAddress, on_delete=models.CASCADE)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # TODO: enforce ending_date - starting_date to be larger than recurring_period.
 | 
					 | 
				
			||||||
    creation_date = models.DateTimeField(auto_now_add=True)
 | 
					 | 
				
			||||||
    starting_date = models.DateTimeField(default=timezone.now)
 | 
					 | 
				
			||||||
    ending_date = models.DateTimeField(blank=True,
 | 
					 | 
				
			||||||
                                       null=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bill = models.ManyToManyField(Bill,
 | 
					 | 
				
			||||||
                                  editable=False,
 | 
					 | 
				
			||||||
                                  blank=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    recurring_period = models.IntegerField(choices = RecurringPeriod.choices,
 | 
					 | 
				
			||||||
                                           default = RecurringPeriod.PER_30D)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Trigger initial bill generation at order creation.
 | 
					 | 
				
			||||||
    def save(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        if self.ending_date and self.ending_date < self.starting_date:
 | 
					 | 
				
			||||||
            raise ValidationError("End date cannot be before starting date")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        super().save(*args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Bill.generate_for(self.starting_date.year, self.starting_date.month, self.owner)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def records(self):
 | 
					    def records(self):
 | 
				
			||||||
        return OrderRecord.objects.filter(order=self)
 | 
					        return OrderRecord.objects.filter(order=self)
 | 
				
			||||||
| 
						 | 
					@ -988,3 +690,16 @@ class Product(UncloudModel):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # FIXME: use the right type of exception here!
 | 
					            # FIXME: use the right type of exception here!
 | 
				
			||||||
            raise Exception("Did not implement the discounter for this case")
 | 
					            raise Exception("Did not implement the discounter for this case")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Interesting snippets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # # Trigger initial bill generation at order creation.
 | 
				
			||||||
 | 
					    # def save(self, *args, **kwargs):
 | 
				
			||||||
 | 
					    #     if self.ending_date and self.ending_date < self.starting_date:
 | 
				
			||||||
 | 
					    #         raise ValidationError("End date cannot be before starting date")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #     super().save(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #     Bill.generate_for(self.starting_date.year, self.starting_date.month, self.owner)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,8 @@ from datetime import datetime, date, timedelta
 | 
				
			||||||
from .models import *
 | 
					from .models import *
 | 
				
			||||||
from uncloud_service.models import GenericServiceProduct
 | 
					from uncloud_service.models import GenericServiceProduct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BillingTestCase(TestCase):
 | 
					class NotABillingTC(TestCase):
 | 
				
			||||||
 | 
					#class BillingTestCase(TestCase):
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.user = get_user_model().objects.create(
 | 
					        self.user = get_user_model().objects.create(
 | 
				
			||||||
                username='jdoe',
 | 
					                username='jdoe',
 | 
				
			||||||
| 
						 | 
					@ -22,15 +23,16 @@ class BillingTestCase(TestCase):
 | 
				
			||||||
        description = "Test Product 1"
 | 
					        description = "Test Product 1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Three months: full, full, partial.
 | 
					        # Three months: full, full, partial.
 | 
				
			||||||
        starting_date = datetime.fromisoformat('2020-03-01')
 | 
					#        starting_date = datetime.fromisoformat('2020-03-01')
 | 
				
			||||||
        ending_date   = datetime.fromisoformat('2020-05-08')
 | 
					        starting_date = datetime(2020,3,1)
 | 
				
			||||||
 | 
					        ending_date   = datetime(2020,5,8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create order to be billed.
 | 
					        # Create order to be billed.
 | 
				
			||||||
        order = Order.objects.create(
 | 
					        order = Order.objects.create(
 | 
				
			||||||
                owner=self.user,
 | 
					                owner=self.user,
 | 
				
			||||||
                starting_date=starting_date,
 | 
					                starting_date=starting_date,
 | 
				
			||||||
                ending_date=ending_date,
 | 
					                ending_date=ending_date,
 | 
				
			||||||
                recurring_period=RecurringPeriod.PER_MONTH,
 | 
					                recurring_period=RecurringPeriod.PER_30D,
 | 
				
			||||||
                recurring_price=recurring_price,
 | 
					                recurring_price=recurring_price,
 | 
				
			||||||
                one_time_price=one_time_price,
 | 
					                one_time_price=one_time_price,
 | 
				
			||||||
                description=description,
 | 
					                description=description,
 | 
				
			||||||
| 
						 | 
					@ -67,7 +69,7 @@ class BillingTestCase(TestCase):
 | 
				
			||||||
        order = Order.objects.create(
 | 
					        order = Order.objects.create(
 | 
				
			||||||
                owner=self.user,
 | 
					                owner=self.user,
 | 
				
			||||||
                starting_date=starting_date,
 | 
					                starting_date=starting_date,
 | 
				
			||||||
                recurring_period=RecurringPeriod.PER_YEAR,
 | 
					                recurring_period=RecurringPeriod.PER_365D,
 | 
				
			||||||
                recurring_price=recurring_price,
 | 
					                recurring_price=recurring_price,
 | 
				
			||||||
                one_time_price=one_time_price,
 | 
					                one_time_price=one_time_price,
 | 
				
			||||||
                description=description,
 | 
					                description=description,
 | 
				
			||||||
| 
						 | 
					@ -150,7 +152,7 @@ class ProductActivationTestCase(TestCase):
 | 
				
			||||||
        order = Order.objects.create(
 | 
					        order = Order.objects.create(
 | 
				
			||||||
                owner=self.user,
 | 
					                owner=self.user,
 | 
				
			||||||
                starting_date=starting_date,
 | 
					                starting_date=starting_date,
 | 
				
			||||||
                recurring_period=RecurringPeriod.PER_MONTH,
 | 
					                recurring_period=RecurringPeriod.PER_30D,
 | 
				
			||||||
                recurring_price=recurring_price,
 | 
					                recurring_price=recurring_price,
 | 
				
			||||||
                one_time_price=one_time_price,
 | 
					                one_time_price=one_time_price,
 | 
				
			||||||
                description=description,
 | 
					                description=description,
 | 
				
			||||||
| 
						 | 
					@ -205,12 +207,12 @@ class BillingAddressTestCase(TestCase):
 | 
				
			||||||
        order_01 = Order.objects.create(
 | 
					        order_01 = Order.objects.create(
 | 
				
			||||||
                owner=self.user,
 | 
					                owner=self.user,
 | 
				
			||||||
                starting_date=starting_date,
 | 
					                starting_date=starting_date,
 | 
				
			||||||
                recurring_period=RecurringPeriod.PER_MONTH,
 | 
					                recurring_period=RecurringPeriod.PER_30D,
 | 
				
			||||||
                billing_address=self.billing_address_01)
 | 
					                billing_address=self.billing_address_01)
 | 
				
			||||||
        order_02 = Order.objects.create(
 | 
					        order_02 = Order.objects.create(
 | 
				
			||||||
                owner=self.user,
 | 
					                owner=self.user,
 | 
				
			||||||
                starting_date=starting_date,
 | 
					                starting_date=starting_date,
 | 
				
			||||||
                recurring_period=RecurringPeriod.PER_MONTH,
 | 
					                recurring_period=RecurringPeriod.PER_30D,
 | 
				
			||||||
                billing_address=self.billing_address_01)
 | 
					                billing_address=self.billing_address_01)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # We need a single bill since we work with a single address.
 | 
					        # We need a single bill since we work with a single address.
 | 
				
			||||||
| 
						 | 
					@ -225,12 +227,12 @@ class BillingAddressTestCase(TestCase):
 | 
				
			||||||
        order_01 = Order.objects.create(
 | 
					        order_01 = Order.objects.create(
 | 
				
			||||||
                owner=self.user,
 | 
					                owner=self.user,
 | 
				
			||||||
                starting_date=starting_date,
 | 
					                starting_date=starting_date,
 | 
				
			||||||
                recurring_period=RecurringPeriod.PER_MONTH,
 | 
					                recurring_period=RecurringPeriod.PER_30D,
 | 
				
			||||||
                billing_address=self.billing_address_01)
 | 
					                billing_address=self.billing_address_01)
 | 
				
			||||||
        order_02 = Order.objects.create(
 | 
					        order_02 = Order.objects.create(
 | 
				
			||||||
                owner=self.user,
 | 
					                owner=self.user,
 | 
				
			||||||
                starting_date=starting_date,
 | 
					                starting_date=starting_date,
 | 
				
			||||||
                recurring_period=RecurringPeriod.PER_MONTH,
 | 
					                recurring_period=RecurringPeriod.PER_30D,
 | 
				
			||||||
                billing_address=self.billing_address_02)
 | 
					                billing_address=self.billing_address_02)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # We need different bills since we work with different addresses.
 | 
					        # We need different bills since we work with different addresses.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue