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
|
||||
|
||||
# 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 = {}
|
||||
lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)))
|
||||
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
|
||||
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
|
||||
return subtotals
|
||||
|
|
|
@ -146,8 +146,8 @@ def execute(scripts, serialize=False, async=None):
|
|||
|
||||
def collect(instance, action, **kwargs):
|
||||
""" collect operations """
|
||||
operations = kwargs.get('operations', OrderedSet())
|
||||
route_cache = kwargs.get('route_cache', {})
|
||||
operations = kwargs.get('operations') or OrderedSet()
|
||||
route_cache = kwargs.get('route_cache') or {}
|
||||
for backend_cls in ServiceBackend.get_backends():
|
||||
# Check if there exists a related instance to be executed for this backend and action
|
||||
instances = []
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from orchestra.utils.python import AttrDict
|
||||
|
||||
|
||||
def _compute(rates, metric):
|
||||
def _compute_steps(rates, metric):
|
||||
value = 0
|
||||
num = len(rates)
|
||||
accumulated = 0
|
||||
|
@ -65,18 +65,20 @@ def _prepend_missing(rates):
|
|||
|
||||
|
||||
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
|
||||
group = []
|
||||
minimal = (sys.maxsize, [])
|
||||
for plan, rates in rates.group_by('plan').items():
|
||||
rates = _prepend_missing(rates)
|
||||
value, steps = _compute(rates, metric)
|
||||
value, steps = _compute_steps(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)
|
||||
value, steps = _compute_steps(rates, metric)
|
||||
minimal = min(minimal, (value, steps), key=lambda v: v[0])
|
||||
elif len(group) > 1:
|
||||
# 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):
|
||||
if rates.query.order_by != ['plan', 'quantity']:
|
||||
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
|
||||
candidates = []
|
||||
selected = False
|
||||
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):
|
||||
if rates.query.order_by != ['plan', 'quantity']:
|
||||
raise ValueError("rates queryset should be ordered by 'plan' and 'quantity'")
|
||||
candidates = []
|
||||
selected = False
|
||||
prev = None
|
||||
rates = _prepend_missing(rates.distinct())
|
||||
for rate in rates:
|
||||
if prev:
|
||||
if prev.plan != rate.plan:
|
||||
if not selected and prev.quantity <= metric:
|
||||
candidates.append(prev)
|
||||
selected = False
|
||||
if not selected and rate.quantity > metric:
|
||||
if prev.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)
|
||||
if candidates:
|
||||
return [AttrDict(**{
|
||||
'quantity': metric,
|
||||
'price': candidates[0].price,
|
||||
})]
|
||||
return None
|
||||
for plan, rates in rates.group_by('plan').items():
|
||||
rates = _prepend_missing(rates)
|
||||
plan_candidates = []
|
||||
for rate in rates:
|
||||
if rate.quantity > metric:
|
||||
break
|
||||
if plan_candidates:
|
||||
plan_candidates[-1].barrier = rate.quantity
|
||||
plan_candidates.append(AttrDict(
|
||||
price=rate.price,
|
||||
barrier=metric,
|
||||
))
|
||||
candidates.extend(plan_candidates)
|
||||
results = []
|
||||
accumulated = 0
|
||||
for candidate in sorted(candidates, key=lambda c: c.price):
|
||||
if accumulated+candidate.barrier > metric:
|
||||
quantity = metric - accumulated
|
||||
else:
|
||||
quantity = candidate.barrier
|
||||
if quantity:
|
||||
if results and results[-1].price == candidate.price:
|
||||
results[-1].quantity += quantity
|
||||
else:
|
||||
results.append(AttrDict(**{
|
||||
'quantity': quantity,
|
||||
'price': candidate.price
|
||||
}))
|
||||
return results
|
||||
best_price.verbose_name = _("Best price")
|
||||
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):
|
||||
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
|
||||
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'):
|
||||
msg = ("Support for '%s' period and '%s' point is not implemented"
|
||||
% (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:
|
||||
charged = metric
|
||||
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
|
||||
size = self.get_price_size(rini, bp)
|
||||
old_price = self.get_price(account, charged) * size
|
||||
|
|
|
@ -221,15 +221,19 @@ class Service(models.Model):
|
|||
counter += rate['quantity']
|
||||
if counter >= position:
|
||||
return decimal.Decimal(str(rate['price']))
|
||||
|
||||
|
||||
def get_rates(self, account, cache=True):
|
||||
# rates are cached per account
|
||||
if not cache:
|
||||
return self.rates.by_account(account)
|
||||
if not hasattr(self, '__cached_rates'):
|
||||
self.__cached_rates = {}
|
||||
rates = self.__cached_rates.get(account.id, self.rates.by_account(account))
|
||||
return rates
|
||||
try:
|
||||
return self.__cached_rates[account.id]
|
||||
except KeyError:
|
||||
rates = self.rates.by_account(account)
|
||||
self.__cached_rates[account.id] = rates
|
||||
return rates
|
||||
|
||||
@property
|
||||
def rate_method(self):
|
||||
|
|
|
@ -16,7 +16,7 @@ class Order(object):
|
|||
last_id = 0
|
||||
|
||||
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.cancelled_on = kwargs.get('cancelled_on', None)
|
||||
type(self).last_id += 1
|
||||
|
|
|
@ -27,7 +27,7 @@ def get_request_cache():
|
|||
|
||||
class RequestCacheMiddleware(object):
|
||||
def process_request(self, request):
|
||||
cache = _request_cache.get(currentThread(), RequestCache())
|
||||
cache = _request_cache.get(currentThread()) or RequestCache()
|
||||
_request_cache[currentThread()] = cache
|
||||
cache.clear()
|
||||
|
||||
|
|
Loading…
Reference in a new issue