MailboxBillingTest tests passing

This commit is contained in:
Marc 2014-09-23 14:01:58 +00:00
parent 5a031b81cb
commit 259bc07b71
4 changed files with 131 additions and 57 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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):

View File

@ -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: