[REF] product_pricelist, *: clean the price and rule computation methods

A new API is introduced for price computing using pricelists. It is simplier
to use in most use cases, which is computing the product price in a given
pricelist. Old methods return dictionnaries and tuples, which are rarely
used in the code.
This commit is contained in:
Thibault Delavallée
2016-08-01 15:05:32 +02:00
parent 0f0ca1c133
commit 77c9e76e82
9 changed files with 105 additions and 56 deletions
+2 -2
View File
@@ -492,7 +492,7 @@ class RepairLine(models.Model):
'message':
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
else:
price = pricelist.price_get(self.product_id.id, self.product_uom_qty, partner.id)[pricelist.id]
price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner)
if price is False:
warning = {
'title': _('No valid pricelist line found !'),
@@ -554,7 +554,7 @@ class RepairFee(models.Model):
'message':
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
else:
price = pricelist.price_get(self.product_id.id, self.product_uom_qty, partner.id)[pricelist.id]
price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner)
if price is False:
warning = {
'title': _('No valid pricelist line found !'),
+2 -2
View File
@@ -755,8 +755,8 @@ class PosOrderLine(models.Model):
raise UserError(
_('You have to select a pricelist in the sale form !\n'
'Please set one before choosing a product.'))
price = self.order_id.pricelist_id.price_get(
self.product_id.id, self.qty or 1.0, self.order_id.partner_id.id)[self.order_id.pricelist_id.id]
price = self.order_id.pricelist_id.get_product_price(
self.product_id, self.qty or 1.0, self.order_id.partner_id)
self._onchange_qty()
self.price_unit = price
self.tax_ids = self.product_id.taxes_id
+3 -2
View File
@@ -162,9 +162,10 @@ class product_product(osv.osv):
if isinstance(pricelist, (int, long)):
products = self.browse(cr, uid, ids, context=context)
qtys = map(lambda x: (x, quantity, partner), products)
quantities = [quantity] * len(products)
partners = [partner] * len(products)
pl = plobj.browse(cr, uid, pricelist, context=context)
price = plobj._price_get_multi(cr,uid, pl, qtys, context=context)
price = plobj.get_products_price(cr, uid, [pl.id], products, quantities, partners, context=context)
for id in ids:
res[id] = price.get(id, 0.0)
for id in ids:
+77 -31
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import namedtuple, defaultdict
from itertools import chain
import time
@@ -98,32 +99,44 @@ class product_pricelist(osv.osv):
'item_ids': _get_item_ids,
}
def price_rule_get_multi(self, cr, uid, ids, products_by_qty_by_partner, context=None):
"""multi products 'price_get'.
@param ids:
@param products_by_qty:
@param partner:
@param context: {
'date': Date of the pricelist (%Y-%m-%d),}
@return: a dict of dict with product_id as key and a dict 'price by pricelist' as value
"""
def _compute_price_rule_multi(self, cr, uid, ids, products_qty_partner, date=False, uom_id=False, context=None):
""" Low-level method - Multi pricelist, multi products
Returns: dict{product_id: dict{pricelist_id: (price, suitable_rule)} }"""
if not ids:
ids = self.pool.get('product.pricelist').search(cr, uid, [], context=context)
results = {}
for pricelist in self.browse(cr, uid, ids, context=context):
subres = self._price_rule_get_multi(cr, uid, pricelist, products_by_qty_by_partner, context=context)
subres = self._compute_price_rule(cr, uid, [pricelist.id], products_qty_partner, date=date, uom_id=uom_id, context=context)
for product_id, price in subres.items():
results.setdefault(product_id, {})
results[product_id][pricelist.id] = price
return results
def _price_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None):
return dict((key, price[0]) for key, price in self._price_rule_get_multi(cr, uid, pricelist, products_by_qty_by_partner, context=context).items())
def _compute_price_rule(self, cr, uid, ids, products_qty_partner, date=False, uom_id=False, context=None):
""" Low-level method - Mono pricelist, multi products
Returns: dict{product_id: (price, suitable_rule) for the given pricelist}
def _price_rule_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None):
context = context or {}
date = context.get('date') and context['date'][0:10] or time.strftime(DEFAULT_SERVER_DATE_FORMAT)
products = map(lambda x: x[0], products_by_qty_by_partner)
If date in context: Date of the pricelist (%Y-%m-%d)
:param products_qty_partner: list of typles products, quantity, partner
:param datetime date: validity date
:param ID uom_id: intermediate unit of measure
"""
context = context if context is not None else {}
if not date:
date = context.get('date', time.strftime(DEFAULT_SERVER_DATE_FORMAT))
if not uom_id and context.get('uom'):
uom_id = context['uom']
if uom_id:
# rebrowse with uom if given
product_ids = [item[0].id for item in products_qty_partner]
products = self.pool['product.product'].browse(cr, uid, product_ids, context=dict(context, uom=uom_id))
products_qty_partner = [(products[index], data_struct[1], data_struct[2]) for index, data_struct in enumerate(products_qty_partner)]
else:
products = [item[0] for item in products_qty_partner]
pricelist = self.browse(cr, uid, ids[0], context=context)
context = context if context is not None else {}
product_uom_obj = self.pool.get('product.uom')
if not products:
@@ -149,22 +162,23 @@ class product_pricelist(osv.osv):
# Load all rules
cr.execute(
'SELECT i.id '
'FROM product_pricelist_item AS i '
'LEFT JOIN product_category AS c '
'ON i.categ_id = c.id '
'WHERE (product_tmpl_id IS NULL OR product_tmpl_id = any(%s))'
'AND (product_id IS NULL OR product_id = any(%s))'
'AND (categ_id IS NULL OR categ_id = any(%s)) '
'AND (pricelist_id = %s) '
'AND ((i.date_start IS NULL OR i.date_start<=%s) AND (i.date_end IS NULL OR i.date_end>=%s))'
'ORDER BY applied_on, min_quantity desc, c.parent_left desc',
'SELECT item.id '
'FROM product_pricelist_item AS item '
'LEFT JOIN product_category AS categ '
'ON item.categ_id = categ.id '
'WHERE (item.product_tmpl_id IS NULL OR item.product_tmpl_id = any(%s))'
'AND (item.product_id IS NULL OR item.product_id = any(%s))'
'AND (item.categ_id IS NULL OR item.categ_id = any(%s)) '
'AND (item.pricelist_id = %s) '
'AND (item.date_start IS NULL OR item.date_start<=%s) '
'AND (item.date_end IS NULL OR item.date_end>=%s)'
'ORDER BY item.applied_on, item.min_quantity desc, categ.parent_left desc',
(prod_tmpl_ids, prod_ids, categ_ids, pricelist.id, date, date))
item_ids = [x[0] for x in cr.fetchall()]
items = self.pool.get('product.pricelist.item').browse(cr, uid, item_ids, context=context)
results = {}
for product, qty, partner in products_by_qty_by_partner:
for product, qty, partner in products_qty_partner:
results[product.id] = 0.0
suitable_rule = False
@@ -213,7 +227,7 @@ class product_pricelist(osv.osv):
continue
if rule.base == 'pricelist' and rule.base_pricelist_id:
price_tmp = self._price_get_multi(cr, uid, rule.base_pricelist_id, [(product, qty, partner)], context=context)[product.id]
price_tmp = self._compute_price_rule(cr, uid, [rule.base_pricelist_id.id], [(product, qty, partner)], context=context)[product.id][0] # TDE: 0 = price, 1 = rule
ptype_src = rule.base_pricelist_id.currency_id.id
price = self.pool['res.currency'].compute(cr, uid, ptype_src, pricelist.currency_id.id, price_tmp, round=False, context=context)
else:
@@ -255,16 +269,48 @@ class product_pricelist(osv.osv):
price = self.pool['res.currency'].compute(cr, uid, product.currency_id.id, pricelist.currency_id.id, price, round=False, context=context)
results[product.id] = (price, suitable_rule and suitable_rule.id or False)
return results
# New methods: product based
def get_products_price(self, cr, uid, ids, products, quantities, partners, date=False, uom_id=False, context=None):
""" For a given pricelist, return price for products
Returns: dict{product_id: product price}, in the given pricelist """
pricelist = self.browse(cr, uid, ids[0], context=context)
return dict((product_id, res_tuple[0]) for product_id, res_tuple in pricelist._compute_price_rule(zip(products, quantities, partners), date=date, uom_id=uom_id).iteritems())
def get_product_price(self, cr, uid, ids, product, quantity, partner, date=False, uom_id=False, context=None):
""" For a given pricelist, return price for a given product """
pricelist = self.browse(cr, uid, ids[0], context=context)
return pricelist._compute_price_rule([(product, quantity, partner)], date=date, uom_id=uom_id)[product.id][0]
def get_product_price_rule(self, cr, uid, ids, product, quantity, partner, date=False, uom_id=False, context=None):
""" For a given pricelist, return price and rule for a given product """
pricelist = self.browse(cr, uid, ids[0], context=context)
return pricelist._compute_price_rule([(product, quantity, partner)], date=date, uom_id=uom_id)[product.id]
# Compatibility to remove after v10 - DEPRECATED
def _price_rule_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None):
""" Low level method computing the result tuple for a given pricelist and multi products - return tuple """
return self._compute_price_rule(cr, uid, [pricelist.id], products_by_qty_by_partner, context=context)
def price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None):
""" Multi pricelist, mono product - returns price per pricelist """
return dict((key, price[0]) for key, price in self.price_rule_get(cr, uid, ids, prod_id, qty, partner=partner, context=context).items())
def price_rule_get_multi(self, cr, uid, ids, products_by_qty_by_partner, context=None):
""" Multi pricelist, multi product - return tuple """
return self._compute_price_rule_multi(cr, uid, ids, products_by_qty_by_partner, context=context)
def price_rule_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None):
""" Multi pricelist, mono product - return tuple """
product = self.pool.get('product.product').browse(cr, uid, prod_id, context=context)
res_multi = self.price_rule_get_multi(cr, uid, ids, products_by_qty_by_partner=[(product, qty, partner)], context=context)
res = res_multi[prod_id]
return res
return self._compute_price_rule_multi(cr, uid, ids, [(product, qty, partner)], context=context)[prod_id]
def _price_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None):
""" Mono pricelist, multi product - return price per product """
return self.get_products_price(cr, uid, [pricelist.id], zip(**products_by_qty_by_partner), context=context)
class product_pricelist_item(osv.osv):
_name = "product.pricelist.item"
+3 -2
View File
@@ -44,9 +44,10 @@ class product_template(osv.osv):
if isinstance(pricelist, (int, long)):
products = self.browse(cr, uid, ids, context=context)
qtys = map(lambda x: (x, quantity, partner), products)
quantities = [quantity] * len(products)
partners = [partner] * len(products)
pl = plobj.browse(cr, uid, pricelist, context=context)
price = plobj._price_get_multi(cr, uid, pl, qtys, context=context)
price = plobj.get_products_price(cr, uid, [pl.id], products, quantities, partners, context=context)
for id in ids:
res[id] = price.get(id, 0.0)
for id in ids:
+4 -3
View File
@@ -87,9 +87,10 @@ class product_pricelist(report_sxw.rml_parse):
def _get_price(self, pricelist_id, product_id, qty):
sale_price_digits = self.get_digits(dp='Product Price')
pricelist = self.pool.get('product.pricelist').browse(self.cr, self.uid, [pricelist_id], context=self.localcontext)[0]
price_dict = self.pool.get('product.pricelist').price_get(self.cr, self.uid, [pricelist_id], product_id, qty, context=self.localcontext)
if price_dict[pricelist_id]:
price = float_round(price_dict[pricelist_id], precision_digits=sale_price_digits)
product = self.pool['product.product'].browse(self.cr, self.uid, product_id, context=self.localcontext)
price = self.pool.get('product.pricelist').get_product_price(self.cr, self.uid, [pricelist_id], product, qty, False, context=self.localcontext)
if price:
price = float_round(price, precision_digits=sale_price_digits)
else:
res = self.pool.get('product.product').read(self.cr, self.uid, [product_id], ['list_price'])
price = float_round(res[0]['list_price'], precision_digits=sale_price_digits)
+5 -3
View File
@@ -86,6 +86,7 @@ class TestPricelist(TransactionCase):
'list_price': tonne_price,
'type': 'consu',
})
self.registry('product.pricelist.item').create(cr, uid, {
'pricelist_id': self.public_pricelist_id,
'sequence': 10,
@@ -98,9 +99,10 @@ class TestPricelist(TransactionCase):
pricelist_id = self.public_pricelist_id
def test_unit_price(qty, uom, expected_unit_price):
unit_price = self.registry('product.pricelist').price_get(cr, uid, [pricelist_id],
spam_id, qty,
context={'uom': uom})[pricelist_id]
spam = self.product_product.browse(cr, uid, spam_id, {'uom': uom})
unit_price = self.registry('product.pricelist').get_product_price(cr, uid, [pricelist_id],
spam, qty, False,
context={'uom': uom})
self.assertAlmostEqual(unit_price, expected_unit_price)
# Test prices - they are *per unit*, the quantity is only here to match the pricelist rules!
@@ -7,17 +7,16 @@ from openerp import api, fields, models
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
def _get_real_price_currency(self, product, pricelist_dict, qty, uom, pricelist_id):
def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id):
"""Retrieve the price before applying the pricelist
:param obj product: object of current product record
:parem float qty: total quentity of product
:param dict pricelist_dict: dict contain pricelist id and rule id
:param tuple price_and_rule: tuple(price, suitable_rule) coming from pricelist computation
:param obj uom: unit of measure of current order line
:param integer pricelist_id: pricelist id of sale order"""
PricelistItem = self.env['product.pricelist.item']
field_name = 'lst_price'
currency_id = None
rule_id = pricelist_dict[pricelist_id][1] if pricelist_dict.get(pricelist_id) else False
if rule_id:
pricelist_item = PricelistItem.browse(rule_id)
if pricelist_item.base == 'standard_price':
@@ -55,17 +54,16 @@ class SaleOrderLine(models.Model):
context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id)
pricelist_context = dict(context_partner, uom=self.product_uom.id, date=self.order_id.date_order)
list_price = self.order_id.pricelist_id.with_context(pricelist_context).price_rule_get(self.product_id.id, self.product_uom_qty or 1.0, self.order_id.partner_id)
new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, list_price, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id)
price, rule_id = self.order_id.pricelist_id.with_context(pricelist_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id)
new_list_price = self.env['account.tax']._fix_tax_included_price(new_list_price, self.product_id.taxes_id, self.tax_id)
list_price = list_price[self.order_id.pricelist_id.id][0]
if list_price != 0 and new_list_price != 0:
if price != 0 and new_list_price != 0:
if self.product_id.company_id and self.order_id.pricelist_id.currency_id != self.product_id.company_id.currency_id:
# new_list_price is in company's currency while price in pricelist currency
ctx = dict(context_partner, date=self.order_id.date_order)
new_list_price = self.env['res.currency'].browse(currency_id).with_context(ctx).compute(new_list_price, self.order_id.pricelist_id.currency_id)
discount = (new_list_price - list_price) / new_list_price * 100
discount = (new_list_price - price) / new_list_price * 100
if discount > 0:
self.discount = discount
+3 -3
View File
@@ -213,7 +213,7 @@ class sale_order(osv.osv):
if pricelist_id:
uom_context = context.copy()
uom_context['uom'] = line.product_uom_id.id
price = pricelist_obj.price_get(cr, uid, [pricelist_id], line.product_id.id, 1, context=uom_context)[pricelist_id]
price = pricelist_obj.get_product_price(cr, uid, [pricelist_id], line.product_id, 1, False, context=uom_context)
else:
price = line.price_unit
@@ -241,7 +241,7 @@ class sale_order(osv.osv):
if pricelist_id:
uom_context = context.copy()
uom_context['uom'] = option.uom_id.id
price = pricelist_obj.price_get(cr, uid, [pricelist_id], option.product_id.id, 1, context=uom_context)[pricelist_id]
price = pricelist_obj.get_product_price(cr, uid, [pricelist_id], option.product_id, 1, False, context=uom_context)
else:
price = option.price_unit
options.append((0, 0, {
@@ -461,7 +461,7 @@ class sale_order_option(osv.osv):
pricelist = self.order_id.pricelist_id
if pricelist and product:
partner_id = self.order_id.partner_id.id
self.price_unit = pricelist.with_context(uom=self.uom_id.id).price_get(product.id, self.quantity, partner_id)[pricelist.id]
self.price_unit = pricelist.with_context(uom=self.uom_id.id).get_product_price(product, self.quantity, partner_id)
domain = {'uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
return {'domain': domain}