Merge lp:~lin-yu/purchase-wkfl/add_purchase_price_list into lp:~purchase-core-editors/purchase-wkfl/7.0

Proposed by LIN Yu
Status: Work in progress
Proposed branch: lp:~lin-yu/purchase-wkfl/add_purchase_price_list
Merge into: lp:~purchase-core-editors/purchase-wkfl/7.0
Diff against target: 518 lines (+488/-0)
5 files modified
purchase_price_list_item/__init__.py (+23/-0)
purchase_price_list_item/__openerp__.py (+52/-0)
purchase_price_list_item/i18n/purchase_price_list_item.po (+33/-0)
purchase_price_list_item/purchase.py (+332/-0)
purchase_price_list_item/purchase_view.xml (+48/-0)
To merge this branch: bzr merge lp:~lin-yu/purchase-wkfl/add_purchase_price_list
Reviewer Review Type Date Requested Status
Pedro Manuel Baeza Needs Resubmitting
Joël Grand-Guillaume @ camptocamp code review, no tests Disapprove
Maxime Chambreuil (http://www.savoirfairelinux.com) code review Needs Fixing
Review via email: mp+180796@code.launchpad.net

Description of the change

[ADD] purchase_price_list_item

Improve purchase price managment
================================

    * In Purchase List Item, the price is fixed based on price_surchage if base is 'fixed on UOP'
    * If 'fixed on UOP', if product UOP change, the price list price will be change automtically.
    * Add field 'Qty on Hand', and 'Stock Values' for product
    * Add field 'Qty on Hand', 'Stock Values', UOP in product list view

To post a comment you must log in.
Revision history for this message
Maxime Chambreuil (http://www.savoirfairelinux.com) (max3903) wrote :

Thanks for your contribution Yu.

l163: please use orm.Model instead of osv.osv. Update the import accordingly.
l208, 463: no need to instantiate
l417: 2 lines are enough to comply with PEP8. Run flake8 to check for PEP8 issues

review: Needs Fixing (code review)
Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote :

Hi Lin,

Thanks for this contribs ! Do you really need to override the whole function "price_get_multi" without calling super() ?

This can lead in very unexpected result so for now I marks it as Disapprove for that reason. Other module that may override this method can face trouble due to that...

Thanks for your understanding or explanation if I do missed something !

Regards,

review: Disapprove (code review, no tests)
Revision history for this message
Pedro Manuel Baeza (pedro.baeza) wrote :

This project is now hosted on https://github.com/OCA/purchase-workflow. Please move your proposal there. This guide may help you https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub

review: Needs Resubmitting

Unmerged revisions

16. By LIN Yu

[ADD] module purchase_price_list_item

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'purchase_price_list_item'
=== added file 'purchase_price_list_item/__init__.py'
--- purchase_price_list_item/__init__.py 1970-01-01 00:00:00 +0000
+++ purchase_price_list_item/__init__.py 2013-08-19 07:32:33 +0000
@@ -0,0 +1,23 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (c) 2010-2013 Elico Corp. All Rights Reserved.
6# Author: Jean LELIEVRE <jean.lelievre@elico-corp.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23import purchase
0\ No newline at end of file24\ No newline at end of file
125
=== added file 'purchase_price_list_item/__openerp__.py'
--- purchase_price_list_item/__openerp__.py 1970-01-01 00:00:00 +0000
+++ purchase_price_list_item/__openerp__.py 2013-08-19 07:32:33 +0000
@@ -0,0 +1,52 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (c) 2010-2013 Elico Corp. All Rights Reserved.
6# Author: Jean LELIEVRE <jean.lelievre@elico-corp.com>
7# Andy LU<andy.lu@elico-corp.com>
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU Affero General Public License as
11# published by the Free Software Foundation, either version 3 of the
12# License, or (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU Affero General Public License for more details.
18#
19# You should have received a copy of the GNU Affero General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21#
22##############################################################################
23
24{
25 'name': 'Purchase Price List Item',
26 'version': '1.0',
27 'category': 'Purchase',
28 'sequence': 19,
29 'summary': 'Purchase Price List Item',
30 'description': """
31Improve purchase price managment
32================================
33
34 * In Purchase List Item, the price is fixed based on price_surchage if base is 'fixed on UOP'
35 * If 'fixed on UOP', if product UOP change, the price list price will be change automtically.
36 * Add field 'Qty on Hand', and 'Stock Values' for product
37 * Add field 'Qty on Hand', 'Stock Values', UOP in product list view
38
39 """,
40 'author': 'Elico Corp',
41 'website': 'http://www.elico-corp.com',
42 'images' : [],
43 'depends': ['purchase'],
44 'data': [
45 'purchase_view.xml',
46 ],
47 'test': [],
48 'demo': [],
49 'installable': True,
50 'auto_install': False,
51 'application': False,
52}
0\ No newline at end of file53\ No newline at end of file
154
=== added directory 'purchase_price_list_item/i18n'
=== added file 'purchase_price_list_item/i18n/purchase_price_list_item.po'
--- purchase_price_list_item/i18n/purchase_price_list_item.po 1970-01-01 00:00:00 +0000
+++ purchase_price_list_item/i18n/purchase_price_list_item.po 2013-08-19 07:32:33 +0000
@@ -0,0 +1,33 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * purchase_price_list_item
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-08-14 09:03+0000\n"
10"PO-Revision-Date: 2013-08-14 09:03+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: purchase_price_list_item
19#: code:addons/purchase_price_list_item/purchase.py:34
20#, python-format
21msgid "Fix Price based on UoP"
22msgstr ""
23
24#. module: purchase_price_list_item
25#: model:ir.model,name:purchase_price_list_item.model_product_pricelist_item
26msgid "Pricelist item"
27msgstr ""
28
29#. module: purchase_price_list_item
30#: field:product.pricelist.item,uom_id:0
31msgid "UoM for Fix Price"
32msgstr ""
33
034
=== added file 'purchase_price_list_item/purchase.py'
--- purchase_price_list_item/purchase.py 1970-01-01 00:00:00 +0000
+++ purchase_price_list_item/purchase.py 2013-08-19 07:32:33 +0000
@@ -0,0 +1,332 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (c) 2010-2013 Elico Corp. All Rights Reserved.
6# Author: Jean LELIEVRE <jean.lelievre@elico-corp.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from osv import osv, fields
24from tools.translate import _
25
26import openerp.addons.decimal_precision as dp
27
28import time
29from product._common import rounding
30
31
32class product_pricelist_item(osv.osv):
33 _inherit = "product.pricelist.item"
34
35 def _price_field_get(self, cr, uid, context=None):
36 result = super(product_pricelist_item, self)._price_field_get(cr, uid, context=context)
37
38 result.append((-3, _('Fix Price based on UoP')))
39 return result
40
41 _columns = {
42 'base': fields.selection(_price_field_get, 'Based on',required=True, size=-1, help="Base price for computation."),
43 'uom_id': fields.many2one('product.uom','UoM for Fix Price',store=True),
44 }
45 _defaults = {
46 'price_discount': lambda *a: -1,
47 'base': lambda *a: -3,
48 }
49
50 def product_id_change(self,cr,uid,ids,product_id,context=None):
51 if not product_id:
52 return{}
53 prod = self.pool.get('product.product').browse(cr,uid,product_id,context=context)
54
55 if prod.default_code:
56 return {'value': {'name': prod.default_code,'uom_id': prod.uom_po_id.id}}
57 return{}
58
59 def create(self,cr,uid,vals,context=None):
60 if not vals.get('uom_id',False) and vals.get('product_id', False) and vals.get('base',False) == -3:
61 prod = self.pool.get('product.product').browse(cr,uid,vals.get('product_id',False),context=context)
62 vals['uom_id'] = prod.uom_po_id.id
63
64 return super(product_pricelist_item,self).create(cr,uid,vals,context=context)
65
66 def write(self, cr, uid, ids, vals, context=None):
67 if type(ids) != type([]):
68 ids = [ids]
69 if not vals.get('uom_id', False) and ids:
70 pl_item_obj = self.pool.get('product.pricelist.item')
71 for id in ids:
72 item = pl_item_obj.browse(cr, uid, id, context=context)
73 if item.base == -3 and item.product_id:
74 vals['uom_id'] = item.product_id.uom_po_id.id
75 return super(product_pricelist_item, self).write(cr, uid, ids, vals, context=context)
76
77product_pricelist_item()
78
79
80class product_pricelist(osv.osv):
81 _inherit = "product.pricelist"
82
83 #def price_get_multi(self, cr, uid, product_ids, context=None):
84 def price_get_multi(self, cr, uid, pricelist_ids, products_by_qty_by_partner, context=None):
85 """multi products 'price_get'.
86 @param pricelist_ids:
87 @param products_by_qty:
88 @param partner:
89 @param context: {
90 'date': Date of the pricelist (%Y-%m-%d),}
91 @return: a dict of dict with product_id as key and a dict 'price by pricelist' as value
92 """
93
94 def _create_parent_category_list(id, lst):
95 if not id:
96 return []
97 parent = product_category_tree.get(id)
98 if parent:
99 lst.append(parent)
100 return _create_parent_category_list(parent, lst)
101 else:
102 return lst
103 # _create_parent_category_list
104
105 if context is None:
106 context = {}
107
108 date = time.strftime('%Y-%m-%d')
109 if 'date' in context:
110 date = context['date']
111
112 currency_obj = self.pool.get('res.currency')
113 product_obj = self.pool.get('product.product')
114 product_category_obj = self.pool.get('product.category')
115 product_uom_obj = self.pool.get('product.uom')
116 supplierinfo_obj = self.pool.get('product.supplierinfo')
117 price_type_obj = self.pool.get('product.price.type')
118
119 # product.pricelist.version:
120 if not pricelist_ids:
121 pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [], context=context)
122
123 pricelist_version_ids = self.pool.get('product.pricelist.version').search(cr, uid, [
124 ('pricelist_id', 'in', pricelist_ids),
125 '|',
126 ('date_start', '=', False),
127 ('date_start', '<=', date),
128 '|',
129 ('date_end', '=', False),
130 ('date_end', '>=', date),
131 ])
132 if len(pricelist_ids) != len(pricelist_version_ids):
133 raise osv.except_osv(_('Warning!'), _("At least one pricelist has no active version !\nPlease create or activate one."))
134
135 # product.product:
136 product_ids = [i[0] for i in products_by_qty_by_partner]
137 #products = dict([(item['id'], item) for item in product_obj.read(cr, uid, product_ids, ['categ_id', 'product_tmpl_id', 'uos_id', 'uom_id'])])
138 products = product_obj.browse(cr, uid, product_ids, context=context)
139 products_dict = dict([(item.id, item) for item in products])
140
141 # product.category:
142 product_category_ids = product_category_obj.search(cr, uid, [])
143 product_categories = product_category_obj.read(cr, uid, product_category_ids, ['parent_id'])
144 product_category_tree = dict([(item['id'], item['parent_id'][0]) for item in product_categories if item['parent_id']])
145
146 results = {}
147 for product_id, qty, partner in products_by_qty_by_partner:
148 for pricelist_id in pricelist_ids:
149 price = False
150
151 tmpl_id = products_dict[product_id].product_tmpl_id and products_dict[product_id].product_tmpl_id.id or False
152
153 categ_id = products_dict[product_id].categ_id and products_dict[product_id].categ_id.id or False
154 categ_ids = _create_parent_category_list(categ_id, [categ_id])
155 if categ_ids:
156 categ_where = '(categ_id IN (' + ','.join(map(str, categ_ids)) + '))'
157 else:
158 categ_where = '(categ_id IS NULL)'
159
160 if partner:
161 partner_where = 'base <> -2 OR %s IN (SELECT name FROM product_supplierinfo WHERE product_id = %s) '
162 partner_args = (partner, tmpl_id)
163 else:
164 partner_where = 'base <> -2 '
165 partner_args = ()
166
167 #And
168 pl = self.pool.get('product.pricelist').browse(cr, uid, pricelist_id, context=context)
169 if pl.type == "purchase":
170 product = products_dict[product_id]
171 if 'uom' in context:
172 uom = product.uom_po_id
173 if uom.id != context['uom']:
174 qty = qty * product.uom_po_id.factor / product.uom_id.factor
175
176 cr.execute(
177 'SELECT i.*, pl.currency_id '
178 'FROM product_pricelist_item AS i, '
179 'product_pricelist_version AS v, product_pricelist AS pl '
180 'WHERE (product_tmpl_id IS NULL OR product_tmpl_id = %s) '
181 'AND (product_id IS NULL OR product_id = %s) '
182 'AND (' + categ_where + ' OR (categ_id IS NULL)) '
183 'AND (' + partner_where + ') '
184 'AND price_version_id = %s '
185 'AND (min_quantity IS NULL OR min_quantity <= %s) '
186 'AND i.price_version_id = v.id AND v.pricelist_id = pl.id '
187 'ORDER BY sequence',
188 (tmpl_id, product_id) + partner_args + (pricelist_version_ids[0], qty))
189 res1 = cr.dictfetchall()
190 uom_price_already_computed = False
191 for res in res1:
192 if res:
193 if res['base'] == -1:
194 if not res['base_pricelist_id']:
195 price = 0.0
196 else:
197 price_tmp = self.price_get(cr, uid,
198 [res['base_pricelist_id']], product_id,
199 qty, context=context)[res['base_pricelist_id']]
200 ptype_src = self.browse(cr, uid, res['base_pricelist_id']).currency_id.id
201 uom_price_already_computed = True
202 price = currency_obj.compute(cr, uid, ptype_src, res['currency_id'], price_tmp, round=False)
203 elif res['base'] == -2:
204 # this section could be improved by moving the queries outside the loop:
205 where = []
206 if partner:
207 where = [('name', '=', partner) ]
208 sinfo = supplierinfo_obj.search(cr, uid,
209 [('product_id', '=', tmpl_id)] + where)
210 price = 0.0
211 if sinfo:
212 qty_in_product_uom = qty
213 product_default_uom = product_obj.read(cr, uid, [product_id], ['uom_id'])[0]['uom_id'][0]
214 supplier = supplierinfo_obj.browse(cr, uid, sinfo, context=context)[0]
215 seller_uom = supplier.product_uom and supplier.product_uom.id or False
216 if seller_uom and product_default_uom and product_default_uom != seller_uom:
217 uom_price_already_computed = True
218 qty_in_product_uom = product_uom_obj._compute_qty(cr, uid, product_default_uom, qty, to_uom_id=seller_uom)
219 cr.execute('SELECT * ' \
220 'FROM pricelist_partnerinfo ' \
221 'WHERE suppinfo_id IN %s' \
222 'AND min_quantity <= %s ' \
223 'ORDER BY min_quantity DESC LIMIT 1', (tuple(sinfo), qty_in_product_uom,))
224 res2 = cr.dictfetchone()
225 if res2:
226 price = res2['price']
227 #Add by Andy
228 elif res['base'] == -3:
229 price = False
230 else:
231 price_type = price_type_obj.browse(cr, uid, int(res['base']))
232 uom_price_already_computed = True
233 price = currency_obj.compute(cr, uid,
234 price_type.currency_id.id, res['currency_id'],
235 product_obj.price_get(cr, uid, [product_id],
236 price_type.field, context=context)[product_id], round=False, context=context)
237
238 if price is not False:
239 price_limit = price
240 price = price * (1.0 + (res['price_discount'] or 0.0))
241 price = rounding(price, res['price_round']) #TOFIX: rounding with tools.float_rouding
242 price += (res['price_surcharge'] or 0.0)
243 if res['price_min_margin']:
244 price = max(price, price_limit + res['price_min_margin'])
245 if res['price_max_margin']:
246 price = min(price, price_limit + res['price_max_margin'])
247 break
248 #Add by Andy
249 else:
250 if res['base'] == -3:
251 price = res['price_surcharge'] or 0.0
252 product = products_dict[product_id]
253 if 'uom' in context:
254 uom = product.uom_po_id
255 if uom.id != context['uom']:
256 price = product_uom_obj._compute_price(cr, uid, uom.id, price, context['uom'])
257 uom_price_already_computed = True
258 #Todo: # Use company currency?
259 if 'currency_id' in context:
260 price = currency_obj.compute(cr, uid, 1,
261 context['currency_id'], price, context=context)
262 if price:
263 break
264 else:
265 # False means no valid line found ! But we may not raise an
266 # exception here because it breaks the search
267 price = False
268
269 if price:
270 results['item_id'] = res['id']
271 if 'uom' in context and not uom_price_already_computed:
272 product = products_dict[product_id]
273 uom = product.uos_id or product.uom_id
274 price = product_uom_obj._compute_price(cr, uid, uom.id, price, context['uom'])
275
276 if results.get(product_id):
277 results[product_id][pricelist_id] = price
278 else:
279 results[product_id] = {pricelist_id: price}
280
281 return results
282
283product_pricelist()
284
285
286
287class product_product(osv.osv):
288 _name = "product.product"
289 _inherit = "product.product"
290
291 def _qty_uop(self, cr, uid, ids, name, arg, context=None):
292 res = {}
293
294 uom_obj = self.pool.get('product.uom')
295 for id in ids:
296 res.setdefault(id, 0.0)
297 for product in self.browse(cr, uid, ids):
298 res[product.id] = uom_obj._compute_qty(cr, uid, product.uom_id.id, product.qty_available, product.uom_po_id.id)
299 return res
300
301 def _amount_uom(self, cr, uid, ids, name, arg, context=None):
302 res = {}
303
304 for id in ids:
305 res.setdefault(id, 0.0)
306 for product in self.browse(cr, uid, ids):
307 res[product.id] = product.qty_available * product.standard_price
308 return res
309
310 _columns = {
311 #'price_uop': fields.float('Purchase Price(UoP)', digits_compute=dp.get_precision('Product Price')),
312 'qty_uop': fields.function(_qty_uop, type='float', string='Qty on Hand (UoP)', digits_compute=dp.get_precision('Product Unit of Measure')),
313 'amount_uom': fields.function(_amount_uom, type='float', string='Stock Values', digits_compute=dp.get_precision('Account')),
314 }
315
316 def write(self, cr, uid, ids, vals, context=None):
317 if type(ids) != type([]):
318 ids = [ids]
319 if vals.get('uom_po_id', False) and ids:
320 uom_obj = self.pool.get('product.uom')
321 uop = uom_obj.browse(cr, uid, vals.get('uom_po_id', False), context=context)
322 pl_item_obj = self.pool.get('product.pricelist.item')
323 pl_ids = pl_item_obj.search(cr, uid, [('product_id', '=', ids[0]), ('base', '=', -3)], context=context)
324 for item in pl_item_obj.browse(cr, uid, pl_ids, context=context):
325 if item.uom_id and uop.id == item.uom_id.id:
326 continue
327 new_price = item.price_surcharge / uop.factor * item.uom_id.factor
328 pl_item_obj.write(cr, uid, [item.id], {'uom_id': vals.get('uom_po_id', False), 'price_surcharge': new_price})
329
330 return super(product_product, self).write(cr, uid, ids, vals, context=context)
331
332product_product()
0\ No newline at end of file333\ No newline at end of file
1334
=== added file 'purchase_price_list_item/purchase_view.xml'
--- purchase_price_list_item/purchase_view.xml 1970-01-01 00:00:00 +0000
+++ purchase_price_list_item/purchase_view.xml 2013-08-19 07:32:33 +0000
@@ -0,0 +1,48 @@
1<?xml version="1.0" encoding="UTF-8" ?>
2<openerp>
3 <data>
4
5 <record id="product_pricelist_item_list_view_FC" model="ir.ui.view">
6 <field name="name">product.pricelist.item.list.FC</field>
7 <field name="model">product.pricelist.item</field>
8 <field name="inherit_id" ref="product.product_pricelist_item_tree_view" />
9 <field name="arch" type="xml">
10 <xpath expr="//field[@name='categ_id']" position="before">
11 <field name="price_surcharge" attrs="{'invisible':[('base', '!=', -3)]}"/>
12 <field name="uom_id" attrs="{'invisible':[('base', '!=', -3)],'readonly':1}"/>
13 </xpath>
14 </field>
15 </record>
16
17 <record id="product_pricelist_item_form_view_FC" model="ir.ui.view">
18 <field name="name">product.pricelist.item.form.FC</field>
19 <field name="model">product.pricelist.item</field>
20 <field name="type">form</field>
21 <field name="inherit_id" ref="product.product_pricelist_item_form_view" />
22 <field name="arch" type="xml">
23 <xpath expr="//field[@name='product_id']" position="replace">
24 <field name="product_id" on_change="product_id_change(product_id)" attrs="{'required': [('base','=', -3)]}"/>
25 <field name="uom_id" attrs="{'invisible':[('base', '!=', -3)],'readonly':1}"/>
26 </xpath>
27 </field>
28 </record>
29
30 <record id="product_product_tree_view_uop" model="ir.ui.view">
31 <field name="name">product.product.tree.uop</field>
32 <field name="model">product.product</field>
33 <field name="type">form</field>
34 <field name="inherit_id" ref="product.product_product_tree_view" />
35 <field name="arch" type="xml">
36 <xpath expr="//field[@name='state']" position="before">
37 <field name="qty_uop"/>
38 <field name="uom_po_id"/>
39 </xpath>
40 <xpath expr="//field[@name='standard_price']" position="replace">
41 <field name="standard_price"/>
42 <field name="amount_uom"/>
43 </xpath>
44 </field>
45 </record>
46
47 </data>
48</openerp>
0\ No newline at end of file49\ No newline at end of file

Subscribers

People subscribed via source and target branches