Compute motherfucking rates
This commit is contained in:
parent
3c9b5a4c19
commit
97253d2d10
|
@ -1,85 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '__first__'),
|
||||
('contenttypes', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MetricStorage',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('value', models.BigIntegerField(verbose_name='value')),
|
||||
('created_on', models.DateField(auto_now_add=True, verbose_name='created on')),
|
||||
('updated_on', models.DateField(auto_now=True, verbose_name='updated on')),
|
||||
],
|
||||
options={
|
||||
'get_latest_by': 'created_on',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('object_id', models.PositiveIntegerField(null=True)),
|
||||
('registered_on', models.DateField(auto_now_add=True, verbose_name='registered on')),
|
||||
('cancelled_on', models.DateField(null=True, verbose_name='cancelled on', blank=True)),
|
||||
('billed_on', models.DateField(null=True, verbose_name='billed on', blank=True)),
|
||||
('billed_until', models.DateField(null=True, verbose_name='billed until', blank=True)),
|
||||
('ignore', models.BooleanField(default=False, verbose_name='ignore')),
|
||||
('description', models.TextField(verbose_name='description', blank=True)),
|
||||
('account', models.ForeignKey(related_name=b'orders', verbose_name='account', to='accounts.Account')),
|
||||
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Service',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('description', models.CharField(unique=True, max_length=256, verbose_name='description')),
|
||||
('match', models.CharField(max_length=256, verbose_name='match', blank=True)),
|
||||
('handler_type', models.CharField(blank=True, help_text='Handler used for processing this Service. A handler enables customized behaviour far beyond what options here allow to.', max_length=256, verbose_name='handler', choices=[(b'', 'Default')])),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='is active')),
|
||||
('billing_period', models.CharField(default=b'ANUAL', choices=[(b'', 'One time service'), (b'MONTHLY', 'Monthly billing'), (b'ANUAL', 'Anual billing')], max_length=16, blank=True, help_text='Renewal period for recurring invoicing', verbose_name='billing period')),
|
||||
('billing_point', models.CharField(default=b'ON_FIXED_DATE', help_text='Reference point for calculating the renewal date on recurring invoices', max_length=16, verbose_name='billing point', choices=[(b'ON_REGISTER', 'Registration date'), (b'ON_FIXED_DATE', 'Fixed billing date')])),
|
||||
('delayed_billing', models.CharField(default=b'ONE_MONTH', choices=[(b'', 'No delay (inmediate billing)'), (b'TEN_DAYS', 'Ten days'), (b'ONE_MONTH', 'One month')], max_length=16, blank=True, help_text='Period in which this service will be ignored for billing', verbose_name='delayed billing')),
|
||||
('is_fee', models.BooleanField(default=False, help_text='Designates whether this service should be billed as membership fee or not', verbose_name='is fee')),
|
||||
('metric', models.CharField(help_text='Metric used to compute the pricing rate. Number of orders is used when left blank.', max_length=256, verbose_name='metric', blank=True)),
|
||||
('tax', models.PositiveIntegerField(default=0, verbose_name='tax', choices=[(0, 'Duty free'), (7, '7%'), (21, '21%')])),
|
||||
('pricing_period', models.CharField(default=b'BILLING_PERIOD', help_text='Period used for calculating the metric used on the pricing rate', max_length=16, verbose_name='pricing period', choices=[(b'BILLING_PERIOD', 'Same as billing period'), (b'MONTHLY', 'Monthly data'), (b'ANUAL', 'Anual data')])),
|
||||
('rate_algorithm', models.CharField(default=b'BEST_PRICE', help_text='Algorithm used to interprete the rating table', max_length=16, verbose_name='rate algorithm', choices=[(b'BEST_PRICE', 'Best progressive price'), (b'PROGRESSIVE_PRICE', 'Conservative progressive price'), (b'MATCH_PRICE', 'Match price')])),
|
||||
('orders_effect', models.CharField(default=b'CONCURRENT', help_text='Defines the lookup behaviour when using orders for the pricing rate computation of this service.', max_length=16, verbose_name='orders effect', choices=[(b'REGISTER_OR_RENEW', 'Register or renew events'), (b'CONCURRENT', 'Active at every given time')])),
|
||||
('on_cancel', models.CharField(default=b'DISCOUNT', help_text='Defines the cancellation behaviour of this service', max_length=16, verbose_name='on cancel', choices=[(b'NOTHING', 'Nothing'), (b'DISCOUNT', 'Discount'), (b'COMPENSATE', 'Discount and compensate'), (b'REFOUND', 'Discount, compensate and refound')])),
|
||||
('payment_style', models.CharField(default=b'PREPAY', help_text='Designates whether this service should be paid after consumtion (postpay/on demand) or prepaid', max_length=16, verbose_name='payment style', choices=[(b'PREPAY', 'Prepay'), (b'POSTPAY', 'Postpay (on demand)')])),
|
||||
('trial_period', models.CharField(default=b'', choices=[(b'', 'No trial'), (b'TEN_DAYS', 'Ten days'), (b'ONE_MONTH', 'One month')], max_length=16, blank=True, help_text='Period in which no charge will be issued', verbose_name='trial period')),
|
||||
('refound_period', models.CharField(default=b'', choices=[(b'', 'Never refound'), (b'TEN_DAYS', 'Ten days'), (b'ONE_MONTH', 'One month'), (b'ALWAYS', 'Always refound')], max_length=16, blank=True, help_text='Period in which automatic refound will be performed on service cancellation', verbose_name='refound period')),
|
||||
('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='service',
|
||||
field=models.ForeignKey(related_name=b'orders', verbose_name='service', to='orders.Service'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='metricstorage',
|
||||
name='order',
|
||||
field=models.ForeignKey(verbose_name='order', to='orders.Order'),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='service',
|
||||
name='nominal_price',
|
||||
field=models.DecimalField(default=0.0, verbose_name='nominal price', max_digits=12, decimal_places=2),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -1,43 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '__first__'),
|
||||
('orders', '0002_service_nominal_price'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Plan',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(default=b'basic', max_length=128, verbose_name='plan', choices=[(b'basic', 'Basic'), (b'advanced', 'Advanced')])),
|
||||
('account', models.ForeignKey(related_name=b'plans', verbose_name='account', to='accounts.Account')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Rate',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('plan', models.CharField(blank=True, max_length=128, verbose_name='plan', choices=[(b'', 'default'), (b'basic', 'Basic'), (b'advanced', 'Advanced')])),
|
||||
('quantity', models.PositiveIntegerField(null=True, verbose_name='quantity', blank=True)),
|
||||
('value', models.DecimalField(verbose_name='value', max_digits=12, decimal_places=2)),
|
||||
('service', models.ForeignKey(related_name=b'rates', verbose_name='service', to='orders.Service')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='rate',
|
||||
unique_together=set([('service', 'plan', 'quantity')]),
|
||||
),
|
||||
]
|
|
@ -1,29 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0003_auto_20140908_1409'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='rate',
|
||||
name='value',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rate',
|
||||
name='price',
|
||||
field=models.DecimalField(default=1, verbose_name='price', max_digits=12, decimal_places=2),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rate',
|
||||
name='plan',
|
||||
field=models.CharField(blank=True, max_length=128, verbose_name='plan', choices=[(b'', 'Default'), (b'basic', 'Basic'), (b'advanced', 'Advanced')]),
|
||||
),
|
||||
]
|
|
@ -46,10 +46,10 @@ class RateQuerySet(models.QuerySet):
|
|||
|
||||
def by_account(self, account):
|
||||
# Default allways selected
|
||||
qset = Q(plan='')
|
||||
for plan in account.plans.all():
|
||||
qset |= Q(plan=plan)
|
||||
return self.filter(qset)
|
||||
return self.filter(
|
||||
Q(plan__is_default=True) |
|
||||
Q(plan__contracts__account=account)
|
||||
).order_by('plan', 'quantity').select_related('plan').distinct()
|
||||
|
||||
|
||||
class Rate(models.Model):
|
||||
|
@ -89,10 +89,10 @@ class Service(models.Model):
|
|||
COMPENSATE = 'COMPENSATE'
|
||||
PREPAY = 'PREPAY'
|
||||
POSTPAY = 'POSTPAY'
|
||||
STEPED_PRICE = 'STEPED_PRICE'
|
||||
STEP_PRICE = 'STEP_PRICE'
|
||||
MATCH_PRICE = 'MATCH_PRICE'
|
||||
RATE_METHODS = {
|
||||
STEPED_PRICE: rating.steped_price,
|
||||
STEP_PRICE: rating.step_price,
|
||||
MATCH_PRICE: rating.match_price,
|
||||
}
|
||||
|
||||
|
@ -153,10 +153,10 @@ class Service(models.Model):
|
|||
rate_algorithm = models.CharField(_("rate algorithm"), max_length=16,
|
||||
help_text=_("Algorithm used to interprete the rating table"),
|
||||
choices=(
|
||||
(STEPED_PRICE, _("Steped price")),
|
||||
(STEP_PRICE, _("Step price")),
|
||||
(MATCH_PRICE, _("Match price")),
|
||||
),
|
||||
default=STEPED_PRICE)
|
||||
default=STEP_PRICE)
|
||||
# orders_effect = models.CharField(_("orders effect"), max_length=16,
|
||||
# help_text=_("Defines the lookup behaviour when using orders for "
|
||||
# "the pricing rate computation of this service."),
|
||||
|
@ -257,12 +257,19 @@ class Service(models.Model):
|
|||
return self.billing_period
|
||||
return self.pricing_period
|
||||
|
||||
def get_price(self, order, metric, position=None):
|
||||
def get_price(self, order, metric, rates=None, position=None):
|
||||
"""
|
||||
if position is provided an specific price for that position is returned,
|
||||
accumulated price is returned otherwise
|
||||
"""
|
||||
rates = self.get_rates(order.account, metric)
|
||||
rates = self.get_rates(order.account)
|
||||
if not rates:
|
||||
rates = [{
|
||||
'quantity': metric,
|
||||
'price': self.nominal_price,
|
||||
}]
|
||||
else:
|
||||
rates = self.rate_method(rates, metric)
|
||||
counter = 0
|
||||
if position is None:
|
||||
ant_counter = 0
|
||||
|
@ -281,25 +288,14 @@ class Service(models.Model):
|
|||
if counter >= position:
|
||||
return float(rate['price'])
|
||||
|
||||
|
||||
def get_rates(self, account, metric):
|
||||
def get_rates(self, account, cache=False):
|
||||
# rates are cached per account
|
||||
if not cache:
|
||||
return self.rates.by_account(account)
|
||||
if not hasattr(self, '__cached_rates'):
|
||||
self.__cached_rates = {}
|
||||
if account.id in self.__cached_rates:
|
||||
rates, cache = self.__cached_rates.get(account.id)
|
||||
else:
|
||||
rates = self.rates.by_account(account)
|
||||
cache = {}
|
||||
if not rates:
|
||||
rates = [{
|
||||
'quantity': sys.maxint,
|
||||
'price': self.nominal_price,
|
||||
}]
|
||||
self.__cached_rates[account.id] = (rates, cache)
|
||||
return rates
|
||||
self.__cached_rates[account.id] = (rates, cache)
|
||||
# Caching depends on the specific rating method
|
||||
return self.rate_method(rates, metric, cache=cache)
|
||||
rates = self.__cached_rates.get(account.id, self.rates.by_account(account))
|
||||
return rates
|
||||
|
||||
@property
|
||||
def rate_method(self):
|
||||
|
|
108
orchestra/apps/orders/rating.py
Normal file
108
orchestra/apps/orders/rating.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
import sys
|
||||
|
||||
from orchestra.utils.python import AttributeDict
|
||||
|
||||
|
||||
def _compute(rates, metric):
|
||||
value = 0
|
||||
num = len(rates)
|
||||
accumulated = 0
|
||||
end = False
|
||||
ix = 0
|
||||
steps = []
|
||||
while ix < num and not end:
|
||||
if ix+1 == num:
|
||||
quantity = metric - accumulated
|
||||
else:
|
||||
quantity = rates[ix+1].quantity - rates[ix].quantity
|
||||
if accumulated+quantity > metric:
|
||||
quantity = metric - accumulated
|
||||
end = True
|
||||
price = rates[ix].price
|
||||
steps.append(AttributeDict(**{
|
||||
'quantity': quantity,
|
||||
'price': price,
|
||||
'barrier': accumulated+1,
|
||||
}))
|
||||
accumulated += quantity
|
||||
value += quantity*price
|
||||
ix += 1
|
||||
return value, steps
|
||||
|
||||
|
||||
def step_price(rates, metric):
|
||||
# Step price
|
||||
group = []
|
||||
minimal = (sys.maxint, [])
|
||||
for plan, rates in rates.group_by('plan'):
|
||||
value, steps = _compute(rates, metric)
|
||||
if plan.is_combinable:
|
||||
group.append(steps)
|
||||
else:
|
||||
minimal = min(minimal, (value, steps), key=lambda v: v[0])
|
||||
if len(group) == 1:
|
||||
value, steps = _compute(rates, metric)
|
||||
minimal = min(minimal, (value, steps), key=lambda v: v[0])
|
||||
elif len(group) > 1:
|
||||
# Merge
|
||||
steps = []
|
||||
for rates in group:
|
||||
steps += rates
|
||||
steps.sort(key=lambda s: s.price)
|
||||
result = []
|
||||
counter = 0
|
||||
value = 0
|
||||
ix = 0
|
||||
targets = []
|
||||
while counter < metric:
|
||||
barrier = steps[ix].barrier
|
||||
if barrier <= counter+1:
|
||||
price = steps[ix].price
|
||||
quantity = steps[ix].quantity
|
||||
if quantity + counter > metric:
|
||||
quantity = metric - counter
|
||||
else:
|
||||
for target in targets:
|
||||
if counter + quantity >= target:
|
||||
quantity = (counter+quantity+1) - target
|
||||
steps[ix].quantity -= quantity
|
||||
if not steps[ix].quantity:
|
||||
steps.pop(ix)
|
||||
break
|
||||
else:
|
||||
steps.pop(ix)
|
||||
counter += quantity
|
||||
value += quantity*price
|
||||
if result and result[-1].price == price:
|
||||
result[-1].quantity += quantity
|
||||
else:
|
||||
result.append(AttributeDict(quantity=quantity, price=price))
|
||||
ix = 0
|
||||
targets = []
|
||||
else:
|
||||
targets.append(barrier)
|
||||
ix += 1
|
||||
minimal = min(minimal, (value, result), key=lambda v: v[0])
|
||||
return minimal[1]
|
||||
|
||||
|
||||
def match_price(rates, metric):
|
||||
candidates = []
|
||||
selected = False
|
||||
prev = None
|
||||
for rate in rates:
|
||||
if prev and prev.plan != rate.plan:
|
||||
if not selected and prev.quantity <= metric:
|
||||
candidates.append(prev)
|
||||
selected = False
|
||||
if not selected and rate.quantity > metric:
|
||||
candidates.append(prev)
|
||||
selected = True
|
||||
prev = rate
|
||||
if not selected and prev.quantity <= metric:
|
||||
candidates.append(prev)
|
||||
candidates.sort(key=lambda r: r.price)
|
||||
return [AttributeDict(**{
|
||||
'quantity': metric,
|
||||
'price': candidates[0].price,
|
||||
})]
|
|
@ -37,7 +37,7 @@ class OrderTests(BaseTestCase):
|
|||
is_fee=False,
|
||||
metric='',
|
||||
pricing_period=Service.BILLING_PERIOD,
|
||||
rate_algorithm=Service.STEPED_PRICE,
|
||||
rate_algorithm=Service.STEP_PRICE,
|
||||
# orders_effect=Service.CONCURRENT,
|
||||
on_cancel=Service.DISCOUNT,
|
||||
payment_style=Service.PREPAY,
|
||||
|
@ -239,88 +239,99 @@ class OrderTests(BaseTestCase):
|
|||
|
||||
def get_rates(self, account):
|
||||
rates = self.rates.filter(Q(plan__is_default=True) | Q(plan__contracts__account=account)).order_by('plan', 'quantity').select_related('plan').disctinct()
|
||||
# match price
|
||||
candidates = []
|
||||
selected = False
|
||||
for rate in rates:
|
||||
if prev and prev.plan != rate.plan:
|
||||
if not selected:
|
||||
candidates.append(prev)
|
||||
selected = False
|
||||
if not selected and rate.quantity >= metric:
|
||||
candidates.append(rate)
|
||||
selected = True
|
||||
if not selected:
|
||||
candidates.append(prev)
|
||||
candidates.sort(key=lambda r: r.price)
|
||||
return candidates[0]
|
||||
|
||||
# Step price
|
||||
groups = []
|
||||
prev = None
|
||||
for rate in rates:
|
||||
if rate.quantity <= metric:
|
||||
if not prev or (not rate.is_combinable and prev.plan != rate.plan):
|
||||
groups.append([rate])
|
||||
else:
|
||||
groups[-1].append(rate)
|
||||
results = []
|
||||
for group in groups:
|
||||
ini = None
|
||||
for rate in group:
|
||||
if not ini:
|
||||
ini = rate.quantity
|
||||
|
||||
|
||||
return result
|
||||
|
||||
def test_rates(self):
|
||||
from ...models import Plan
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
service = self.create_service()
|
||||
superplan = Plan.objects.create(name='SUPER', allow_multiple=False, is_combinable=False)
|
||||
|
||||
superplan = Plan.objects.create(name='SUPER', allow_multiple=False, is_combinable=True)
|
||||
service.rates.create(plan=superplan, quantity=1, 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 = self.create_account()
|
||||
account.plans.create(plan=superplan)
|
||||
result = service.get_rates(account, 1)
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 30)
|
||||
rates = [
|
||||
{'price': Decimal('0.00'), 'quantity': 2},
|
||||
{'price': Decimal('10.00'), 'quantity': 1},
|
||||
{'price': Decimal('9.00'), 'quantity': 6},
|
||||
{'price': Decimal('1.00'), 'quantity': sys.maxint}
|
||||
{'price': Decimal('1.00'), 'quantity': 21}
|
||||
]
|
||||
self.assertEqual(rates, result)
|
||||
dupeplan = Plan.objects.create(name='DUPE', allow_multiple=True, is_combinable=False)
|
||||
for rate, result in zip(rates, results):
|
||||
self.assertEqual(rate['price'], result.price)
|
||||
self.assertEqual(rate['quantity'], result.quantity)
|
||||
|
||||
dupeplan = Plan.objects.create(name='DUPE', allow_multiple=True, is_combinable=True)
|
||||
service.rates.create(plan=dupeplan, quantity=1, price=0)
|
||||
service.rates.create(plan=dupeplan, quantity=3, price=9)
|
||||
result = service.get_rates(account, 1)
|
||||
self.assertEqual(rates, result)
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 30)
|
||||
for rate, result in zip(rates, results):
|
||||
self.assertEqual(rate['price'], result.price)
|
||||
self.assertEqual(rate['quantity'], result.quantity)
|
||||
|
||||
account.plans.create(plan=dupeplan)
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 30)
|
||||
rates = [
|
||||
{'price': Decimal('0.00'), 'quantity': 4},
|
||||
{'price': Decimal('10.00'), 'quantity': 1},
|
||||
{'price': Decimal('9.00'), 'quantity': 6},
|
||||
{'price': Decimal('1.00'), 'quantity': sys.maxint}
|
||||
{'price': Decimal('9.00'), 'quantity': 5},
|
||||
{'price': Decimal('1.00'), 'quantity': 21},
|
||||
]
|
||||
result = service.get_rates(account, 1)
|
||||
print 'b', result
|
||||
self.assertEqual(rates, result)
|
||||
service.rates.create(plan='HYPER', quantity=1, price=10)
|
||||
service.rates.create(plan='HYPER', quantity=5, price=0)
|
||||
service.rates.create(plan='HYPER', quantity=6, price=10)
|
||||
account.plans.create(name='HYPER')
|
||||
for rate, result in zip(rates, results):
|
||||
self.assertEqual(rate['price'], result.price)
|
||||
self.assertEqual(rate['quantity'], result.quantity)
|
||||
|
||||
hyperplan = Plan.objects.create(name='HYPER', allow_multiple=True, is_combinable=False)
|
||||
service.rates.create(plan=hyperplan, quantity=1, price=0)
|
||||
service.rates.create(plan=hyperplan, quantity=20, price=5)
|
||||
account.plans.create(plan=hyperplan)
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 30)
|
||||
rates = [
|
||||
{'price': Decimal('0.00'), 'quantity': 4},
|
||||
{'price': Decimal('10.00'), 'quantity': 1},
|
||||
{'price': Decimal('0.00'), 'quantity': 1},
|
||||
{'price': Decimal('9.00'), 'quantity': 6},
|
||||
{'price': Decimal('1.00'), 'quantity': sys.maxint}
|
||||
{'price': Decimal('0.00'), 'quantity': 19},
|
||||
{'price': Decimal('5.00'), 'quantity': 11}
|
||||
]
|
||||
result = service.get_rates(account, 1)
|
||||
self.assertEqual(rates, result)
|
||||
for rate, result in zip(rates, results):
|
||||
self.assertEqual(rate['price'], result.price)
|
||||
self.assertEqual(rate['quantity'], result.quantity)
|
||||
hyperplan.is_combinable = True
|
||||
hyperplan.save()
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 30)
|
||||
rates = [
|
||||
{'price': Decimal('0.00'), 'quantity': 23},
|
||||
{'price': Decimal('1.00'), 'quantity': 7}
|
||||
]
|
||||
for rate, result in zip(rates, results):
|
||||
self.assertEqual(rate['price'], result.price)
|
||||
self.assertEqual(rate['quantity'], result.quantity)
|
||||
|
||||
service.rate_algorithm = service.MATCH_PRICE
|
||||
service.save()
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 30)
|
||||
self.assertEqual(1, len(results))
|
||||
self.assertEqual(Decimal('1.00'), results[0].price)
|
||||
self.assertEqual(30, results[0].quantity)
|
||||
|
||||
hyperplan.delete()
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 8)
|
||||
self.assertEqual(1, len(results))
|
||||
self.assertEqual(Decimal('9.00'), results[0].price)
|
||||
self.assertEqual(8, results[0].quantity)
|
||||
|
||||
superplan.delete()
|
||||
results = service.get_rates(account)
|
||||
results = service.rate_method(results, 30)
|
||||
self.assertEqual(1, len(results))
|
||||
self.assertEqual(Decimal('9.00'), results[0].price)
|
||||
self.assertEqual(30, results[0].quantity)
|
||||
|
||||
# def test_ftp_account_1_year_fiexed(self):
|
||||
# service = self.create_service()
|
||||
|
|
Loading…
Reference in a new issue