MailboxBillingTest tests passing
This commit is contained in:
parent
5a031b81cb
commit
259bc07b71
|
@ -35,7 +35,7 @@ class BillsBackend(object):
|
||||||
# Create bill line
|
# Create bill line
|
||||||
billine = bill.lines.create(
|
billine = bill.lines.create(
|
||||||
rate=service.nominal_price,
|
rate=service.nominal_price,
|
||||||
quantity=line.size,
|
quantity=line.metric*line.size,
|
||||||
subtotal=line.subtotal,
|
subtotal=line.subtotal,
|
||||||
tax=service.tax,
|
tax=service.tax,
|
||||||
description=self.get_line_description(line),
|
description=self.get_line_description(line),
|
||||||
|
@ -54,11 +54,14 @@ class BillsBackend(object):
|
||||||
service = line.order.service
|
service = line.order.service
|
||||||
if service.is_fee:
|
if service.is_fee:
|
||||||
return self.format_period(line.ini, line.end)
|
return self.format_period(line.ini, line.end)
|
||||||
else:
|
description = line.order.description
|
||||||
description = line.order.description
|
if service.billing_period != service.NEVER:
|
||||||
if service.billing_period != service.NEVER:
|
description += " %s" % self.format_period(line.ini, line.end)
|
||||||
description += " %s" % self.format_period(line.ini, line.end)
|
if service.metric and service.billing_period != service.NEVER and service.pricing_period == service.NEVER:
|
||||||
return description
|
metric = format(line.metric, '.2f').rstrip('0').rstrip('.')
|
||||||
|
size = format(line.size, '.2f').rstrip('0').rstrip('.')
|
||||||
|
description += " (%s*%s)" % (metric, size)
|
||||||
|
return description
|
||||||
|
|
||||||
def create_sublines(self, line, discounts):
|
def create_sublines(self, line, discounts):
|
||||||
for discount in discounts:
|
for discount in discounts:
|
||||||
|
|
|
@ -141,14 +141,32 @@ class Order(models.Model):
|
||||||
self.save()
|
self.save()
|
||||||
logger.info("CANCELLED order id: {id}".format(id=self.id))
|
logger.info("CANCELLED order id: {id}".format(id=self.id))
|
||||||
|
|
||||||
def get_metric(self, ini, end):
|
def get_metric(self, ini, end, changes=False):
|
||||||
return MetricStorage.get(self, ini, end)
|
if changes:
|
||||||
|
result = []
|
||||||
|
prev = None
|
||||||
|
for metric in self.metrics.filter(created_on__lt=end).order_by('created_on'):
|
||||||
|
created = metric.created_on.date()
|
||||||
|
if created > ini:
|
||||||
|
cini = prev.created_on.date()
|
||||||
|
if not result:
|
||||||
|
cini = ini
|
||||||
|
result.append((cini, created, prev.value))
|
||||||
|
prev = metric
|
||||||
|
if created < end:
|
||||||
|
result.append((created, end, metric.value))
|
||||||
|
return result
|
||||||
|
try:
|
||||||
|
metrics = self.metrics.filter(updated_on__lt=end, updated_on__gte=ini)
|
||||||
|
return metrics.latest('updated_on').value
|
||||||
|
except MetricStorage.DoesNotExist:
|
||||||
|
return decimal.Decimal(0)
|
||||||
|
|
||||||
|
|
||||||
class MetricStorage(models.Model):
|
class MetricStorage(models.Model):
|
||||||
order = models.ForeignKey(Order, verbose_name=_("order"), related_name='metrics')
|
order = models.ForeignKey(Order, verbose_name=_("order"), related_name='metrics')
|
||||||
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
||||||
created_on = models.DateField(_("created"), auto_now_add=True)
|
created_on = models.DateTimeField(_("created"), auto_now_add=True)
|
||||||
updated_on = models.DateTimeField(_("updated"))
|
updated_on = models.DateTimeField(_("updated"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -171,24 +189,18 @@ class MetricStorage(models.Model):
|
||||||
metric.updated_on = now
|
metric.updated_on = now
|
||||||
metric.save()
|
metric.save()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, order, ini, end):
|
|
||||||
try:
|
|
||||||
return order.metrics.filter(updated_on__lt=end, updated_on__gte=ini).latest('updated_on').value
|
|
||||||
except cls.DoesNotExist:
|
|
||||||
return decimal.Decimal(0)
|
|
||||||
|
|
||||||
|
|
||||||
_excluded_models = (MetricStorage, LogEntry, Order, ContentType, MigrationRecorder.Migration)
|
_excluded_models = (MetricStorage, LogEntry, Order, ContentType, MigrationRecorder.Migration)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, dispatch_uid="orders.cancel_orders")
|
@receiver(post_delete, dispatch_uid="orders.cancel_orders")
|
||||||
def cancel_orders(sender, **kwargs):
|
def cancel_orders(sender, **kwargs):
|
||||||
if sender not in _excluded_models:
|
if sender not in _excluded_models:
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
if sender in services:
|
if hasattr(instance, 'account'):
|
||||||
for order in Order.objects.by_object(instance).active():
|
for order in Order.objects.by_object(instance).active():
|
||||||
order.cancel()
|
order.cancel()
|
||||||
elif not hasattr(instance, 'account'):
|
else:
|
||||||
related = helpers.get_related_objects(instance)
|
related = helpers.get_related_objects(instance)
|
||||||
if related and related != instance:
|
if related and related != instance:
|
||||||
Order.update_orders(related)
|
Order.update_orders(related)
|
||||||
|
@ -198,12 +210,13 @@ def cancel_orders(sender, **kwargs):
|
||||||
def update_orders(sender, **kwargs):
|
def update_orders(sender, **kwargs):
|
||||||
if sender not in _excluded_models:
|
if sender not in _excluded_models:
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
if sender in services:
|
if hasattr(instance, 'account'):
|
||||||
Order.update_orders(instance)
|
Order.update_orders(instance)
|
||||||
elif not hasattr(instance, 'account'):
|
else:
|
||||||
related = helpers.get_related_objects(instance)
|
related = helpers.get_related_objects(instance)
|
||||||
if related and related != instance:
|
if related and related != instance:
|
||||||
Order.update_orders(related)
|
Order.update_orders(related)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
accounts.register(Order)
|
accounts.register(Order)
|
||||||
|
|
|
@ -311,7 +311,9 @@ class TrafficBillingTest(BaseBillingTest):
|
||||||
self.assertEqual(0, bills[0].get_total())
|
self.assertEqual(0, bills[0].get_total())
|
||||||
|
|
||||||
self.report_traffic(account, date, 10**10*9)
|
self.report_traffic(account, date, 10**10*9)
|
||||||
order.metrics.filter(id=3).update(updated_on=F('updated_on')-delta)
|
metric = order.metrics.latest()
|
||||||
|
metric.updated_on -= delta
|
||||||
|
metric.save()
|
||||||
bills = service.orders.bill(proforma=True)
|
bills = service.orders.bill(proforma=True)
|
||||||
self.assertEqual(900, bills[0].get_total())
|
self.assertEqual(900, bills[0].get_total())
|
||||||
|
|
||||||
|
@ -320,6 +322,11 @@ class TrafficBillingTest(BaseBillingTest):
|
||||||
resource = self.create_traffic_resource()
|
resource = self.create_traffic_resource()
|
||||||
account1 = self.create_account()
|
account1 = self.create_account()
|
||||||
account2 = self.create_account()
|
account2 = self.create_account()
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
def test_traffic_prepay(self):
|
||||||
|
pass
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
|
||||||
class MailboxBillingTest(BaseBillingTest):
|
class MailboxBillingTest(BaseBillingTest):
|
||||||
|
@ -361,8 +368,7 @@ class MailboxBillingTest(BaseBillingTest):
|
||||||
nominal_price=10
|
nominal_price=10
|
||||||
)
|
)
|
||||||
plan = Plan.objects.create(is_default=True, name='Default')
|
plan = Plan.objects.create(is_default=True, name='Default')
|
||||||
service.rates.create(plan=plan, quantity=1, price=0)
|
service.rates.create(plan=plan, quantity=1, price=10)
|
||||||
service.rates.create(plan=plan, quantity=2, price=10)
|
|
||||||
return service
|
return service
|
||||||
|
|
||||||
def create_disk_resource(self):
|
def create_disk_resource(self):
|
||||||
|
@ -398,18 +404,56 @@ class MailboxBillingTest(BaseBillingTest):
|
||||||
self.allocate_disk(mailbox, 10)
|
self.allocate_disk(mailbox, 10)
|
||||||
bill = service.orders.bill()[0]
|
bill = service.orders.bill()[0]
|
||||||
self.assertEqual(0, bill.get_total())
|
self.assertEqual(0, bill.get_total())
|
||||||
bill = disk_service.orders.bill()[0]
|
bp = timezone.now().date() + relativedelta.relativedelta(years=1)
|
||||||
for line in bill.lines.all():
|
bill = disk_service.orders.bill(billing_point=bp, fixed_point=True)[0]
|
||||||
for discount in line.sublines.all():
|
self.assertEqual(90, bill.get_total())
|
||||||
print discount.__dict__
|
|
||||||
self.assertEqual(80, bill.get_total())
|
|
||||||
mailbox = self.create_mailbox(account=account)
|
mailbox = self.create_mailbox(account=account)
|
||||||
mailbox = self.create_mailbox(account=account)
|
mailbox = self.create_mailbox(account=account)
|
||||||
mailbox = self.create_mailbox(account=account)
|
mailbox = self.create_mailbox(account=account)
|
||||||
mailbox = self.create_mailbox(account=account)
|
mailbox = self.create_mailbox(account=account)
|
||||||
mailbox = self.create_mailbox(account=account)
|
mailbox = self.create_mailbox(account=account)
|
||||||
bill = service.orders.bill()[0]
|
mailbox = self.create_mailbox(account=account)
|
||||||
print disk_service.orders.bill()[0].get_total()
|
bill = service.orders.bill(billing_point=bp, fixed_point=True)[0]
|
||||||
|
self.assertEqual(120, bill.get_total())
|
||||||
|
|
||||||
|
def test_mailbox_size_with_changes(self):
|
||||||
|
service = self.create_mailbox_disk_service()
|
||||||
|
self.create_disk_resource()
|
||||||
|
account = self.create_account()
|
||||||
|
mailbox = self.create_mailbox(account=account)
|
||||||
|
now = timezone.now()
|
||||||
|
bp = now.date() + relativedelta.relativedelta(years=1)
|
||||||
|
|
||||||
|
self.allocate_disk(mailbox, 10)
|
||||||
|
bill = service.orders.bill(billing_point=bp, fixed_point=True, proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(9*10, bill.get_total())
|
||||||
|
|
||||||
|
self.allocate_disk(mailbox, 20)
|
||||||
|
created_on = now+relativedelta.relativedelta(months=6)
|
||||||
|
order = service.orders.get()
|
||||||
|
metric = order.metrics.latest('id')
|
||||||
|
metric.created_on = created_on
|
||||||
|
metric.save()
|
||||||
|
bill = service.orders.bill(billing_point=bp, fixed_point=True, proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(9*10*0.5 + 19*10*0.5, bill.get_total())
|
||||||
|
|
||||||
|
self.allocate_disk(mailbox, 30)
|
||||||
|
created_on = now+relativedelta.relativedelta(months=9)
|
||||||
|
order = service.orders.get()
|
||||||
|
metric = order.metrics.latest('id')
|
||||||
|
metric.created_on = created_on
|
||||||
|
metric.save()
|
||||||
|
bill = service.orders.bill(billing_point=bp, fixed_point=True, proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(9*10*0.5 + 19*10*0.25 + 29*10*0.25, bill.get_total())
|
||||||
|
|
||||||
|
self.allocate_disk(mailbox, 10)
|
||||||
|
created_on = now+relativedelta.relativedelta(years=1)
|
||||||
|
order = service.orders.get()
|
||||||
|
metric = order.metrics.latest('id')
|
||||||
|
metric.created_on = created_on
|
||||||
|
metric.save()
|
||||||
|
bill = service.orders.bill(billing_point=bp, fixed_point=True, proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(9*10*0.5 + 19*10*0.25 + 29*10*0.25, bill.get_total())
|
||||||
|
|
||||||
|
|
||||||
class JobBillingTest(BaseBillingTest):
|
class JobBillingTest(BaseBillingTest):
|
||||||
|
@ -418,10 +462,10 @@ class JobBillingTest(BaseBillingTest):
|
||||||
description="Random job",
|
description="Random job",
|
||||||
content_type=ContentType.objects.get_for_model(Miscellaneous),
|
content_type=ContentType.objects.get_for_model(Miscellaneous),
|
||||||
match="miscellaneous.is_active and miscellaneous.service.name.lower() == 'job'",
|
match="miscellaneous.is_active and miscellaneous.service.name.lower() == 'job'",
|
||||||
billing_period=Service.MONTHLY,
|
billing_period=Service.NEVER,
|
||||||
billing_point=Service.FIXED_DATE,
|
billing_point=Service.ON_REGISTER,
|
||||||
is_fee=False,
|
is_fee=False,
|
||||||
metric='mailbox.resources.disk.allocated',
|
metric='miscellaneous.amount',
|
||||||
pricing_period=Service.BILLING_PERIOD,
|
pricing_period=Service.BILLING_PERIOD,
|
||||||
rate_algorithm=Service.STEP_PRICE,
|
rate_algorithm=Service.STEP_PRICE,
|
||||||
on_cancel=Service.NOTHING,
|
on_cancel=Service.NOTHING,
|
||||||
|
@ -434,15 +478,19 @@ class JobBillingTest(BaseBillingTest):
|
||||||
service.rates.create(plan=plan, quantity=11, price=10)
|
service.rates.create(plan=plan, quantity=11, price=10)
|
||||||
return service
|
return service
|
||||||
|
|
||||||
def create_job(self, account=None):
|
def create_job(self, amount, account=None):
|
||||||
if not account:
|
if not account:
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
job_name = '%s.es' % random_ascii(10)
|
job_name = '%s.es' % random_ascii(10)
|
||||||
job_service, __ = MiscService.objects.get_or_create(name='job', description='Random job')
|
job_service, __ = MiscService.objects.get_or_create(name='job', description='Random job', has_amount=True)
|
||||||
return Miscellaneous.objects.create(service=job_service, description=job_name, account=account)
|
return Miscellaneous.objects.create(service=job_service, description=job_name, account=account, amount=amount)
|
||||||
|
|
||||||
def test_job(self):
|
def test_job(self):
|
||||||
pass
|
service = self.create_job_service()
|
||||||
|
account = self.create_account()
|
||||||
|
job = self.create_job(10, account=account)
|
||||||
|
print service.orders.all()
|
||||||
|
print service.orders.bill()[0].get_total()
|
||||||
|
|
||||||
|
|
||||||
class PlanBillingTest(BaseBillingTest):
|
class PlanBillingTest(BaseBillingTest):
|
||||||
|
|
|
@ -150,14 +150,29 @@ class ServiceHandler(plugins.Plugin):
|
||||||
'total': price,
|
'total': price,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
def generate_line(self, order, price, size, ini, end, discounts=[]):
|
def generate_line(self, order, price, *dates, **kwargs):
|
||||||
subtotal = self.nominal_price * size
|
if len(dates) == 2:
|
||||||
|
ini, end = dates
|
||||||
|
elif len(dates) == 1:
|
||||||
|
ini, end = dates[0], dates[0]
|
||||||
|
else:
|
||||||
|
raise AttributeError
|
||||||
|
metric = kwargs.pop('metric', 1)
|
||||||
|
discounts = kwargs.pop('discounts', ())
|
||||||
|
computed = kwargs.pop('computed', False)
|
||||||
|
if kwargs:
|
||||||
|
raise AttributeError
|
||||||
|
size = self.get_price_size(ini, end)
|
||||||
|
if not computed:
|
||||||
|
price = price * size
|
||||||
|
subtotal = self.nominal_price * size * metric
|
||||||
line = AttributeDict(**{
|
line = AttributeDict(**{
|
||||||
'order': order,
|
'order': order,
|
||||||
'subtotal': subtotal,
|
'subtotal': subtotal,
|
||||||
'size': size,
|
|
||||||
'ini': ini,
|
'ini': ini,
|
||||||
'end': end,
|
'end': end,
|
||||||
|
'size': size,
|
||||||
|
'metric': metric,
|
||||||
'discounts': [],
|
'discounts': [],
|
||||||
})
|
})
|
||||||
discounted = 0
|
discounted = 0
|
||||||
|
@ -249,7 +264,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
csize += self.get_price_size(intersect.ini, intersect.end)
|
csize += self.get_price_size(intersect.ini, intersect.end)
|
||||||
price = self.get_price(account, metric, position=position, rates=rates)
|
price = self.get_price(account, metric, position=position, rates=rates)
|
||||||
price = price * size
|
price = price * size
|
||||||
cprice = price * (size-csize)
|
cprice = price * csize
|
||||||
if order in priced:
|
if order in priced:
|
||||||
priced[order][0] += price
|
priced[order][0] += price
|
||||||
priced[order][1] += cprice
|
priced[order][1] += cprice
|
||||||
|
@ -269,7 +284,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
size = self.get_price_size(order.new_billed_until, new_end)
|
size = self.get_price_size(order.new_billed_until, new_end)
|
||||||
price += price*size
|
price += price*size
|
||||||
order.new_billed_until = new_end
|
order.new_billed_until = new_end
|
||||||
line = self.generate_line(order, price, size, ini, end, discounts=discounts)
|
line = self.generate_line(order, price, ini, new_end or end, discounts=discounts, computed=True)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
if commit:
|
if commit:
|
||||||
order.billed_until = order.new_billed_until
|
order.billed_until = order.new_billed_until
|
||||||
|
@ -302,8 +317,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
order.new_billed_until = new_end
|
order.new_billed_until = new_end
|
||||||
end = new_end
|
end = new_end
|
||||||
size = self.get_price_size(ini, end)
|
size = self.get_price_size(ini, end)
|
||||||
price = price * size
|
line = self.generate_line(order, price, ini, end, discounts=discounts)
|
||||||
line = self.generate_line(order, price, size, ini, end, discounts=discounts)
|
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
if commit:
|
if commit:
|
||||||
order.billed_until = order.new_billed_until
|
order.billed_until = order.new_billed_until
|
||||||
|
@ -322,6 +336,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
bp = None
|
bp = None
|
||||||
ini = datetime.date.max
|
ini = datetime.date.max
|
||||||
end = datetime.date.min
|
end = datetime.date.min
|
||||||
|
# TODO compensation with one time billing?
|
||||||
for order in orders:
|
for order in orders:
|
||||||
cini = order.registered_on
|
cini = order.registered_on
|
||||||
if order.billed_until:
|
if order.billed_until:
|
||||||
|
@ -370,8 +385,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
if new_end:
|
if new_end:
|
||||||
order.new_billed_until = new_end
|
order.new_billed_until = new_end
|
||||||
end = new_end
|
end = new_end
|
||||||
size = self.get_price_size(ini, end)
|
line = self.generate_line(order, price, ini, end, discounts=discounts)
|
||||||
line = self.generate_line(order, price*size, size, ini, end, discounts=discounts)
|
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
if commit:
|
if commit:
|
||||||
order.billed_until = order.new_billed_until
|
order.billed_until = order.new_billed_until
|
||||||
|
@ -379,40 +393,36 @@ class ServiceHandler(plugins.Plugin):
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def bill_with_metric(self, orders, account, **options):
|
def bill_with_metric(self, orders, account, **options):
|
||||||
# TODO filter out orders with cancelled_on < billed_until ?
|
|
||||||
lines = []
|
lines = []
|
||||||
commit = options.get('commit', True)
|
commit = options.get('commit', True)
|
||||||
bp = None
|
bp = None
|
||||||
for order in orders:
|
for order in orders:
|
||||||
|
if order.billed_until and order.cancelled_on >= order.billed_until:
|
||||||
|
continue
|
||||||
bp = self.get_billing_point(order, bp=bp, **options)
|
bp = self.get_billing_point(order, bp=bp, **options)
|
||||||
ini = order.billed_until or order.registered_on
|
ini = order.billed_until or order.registered_on
|
||||||
if bp <= ini:
|
if bp <= ini:
|
||||||
|
# TODO except one time service
|
||||||
continue
|
continue
|
||||||
order.new_billed_until = bp
|
order.new_billed_until = bp
|
||||||
# weighted metric; bill line per pricing period
|
|
||||||
prev = None
|
|
||||||
lines_info = []
|
|
||||||
if self.billing_period != self.NEVER:
|
if self.billing_period != self.NEVER:
|
||||||
if self.get_pricing_period() == self.NEVER:
|
if self.get_pricing_period() == self.NEVER:
|
||||||
# Changes
|
# Changes
|
||||||
for ini, end, metric in order.get_metric(ini, end, changes=True)
|
for ini, end, metric in order.get_metric(ini, bp, changes=True):
|
||||||
size = self.get_price_size(ini, end)
|
|
||||||
price = self.get_price(order, metric)
|
price = self.get_price(order, metric)
|
||||||
price = price * size
|
lines.append(self.generate_line(order, price, ini, end, metric=metric))
|
||||||
# TODO metric and size in invoice (period and quantity)
|
|
||||||
lines.append(self.generate_line(order, price, metric, ini, end))
|
|
||||||
else:
|
else:
|
||||||
# pricing_slots
|
# pricing_slots
|
||||||
for ini, end in self.get_pricing_slots(ini, bp):
|
for ini, end in self.get_pricing_slots(ini, bp):
|
||||||
metric = order.get_metric(ini, end)
|
metric = order.get_metric(ini, end)
|
||||||
price = self.get_price(order, metric)
|
price = self.get_price(order, metric)
|
||||||
lines.append(self.generate_line(order, price, metric, ini, end))
|
lines.append(self.generate_line(order, price, ini, end, metric=metric))
|
||||||
else:
|
else:
|
||||||
if self.get_pricing_period() == self.NEVER:
|
if self.get_pricing_period() == self.NEVER:
|
||||||
# get metric
|
# get metric
|
||||||
metric = order.get_metric(ini, end)
|
metric = order.get_metric(ini, end)
|
||||||
price = self.get_price(order, metric)
|
price = self.get_price(order, metric)
|
||||||
lines.append(self.generate_line(order, price, metric, ini, end))
|
lines.append(self.generate_line(order, price, ini, bp, metric=metric))
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
if commit:
|
if commit:
|
||||||
|
|
Loading…
Reference in New Issue