added best_price rating method
This commit is contained in:
parent
b5ede03858
commit
8fdec05908
7
TODO.md
7
TODO.md
|
@ -355,4 +355,9 @@ resorce monitoring more efficient, less mem an better queries for calc current d
|
||||||
|
|
||||||
# best_price rating method
|
# best_price rating method
|
||||||
|
|
||||||
# select contact with one result: redirect
|
# bill this https://orchestra.pangea.org/admin/orders/order/8236/ should be already billed, <= vs <
|
||||||
|
# Convert rating method from function to PluginClass
|
||||||
|
# Tests can not run because django.db.utils.ProgrammingError: relation "accounts_account" does not exist
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ class Bill(models.Model):
|
||||||
subtotals = {}
|
subtotals = {}
|
||||||
lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)))
|
lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)))
|
||||||
for tax, total in lines.values_list('tax', 'totals'):
|
for tax, total in lines.values_list('tax', 'totals'):
|
||||||
subtotal, taxes = subtotals.get(tax, (0, 0))
|
subtotal, taxes = subtotals.get(tax) or (0, 0)
|
||||||
subtotal += total
|
subtotal += total
|
||||||
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
|
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
|
||||||
return subtotals
|
return subtotals
|
||||||
|
|
|
@ -146,8 +146,8 @@ def execute(scripts, serialize=False, async=None):
|
||||||
|
|
||||||
def collect(instance, action, **kwargs):
|
def collect(instance, action, **kwargs):
|
||||||
""" collect operations """
|
""" collect operations """
|
||||||
operations = kwargs.get('operations', OrderedSet())
|
operations = kwargs.get('operations') or OrderedSet()
|
||||||
route_cache = kwargs.get('route_cache', {})
|
route_cache = kwargs.get('route_cache') or {}
|
||||||
for backend_cls in ServiceBackend.get_backends():
|
for backend_cls in ServiceBackend.get_backends():
|
||||||
# Check if there exists a related instance to be executed for this backend and action
|
# Check if there exists a related instance to be executed for this backend and action
|
||||||
instances = []
|
instances = []
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra.utils.python import AttrDict
|
from orchestra.utils.python import AttrDict
|
||||||
|
|
||||||
|
|
||||||
def _compute(rates, metric):
|
def _compute_steps(rates, metric):
|
||||||
value = 0
|
value = 0
|
||||||
num = len(rates)
|
num = len(rates)
|
||||||
accumulated = 0
|
accumulated = 0
|
||||||
|
@ -65,18 +65,20 @@ def _prepend_missing(rates):
|
||||||
|
|
||||||
|
|
||||||
def step_price(rates, metric):
|
def step_price(rates, metric):
|
||||||
|
if rates.query.order_by != ['plan', 'quantity']:
|
||||||
|
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
|
||||||
# Step price
|
# Step price
|
||||||
group = []
|
group = []
|
||||||
minimal = (sys.maxsize, [])
|
minimal = (sys.maxsize, [])
|
||||||
for plan, rates in rates.group_by('plan').items():
|
for plan, rates in rates.group_by('plan').items():
|
||||||
rates = _prepend_missing(rates)
|
rates = _prepend_missing(rates)
|
||||||
value, steps = _compute(rates, metric)
|
value, steps = _compute_steps(rates, metric)
|
||||||
if plan.is_combinable:
|
if plan.is_combinable:
|
||||||
group.append(steps)
|
group.append(steps)
|
||||||
else:
|
else:
|
||||||
minimal = min(minimal, (value, steps), key=lambda v: v[0])
|
minimal = min(minimal, (value, steps), key=lambda v: v[0])
|
||||||
if len(group) == 1:
|
if len(group) == 1:
|
||||||
value, steps = _compute(rates, metric)
|
value, steps = _compute_steps(rates, metric)
|
||||||
minimal = min(minimal, (value, steps), key=lambda v: v[0])
|
minimal = min(minimal, (value, steps), key=lambda v: v[0])
|
||||||
elif len(group) > 1:
|
elif len(group) > 1:
|
||||||
# Merge
|
# Merge
|
||||||
|
@ -125,6 +127,8 @@ step_price.help_text = _("All rates with a quantity lower than the metric are ap
|
||||||
|
|
||||||
|
|
||||||
def match_price(rates, metric):
|
def match_price(rates, metric):
|
||||||
|
if rates.query.order_by != ['plan', 'quantity']:
|
||||||
|
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
|
||||||
candidates = []
|
candidates = []
|
||||||
selected = False
|
selected = False
|
||||||
prev = None
|
prev = None
|
||||||
|
@ -155,29 +159,37 @@ match_price.help_text = _("Only <b>the rate</b> with a) inmediate inferior metri
|
||||||
|
|
||||||
|
|
||||||
def best_price(rates, metric):
|
def best_price(rates, metric):
|
||||||
|
if rates.query.order_by != ['plan', 'quantity']:
|
||||||
|
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
|
||||||
candidates = []
|
candidates = []
|
||||||
selected = False
|
for plan, rates in rates.group_by('plan').items():
|
||||||
prev = None
|
rates = _prepend_missing(rates)
|
||||||
rates = _prepend_missing(rates.distinct())
|
plan_candidates = []
|
||||||
for rate in rates:
|
for rate in rates:
|
||||||
if prev:
|
if rate.quantity > metric:
|
||||||
if prev.plan != rate.plan:
|
break
|
||||||
if not selected and prev.quantity <= metric:
|
if plan_candidates:
|
||||||
candidates.append(prev)
|
plan_candidates[-1].barrier = rate.quantity
|
||||||
selected = False
|
plan_candidates.append(AttrDict(
|
||||||
if not selected and rate.quantity > metric:
|
price=rate.price,
|
||||||
if prev.quantity <= metric:
|
barrier=metric,
|
||||||
candidates.append(prev)
|
))
|
||||||
selected = True
|
candidates.extend(plan_candidates)
|
||||||
prev = rate
|
results = []
|
||||||
if not selected and prev.quantity <= metric:
|
accumulated = 0
|
||||||
candidates.append(prev)
|
for candidate in sorted(candidates, key=lambda c: c.price):
|
||||||
candidates.sort(key=lambda r: r.price)
|
if accumulated+candidate.barrier > metric:
|
||||||
if candidates:
|
quantity = metric - accumulated
|
||||||
return [AttrDict(**{
|
else:
|
||||||
'quantity': metric,
|
quantity = candidate.barrier
|
||||||
'price': candidates[0].price,
|
if quantity:
|
||||||
})]
|
if results and results[-1].price == candidate.price:
|
||||||
return None
|
results[-1].quantity += quantity
|
||||||
|
else:
|
||||||
|
results.append(AttrDict(**{
|
||||||
|
'quantity': quantity,
|
||||||
|
'price': candidate.price
|
||||||
|
}))
|
||||||
|
return results
|
||||||
best_price.verbose_name = _("Best price")
|
best_price.verbose_name = _("Best price")
|
||||||
best_price.help_text = _("Produces the best possible price given all active rating lines.")
|
best_price.help_text = _("Produces the best possible price given all active rating lines.")
|
||||||
|
|
|
@ -138,7 +138,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
def get_billing_point(self, order, bp=None, **options):
|
def get_billing_point(self, order, bp=None, **options):
|
||||||
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
|
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
|
||||||
if not_cachable or bp is None:
|
if not_cachable or bp is None:
|
||||||
bp = options.get('billing_point', timezone.now().date())
|
bp = options.get('billing_point') or timezone.now().date()
|
||||||
if not options.get('fixed_point'):
|
if not options.get('fixed_point'):
|
||||||
msg = ("Support for '%s' period and '%s' point is not implemented"
|
msg = ("Support for '%s' period and '%s' point is not implemented"
|
||||||
% (self.get_billing_period_display(), self.get_billing_point_display()))
|
% (self.get_billing_period_display(), self.get_billing_point_display()))
|
||||||
|
@ -501,7 +501,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
if charged is None:
|
if charged is None:
|
||||||
charged = metric
|
charged = metric
|
||||||
size = self.get_price_size(cini, cend)
|
size = self.get_price_size(cini, cend)
|
||||||
new_price += self.get_price(order, metric) * size
|
new_price += self.get_price(account, metric) * size
|
||||||
new_metric += metric
|
new_metric += metric
|
||||||
size = self.get_price_size(rini, bp)
|
size = self.get_price_size(rini, bp)
|
||||||
old_price = self.get_price(account, charged) * size
|
old_price = self.get_price(account, charged) * size
|
||||||
|
|
|
@ -228,7 +228,11 @@ class Service(models.Model):
|
||||||
return self.rates.by_account(account)
|
return self.rates.by_account(account)
|
||||||
if not hasattr(self, '__cached_rates'):
|
if not hasattr(self, '__cached_rates'):
|
||||||
self.__cached_rates = {}
|
self.__cached_rates = {}
|
||||||
rates = self.__cached_rates.get(account.id, self.rates.by_account(account))
|
try:
|
||||||
|
return self.__cached_rates[account.id]
|
||||||
|
except KeyError:
|
||||||
|
rates = self.rates.by_account(account)
|
||||||
|
self.__cached_rates[account.id] = rates
|
||||||
return rates
|
return rates
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Order(object):
|
||||||
last_id = 0
|
last_id = 0
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.registered_on = kwargs.get('registered_on', timezone.now().date())
|
self.registered_on = kwargs.get('registered_on') or timezone.now().date()
|
||||||
self.billed_until = kwargs.get('billed_until', None)
|
self.billed_until = kwargs.get('billed_until', None)
|
||||||
self.cancelled_on = kwargs.get('cancelled_on', None)
|
self.cancelled_on = kwargs.get('cancelled_on', None)
|
||||||
type(self).last_id += 1
|
type(self).last_id += 1
|
||||||
|
|
|
@ -27,7 +27,7 @@ def get_request_cache():
|
||||||
|
|
||||||
class RequestCacheMiddleware(object):
|
class RequestCacheMiddleware(object):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
cache = _request_cache.get(currentThread(), RequestCache())
|
cache = _request_cache.get(currentThread()) or RequestCache()
|
||||||
_request_cache[currentThread()] = cache
|
_request_cache[currentThread()] = cache
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue