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

View file

@ -185,10 +185,10 @@ _excluded_models = (MetricStorage, LogEntry, Order, ContentType, MigrationRecord
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 hasattr(instance, 'account'): if sender in services:
for order in Order.objects.by_object(instance).active(): for order in Order.objects.by_object(instance).active():
order.cancel() order.cancel()
else: elif not hasattr(instance, 'account'):
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,9 +198,9 @@ 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 hasattr(instance, 'account'): if sender in services:
Order.update_orders(instance) Order.update_orders(instance)
else: elif not hasattr(instance, 'account'):
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)

View file

@ -8,6 +8,9 @@ from django.db.models import F
from django.utils import timezone from django.utils import timezone
from orchestra.apps.accounts.models import Account 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.models import Service, Plan
from orchestra.apps.services import settings as services_settings from orchestra.apps.services import settings as services_settings
from orchestra.apps.users.models import User from orchestra.apps.users.models import User
@ -17,7 +20,7 @@ from orchestra.utils.tests import BaseTestCase, random_ascii
class BaseBillingTest(BaseTestCase): class BaseBillingTest(BaseTestCase):
def create_account(self): def create_account(self):
account = Account.objects.create() 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.user = user
account.save() account.save()
return account return account
@ -110,9 +113,12 @@ class FTPBillingTest(BaseBillingTest):
self.assertEqual(first_bp, order.billed_until) self.assertEqual(first_bp, order.billed_until)
self.assertEqual(decimal.Decimal(0), bills[0].get_total()) self.assertEqual(decimal.Decimal(0), bills[0].get_total())
def test_ftp_account_with_rates(self):
pass
class DomainBillingTest(BaseBillingTest): class DomainBillingTest(BaseBillingTest):
def create_domain_service(self): def create_domain_service(self):
from orchestra.apps.miscellaneous.models import MiscService, Miscellaneous
service = Service.objects.create( service = Service.objects.create(
description="Domain .ES", description="Domain .ES",
content_type=ContentType.objects.get_for_model(Miscellaneous), content_type=ContentType.objects.get_for_model(Miscellaneous),
@ -136,7 +142,6 @@ class DomainBillingTest(BaseBillingTest):
return service return service
def create_domain(self, account=None): def create_domain(self, account=None):
from orchestra.apps.miscellaneous.models import MiscService, Miscellaneous
if not account: if not account:
account = self.create_account() account = self.create_account()
domain_name = '%s.es' % random_ascii(10) domain_name = '%s.es' % random_ascii(10)
@ -278,7 +283,6 @@ class TrafficBillingTest(BaseBillingTest):
return self.resource return self.resource
def report_traffic(self, account, date, value): def report_traffic(self, account, date, value):
from orchestra.apps.resources.models import ResourceData, MonitorData
ct = ContentType.objects.get_for_model(Account) ct = ContentType.objects.get_for_model(Account)
object_id = account.pk object_id = account.pk
MonitorData.objects.create(monitor='FTPTraffic', content_object=account.user, value=value, date=date) 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) order.metrics.filter(id=3).update(updated_on=F('updated_on')-delta)
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())
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) return "{}-{}".format(str(self.content_type), self.name)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# created = not self.pk created = not self.pk
super(Resource, self).save(*args, **kwargs) super(Resource, self).save(*args, **kwargs)
# Create Celery periodic task # Create Celery periodic task
name = 'monitor.%s' % str(self) name = 'monitor.%s' % str(self)
@ -100,8 +100,8 @@ class Resource(models.Model):
elif task.crontab != self.crontab: elif task.crontab != self.crontab:
task.crontab = self.crontab task.crontab = self.crontab
task.save() task.save()
# if created: if created:
# create_resource_relation() create_resource_relation()
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
super(Resource, self).delete(*args, **kwargs) super(Resource, self).delete(*args, **kwargs)

View file

@ -218,7 +218,6 @@ class ServiceHandler(plugins.Plugin):
return dsize, cend return dsize, cend
def get_register_or_renew_events(self, porders, ini, end): def get_register_or_renew_events(self, porders, ini, end):
# TODO count intermediat billing points too
counter = 0 counter = 0
for order in porders: for order in porders:
bu = getattr(order, 'new_billed_until', order.billed_until) 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 = self.get_price(account, metric, position=position, rates=rates)
price = price * size price = price * size
cprice = price * (size-csize) cprice = price * (size-csize)
if order in prices: if order in priced:
priced[order][0] += price priced[order][0] += price
priced[order][1] += cprice priced[order][1] += cprice
else: else:
priced[order] = (price, cprice) priced[order] = (price, cprice)
lines = [] lines = []
for order, prices in priced.iteritems(): for order, prices in priced.iteritems():
discounts = ()
# Generate lines and discounts from order.nominal_price # Generate lines and discounts from order.nominal_price
price, cprice = prices price, cprice = prices
# Compensations > new_billed_until # Compensations > new_billed_until
@ -351,8 +351,8 @@ class ServiceHandler(plugins.Plugin):
porders = related_orders.pricing_orders(ini, end) porders = related_orders.pricing_orders(ini, end)
porders = list(set(orders).union(set(porders))) porders = list(set(orders).union(set(porders)))
porders.sort(cmp=helpers.cmp_billed_until_or_registered_on) porders.sort(cmp=helpers.cmp_billed_until_or_registered_on)
if self.billing_period != self.NEVER and self.get_pricing_period == self.NEVER: 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) lines = self.bill_concurrent_orders(account, porders, rates, ini, end, commit=commit)
else: else:
# TODO compensation in this case? # TODO compensation in this case?
lines = self.bill_registered_or_renew_events(account, porders, rates, commit=commit) 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 # weighted metric; bill line per pricing period
prev = None prev = None
lines_info = [] lines_info = []
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): 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, 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: if commit:
order.billed_until = order.new_billed_until order.billed_until = order.new_billed_until
order.save() order.save()