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):
|
def by_account(self, account):
|
||||||
# Default allways selected
|
# Default allways selected
|
||||||
qset = Q(plan='')
|
return self.filter(
|
||||||
for plan in account.plans.all():
|
Q(plan__is_default=True) |
|
||||||
qset |= Q(plan=plan)
|
Q(plan__contracts__account=account)
|
||||||
return self.filter(qset)
|
).order_by('plan', 'quantity').select_related('plan').distinct()
|
||||||
|
|
||||||
|
|
||||||
class Rate(models.Model):
|
class Rate(models.Model):
|
||||||
|
@ -89,10 +89,10 @@ class Service(models.Model):
|
||||||
COMPENSATE = 'COMPENSATE'
|
COMPENSATE = 'COMPENSATE'
|
||||||
PREPAY = 'PREPAY'
|
PREPAY = 'PREPAY'
|
||||||
POSTPAY = 'POSTPAY'
|
POSTPAY = 'POSTPAY'
|
||||||
STEPED_PRICE = 'STEPED_PRICE'
|
STEP_PRICE = 'STEP_PRICE'
|
||||||
MATCH_PRICE = 'MATCH_PRICE'
|
MATCH_PRICE = 'MATCH_PRICE'
|
||||||
RATE_METHODS = {
|
RATE_METHODS = {
|
||||||
STEPED_PRICE: rating.steped_price,
|
STEP_PRICE: rating.step_price,
|
||||||
MATCH_PRICE: rating.match_price,
|
MATCH_PRICE: rating.match_price,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,10 +153,10 @@ class Service(models.Model):
|
||||||
rate_algorithm = models.CharField(_("rate algorithm"), max_length=16,
|
rate_algorithm = models.CharField(_("rate algorithm"), max_length=16,
|
||||||
help_text=_("Algorithm used to interprete the rating table"),
|
help_text=_("Algorithm used to interprete the rating table"),
|
||||||
choices=(
|
choices=(
|
||||||
(STEPED_PRICE, _("Steped price")),
|
(STEP_PRICE, _("Step price")),
|
||||||
(MATCH_PRICE, _("Match price")),
|
(MATCH_PRICE, _("Match price")),
|
||||||
),
|
),
|
||||||
default=STEPED_PRICE)
|
default=STEP_PRICE)
|
||||||
# orders_effect = models.CharField(_("orders effect"), max_length=16,
|
# orders_effect = models.CharField(_("orders effect"), max_length=16,
|
||||||
# help_text=_("Defines the lookup behaviour when using orders for "
|
# help_text=_("Defines the lookup behaviour when using orders for "
|
||||||
# "the pricing rate computation of this service."),
|
# "the pricing rate computation of this service."),
|
||||||
|
@ -257,12 +257,19 @@ class Service(models.Model):
|
||||||
return self.billing_period
|
return self.billing_period
|
||||||
return self.pricing_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,
|
if position is provided an specific price for that position is returned,
|
||||||
accumulated price is returned otherwise
|
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
|
counter = 0
|
||||||
if position is None:
|
if position is None:
|
||||||
ant_counter = 0
|
ant_counter = 0
|
||||||
|
@ -281,25 +288,14 @@ class Service(models.Model):
|
||||||
if counter >= position:
|
if counter >= position:
|
||||||
return float(rate['price'])
|
return float(rate['price'])
|
||||||
|
|
||||||
|
def get_rates(self, account, cache=False):
|
||||||
def get_rates(self, account, metric):
|
# rates are cached per account
|
||||||
|
if not cache:
|
||||||
|
return self.rates.by_account(account)
|
||||||
if not hasattr(self, '__cached_rates'):
|
if not hasattr(self, '__cached_rates'):
|
||||||
self.__cached_rates = {}
|
self.__cached_rates = {}
|
||||||
if account.id in self.__cached_rates:
|
rates = self.__cached_rates.get(account.id, self.rates.by_account(account))
|
||||||
rates, cache = self.__cached_rates.get(account.id)
|
return rates
|
||||||
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)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rate_method(self):
|
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,
|
is_fee=False,
|
||||||
metric='',
|
metric='',
|
||||||
pricing_period=Service.BILLING_PERIOD,
|
pricing_period=Service.BILLING_PERIOD,
|
||||||
rate_algorithm=Service.STEPED_PRICE,
|
rate_algorithm=Service.STEP_PRICE,
|
||||||
# orders_effect=Service.CONCURRENT,
|
# orders_effect=Service.CONCURRENT,
|
||||||
on_cancel=Service.DISCOUNT,
|
on_cancel=Service.DISCOUNT,
|
||||||
payment_style=Service.PREPAY,
|
payment_style=Service.PREPAY,
|
||||||
|
@ -239,89 +239,100 @@ class OrderTests(BaseTestCase):
|
||||||
|
|
||||||
def get_rates(self, account):
|
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()
|
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):
|
def test_rates(self):
|
||||||
|
from ...models import Plan
|
||||||
|
import sys
|
||||||
|
from decimal import Decimal
|
||||||
service = self.create_service()
|
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=1, price=0)
|
||||||
service.rates.create(plan=superplan, quantity=3, price=10)
|
service.rates.create(plan=superplan, quantity=3, price=10)
|
||||||
service.rates.create(plan=superplan, quantity=4, price=9)
|
service.rates.create(plan=superplan, quantity=4, price=9)
|
||||||
service.rates.create(plan=superplan, quantity=10, price=1)
|
service.rates.create(plan=superplan, quantity=10, price=1)
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
account.plans.create(plan=superplan)
|
account.plans.create(plan=superplan)
|
||||||
result = service.get_rates(account, 1)
|
results = service.get_rates(account)
|
||||||
import sys
|
results = service.rate_method(results, 30)
|
||||||
from decimal import Decimal
|
|
||||||
rates = [
|
rates = [
|
||||||
{'price': Decimal('0.00'), 'quantity': 2},
|
{'price': Decimal('0.00'), 'quantity': 2},
|
||||||
{'price': Decimal('10.00'), 'quantity': 1},
|
{'price': Decimal('10.00'), 'quantity': 1},
|
||||||
{'price': Decimal('9.00'), 'quantity': 6},
|
{'price': Decimal('9.00'), 'quantity': 6},
|
||||||
{'price': Decimal('1.00'), 'quantity': sys.maxint}
|
{'price': Decimal('1.00'), 'quantity': 21}
|
||||||
]
|
]
|
||||||
self.assertEqual(rates, result)
|
for rate, result in zip(rates, results):
|
||||||
dupeplan = Plan.objects.create(name='DUPE', allow_multiple=True, is_combinable=False)
|
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=1, price=0)
|
||||||
service.rates.create(plan=dupeplan, quantity=3, price=9)
|
service.rates.create(plan=dupeplan, quantity=3, price=9)
|
||||||
result = service.get_rates(account, 1)
|
results = service.get_rates(account)
|
||||||
self.assertEqual(rates, result)
|
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)
|
account.plans.create(plan=dupeplan)
|
||||||
|
results = service.get_rates(account)
|
||||||
|
results = service.rate_method(results, 30)
|
||||||
rates = [
|
rates = [
|
||||||
{'price': Decimal('0.00'), 'quantity': 4},
|
{'price': Decimal('0.00'), 'quantity': 4},
|
||||||
{'price': Decimal('10.00'), 'quantity': 1},
|
{'price': Decimal('9.00'), 'quantity': 5},
|
||||||
{'price': Decimal('9.00'), 'quantity': 6},
|
{'price': Decimal('1.00'), 'quantity': 21},
|
||||||
{'price': Decimal('1.00'), 'quantity': sys.maxint}
|
|
||||||
]
|
]
|
||||||
result = service.get_rates(account, 1)
|
for rate, result in zip(rates, results):
|
||||||
print 'b', result
|
self.assertEqual(rate['price'], result.price)
|
||||||
self.assertEqual(rates, result)
|
self.assertEqual(rate['quantity'], result.quantity)
|
||||||
service.rates.create(plan='HYPER', quantity=1, price=10)
|
|
||||||
service.rates.create(plan='HYPER', quantity=5, price=0)
|
hyperplan = Plan.objects.create(name='HYPER', allow_multiple=True, is_combinable=False)
|
||||||
service.rates.create(plan='HYPER', quantity=6, price=10)
|
service.rates.create(plan=hyperplan, quantity=1, price=0)
|
||||||
account.plans.create(name='HYPER')
|
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 = [
|
rates = [
|
||||||
{'price': Decimal('0.00'), 'quantity': 4},
|
{'price': Decimal('0.00'), 'quantity': 19},
|
||||||
{'price': Decimal('10.00'), 'quantity': 1},
|
{'price': Decimal('5.00'), 'quantity': 11}
|
||||||
{'price': Decimal('0.00'), 'quantity': 1},
|
|
||||||
{'price': Decimal('9.00'), 'quantity': 6},
|
|
||||||
{'price': Decimal('1.00'), 'quantity': sys.maxint}
|
|
||||||
]
|
]
|
||||||
result = service.get_rates(account, 1)
|
for rate, result in zip(rates, results):
|
||||||
self.assertEqual(rates, result)
|
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):
|
# def test_ftp_account_1_year_fiexed(self):
|
||||||
# service = self.create_service()
|
# service = self.create_service()
|
||||||
# now = timezone.now().date()etb
|
# now = timezone.now().date()etb
|
||||||
|
|
Loading…
Reference in a new issue