begin to change to day based differences

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
This commit is contained in:
Nico Schottelius 2020-05-23 23:32:45 +02:00
parent 18b862c2e1
commit 15535433e8
4 changed files with 67 additions and 70 deletions

View file

@ -173,7 +173,7 @@ class VPNNetwork(Product):
wireguard_public_key = models.CharField(max_length=48) wireguard_public_key = models.CharField(max_length=48)
default_recurring_period = RecurringPeriod.PER_YEAR default_recurring_period = RecurringPeriod.PER_365D
@property @property
def recurring_price(self): def recurring_price(self):

View file

@ -30,19 +30,15 @@ BILL_PAYMENT_DELAY=timedelta(days=10)
logger = logging.getLogger(__name__) 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.TextChoices): class RecurringPeriod(models.IntegerChoices):
PER_YEAR = 'YEAR', _('Per Year') # this is broken - we can make it 365 days - REMOVE ME PER_365D = 365*24*3600, _('Per 365 days')
PER_MONTH = 'MONTH', _('Per Month') # this is broken - varying times - REMOVE ME PER_30D = 30*24*3600, _('Per 30 days')
PER_WEEK = 7*24*3600, _('Per Week')
ONE_TIME = 'ONCE', _('Onetime') # this is ok PER_DAY = 24*3600, _('Per Day')
PER_365D = '365D', _('Per 365 days') # this is ok PER_HOUR = 3600, _('Per Hour')
PER_30D = '30D', _('Per 30 days') # this is ok PER_MINUTE = 60, _('Per Minute')
PER_WEEK = 'WEEK', _('Per Week') # this is ok PER_SECOND = 1, _('Per Second')
PER_DAY = 'DAY', _('Per Day') # this is ok ONE_TIME = 0, _('Onetime')
PER_HOUR = 'HOUR', _('Per Hour') # this is ok
PER_MINUTE = 'MINUTE', _('Per Minute') # this is ok
PER_SECOND = 'SECOND', _('Per Second') # this is ok
class CountryField(models.CharField): class CountryField(models.CharField):
@ -292,6 +288,9 @@ class BillNico(models.Model):
Can we do this even for recurring / all of them Can we do this even for recurring / all of them
""" """
# FIXME: add something to check whether the order should be billed at all - i.e. a marker that
# disables searching -> optimization for later
for order in Order.objects.filter(Q(starting_date__gte=self.starting_date), for order in Order.objects.filter(Q(starting_date__gte=self.starting_date),
Q(starting_date__lte=self.ending_date), Q(starting_date__lte=self.ending_date),
owner=owner): owner=owner):
@ -427,28 +426,28 @@ class Bill(models.Model):
previous_bill = None previous_bill = None
# FIXME: control flow is confusing in this block. # FIXME: control flow is confusing in this block.
if order.recurring_period == RecurringPeriod.PER_YEAR: # if order.recurring_period == RecurringPeriod.PER_YEAR:
# We ignore anything smaller than a day in here. # # We ignore anything smaller than a day in here.
next_yearly_bill_start_on = None # next_yearly_bill_start_on = None
if previous_bill == None: # if previous_bill == None:
next_yearly_bill_start_on = order.starting_date # next_yearly_bill_start_on = order.starting_date
elif previous_bill.ending_date <= ending_date: # elif previous_bill.ending_date <= ending_date:
next_yearly_bill_start_on = (previous_bill.ending_date + timedelta(days=1)) # 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. # # Store for bill generation. One bucket per day of month with a starting bill.
# bucket is a reference here, no need to reassign. # # bucket is a reference here, no need to reassign.
if next_yearly_bill_start_on: # if next_yearly_bill_start_on:
# We want to group orders by date but keep using datetimes. # # We want to group orders by date but keep using datetimes.
next_yearly_bill_start_on = next_yearly_bill_start_on.replace( # next_yearly_bill_start_on = next_yearly_bill_start_on.replace(
minute=0, hour=0, second=0, microsecond=0) # minute=0, hour=0, second=0, microsecond=0)
bucket = unpaid_orders['yearly'].get(next_yearly_bill_start_on) # bucket = unpaid_orders['yearly'].get(next_yearly_bill_start_on)
if bucket == None: # if bucket == None:
unpaid_orders['yearly'][next_yearly_bill_start_on] = [order] # unpaid_orders['yearly'][next_yearly_bill_start_on] = [order]
else: # else:
unpaid_orders['yearly'][next_yearly_bill_start_on] = bucket + [order] # unpaid_orders['yearly'][next_yearly_bill_start_on] = bucket + [order]
else: # else:
if previous_bill == None or previous_bill.ending_date < ending_date: # if previous_bill == None or previous_bill.ending_date < ending_date:
unpaid_orders['monthly_or_less'].append(order) # unpaid_orders['monthly_or_less'].append(order)
# Handle working month's billing. # Handle working month's billing.
if len(unpaid_orders['monthly_or_less']) > 0: if len(unpaid_orders['monthly_or_less']) > 0:
@ -586,25 +585,25 @@ class BillRecord():
# TODO: refactor this thing? # TODO: refactor this thing?
# TODO: weekly # TODO: weekly
if self.recurring_period == RecurringPeriod.PER_YEAR: # if self.recurring_period == RecurringPeriod.PER_YEAR:
# XXX: Should always be one => we do not bill for more than one year. # # XXX: Should always be one => we do not bill for more than one year.
# TODO: check billed_delta is ~365 days. # # TODO: check billed_delta is ~365 days.
return 1 # return 1
elif self.recurring_period == RecurringPeriod.PER_MONTH: # elif self.recurring_period == RecurringPeriod.PER_MONTH:
days = ceil(billed_delta / timedelta(days=1)) # days = ceil(billed_delta / timedelta(days=1))
# Monthly bills always cover one single month. # # Monthly bills always cover one single month.
if (self.bill.starting_date.year != self.bill.starting_date.year or # if (self.bill.starting_date.year != self.bill.starting_date.year or
self.bill.starting_date.month != self.bill.ending_date.month): # self.bill.starting_date.month != self.bill.ending_date.month):
raise Exception('Bill {} covers more than one month. Cannot bill PER_MONTH.'. # raise Exception('Bill {} covers more than one month. Cannot bill PER_MONTH.'.
format(self.bill.uuid)) # format(self.bill.uuid))
# XXX: minumal length of monthly order is to be enforced somewhere else. # # XXX: minumal length of monthly order is to be enforced somewhere else.
(_, days_in_month) = monthrange( # (_, days_in_month) = monthrange(
self.bill.starting_date.year, # self.bill.starting_date.year,
self.bill.starting_date.month) # self.bill.starting_date.month)
return round(days / days_in_month, AMOUNT_DECIMALS) # return round(days / days_in_month, AMOUNT_DECIMALS)
elif self.recurring_period == RecurringPeriod.PER_WEEK: if self.recurring_period == RecurringPeriod.PER_WEEK:
weeks = ceil(billed_delta / timedelta(week=1)) weeks = ceil(billed_delta / timedelta(week=1))
return weeks return weeks
elif self.recurring_period == RecurringPeriod.PER_DAY: elif self.recurring_period == RecurringPeriod.PER_DAY:
@ -663,8 +662,7 @@ class Order(models.Model):
blank=True) blank=True)
recurring_period = models.CharField(max_length=32, recurring_period = models.CharField(max_length=32,
choices = RecurringPeriod.choices, choices = RecurringPeriod.choices, default = RecurringPeriod.PER_30D)
default = RecurringPeriod.PER_MONTH)
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,
@ -712,8 +710,7 @@ class Order(models.Model):
self.save() self.save()
def is_to_be_charged_in(year, month): def is_to_be_charged_in(year, month):
if self.recurring_period == RecurringPeriod.PER_YEAR: pass
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):
@ -771,7 +768,7 @@ class OrderTimothee(models.Model):
recurring_period = models.CharField(max_length=32, recurring_period = models.CharField(max_length=32,
choices = RecurringPeriod.choices, choices = RecurringPeriod.choices,
default = RecurringPeriod.PER_MONTH) default = RecurringPeriod.PER_30D)
# Trigger initial bill generation at order creation. # Trigger initial bill generation at order creation.
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -873,7 +870,7 @@ class Product(UncloudModel):
null=True) null=True)
# Default period for all products # Default period for all products
default_recurring_period = RecurringPeriod.PER_MONTH default_recurring_period = RecurringPeriod.PER_30D
# Used to save records. # Used to save records.
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -966,26 +963,26 @@ class Product(UncloudModel):
""" """
if self.default_recurring_period == RecurringPeriod.PER_YEAR: if self.default_recurring_period == RecurringPeriod.PER_365D:
if requested_period == RecurringPeriod.PER_YEAR: if requested_period == RecurringPeriod.PER_365D:
return self.recurring_price return self.recurring_price
if requested_period == RecurringPeriod.PER_MONTH: if requested_period == RecurringPeriod.PER_30D:
return self.recurring_price/11. return self.recurring_price/11.
if requested_period == RecurringPeriod.PER_DAY: if requested_period == RecurringPeriod.PER_DAY:
return self.recurring_price/11./28. return self.recurring_price/11./28.
elif self.default_recurring_period == RecurringPeriod.PER_MONTH: elif self.default_recurring_period == RecurringPeriod.PER_30D:
if requested_period == RecurringPeriod.PER_YEAR: if requested_period == RecurringPeriod.PER_365D:
return self.recurring_price*11 return self.recurring_price*11
if requested_period == RecurringPeriod.PER_MONTH: if requested_period == RecurringPeriod.PER_30D:
return self.recurring_price return self.recurring_price
if requested_period == RecurringPeriod.PER_DAY: if requested_period == RecurringPeriod.PER_DAY:
return self.recurring_price/28. return self.recurring_price/28.
elif self.default_recurring_period == RecurringPeriod.PER_DAY: elif self.default_recurring_period == RecurringPeriod.PER_DAY:
if requested_period == RecurringPeriod.PER_YEAR: if requested_period == RecurringPeriod.PER_365D:
return self.recurring_price*11*28 return self.recurring_price*11*28
if requested_period == RecurringPeriod.PER_MONTH: if requested_period == RecurringPeriod.PER_30D:
return self.recurring_price*28 return self.recurring_price*28
if requested_period == RecurringPeriod.PER_DAY: if requested_period == RecurringPeriod.PER_DAY:
return self.recurring_price return self.recurring_price

View file

@ -17,7 +17,7 @@ class MatrixServiceProduct(Product):
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_MONTH): def recurring_price(self, recurring_period=RecurringPeriod.PER_30D):
return self.monthly_managment_fee return self.monthly_managment_fee
@staticmethod @staticmethod
@ -28,7 +28,7 @@ class MatrixServiceProduct(Product):
@staticmethod @staticmethod
def allowed_recurring_periods(): def allowed_recurring_periods():
return list(filter( return list(filter(
lambda pair: pair[0] in [RecurringPeriod.PER_MONTH], lambda pair: pair[0] in [RecurringPeriod.PER_30D],
RecurringPeriod.choices)) RecurringPeriod.choices))
@property @property

View file

@ -88,8 +88,8 @@ class VMProduct(Product):
@staticmethod @staticmethod
def allowed_recurring_periods(): def allowed_recurring_periods():
return list(filter( return list(filter(
lambda pair: pair[0] in [RecurringPeriod.PER_YEAR, lambda pair: pair[0] in [RecurringPeriod.PER_365D,
RecurringPeriod.PER_MONTH, RecurringPeriod.PER_HOUR], RecurringPeriod.PER_30D, RecurringPeriod.PER_HOUR],
RecurringPeriod.choices)) RecurringPeriod.choices))
def __str__(self): def __str__(self):