Enough of fucking billing tests0
This commit is contained in:
parent
c8651cf4ed
commit
af323ebe25
|
@ -100,9 +100,13 @@ class Order(models.Model):
|
||||||
return str(self.service)
|
return str(self.service)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_orders(cls, instance):
|
def update_orders(cls, instance, service=None):
|
||||||
Service = get_model(*settings.ORDERS_SERVICE_MODEL.split('.'))
|
if service is None:
|
||||||
for service in Service.get_services(instance):
|
Service = get_model(*settings.ORDERS_SERVICE_MODEL.split('.'))
|
||||||
|
services = Service.get_services(instance)
|
||||||
|
else:
|
||||||
|
services = [service]
|
||||||
|
for service in services:
|
||||||
orders = Order.objects.by_object(instance, service=service).active()
|
orders = Order.objects.by_object(instance, service=service).active()
|
||||||
if service.handler.matches(instance):
|
if service.handler.matches(instance):
|
||||||
if not orders:
|
if not orders:
|
||||||
|
|
|
@ -101,6 +101,7 @@ class Resource(models.Model):
|
||||||
task.crontab = self.crontab
|
task.crontab = self.crontab
|
||||||
task.save()
|
task.save()
|
||||||
if created:
|
if created:
|
||||||
|
# This only work on tests because of multiprocessing used on real deployments
|
||||||
create_resource_relation()
|
create_resource_relation()
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def update_orders(modeladmin, request, queryset):
|
||||||
|
for service in queryset:
|
||||||
|
service.update_orders()
|
||||||
|
modeladmin.log_change(request, transaction, 'Update orders')
|
||||||
|
msg = _("Orders for %s selected services have been updated.") % queryset.count()
|
||||||
|
modeladmin.message_user(request, msg)
|
||||||
|
update_orders.url_name = 'update-orders'
|
||||||
|
update_orders.verbose_name = _("Update orders")
|
|
@ -4,10 +4,12 @@ from django.core.urlresolvers import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.admin import ChangeViewActionsMixin
|
||||||
from orchestra.admin.filters import UsedContentTypeFilter
|
from orchestra.admin.filters import UsedContentTypeFilter
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
|
|
||||||
|
from .actions import update_orders
|
||||||
from .models import Plan, ContractedPlan, Rate, Service
|
from .models import Plan, ContractedPlan, Rate, Service
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +29,7 @@ class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||||
list_filter = ('plan__name',)
|
list_filter = ('plan__name',)
|
||||||
|
|
||||||
|
|
||||||
class ServiceAdmin(admin.ModelAdmin):
|
class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'description', 'content_type', 'handler_type', 'num_orders', 'is_active'
|
'description', 'content_type', 'handler_type', 'num_orders', 'is_active'
|
||||||
)
|
)
|
||||||
|
@ -49,6 +51,8 @@ class ServiceAdmin(admin.ModelAdmin):
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
inlines = [RateInline]
|
inlines = [RateInline]
|
||||||
|
actions = [update_orders]
|
||||||
|
change_view_actions = actions
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Improve performance of account field and filter by account """
|
""" Improve performance of account field and filter by account """
|
||||||
|
|
|
@ -18,6 +18,8 @@ class ServiceHandler(plugins.Plugin):
|
||||||
"""
|
"""
|
||||||
Separates all the logic of billing handling from the model allowing to better
|
Separates all the logic of billing handling from the model allowing to better
|
||||||
customize its behaviout
|
customize its behaviout
|
||||||
|
|
||||||
|
Relax and enjoy the journey.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = None
|
model = None
|
||||||
|
@ -213,7 +215,6 @@ class ServiceHandler(plugins.Plugin):
|
||||||
compensations, used_compensations = helpers.compensate(interval, compensations)
|
compensations, used_compensations = helpers.compensate(interval, compensations)
|
||||||
order._compensations = used_compensations
|
order._compensations = used_compensations
|
||||||
for comp in used_compensations:
|
for comp in used_compensations:
|
||||||
# TODO get min right
|
|
||||||
comp.order.new_billed_until = min(comp.order.billed_until, comp.ini,
|
comp.order.new_billed_until = min(comp.order.billed_until, comp.ini,
|
||||||
getattr(comp.order, 'new_billed_until', datetime.date.max))
|
getattr(comp.order, 'new_billed_until', datetime.date.max))
|
||||||
if options.get('commit', True):
|
if options.get('commit', True):
|
||||||
|
@ -337,7 +338,6 @@ class ServiceHandler(plugins.Plugin):
|
||||||
# In most cases:
|
# In most cases:
|
||||||
# ini >= registered_date, end < registered_date
|
# ini >= registered_date, end < registered_date
|
||||||
# boundary lookup and exclude cancelled and billed
|
# boundary lookup and exclude cancelled and billed
|
||||||
# TODO service.payment_style == self.POSTPAY no discounts no shit on_cancel
|
|
||||||
orders_ = []
|
orders_ = []
|
||||||
bp = None
|
bp = None
|
||||||
ini = datetime.date.max
|
ini = datetime.date.max
|
||||||
|
@ -359,7 +359,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
|
|
||||||
# Compensation
|
# Compensation
|
||||||
related_orders = account.orders.filter(service=self.service)
|
related_orders = account.orders.filter(service=self.service)
|
||||||
if self.on_cancel == self.COMPENSATE:
|
if self.payment_style == self.PREPAY and self.on_cancel == self.COMPENSATE:
|
||||||
# Get orders pending for compensation
|
# Get orders pending for compensation
|
||||||
givers = list(related_orders.givers(ini, end))
|
givers = list(related_orders.givers(ini, end))
|
||||||
givers.sort(cmp=helpers.cmp_billed_until_or_registered_on)
|
givers.sort(cmp=helpers.cmp_billed_until_or_registered_on)
|
||||||
|
@ -381,7 +381,6 @@ class ServiceHandler(plugins.Plugin):
|
||||||
# Periodic billing with no pricing period
|
# Periodic billing with no pricing period
|
||||||
lines = self.bill_concurrent_orders(account, porders, rates, ini, end)
|
lines = self.bill_concurrent_orders(account, porders, rates, ini, end)
|
||||||
else:
|
else:
|
||||||
# TODO compensation in this case?
|
|
||||||
# Periodic and one-time billing with pricing period
|
# Periodic and one-time billing with pricing period
|
||||||
lines = self.bill_registered_or_renew_events(account, porders, rates)
|
lines = self.bill_registered_or_renew_events(account, porders, rates)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -3,6 +3,7 @@ import sys
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
|
from django.db.models.loading import get_model
|
||||||
from django.db.models.signals import pre_delete, post_delete, post_save
|
from django.db.models.signals import pre_delete, post_delete, post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes import generic
|
||||||
|
@ -324,6 +325,12 @@ class Service(models.Model):
|
||||||
@property
|
@property
|
||||||
def rate_method(self):
|
def rate_method(self):
|
||||||
return self.RATE_METHODS[self.rate_algorithm]
|
return self.RATE_METHODS[self.rate_algorithm]
|
||||||
|
|
||||||
|
def update_orders(self):
|
||||||
|
order_model = get_model(settings.SERVICES_ORDER_MODEL)
|
||||||
|
related_model = self.content_type.model_class()
|
||||||
|
for instance in related_model.objects.all():
|
||||||
|
order_model.update_orders(instance, service=self)
|
||||||
|
|
||||||
|
|
||||||
accounts.register(ContractedPlan)
|
accounts.register(ContractedPlan)
|
||||||
|
|
|
@ -42,11 +42,32 @@ def _compute(rates, metric):
|
||||||
return value, steps
|
return value, steps
|
||||||
|
|
||||||
|
|
||||||
|
def _prepend_missing(rates):
|
||||||
|
"""
|
||||||
|
Support for incomplete rates
|
||||||
|
When first rate (quantity=5, price=10) defaults to nominal_price
|
||||||
|
"""
|
||||||
|
if rates:
|
||||||
|
first = rates[0]
|
||||||
|
if first.quantity == 0:
|
||||||
|
first.quantity = 1
|
||||||
|
elif first.quantity > 1:
|
||||||
|
if not isinstance(rates, list):
|
||||||
|
rates = list(rates)
|
||||||
|
service = first.service
|
||||||
|
rate_class = type(first)
|
||||||
|
rates.insert(0,
|
||||||
|
rate_class(service=service, plan=first.plan, quantity=1, price=service.nominal_price)
|
||||||
|
)
|
||||||
|
return rates
|
||||||
|
|
||||||
|
|
||||||
def step_price(rates, metric):
|
def step_price(rates, metric):
|
||||||
# Step price
|
# Step price
|
||||||
group = []
|
group = []
|
||||||
minimal = (sys.maxint, [])
|
minimal = (sys.maxint, [])
|
||||||
for plan, rates in rates.group_by('plan').iteritems():
|
for plan, rates in rates.group_by('plan').iteritems():
|
||||||
|
rates = _prepend_missing(rates)
|
||||||
value, steps = _compute(rates, metric)
|
value, steps = _compute(rates, metric)
|
||||||
if plan.is_combinable:
|
if plan.is_combinable:
|
||||||
group.append(steps)
|
group.append(steps)
|
||||||
|
@ -102,7 +123,8 @@ def match_price(rates, metric):
|
||||||
candidates = []
|
candidates = []
|
||||||
selected = False
|
selected = False
|
||||||
prev = None
|
prev = None
|
||||||
for rate in rates.distinct():
|
rates = _prepend_missing(rates.distinct())
|
||||||
|
for rate in rates:
|
||||||
if prev:
|
if prev:
|
||||||
if prev.plan != rate.plan:
|
if prev.plan != rate.plan:
|
||||||
if not selected and prev.quantity <= metric:
|
if not selected and prev.quantity <= metric:
|
||||||
|
|
|
@ -12,3 +12,6 @@ SERVICES_SERVICE_DEFAUL_TAX = getattr(settings, 'ORDERS_SERVICE_DFAULT_TAX', 0)
|
||||||
|
|
||||||
|
|
||||||
SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH', 4)
|
SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH', 4)
|
||||||
|
|
||||||
|
|
||||||
|
SERVICES_ORDER_MODEL = getattr(settings, 'SERVICES_ORDER_MODEL', 'orders.Order')
|
||||||
|
|
|
@ -36,8 +36,9 @@ class DomainBillingTest(BaseBillingTest):
|
||||||
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)
|
||||||
domain_service, __ = MiscService.objects.get_or_create(name='domain .es', description='Domain .ES')
|
domain_service, __ = MiscService.objects.get_or_create(name='domain .es',
|
||||||
return Miscellaneous.objects.create(service=domain_service, description=domain_name, account=account)
|
description='Domain .ES')
|
||||||
|
return account.miscellaneous.create(service=domain_service, description=domain_name)
|
||||||
|
|
||||||
def test_domain(self):
|
def test_domain(self):
|
||||||
service = self.create_domain_service()
|
service = self.create_domain_service()
|
||||||
|
|
|
@ -98,6 +98,3 @@ class FTPBillingTest(BaseBillingTest):
|
||||||
order = account.orders.order_by('-id').first()
|
order = account.orders.order_by('-id').first()
|
||||||
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
|
|
||||||
|
|
|
@ -34,8 +34,9 @@ class JobBillingTest(BaseBillingTest):
|
||||||
if not account:
|
if not account:
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
description = 'Random Job %s' % random_ascii(10)
|
description = 'Random Job %s' % random_ascii(10)
|
||||||
service, __ = MiscService.objects.get_or_create(name='job', description=description, has_amount=True)
|
service, __ = MiscService.objects.get_or_create(name='job', description=description,
|
||||||
return Miscellaneous.objects.create(service=service, description=description, account=account, amount=amount)
|
has_amount=True)
|
||||||
|
return account.miscellaneous.create(service=service, description=description, amount=amount)
|
||||||
|
|
||||||
def test_job(self):
|
def test_job(self):
|
||||||
service = self.create_job_service()
|
service = self.create_job_service()
|
||||||
|
@ -48,5 +49,3 @@ class JobBillingTest(BaseBillingTest):
|
||||||
job = self.create_job(100, account=account)
|
job = self.create_job(100, account=account)
|
||||||
bill = account.orders.bill(new_open=True)[0]
|
bill = account.orders.bill(new_open=True)[0]
|
||||||
self.assertEqual(100*15, bill.get_total())
|
self.assertEqual(100*15, bill.get_total())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -156,4 +156,3 @@ class MailboxBillingTest(BaseBillingTest):
|
||||||
with freeze_time(now+relativedelta(months=6)):
|
with freeze_time(now+relativedelta(months=6)):
|
||||||
bills = service.orders.bill(new_open=True, **options)
|
bills = service.orders.bill(new_open=True, **options)
|
||||||
self.assertEqual([], bills)
|
self.assertEqual([], bills)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from orchestra.apps.miscellaneous.models import MiscService, Miscellaneous
|
from ...models import Service, Plan, ContractedPlan
|
||||||
|
|
||||||
from ...models import Service, Plan
|
|
||||||
|
|
||||||
from . import BaseBillingTest
|
from . import BaseBillingTest
|
||||||
|
|
||||||
|
@ -11,8 +9,8 @@ class PlanBillingTest(BaseBillingTest):
|
||||||
def create_plan_service(self):
|
def create_plan_service(self):
|
||||||
service = Service.objects.create(
|
service = Service.objects.create(
|
||||||
description="Association membership fee",
|
description="Association membership fee",
|
||||||
content_type=ContentType.objects.get_for_model(Miscellaneous),
|
content_type=ContentType.objects.get_for_model(ContractedPlan),
|
||||||
match="account.is_active and account.type == 'ASSOCIATION'",
|
match="contractedplan.plan.name == 'association_fee'",
|
||||||
billing_period=Service.ANUAL,
|
billing_period=Service.ANUAL,
|
||||||
billing_point=Service.FIXED_DATE,
|
billing_point=Service.FIXED_DATE,
|
||||||
is_fee=True,
|
is_fee=True,
|
||||||
|
@ -26,12 +24,28 @@ class PlanBillingTest(BaseBillingTest):
|
||||||
)
|
)
|
||||||
return service
|
return service
|
||||||
|
|
||||||
def create_plan(self):
|
def create_plan(self, account=None):
|
||||||
if not account:
|
if not account:
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
domain_name = '%s.es' % random_ascii(10)
|
plan, __ = Plan.objects.get_or_create(name='association_fee')
|
||||||
domain_service, __ = MiscService.objects.get_or_create(name='domain .es', description='Domain .ES')
|
return plan.contracts.create(account=account)
|
||||||
return Miscellaneous.objects.create(service=domain_service, description=domain_name, account=account)
|
|
||||||
|
def test_update_orders(self):
|
||||||
|
account = self.create_account()
|
||||||
|
account1 = self.create_account()
|
||||||
|
self.create_plan(account=account)
|
||||||
|
self.create_plan(account=account1)
|
||||||
|
service = self.create_plan_service()
|
||||||
|
self.assertEqual(0, service.orders.count())
|
||||||
|
service.update_orders()
|
||||||
|
self.assertEqual(2, service.orders.count())
|
||||||
|
|
||||||
def test_plan(self):
|
def test_plan(self):
|
||||||
pass
|
account = self.create_account()
|
||||||
|
service = self.create_plan_service()
|
||||||
|
self.create_plan(account=account)
|
||||||
|
bill = account.orders.bill().pop()
|
||||||
|
self.assertEqual(bill.FEE, bill.type)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO test price with multiple plans
|
||||||
|
|
|
@ -54,7 +54,8 @@ class BaseTrafficBillingTest(BaseBillingTest):
|
||||||
def report_traffic(self, account, value):
|
def report_traffic(self, account, value):
|
||||||
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=timezone.now())
|
MonitorData.objects.create(monitor='FTPTraffic', content_object=account.user,
|
||||||
|
value=value, date=timezone.now())
|
||||||
data = ResourceData.get_or_create(account, self.resource)
|
data = ResourceData.get_or_create(account, self.resource)
|
||||||
data.update()
|
data.update()
|
||||||
|
|
||||||
|
@ -85,11 +86,20 @@ class TrafficBillingTest(BaseTrafficBillingTest):
|
||||||
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
|
self.report_traffic(account1, 10**10)
|
||||||
|
self.report_traffic(account2, 10**10*5)
|
||||||
|
with freeze_time(timezone.now()+relativedelta(months=1)):
|
||||||
|
bill1 = account1.orders.bill().pop()
|
||||||
|
bill2 = account2.orders.bill().pop()
|
||||||
|
self.assertNotEqual(bill1.get_total(), bill2.get_total())
|
||||||
|
|
||||||
|
|
||||||
class TrafficPrepayBillingTest(BaseTrafficBillingTest):
|
class TrafficPrepayBillingTest(BaseTrafficBillingTest):
|
||||||
METRIC = "max((account.resources.traffic.used or 0) - getattr(account.miscellaneous.filter(is_active=True, service__name='traffic prepay').last(), 'amount', 0), 0)"
|
METRIC = ("max("
|
||||||
|
"(account.resources.traffic.used or 0) - "
|
||||||
|
"getattr(account.miscellaneous.filter(is_active=True, service__name='traffic prepay').last(), 'amount', 0)"
|
||||||
|
", 0)"
|
||||||
|
)
|
||||||
|
|
||||||
def create_prepay_service(self):
|
def create_prepay_service(self):
|
||||||
service = Service.objects.create(
|
service = Service.objects.create(
|
||||||
|
@ -114,8 +124,9 @@ class TrafficPrepayBillingTest(BaseTrafficBillingTest):
|
||||||
if not account:
|
if not account:
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
name = 'traffic prepay'
|
name = 'traffic prepay'
|
||||||
service, __ = MiscService.objects.get_or_create(name='traffic prepay', description='Traffic prepay', has_amount=True)
|
service, __ = MiscService.objects.get_or_create(name='traffic prepay',
|
||||||
return Miscellaneous.objects.create(service=service, description=name, account=account, amount=amount)
|
description='Traffic prepay', has_amount=True)
|
||||||
|
return account.miscellaneous.create(service=service, description=name, amount=amount)
|
||||||
|
|
||||||
def test_traffic_prepay(self):
|
def test_traffic_prepay(self):
|
||||||
service = self.create_traffic_service()
|
service = self.create_traffic_service()
|
||||||
|
|
|
@ -313,6 +313,47 @@ class HandlerTests(BaseTestCase):
|
||||||
self.assertEqual(decimal.Decimal('9.00'), results[0].price)
|
self.assertEqual(decimal.Decimal('9.00'), results[0].price)
|
||||||
self.assertEqual(30, results[0].quantity)
|
self.assertEqual(30, results[0].quantity)
|
||||||
|
|
||||||
|
def test_incomplete_rates(self):
|
||||||
|
service = self.create_ftp_service()
|
||||||
|
account = self.create_account()
|
||||||
|
superplan = Plan.objects.create(
|
||||||
|
name='SUPER', allow_multiple=False, is_combinable=True)
|
||||||
|
service.rates.create(plan=superplan, quantity=4, price=9)
|
||||||
|
service.rates.create(plan=superplan, quantity=10, price=1)
|
||||||
|
account.plans.create(plan=superplan)
|
||||||
|
results = service.get_rates(account, cache=False)
|
||||||
|
results = service.rate_method(results, 30)
|
||||||
|
rates = [
|
||||||
|
{'price': decimal.Decimal('10.00'), 'quantity': 3},
|
||||||
|
{'price': decimal.Decimal('9.00'), 'quantity': 6},
|
||||||
|
{'price': decimal.Decimal('1.00'), 'quantity': 21}
|
||||||
|
]
|
||||||
|
for rate, result in zip(rates, results):
|
||||||
|
self.assertEqual(rate['price'], result.price)
|
||||||
|
self.assertEqual(rate['quantity'], result.quantity)
|
||||||
|
|
||||||
|
def test_zero_rates(self):
|
||||||
|
service = self.create_ftp_service()
|
||||||
|
account = self.create_account()
|
||||||
|
superplan = Plan.objects.create(
|
||||||
|
name='SUPER', allow_multiple=False, is_combinable=True)
|
||||||
|
service.rates.create(plan=superplan, quantity=0, price=0)
|
||||||
|
service.rates.create(plan=superplan, quantity=3, price=10)
|
||||||
|
service.rates.create(plan=superplan, quantity=4, price=9)
|
||||||
|
service.rates.create(plan=superplan, quantity=10, price=1)
|
||||||
|
account.plans.create(plan=superplan)
|
||||||
|
results = service.get_rates(account, cache=False)
|
||||||
|
results = service.rate_method(results, 30)
|
||||||
|
rates = [
|
||||||
|
{'price': decimal.Decimal('0.00'), 'quantity': 2},
|
||||||
|
{'price': decimal.Decimal('10.00'), 'quantity': 1},
|
||||||
|
{'price': decimal.Decimal('9.00'), 'quantity': 6},
|
||||||
|
{'price': decimal.Decimal('1.00'), 'quantity': 21}
|
||||||
|
]
|
||||||
|
for rate, result in zip(rates, results):
|
||||||
|
self.assertEqual(rate['price'], result.price)
|
||||||
|
self.assertEqual(rate['quantity'], result.quantity)
|
||||||
|
|
||||||
def test_rates_allow_multiple(self):
|
def test_rates_allow_multiple(self):
|
||||||
service = self.create_ftp_service()
|
service = self.create_ftp_service()
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
|
@ -352,20 +393,3 @@ class HandlerTests(BaseTestCase):
|
||||||
for rate, result in zip(rates, results):
|
for rate, result in zip(rates, results):
|
||||||
self.assertEqual(rate['price'], result.price)
|
self.assertEqual(rate['price'], result.price)
|
||||||
self.assertEqual(rate['quantity'], result.quantity)
|
self.assertEqual(rate['quantity'], result.quantity)
|
||||||
|
|
||||||
def test_generate_bill_lines_with_compensation(self):
|
|
||||||
service = self.create_ftp_service()
|
|
||||||
account = self.create_account()
|
|
||||||
now = timezone.now().date()
|
|
||||||
order = Order(
|
|
||||||
cancelled_on=now,
|
|
||||||
billed_until=now+relativedelta.relativedelta(years=2)
|
|
||||||
)
|
|
||||||
order1 = Order()
|
|
||||||
orders = [order, order1]
|
|
||||||
lines = service.handler.generate_bill_lines(orders, account, commit=False)
|
|
||||||
print lines
|
|
||||||
print len(lines)
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
# TODO test incomplete rate 1 -> nominal_price 10 -> rate
|
|
||||||
|
|
Loading…
Reference in New Issue