Fixes on billing with metric

This commit is contained in:
Marc 2014-09-23 11:13:50 +00:00
parent 8f1d05873c
commit 5a031b81cb
5 changed files with 189 additions and 22 deletions

View file

@ -11,7 +11,7 @@ from . import settings
class MailSystemUserBackend(ServiceController):
verbose_name = _("Mail system user")
model = 'mail.Mailbox'
model = 'mails.Mailbox'
# TODO related_models = ('resources__content_type') ??
DEFAULT_GROUP = 'postfix'
@ -66,7 +66,7 @@ class MailSystemUserBackend(ServiceController):
class PostfixAddressBackend(ServiceController):
verbose_name = _("Postfix address")
model = 'mail.Address'
model = 'mails.Address'
def include_virtdomain(self, context):
self.append(
@ -140,7 +140,7 @@ class AutoresponseBackend(ServiceController):
class MaildirDisk(ServiceMonitor):
model = 'email.Mailbox'
model = 'mails.Mailbox'
resource = ServiceMonitor.DISK
verbose_name = _("Maildir disk usage")

View file

@ -185,10 +185,10 @@ _excluded_models = (MetricStorage, LogEntry, Order, ContentType, MigrationRecord
def cancel_orders(sender, **kwargs):
if sender not in _excluded_models:
instance = kwargs['instance']
if hasattr(instance, 'account'):
if sender in services:
for order in Order.objects.by_object(instance).active():
order.cancel()
else:
elif not hasattr(instance, 'account'):
related = helpers.get_related_objects(instance)
if related and related != instance:
Order.update_orders(related)
@ -198,9 +198,9 @@ def cancel_orders(sender, **kwargs):
def update_orders(sender, **kwargs):
if sender not in _excluded_models:
instance = kwargs['instance']
if hasattr(instance, 'account'):
if sender in services:
Order.update_orders(instance)
else:
elif not hasattr(instance, 'account'):
related = helpers.get_related_objects(instance)
if related and related != instance:
Order.update_orders(related)

View file

@ -8,6 +8,9 @@ from django.db.models import F
from django.utils import timezone
from orchestra.apps.accounts.models import Account
from orchestra.apps.mails.models import Mailbox
from orchestra.apps.miscellaneous.models import MiscService, Miscellaneous
from orchestra.apps.resources.models import Resource, ResourceData, MonitorData
from orchestra.apps.services.models import Service, Plan
from orchestra.apps.services import settings as services_settings
from orchestra.apps.users.models import User
@ -17,7 +20,7 @@ from orchestra.utils.tests import BaseTestCase, random_ascii
class BaseBillingTest(BaseTestCase):
def create_account(self):
account = Account.objects.create()
user = User.objects.create_user(username='rata_palida', account=account)
user = User.objects.create_user(username='account_%s' % random_ascii(5), account=account)
account.user = user
account.save()
return account
@ -109,10 +112,13 @@ class FTPBillingTest(BaseBillingTest):
order = service.orders.order_by('-id').first()
self.assertEqual(first_bp, order.billed_until)
self.assertEqual(decimal.Decimal(0), bills[0].get_total())
def test_ftp_account_with_rates(self):
pass
class DomainBillingTest(BaseBillingTest):
def create_domain_service(self):
from orchestra.apps.miscellaneous.models import MiscService, Miscellaneous
service = Service.objects.create(
description="Domain .ES",
content_type=ContentType.objects.get_for_model(Miscellaneous),
@ -136,7 +142,6 @@ class DomainBillingTest(BaseBillingTest):
return service
def create_domain(self, account=None):
from orchestra.apps.miscellaneous.models import MiscService, Miscellaneous
if not account:
account = self.create_account()
domain_name = '%s.es' % random_ascii(10)
@ -278,7 +283,6 @@ class TrafficBillingTest(BaseBillingTest):
return self.resource
def report_traffic(self, account, date, value):
from orchestra.apps.resources.models import ResourceData, MonitorData
ct = ContentType.objects.get_for_model(Account)
object_id = account.pk
MonitorData.objects.create(monitor='FTPTraffic', content_object=account.user, value=value, date=date)
@ -310,3 +314,147 @@ class TrafficBillingTest(BaseBillingTest):
order.metrics.filter(id=3).update(updated_on=F('updated_on')-delta)
bills = service.orders.bill(proforma=True)
self.assertEqual(900, bills[0].get_total())
def test_multiple_traffics(self):
service = self.create_traffic_service()
resource = self.create_traffic_resource()
account1 = self.create_account()
account2 = self.create_account()
class MailboxBillingTest(BaseBillingTest):
def create_mailbox_service(self):
service = Service.objects.create(
description="Mailbox",
content_type=ContentType.objects.get_for_model(Mailbox),
match="True",
billing_period=Service.ANUAL,
billing_point=Service.FIXED_DATE,
is_fee=False,
metric='',
pricing_period=Service.NEVER,
rate_algorithm=Service.STEP_PRICE,
on_cancel=Service.DISCOUNT,
payment_style=Service.PREPAY,
tax=0,
nominal_price=10
)
plan = Plan.objects.create(is_default=True, name='Default')
service.rates.create(plan=plan, quantity=1, price=0)
service.rates.create(plan=plan, quantity=5, price=10)
return service
def create_mailbox_disk_service(self):
service = Service.objects.create(
description="Mailbox disk",
content_type=ContentType.objects.get_for_model(Mailbox),
match="True",
billing_period=Service.ANUAL,
billing_point=Service.FIXED_DATE,
is_fee=False,
metric='max((mailbox.resources.disk.allocated or 0) -1, 0)',
pricing_period=Service.NEVER,
rate_algorithm=Service.STEP_PRICE,
on_cancel=Service.DISCOUNT,
payment_style=Service.PREPAY,
tax=0,
nominal_price=10
)
plan = Plan.objects.create(is_default=True, name='Default')
service.rates.create(plan=plan, quantity=1, price=0)
service.rates.create(plan=plan, quantity=2, price=10)
return service
def create_disk_resource(self):
self.resource = Resource.objects.create(
name='disk',
content_type=ContentType.objects.get_for_model(Mailbox),
period=Resource.LAST,
verbose_name='Mailbox disk',
unit='GB',
scale=10**9,
ondemand=False,
monitors='MaildirDisk',
)
return self.resource
def allocate_disk(self, mailbox, value):
data = ResourceData.get_or_create(mailbox, self.resource)
data.allocated = value
data.save()
def create_mailbox(self, account=None):
if not account:
account = self.create_account()
mailbox_name = '%s@orchestra.lan' % random_ascii(10)
return Mailbox.objects.create(name=mailbox_name, account=account)
def test_mailbox_size(self):
service = self.create_mailbox_service()
disk_service = self.create_mailbox_disk_service()
self.create_disk_resource()
account = self.create_account()
mailbox = self.create_mailbox(account=account)
self.allocate_disk(mailbox, 10)
bill = service.orders.bill()[0]
self.assertEqual(0, bill.get_total())
bill = disk_service.orders.bill()[0]
for line in bill.lines.all():
for discount in line.sublines.all():
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)
bill = service.orders.bill()[0]
print disk_service.orders.bill()[0].get_total()
class JobBillingTest(BaseBillingTest):
def create_job_service(self):
service = Service.objects.create(
description="Random job",
content_type=ContentType.objects.get_for_model(Miscellaneous),
match="miscellaneous.is_active and miscellaneous.service.name.lower() == 'job'",
billing_period=Service.MONTHLY,
billing_point=Service.FIXED_DATE,
is_fee=False,
metric='mailbox.resources.disk.allocated',
pricing_period=Service.BILLING_PERIOD,
rate_algorithm=Service.STEP_PRICE,
on_cancel=Service.NOTHING,
payment_style=Service.POSTPAY,
tax=0,
nominal_price=10
)
plan = Plan.objects.create(is_default=True, name='Default')
service.rates.create(plan=plan, quantity=1, price=0)
service.rates.create(plan=plan, quantity=11, price=10)
return service
def create_job(self, account=None):
if not account:
account = self.create_account()
job_name = '%s.es' % random_ascii(10)
job_service, __ = MiscService.objects.get_or_create(name='job', description='Random job')
return Miscellaneous.objects.create(service=job_service, description=job_name, account=account)
def test_job(self):
pass
class PlanBillingTest(BaseBillingTest):
def create_plan_service(self):
pass
def create_plan(self):
if not account:
account = self.create_account()
domain_name = '%s.es' % random_ascii(10)
domain_service, __ = MiscService.objects.get_or_create(name='domain .es', description='Domain .ES')
return Miscellaneous.objects.create(service=domain_service, description=domain_name, account=account)
def test_plan(self):
pass

View file

@ -80,7 +80,7 @@ class Resource(models.Model):
return "{}-{}".format(str(self.content_type), self.name)
def save(self, *args, **kwargs):
# created = not self.pk
created = not self.pk
super(Resource, self).save(*args, **kwargs)
# Create Celery periodic task
name = 'monitor.%s' % str(self)
@ -100,8 +100,8 @@ class Resource(models.Model):
elif task.crontab != self.crontab:
task.crontab = self.crontab
task.save()
# if created:
# create_resource_relation()
if created:
create_resource_relation()
def delete(self, *args, **kwargs):
super(Resource, self).delete(*args, **kwargs)

View file

@ -218,7 +218,6 @@ class ServiceHandler(plugins.Plugin):
return dsize, cend
def get_register_or_renew_events(self, porders, ini, end):
# TODO count intermediat billing points too
counter = 0
for order in porders:
bu = getattr(order, 'new_billed_until', order.billed_until)
@ -251,13 +250,14 @@ class ServiceHandler(plugins.Plugin):
price = self.get_price(account, metric, position=position, rates=rates)
price = price * size
cprice = price * (size-csize)
if order in prices:
if order in priced:
priced[order][0] += price
priced[order][1] += cprice
else:
priced[order] = (price, cprice)
lines = []
for order, prices in priced.iteritems():
discounts = ()
# Generate lines and discounts from order.nominal_price
price, cprice = prices
# Compensations > new_billed_until
@ -351,8 +351,8 @@ class ServiceHandler(plugins.Plugin):
porders = related_orders.pricing_orders(ini, end)
porders = list(set(orders).union(set(porders)))
porders.sort(cmp=helpers.cmp_billed_until_or_registered_on)
if self.billing_period != self.NEVER and self.get_pricing_period == self.NEVER:
liens = self.bill_concurrent_orders(account, porders, rates, ini, end, commit=commit)
if self.billing_period != self.NEVER and self.get_pricing_period() == self.NEVER:
lines = self.bill_concurrent_orders(account, porders, rates, ini, end, commit=commit)
else:
# TODO compensation in this case?
lines = self.bill_registered_or_renew_events(account, porders, rates, commit=commit)
@ -392,10 +392,29 @@ class ServiceHandler(plugins.Plugin):
# weighted metric; bill line per pricing period
prev = None
lines_info = []
for ini, end in self.get_pricing_slots(ini, bp):
metric = order.get_metric(ini, end)
price = self.get_price(order, metric)
lines.append(self.generate_line(order, price, metric, ini, end))
if self.billing_period != self.NEVER:
if self.get_pricing_period() == self.NEVER:
# Changes
for ini, end, metric in order.get_metric(ini, end, changes=True)
size = self.get_price_size(ini, end)
price = self.get_price(order, metric)
price = price * size
# TODO metric and size in invoice (period and quantity)
lines.append(self.generate_line(order, price, metric, ini, end))
else:
# pricing_slots
for ini, end in self.get_pricing_slots(ini, bp):
metric = order.get_metric(ini, end)
price = self.get_price(order, metric)
lines.append(self.generate_line(order, price, metric, ini, end))
else:
if self.get_pricing_period() == self.NEVER:
# get metric
metric = order.get_metric(ini, end)
price = self.get_price(order, metric)
lines.append(self.generate_line(order, price, metric, ini, end))
else:
raise NotImplementedError
if commit:
order.billed_until = order.new_billed_until
order.save()