[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:
@@ -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 !'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user