Merge lp:~camptocamp/sale-financial/7.0-port-sale_markup into lp:~sale-core-editors/sale-financial/7.0

Proposed by Yannick Vaucher @ Camptocamp
Status: Needs review
Proposed branch: lp:~camptocamp/sale-financial/7.0-port-sale_markup
Merge into: lp:~sale-core-editors/sale-financial/7.0
Diff against target: 1380 lines (+840/-267)
9 files modified
sale_markup/__openerp__.py (+21/-12)
sale_markup/i18n/fr.po (+76/-0)
sale_markup/i18n/sale_markup.pot (+76/-0)
sale_markup/product_markup.py (+105/-91)
sale_markup/product_view.xml (+3/-5)
sale_markup/sale_markup.py (+310/-131)
sale_markup/sale_view.xml (+31/-28)
sale_markup/tests/__init__.py (+21/-0)
sale_markup/tests/test_sale_markup.py (+197/-0)
To merge this branch: bzr merge lp:~camptocamp/sale-financial/7.0-port-sale_markup
Reviewer Review Type Date Requested Status
Alexandre Fayolle - camptocamp Needs Resubmitting
Alexis de Lattre Pending
Review via email: mp+214061@code.launchpad.net

Commit message

Portage to v7 of module sale_markup

Description of the change

Portage of module sale_markup

On changes were rewritten to use web_context_tunnel and ensure there are no infinite chained on_change calls as in V7 returned values of on change will trigger next on_change and with rounding this can go wild.

Requires sale_line_watcher ported in

https://code.launchpad.net/~camptocamp/sale-financial/7.0-port-sale_line_watcher/+merge/214058

To post a comment you must log in.
31. By Yannick Vaucher @ Camptocamp on 2014-04-15

Fix markup computation method on product

32. By Yannick Vaucher @ Camptocamp on 2014-04-15

improve view inheritance by using more specific paths and add more context to product_id_change

33. By Yannick Vaucher @ Camptocamp on 2014-04-15

fix onchange margin, use new_discount instead of discount rate

34. By Yannick Vaucher @ Camptocamp on 2014-05-01

[FIX] imports of decimal_precision

35. By Yannick Vaucher @ Camptocamp on 2014-05-01

Improve check on super returned values as it can be an empty dict

36. By Yannick Vaucher @ Camptocamp on 2014-05-01

Fix using sums multi group fails as the registred function for amount totals is the super one. Here I separate the computationto ensure it will be triggered.

37. By Yannick Vaucher @ Camptocamp on 2014-05-05

add tests

38. By Guewen Baconnier @ Camptocamp on 2014-06-10

Creation of a sale.order.line must trigger the sale_order.markup_rate function field

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

Hello,

The management of the project has moved to Github: https://github.com/OCA/sale-financial

Please migrate your merge proposal to Github. You may want to check https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub for an explanation on how to proceed.

Thanks for contributing to the project

review: Needs Resubmitting
Revision history for this message
Vincent Renaville@camptocamp (vrenaville-c2c) wrote :
Revision history for this message
Vincent Renaville@camptocamp (vrenaville-c2c) wrote :

Unmerged revisions

38. By Guewen Baconnier @ Camptocamp on 2014-06-10

Creation of a sale.order.line must trigger the sale_order.markup_rate function field

37. By Yannick Vaucher @ Camptocamp on 2014-05-05

add tests

36. By Yannick Vaucher @ Camptocamp on 2014-05-01

Fix using sums multi group fails as the registred function for amount totals is the super one. Here I separate the computationto ensure it will be triggered.

35. By Yannick Vaucher @ Camptocamp on 2014-05-01

Improve check on super returned values as it can be an empty dict

34. By Yannick Vaucher @ Camptocamp on 2014-05-01

[FIX] imports of decimal_precision

33. By Yannick Vaucher @ Camptocamp on 2014-04-15

fix onchange margin, use new_discount instead of discount rate

32. By Yannick Vaucher @ Camptocamp on 2014-04-15

improve view inheritance by using more specific paths and add more context to product_id_change

31. By Yannick Vaucher @ Camptocamp on 2014-04-15

Fix markup computation method on product

30. By Yannick Vaucher @ Camptocamp on 2014-04-11

hide break on change fields

29. By Yannick Vaucher @ Camptocamp on 2014-04-11

fixes missing on change context on discount and add checks to not break on_changes which won't be triggered as same value is set

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'sale_markup/__openerp__.py'
2--- sale_markup/__openerp__.py 2013-11-14 08:16:22 +0000
3+++ sale_markup/__openerp__.py 2014-06-10 15:19:47 +0000
4@@ -23,19 +23,28 @@
5 'author' : 'Camptocamp',
6 'maintainer': 'Camptocamp',
7 'category': 'version',
8- 'complexity': "normal", # easy, normal, expert
9- 'depends' : ['base',
10- 'product_get_cost_field',
11- 'mrp',
12- 'sale',
13- 'sale_floor_price'],
14- 'description': """display the product and sale markup in the appropriate views""",
15+ 'complexity': "normal",
16+ 'depends' : [
17+ 'base',
18+ 'product_get_cost_field',
19+ 'mrp',
20+ 'sale',
21+ 'web_context_tunnel',
22+ 'sale_line_watcher',
23+ ],
24+ 'description': """
25+Markup rate on product and sales
26+================================
27+
28+Display the product and sale markup in the appropriate views
29+""",
30 'website': 'http://www.camptocamp.com/',
31- 'init_xml': [],
32- 'update_xml': ['sale_view.xml', 'product_view.xml'],
33- 'demo_xml': [],
34- 'tests': [],
35- 'installable': False,
36+ 'data': [
37+ 'sale_view.xml',
38+ 'product_view.xml',
39+ ],
40+ 'test': [],
41+ 'installable': True,
42 'auto_install': False,
43 'license': 'AGPL-3',
44 'application': True}
45
46=== added file 'sale_markup/i18n/fr.po'
47--- sale_markup/i18n/fr.po 1970-01-01 00:00:00 +0000
48+++ sale_markup/i18n/fr.po 2014-06-10 15:19:47 +0000
49@@ -0,0 +1,76 @@
50+# Translation of OpenERP Server.
51+# This file contains the translation of the following modules:
52+# * sale_markup
53+#
54+msgid ""
55+msgstr ""
56+"Project-Id-Version: OpenERP Server 7.0\n"
57+"Report-Msgid-Bugs-To: \n"
58+"POT-Creation-Date: 2014-04-03 14:11+0000\n"
59+"PO-Revision-Date: 2014-04-03 16:32+0100\n"
60+"Last-Translator: Yannick Vaucher <yannick.vaucher@camptocamp.com>\n"
61+"Language-Team: \n"
62+"MIME-Version: 1.0\n"
63+"Content-Type: text/plain; charset=UTF-8\n"
64+"Content-Transfer-Encoding: 8bit\n"
65+"Plural-Forms: \n"
66+
67+#. module: sale_markup
68+#: field:product.product,cost_price:0
69+msgid "Cost Price"
70+msgstr "Coût de revient"
71+
72+#. module: sale_markup
73+#: field:sale.order.line,cost_price:0
74+msgid "Historical Cost Price"
75+msgstr "Coût de revient historique"
76+
77+#. module: sale_markup
78+#: field:product.product,commercial_margin:0
79+#: field:sale.order.line,commercial_margin:0
80+msgid "Margin"
81+msgstr "Marge"
82+
83+#. module: sale_markup
84+#: help:product.product,commercial_margin:0
85+msgid "Margin is [ sale_price - cost_price ] (not based on historical values)"
86+msgstr "La marge est calculée ainsi [ prix_vente - prix_achat ] (n'est pas basé sur l'historique des valeurs)"
87+
88+#. module: sale_markup
89+#: help:sale.order.line,commercial_margin:0
90+msgid "Margin is [ sale_price - cost_price ], changing it will update the discount"
91+msgstr "La marge est calculée ainsi [ prix_vente - prix_achat ], changer la marge adapte la remise."
92+
93+#. module: sale_markup
94+#: help:sale.order.line,markup_rate:0
95+msgid "Markup is [ margin / sale_price ], changing it will update the discount"
96+msgstr "Le taux de marque est calculé ainsi [ marge / prix_vente ], changer le taux de marque adapte la remise."
97+
98+#. module: sale_markup
99+#: view:sale.order:0
100+#: field:sale.order,markup_rate:0
101+#: field:sale.order.line,markup_rate:0
102+#: field:product.product,markup_rate:0
103+msgid "Markup"
104+msgstr "Taux de marque"
105+
106+#. module: sale_markup
107+#: help:product.product,markup_rate:0
108+msgid "Markup is [ margin / sale_price ] (not based on historical values)"
109+msgstr "Le taux de marque est [ marge / prix_vente ] (n'est pas basé sur l'historique des valeurs)"
110+
111+#. module: sale_markup
112+#: help:product.product,cost_price:0
113+msgid "The cost price is the standard price unless you install the product_cost_incl_bom module."
114+msgstr "Le coût de revient est le prix standard à moins que le module product_cost_incl_bom ne soit installé."
115+
116+#. module: sale_markup
117+#: help:sale.order.line,cost_price:0
118+msgid "The cost price of the product at the time of the creation of the sale order"
119+msgstr "Le coût de revient du produit au moment de la création de la commande client."
120+
121+#. module: sale_markup
122+#: view:sale.order:0
123+msgid "product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id,False,True,parent.date_order,product_packaging,parent.fiscal_position,False,price_unit,discount,context)"
124+msgstr "product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id,False,True,parent.date_order,product_packaging,parent.fiscal_position,False,price_unit,discount,context)"
125+
126
127=== added file 'sale_markup/i18n/sale_markup.pot'
128--- sale_markup/i18n/sale_markup.pot 1970-01-01 00:00:00 +0000
129+++ sale_markup/i18n/sale_markup.pot 2014-06-10 15:19:47 +0000
130@@ -0,0 +1,76 @@
131+# Translation of OpenERP Server.
132+# This file contains the translation of the following modules:
133+# * sale_markup
134+#
135+msgid ""
136+msgstr ""
137+"Project-Id-Version: OpenERP Server 7.0\n"
138+"Report-Msgid-Bugs-To: \n"
139+"POT-Creation-Date: 2014-04-03 14:11+0000\n"
140+"PO-Revision-Date: 2014-04-03 14:11+0000\n"
141+"Last-Translator: <>\n"
142+"Language-Team: \n"
143+"MIME-Version: 1.0\n"
144+"Content-Type: text/plain; charset=UTF-8\n"
145+"Content-Transfer-Encoding: \n"
146+"Plural-Forms: \n"
147+
148+#. module: sale_markup
149+#: field:product.product,cost_price:0
150+msgid "Cost Price"
151+msgstr ""
152+
153+#. module: sale_markup
154+#: field:sale.order.line,cost_price:0
155+msgid "Historical Cost Price"
156+msgstr ""
157+
158+#. module: sale_markup
159+#: field:product.product,commercial_margin:0
160+#: field:sale.order.line,commercial_margin:0
161+msgid "Margin"
162+msgstr ""
163+
164+#. module: sale_markup
165+#: help:product.product,commercial_margin:0
166+msgid "Margin is [ sale_price - cost_price ] (not based on historical values)"
167+msgstr ""
168+
169+#. module: sale_markup
170+#: help:sale.order.line,commercial_margin:0
171+msgid "Margin is [ sale_price - cost_price ], changing it will update the discount"
172+msgstr ""
173+
174+#. module: sale_markup
175+#: help:sale.order.line,markup_rate:0
176+msgid "Markup is [ margin / sale_price ], changing it will update the discount"
177+msgstr ""
178+
179+#. module: sale_markup
180+#: view:sale.order:0
181+#: field:sale.order,markup_rate:0
182+#: field:sale.order.line,markup_rate:0
183+#: field:product.product,markup_rate:0
184+msgid "Markup"
185+msgstr ""
186+
187+#. module: sale_markup
188+#: help:product.product,markup_rate:0
189+msgid "Markup is [ margin / sale_price ] (not based on historical values)"
190+msgstr ""
191+
192+#. module: sale_markup
193+#: help:product.product,cost_price:0
194+msgid "The cost price is the standard price unless you install the product_cost_incl_bom module."
195+msgstr ""
196+
197+#. module: sale_markup
198+#: help:sale.order.line,cost_price:0
199+msgid "The cost price of the product at the time of the creation of the sale order"
200+msgstr ""
201+
202+#. module: sale_markup
203+#: view:sale.order:0
204+msgid "product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id,False,True,parent.date_order,product_packaging,parent.fiscal_position,False,price_unit,discount,context)"
205+msgstr ""
206+
207
208=== modified file 'sale_markup/product_markup.py'
209--- sale_markup/product_markup.py 2013-04-30 12:33:48 +0000
210+++ sale_markup/product_markup.py 2014-06-10 15:19:47 +0000
211@@ -1,10 +1,8 @@
212 # -*- coding: utf-8 -*-
213 ##############################################################################
214 #
215-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
216-# All Right Reserved
217-#
218-# Author : Yannick Vaucher, Joel Grand-Guillaume
219+# Author: Yannick Vaucher, Joel Grand-Guillaume
220+# Copyright 2011 Camptocamp SA
221 #
222 # This program is free software: you can redistribute it and/or modify
223 # it under the terms of the GNU Affero General Public License as
224@@ -21,80 +19,98 @@
225 #
226 ##############################################################################
227
228-from osv import fields
229-from osv.orm import Model
230-import decimal_precision as dp
231-
232-
233-class Product(Model):
234+from openerp.osv import orm, fields
235+from openerp.addons import decimal_precision as dp
236+
237+
238+class Product(orm.Model):
239 _inherit = 'product.product'
240
241- def _convert_to_foreign_currency(self, cursor, user, pricelist, amount_dict, context=None):
242- if context is None:
243- context = {}
244+ def _convert_to_foreign_currency(self, cr, uid, pricelist,
245+ amount_dict, context=None):
246+ """
247+ Apply purchase pricelist
248+ """
249 if not pricelist:
250 return amount_dict
251- pricelist_obj = self.pool.get('product.pricelist')
252- currency_obj = self.pool.get('res.currency')
253- user_obj = self.pool.get('res.users')
254- pricelist = pricelist_obj.browse(cursor, user, pricelist)
255- company_currency_id = user_obj.browse(cursor, user, user).company_id.currency_id.id
256- converted_price = {}
257+ pricelist_obj = self.pool['product.pricelist']
258+ currency_obj = self.pool['res.currency']
259+ user_obj = self.pool['res.users']
260+ pricelist = pricelist_obj.browse(cr, uid, pricelist, context=context)
261+ user = user_obj.browse(cr, uid, uid, context=context)
262+ company_currency_id = user.company_id.currency_id.id
263+ converted_prices = {}
264 for product_id, amount in amount_dict.iteritems():
265- converted_price[product_id] = currency_obj.compute(cursor, user,
266- company_currency_id,
267- pricelist.currency_id.id,
268- amount,
269- round=False)
270- return converted_price
271-
272- def compute_markup(self, cursor, user, ids,
273- product_uom = None,
274- pricelist = None,
275- sale_price = None,
276- properties = None,
277- context = None):
278- '''
279+ converted_prices[product_id] = currency_obj.compute(
280+ cr, uid, company_currency_id, pricelist.currency_id.id, amount,
281+ round=False)
282+ return converted_prices
283+
284+ @staticmethod
285+ def _compute_markup(sale_price, purchase_price):
286+ """
287+ Return markup as a rate
288+
289+ Markup = SP - PP / SP
290+
291+ Where SP = Sale price
292+ PP = Purchase price
293+ """
294+ if not sale_price:
295+ return 0.0
296+ return (sale_price - purchase_price) / sale_price * 100
297+
298+ def compute_markup(self, cr, uid, ids,
299+ product_uom=None, pricelist=None, sale_price=None,
300+ properties=None, context=None):
301+ """
302 compute markup
303
304 If properties, pricelist and sale_price arguments are set, it
305 will be used to compute all results
306- '''
307+ """
308 properties = properties or []
309 pricelist = pricelist or []
310 if context is None:
311 context = {}
312 if isinstance(ids, (int, long)):
313- ids = [ids]
314+ ids = [ids]
315 res = {}
316
317- # cost_price_context will be used by product_get_cost_field if it is installed
318- cost_price_context = context.copy().update({'produc_uom': product_uom,
319- 'properties': properties})
320- purchase_prices = self.get_cost_field(cursor, user, ids, cost_price_context)
321+ # cost_price_context will be used by product_get_cost_field if it is
322+ # installed
323+ cost_price_context = context.copy()
324+ cost_price_context.update({
325+ 'product_uom': product_uom,
326+ 'properties': properties})
327+ purchase_prices = self.get_cost_field(cr, uid, ids, cost_price_context)
328 # if purchase prices failed returned a dict of default values
329- if not purchase_prices: return dict([(id, {'commercial_margin': 0.0,
330- 'markup_rate': 0.0,
331- 'cost_price': 0.0,}) for id in ids])
332+ if not purchase_prices:
333+ return dict([(id, {'commercial_margin': 0.0,
334+ 'markup_rate': 0.0,
335+ 'cost_price': 0.0,
336+ }) for id in ids])
337
338- purchase_price = self._convert_to_foreign_currency(cursor, user, pricelist, purchase_prices)
339- for pr in self.browse(cursor, user, ids):
340+ purchase_prices = self._convert_to_foreign_currency(cr, uid, pricelist,
341+ purchase_prices,
342+ context=context)
343+ for pr in self.browse(cr, uid, ids, context=context):
344 res[pr.id] = {}
345 if sale_price is None:
346 catalog_price = pr.list_price
347 else:
348 catalog_price = sale_price
349
350- res[pr.id]['commercial_margin'] = catalog_price - purchase_prices[pr.id]
351-
352- res[pr.id]['markup_rate'] = (catalog_price and
353- (catalog_price - purchase_prices[pr.id]) / catalog_price * 100 or 0.0)
354-
355- res[pr.id]['cost_price'] = purchase_prices[pr.id]
356+ res[pr.id].update({
357+ 'commercial_margin': catalog_price - purchase_prices[pr.id],
358+ 'markup_rate': self._compute_markup(catalog_price,
359+ purchase_prices[pr.id]),
360+ 'cost_price': purchase_prices[pr.id]
361+ })
362
363 return res
364
365- def _get_bom_product(self,cursor, user, ids, context=None):
366+ def _get_bom_product(self, cr, uid, ids, context=None):
367 """return ids of modified product and ids of all product that use
368 as sub-product one of this ids. Ex:
369 BoM :
370@@ -103,63 +119,61 @@
371 - Product C
372 => If we change standard_price of product B, we want to update Product
373 A as well..."""
374- if context is None:
375- context = {}
376 def _get_parent_bom(bom_record):
377 """Recursvely find the parent bom"""
378- result=[]
379+ result = []
380 if bom_record.bom_id:
381 result.append(bom_record.bom_id.id)
382 result.extend(_get_parent_bom(bom_record.bom_id))
383 return result
384 res = []
385- bom_obj = self.pool.get('mrp.bom')
386- bom_ids = bom_obj.search(cursor, user, [('product_id','in',ids)])
387- for bom in bom_obj.browse(cursor, user, bom_ids):
388+ bom_obj = self.pool['mrp.bom']
389+ bom_ids = bom_obj.search(cr, uid, [('product_id', 'in', ids)],
390+ context=context)
391+ for bom in bom_obj.browse(cr, uid, bom_ids, context=context):
392 res += _get_parent_bom(bom)
393 final_bom_ids = list(set(res + bom_ids))
394- return list(set(ids + self._get_product(cursor, user, final_bom_ids, context)))
395+ return list(set(ids + self._get_product(cr, uid, final_bom_ids,
396+ context=context)))
397
398- def _get_product(self, cursor, user, ids, context = None):
399- if context is None:
400- context = {}
401- bom_obj = self.pool.get('mrp.bom')
402+ def _get_product(self, cr, uid, ids, context=None):
403+ bom_obj = self.pool['mrp.bom']
404
405 res = {}
406- for bom in bom_obj.browse(cursor, user, ids, context=context):
407+ for bom in bom_obj.browse(cr, uid, ids, context=context):
408 res[bom.product_id.id] = True
409 return res.keys()
410
411- def _compute_all_markup(self, cursor, user, ids, field_name, arg, context = None):
412- '''
413+ def _compute_all_markup(self, cr, uid, ids, field_name, arg,
414+ context=None):
415+ """
416 method for product function field on multi 'markup'
417- '''
418- if context is None:
419- context = {}
420- res = self.compute_markup(cursor, user, ids, context=context)
421- return res
422+ """
423+ return self.compute_markup(cr, uid, ids, context=context)
424
425- _store_cfg = {'product.product': (_get_bom_product, ['list_price' ,'standard_price'], 20),
426+ _store_cfg = {'product.product': (_get_bom_product,
427+ ['list_price', 'standard_price'], 20),
428 'mrp.bom': (_get_product,
429- ['bom_id', 'bom_lines', 'product_id', 'product_uom',
430- 'product_qty', 'product_uos', 'product_uos_qty',
431- 'property_ids'], 20)}
432-
433-
434+ ['bom_id', 'bom_lines', 'product_id',
435+ 'product_uom', 'product_qty', 'product_uos',
436+ 'product_uos_qty', 'property_ids'], 20)
437+ }
438
439 _columns = {
440- 'commercial_margin' : fields.function(_compute_all_markup,
441- method=True,
442- string='Margin',
443- digits_compute=dp.get_precision('Sale Price'),
444- store =_store_cfg,
445- multi ='markup',
446- help='Margin is [ sale_price - cost_price ] (not based on historical values)'),
447- 'markup_rate' : fields.function(_compute_all_markup,
448- method=True,
449- string='Markup rate (%)',
450- digits_compute=dp.get_precision('Sale Price'),
451- store=_store_cfg,
452- multi='markup',
453- help='Markup rate is [ margin / sale_price ] (not based on historical values)'),
454- }
455+ 'commercial_margin': fields.function(
456+ _compute_all_markup,
457+ string='Margin',
458+ digits_compute=dp.get_precision('Sale Price'),
459+ store=_store_cfg,
460+ multi='markup',
461+ help='Margin is [ sale_price - cost_price ] (not based on '
462+ 'historical values)'),
463+ 'markup_rate': fields.function(
464+ _compute_all_markup,
465+ string='Markup',
466+ digits_compute=dp.get_precision('Sale Price'),
467+ store=_store_cfg,
468+ multi='markup',
469+ help='Markup is [ margin / sale_price ] (not based on '
470+ 'historical values)'),
471+ }
472
473=== modified file 'sale_markup/product_view.xml'
474--- sale_markup/product_view.xml 2012-05-15 15:56:19 +0000
475+++ sale_markup/product_view.xml 2014-06-10 15:19:47 +0000
476@@ -3,28 +3,26 @@
477 <data>
478 <record model="ir.ui.view" id="sale_markup_product_form">
479 <field name="name">product.markup.view.form</field>
480- <field name="type">form</field>
481 <field name="model">product.product</field>
482 <field name="inherit_id" ref="product.product_normal_form_view" />
483 <field name="arch" type="xml">
484 <field name="list_price" position="after">
485 <field name="markup_rate"
486- groups="sale.group_sale_manager"/>
487+ groups="base.group_sale_manager"/>
488 <field name="commercial_margin"
489- groups="sale.group_sale_manager"/>
490+ groups="base.group_sale_manager"/>
491 </field>
492 </field>
493 </record>
494
495 <record model="ir.ui.view" id="sale_markup_product_tree">
496 <field name="name">product.markup.view.tree</field>
497- <field name="type">tree</field>
498 <field name="model">product.product</field>
499 <field name="inherit_id" ref="product.product_product_tree_view" />
500 <field name="arch" type="xml">
501 <field name="standard_price" position="after">
502 <field name="markup_rate"
503- groups="sale.group_sale_manager"/>
504+ groups="base.group_sale_manager"/>
505 </field>
506 </field>
507 </record>
508
509=== modified file 'sale_markup/sale_markup.py'
510--- sale_markup/sale_markup.py 2012-07-12 13:56:17 +0000
511+++ sale_markup/sale_markup.py 2014-06-10 15:19:47 +0000
512@@ -1,10 +1,8 @@
513 # -*- coding: utf-8 -*-
514 ##############################################################################
515 #
516-# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
517-# All Right Reserved
518-#
519-# Author : Yannick Vaucher, Joel Grand-Guillaume
520+# Author: Yannick Vaucher, Joel Grand-Guillaume
521+# Copyright 2011 Camptocamp SA
522 #
523 # This program is free software: you can redistribute it and/or modify
524 # it under the terms of the GNU Affero General Public License as
525@@ -22,202 +20,383 @@
526 ##############################################################################
527
528 from openerp.osv.orm import Model, fields
529-import decimal_precision as dp
530+from openerp.addons import decimal_precision as dp
531+from openerp.tools import float_compare
532+
533
534 def _prec(obj, cr, uid, mode=None):
535 # This function use orm cache it should be efficient
536 mode = mode or 'Sale Price'
537- return obj.pool.get('decimal.precision').precision_get(cr, uid, mode)
538+ return obj.pool['decimal.precision'].precision_get(cr, uid, mode)
539+
540
541 class SaleOrder(Model):
542 _inherit = 'sale.order'
543
544- def _amount_all(self, cursor, user, ids, field_name, arg, context = None):
545- '''Calculate the markup rate based on sums'''
546-
547- if context is None:
548- context = {}
549- res = {}
550- res = super(SaleOrder, self)._amount_all(cursor, user, ids, field_name, arg, context)
551-
552- for sale_order in self.browse(cursor, user, ids):
553+ def _amount_markup(self, cr, user, ids, field_name, arg, context=None):
554+ """Calculate the markup rate based on sums"""
555+
556+ res = dict.fromkeys(ids, {})
557+
558+ for sale_order in self.browse(cr, user, ids):
559 cost_sum = 0.0
560 sale_sum = 0.0
561 for line in sale_order.order_line:
562 cost_sum += line.cost_price
563 sale_sum += line.price_unit * (100 - line.discount) / 100.0
564- res[sale_order.id]['markup_rate'] = sale_sum and (sale_sum - cost_sum) / sale_sum * 100 or 0.0
565+ markup_rate = ((sale_sum - cost_sum) / sale_sum * 100 if sale_sum
566+ else 0.0)
567+ res[sale_order.id]['markup_rate'] = markup_rate
568 return res
569
570-
571 def _get_order(self, cr, uid, ids, context=None):
572- if context is None:
573- context = {}
574+ sale_order_line_obj = self.pool['sale.order.line']
575+ sale_order_lines = sale_order_line_obj.browse(cr, uid, ids,
576+ context=context)
577 result = set()
578- for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
579+ for line in sale_order_lines:
580 result.add(line.order_id.id)
581 return list(result)
582
583 _store_sums = {
584- 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
585- 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty',
586- 'product_id','commercial_margin', 'markup_rate'], 10)}
587-
588-
589- _columns = {'markup_rate': fields.function(_amount_all,
590- method = True,
591- string = 'Markup Rate',
592- digits_compute=dp.get_precision('Sale Price'),
593- store = _store_sums,
594- multi='sums')}
595+ 'sale.order': (lambda self, cr, uid, ids, c=None: ids,
596+ ['order_line'], 10),
597+ 'sale.order.line': (_get_order,
598+ ['price_unit', 'tax_id', 'discount',
599+ 'product_uom_qty', 'product_id', 'order_id',
600+ 'commercial_margin', 'markup_rate'], 10)
601+ }
602+
603+ _columns = {
604+ 'markup_rate': fields.function(
605+ _amount_markup,
606+ string='Markup',
607+ digits_compute=dp.get_precision('Sale Price'),
608+ store=_store_sums,
609+ multi='sums_markup'),
610+ }
611
612
613 class SaleOrderLine(Model):
614
615 _inherit = 'sale.order.line'
616
617- _columns = {'commercial_margin': fields.float('Margin',
618- digits_compute=dp.get_precision('Sale Price'),
619- help='Margin is [ sale_price - cost_price ],'
620- ' changing it will update the discount'),
621- 'markup_rate': fields.float('Markup Rate (%)',
622- digits_compute=dp.get_precision('Sale Price'),
623- help='Margin rate is [ margin / sale_price ],'
624- 'changing it will update the discount'),
625- 'cost_price': fields.float('Historical Cost Price',
626- digits_compute=dp.get_precision('Sale Price'),
627- help="The cost price of the product at the time of the creation of the sale order"),
628- }
629-
630-
631-
632- def onchange_price_unit(self, cursor, uid, ids, price_unit, product_id, discount,
633- product_uom, pricelist, **kwargs):
634- '''
635- If price unit change, compute the new markup rate.
636- '''
637- res = super(SaleOrderLine,self).onchange_price_unit(cursor, uid, ids,
638- price_unit,
639- product_id,
640- discount,
641- product_uom,
642- pricelist)
643-
644+ def _get_break(self, cr, uid, ids, field_name, arg, context=None):
645+ return dict.fromkeys(ids, False)
646+
647+ _columns = {
648+ 'commercial_margin': fields.float(
649+ 'Margin',
650+ digits_compute=dp.get_precision('Sale Price'),
651+ help='Margin is [ sale_price - cost_price ], changing it will '
652+ 'update the discount'),
653+ 'markup_rate': fields.float(
654+ 'Markup',
655+ digits_compute=dp.get_precision('Sale Price'),
656+ help='Markup is [ margin / sale_price ], changing it will '
657+ 'update the discount'),
658+ 'cost_price': fields.float(
659+ 'Historical Cost Price',
660+ digits_compute=dp.get_precision('Sale Price'),
661+ help='The cost price of the product at the time of the creation '
662+ 'of the sale order'),
663+ # boolean fields to skip onchange loop
664+ 'break_onchange_discount': fields.function(
665+ _get_break,
666+ string='Break onchange', type='boolean'),
667+ 'break_onchange_markup_rate': fields.function(
668+ _get_break,
669+ string='Break onchange', type='boolean'),
670+ 'break_onchange_commercial_margin': fields.function(
671+ _get_break,
672+ string='Break onchange', type='boolean'),
673+ }
674+
675+ def onchange_price_unit(self, cr, uid, ids, context=None, **kwargs):
676+ """
677+ If price unit changes, compute the new markup rate and
678+ commercial margin
679+
680+ context arguments:
681+ price_unit
682+ product_id
683+ discount
684+ product_uom
685+ pricelist
686+ markup_rate
687+ commercial_margin
688+
689+ Will change:
690+ markup_rate
691+ commercial_margin
692+ """
693+ if context is None:
694+ context = {}
695+ res = super(SaleOrderLine, self
696+ ).onchange_price_unit(cr, uid, ids, context=context)
697+ product_id = context.get('product_id')
698 if product_id:
699- product_obj = self.pool.get('product.product')
700- if res['value'].has_key('price_unit'):
701+ price_unit = context.get('price_unit')
702+ discount = context.get('discount')
703+ product_uom = context.get('product_uom')
704+ pricelist = context.get('pricelist')
705+ product_obj = self.pool['product.product']
706+ markup = context.get('markup_rate', 0.0)
707+ margin = context.get('commercial_margin', 0.0)
708+ if not 'value' in res:
709+ res['value'] = {}
710+ if 'price_unit' in res['value']:
711 price_unit = res['value']['price_unit']
712 sale_price = price_unit * (100 - discount) / 100.0
713- markup_res = product_obj.compute_markup(cursor, uid,
714+ markup_res = product_obj.compute_markup(cr, uid,
715 product_id,
716 product_uom,
717 pricelist,
718 sale_price)[product_id]
719
720-
721- res['value']['commercial_margin'] = round(markup_res['commercial_margin'], _prec(self, cursor, uid))
722- res['value']['markup_rate'] = round(markup_res['markup_rate'], _prec(self, cursor, uid))
723+ new_margin = round(markup_res['commercial_margin'],
724+ _prec(self, cr, uid))
725+ if not float_compare(margin, new_margin,
726+ precision_digits=_prec(self, cr, uid)) == 0:
727+ res['value'].update({
728+ 'commercial_margin': new_margin,
729+ 'break_onchange_commercial_margin': True,
730+ })
731+ new_markup = round(markup_res['markup_rate'],
732+ _prec(self, cr, uid))
733+ if not float_compare(markup, new_markup,
734+ precision_digits=_prec(self, cr, uid)) == 0:
735+ res['value'].update({
736+ 'markup_rate': new_markup,
737+ 'break_onchange_markup_rate': True,
738+ })
739 return res
740
741-
742-
743- def onchange_discount(self, cursor, uid, ids,
744- price_unit, product_id, discount, product_uom, pricelist, **kwargs):
745- '''
746- If discount change, compute the new markup rate
747- '''
748- res = super(SaleOrderLine,self).onchange_discount(cursor, uid, ids,
749- price_unit,
750- product_id,
751- discount,
752- product_uom,
753- pricelist)
754-
755- if product_id:
756- product_obj = self.pool.get('product.product')
757- if res['value'].has_key('price_unit'):
758+ def onchange_discount(self, cr, uid, ids, context=None, **kwargs):
759+ """
760+ If discount changes, compute the new markup rate and commercial margin.
761+
762+ context arguments:
763+ product_id
764+ price_unit
765+ discount
766+ product_uom
767+ pricelist
768+ markup_rate
769+ commercial_margin
770+ break_onchange
771+
772+ Will change:
773+ markup_rate
774+ commercial_margin
775+ """
776+ if context is None:
777+ context = {}
778+ res = super(SaleOrderLine, self
779+ ).onchange_discount(cr, uid, ids, context=context)
780+ product_id = context.get('product_id')
781+ if context.get('break_onchange'):
782+ res['value']['break_onchange_discount'] = False
783+ elif product_id:
784+ price_unit = context.get('price_unit')
785+ discount = context.get('discount')
786+ product_uom = context.get('product_uom')
787+ pricelist = context.get('pricelist')
788+ markup = context.get('markup_rate', 0.0)
789+ margin = context.get('commercial_margin', 0.0)
790+
791+ product_obj = self.pool['product.product']
792+ if not 'value' in res:
793+ res['value'] = {}
794+ if 'price_unit' in res['value']:
795 price_unit = res['value']['price_unit']
796- if res['value'].has_key('discount'):
797+ if 'discount' in res['value']:
798 discount = res['value']['discount']
799 sale_price = price_unit * (100 - discount) / 100.0
800- markup_res = product_obj.compute_markup(cursor, uid,
801+ markup_res = product_obj.compute_markup(cr, uid,
802 product_id,
803 product_uom,
804 pricelist,
805 sale_price)[product_id]
806
807-
808- res['value']['commercial_margin'] = round(markup_res['commercial_margin'] , _prec(self, cursor, uid))
809- res['value']['markup_rate'] = round(markup_res['markup_rate'], _prec(self, cursor, uid))
810+ new_margin = round(markup_res['commercial_margin'],
811+ _prec(self, cr, uid))
812+ if not float_compare(margin, new_margin,
813+ precision_digits=_prec(self, cr, uid)) == 0:
814+ res['value'].update({
815+ 'commercial_margin': new_margin,
816+ 'break_onchange_commercial_margin': True,
817+ })
818+ new_markup = round(markup_res['markup_rate'],
819+ _prec(self, cr, uid))
820+ if not float_compare(markup, new_markup,
821+ precision_digits=_prec(self, cr, uid)) == 0:
822+ res['value'].update({
823+ 'markup_rate': new_markup,
824+ 'break_onchange_markup_rate': True,
825+ })
826 return res
827
828-
829- def product_id_change(self, cursor, uid, ids, pricelist, product, qty=0,
830- uom=False, qty_uos=0, uos=False, name='', partner_id=False,
831- lang=False, update_tax=True, date_order=False, packaging=False,
832- fiscal_position=False, flag=False, discount=None, price_unit=None, context=None):
833- '''
834+ def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
835+ uom=False, qty_uos=0, uos=False, name='',
836+ partner_id=False, lang=False, update_tax=True,
837+ date_order=False, packaging=False,
838+ fiscal_position=False, flag=False,
839+ context=None):
840+ """
841 Overload method
842- If product change, compute the new markup.
843- Added params : - price_unit,
844+ If product changes, compute the new markup, cost_price and
845+ commercial_margin.
846+ Added params : - price_unit
847 - discount
848- - properties
849- '''
850+ - markup_rate
851+ - commercial_margin
852+
853+ Will change:
854+ markup_rate
855+ commercial_margin
856+ """
857 if context is None:
858 context = {}
859- discount = discount or 0.0
860- price_unit = price_unit or 0.0
861- res = {}
862- res = super(SaleOrderLine, self).product_id_change(cursor, uid, ids, pricelist, product, qty,
863- uom, qty_uos, uos, name, partner_id,
864- lang, update_tax, date_order, packaging,
865- fiscal_position, flag, context)
866+ res = super(SaleOrderLine, self
867+ ).product_id_change(cr, uid, ids, pricelist, product, qty,
868+ uom, qty_uos, uos, name, partner_id,
869+ lang, update_tax, date_order,
870+ packaging, fiscal_position, flag,
871+ context=context)
872
873 if product:
874- if res['value'].has_key('price_unit'):
875+ price_unit = context.get('price_unit', 0.0)
876+ discount = context.get('discount', 0.0)
877+ markup = context.get('markup_rate', 0.0)
878+ margin = context.get('commercial_margin', 0.0)
879+
880+ if not 'value' in res:
881+ res['value'] = {}
882+ if 'price_unit' in res['value']:
883 price_unit = res['value']['price_unit']
884 sale_price = price_unit * (100 - discount) / 100.0
885
886- product_obj = self.pool.get('product.product')
887- markup_res = product_obj.compute_markup(cursor, uid,
888+ product_obj = self.pool['product.product']
889+ markup_res = product_obj.compute_markup(cr, uid,
890 product,
891 uom,
892 pricelist,
893 sale_price)[product]
894
895- res['value']['commercial_margin'] = round(markup_res['commercial_margin'], _prec(self, cursor, uid))
896- res['value']['markup_rate'] = round(int(markup_res['markup_rate'] * 100) / 100.0, _prec(self, cursor, uid))
897- res['value']['cost_price'] = round(markup_res['cost_price'], _prec(self, cursor, uid))
898+ res['value']['cost_price'] = round(markup_res['cost_price'],
899+ _prec(self, cr, uid))
900+ new_margin = round(markup_res['commercial_margin'],
901+ _prec(self, cr, uid))
902+ if not float_compare(margin, new_margin,
903+ precision_digits=_prec(self, cr, uid)) == 0:
904+ res['value'].update({
905+ 'commercial_margin': new_margin,
906+ 'break_onchange_commercial_margin': True,
907+ })
908+ new_markup = round(int(markup_res['markup_rate'] * 100) / 100.0,
909+ _prec(self, cr, uid))
910+ if not float_compare(markup, new_markup,
911+ precision_digits=_prec(self, cr, uid)) == 0:
912+ res['value'].update({
913+ 'markup_rate': new_markup,
914+ 'break_onchange_markup_rate': True,
915+ })
916
917 return res
918
919-
920- def onchange_markup_rate(self, cursor, uid, ids,
921- markup, cost_price, price_unit, context=None):
922- ''' If markup rate change compute the discount '''
923+ def onchange_markup_rate(self, cr, uid, ids, context=None):
924+ """ If markup rate changes compute the discount
925+
926+ context arguments:
927+ markup_rate
928+ commercial_margin
929+ cost_price
930+ price_unit
931+ break_onchange
932+
933+ Will change:
934+ discount
935+ commercial_margin
936+ """
937 if context is None:
938 context = {}
939- res = {}
940- res['value'] = {}
941+ res = {'value': {}}
942+ if context.get('break_onchange'):
943+ res['value']['break_onchange_markup_rate'] = False
944+ return res
945+
946+ markup = context.get('markup_rate')
947+ price_unit = context.get('price_unit')
948 markup = markup / 100.0
949- if not price_unit or markup == 1: return {'value': {}}
950- discount = 1 + cost_price / (markup - 1) / price_unit
951- sale_price = price_unit * (1 - discount)
952- res['value']['discount'] = round(discount * 100, _prec(self, cursor, uid))
953- res['value']['commercial_margin'] = round(sale_price - cost_price, _prec(self, cursor, uid))
954+ if price_unit and not markup == 1:
955+ cost_price = context.get('cost_price')
956+ margin = context.get('commercial_margin')
957+ discount = context.get('discount')
958+
959+ new_discount = 1 + cost_price / (markup - 1) / price_unit
960+ sale_price = price_unit * (1 - new_discount)
961+
962+ new_discount = round(new_discount * 100, _prec(self, cr, uid))
963+ if not float_compare(discount, new_discount,
964+ precision_digits=_prec(self, cr, uid)) == 0:
965+ res['value'].update({
966+ 'discount': new_discount,
967+ 'break_onchange_discount': True,
968+ })
969+
970+ new_margin = round(sale_price - cost_price, _prec(self, cr, uid))
971+ if not float_compare(margin, new_margin,
972+ precision_digits=_prec(self, cr, uid)) == 0:
973+ res['value'].update({
974+ 'commercial_margin': new_margin,
975+ 'break_onchange_commercial_margin': True,
976+ })
977 return res
978
979-
980- def onchange_commercial_margin(self, cursor, uid, ids,
981- margin, cost_price, price_unit, context=None):
982- ''' If markup rate change compute the discount '''
983+ def onchange_commercial_margin(self, cr, uid, ids, context=None):
984+ """ If commercial margin changes compute the discount
985+
986+ context arguments:
987+ commercial_margin
988+ markup_rate
989+ discount
990+ cost_price
991+ price_unit
992+ break_onchange
993+
994+ Will change:
995+ discount
996+ markup_rate
997+ """
998 if context is None:
999 context = {}
1000- res = {}
1001- res['value'] = {}
1002- if not price_unit: return {'value': {}}
1003- discount = 1 - ((cost_price + margin) / price_unit)
1004- sale_price = price_unit * (1 - discount)
1005- res['value']['discount'] = round(discount * 100, _prec(self, cursor, uid))
1006- res['value']['markup_rate'] = round(margin / (sale_price or 1.0) * 100, _prec(self, cursor, uid))
1007+ res = {'value': {}}
1008+ price_unit = context.get('price_unit')
1009+ if context.get('break_onchange'):
1010+ res['value']['break_onchange_commercial_margin'] = False
1011+ elif price_unit:
1012+ margin = context.get('commercial_margin')
1013+ markup = context.get('markup_rate')
1014+ cost_price = context.get('cost_price')
1015+ discount = context.get('discount')
1016+
1017+ new_discount = 1 - ((cost_price + margin) / price_unit)
1018+ sale_price = price_unit * (1 - new_discount)
1019+
1020+ new_discount = round(new_discount * 100, _prec(self, cr, uid))
1021+ if not float_compare(discount, new_discount,
1022+ precision_digits=_prec(self, cr, uid)) == 0:
1023+ res['value'].update({
1024+ 'discount': new_discount,
1025+ 'break_onchange_discount': True,
1026+ })
1027+ new_markup = round(margin / (sale_price or 1.0) * 100,
1028+ _prec(self, cr, uid))
1029+ if not float_compare(markup, new_markup,
1030+ precision_digits=_prec(self, cr, uid)) == 0:
1031+ res['value'].update({
1032+ 'markup_rate': new_markup,
1033+ 'break_onchange_markup_rate': True,
1034+ })
1035 return res
1036
1037=== modified file 'sale_markup/sale_view.xml'
1038--- sale_markup/sale_view.xml 2012-07-23 12:01:18 +0000
1039+++ sale_markup/sale_view.xml 2014-06-10 15:19:47 +0000
1040@@ -7,13 +7,15 @@
1041 <!-- Add markup rate of the order in form view after Compute button -->
1042 <record model="ir.ui.view" id="sale_markup_sale_order_view">
1043 <field name="name">sale.order.markup.view.form</field>
1044- <field name="type">form</field>
1045 <field name="model">sale.order</field>
1046 <field name="inherit_id" ref="sale.view_order_form" />
1047 <field name="arch" type="xml">
1048- <xpath expr="//button[@name='button_dummy']" position="before">
1049- <field name="markup_rate"
1050- groups="base.group_sale_manager"/>
1051+ <xpath expr="//group[@name='sale_total']" position="after">
1052+ <group class="oe_subtotal_footer oe_right" colspan="2" name="sale_markup"
1053+ groups="base.group_sale_manager">
1054+ <field name="markup_rate"
1055+ groups="base.group_sale_manager"/>
1056+ </group>
1057 </xpath>
1058 </field>
1059 </record>
1060@@ -21,7 +23,6 @@
1061 <!-- Add markup rate of the order in tree view after state field -->
1062 <record model="ir.ui.view" id="sale_markup_sale_order_tree">
1063 <field name="name">sale.order.markup.view.tree</field>
1064- <field name="type">tree</field>
1065 <field name="model">sale.order</field>
1066 <field name="inherit_id" ref="sale.view_order_tree" />
1067 <field name="arch" type="xml">
1068@@ -34,7 +35,6 @@
1069
1070 <record model="ir.ui.view" id="sale_markup_sale_order_line_tree">
1071 <field name="name">sale.order.line.markup.view.tree</field>
1072- <field name="type">tree</field>
1073 <field name="model">sale.order.line</field>
1074 <field name="inherit_id" ref="sale.view_order_line_tree" />
1075 <field name="arch" type="xml">
1076@@ -52,37 +52,41 @@
1077 <!-- Add onchanges on unit price and discount to update margin and markup -->
1078 <record model="ir.ui.view" id="sale_markup_sale_order_form_line_form">
1079 <field name="name">sale.order.markup.view.form2</field>
1080- <field name="type">form</field>
1081 <field name="model">sale.order</field>
1082 <field name="inherit_id" ref="sale.view_order_form" />
1083 <field name="arch" type="xml">
1084- <xpath expr="//field[@name='product_id']"
1085- position="replace">
1086- <field colspan="4"
1087- name="product_id"
1088- context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'shop':parent.shop_id, 'uom':product_uom}"
1089- on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position, False, price_unit, discount, context)" />
1090- <newline/>
1091- </xpath>
1092- <xpath expr="/form/notebook/page/field[@name='order_line']/form/notebook/page/group/field[@name='name']"
1093- position="attributes">
1094- <attribute name="colspan">5</attribute>
1095- </xpath>
1096- <xpath expr="/form/notebook/page/field[@name='order_line']/form/notebook/page/group/field[@name='name']"
1097+ <xpath expr="//field[@name='order_line']/form//field[@name='product_id']"
1098+ position="attributes">
1099+ <attribute name="context_markup">{'discount': discount, 'price_unit': price_unit, 'markup_rate': markup_rate, 'commercial_margin': commercial_margin}</attribute>
1100+ </xpath>
1101+ <xpath expr="//field[@name='order_line']/form//field[@name='price_unit']"
1102+ position="attributes">
1103+ <attribute name="context_markup">{'price_unit': price_unit, 'product_id': product_id, 'discount': discount, 'product_uom': product_uom, 'pricelist': parent.pricelist_id, 'markup_rate': markup_rate, 'commercial_margin': commercial_margin}</attribute>
1104+ </xpath>
1105+ <xpath expr="//field[@name='order_line']/form//field[@name='discount']"
1106+ position="attributes">
1107+ <attribute name="context_markup">{'product_id': product_id, 'price_unit': price_unit, 'discount': discount, 'product_uom': product_uom, 'pricelist': parent.pricelist_id, 'markup_rate': markup_rate, 'commercial_margin': commercial_margin, 'break_onchange': break_onchange_discount}</attribute>
1108+ </xpath>
1109+ <xpath expr="//field[@name='order_line']/form//field[@name='name']"
1110 position="after">
1111- <separator string="Markup" colspan="5" groups="base.group_sale_manager"/>
1112+ <group string="Markup" groups="base.group_sale_manager">
1113 <field name="commercial_margin"
1114- on_change="onchange_commercial_margin(commercial_margin, cost_price, price_unit)"
1115+ context="{'commercial_margin': commercial_margin, 'markup_rate': markup_rate, 'discount': discount, 'cost_price': cost_price, 'price_unit': price_unit, 'break_onchange': break_onchange_commercial_margin}"
1116+ on_change="onchange_commercial_margin(context)"
1117 groups="base.group_sale_manager"/>
1118 <field name="markup_rate"
1119- on_change="onchange_markup_rate(markup_rate, cost_price, price_unit)"
1120+ context="{'markup_rate': markup_rate, 'commercial_margin': commercial_margin, 'discount': discount, 'cost_price': cost_price, 'price_unit': price_unit, 'break_onchange': break_onchange_markup_rate}"
1121+ on_change="onchange_markup_rate(context)"
1122 groups="base.group_sale_manager"/>
1123 <field name="cost_price"
1124 groups="base.group_sale_manager" invisible="1"/>
1125- <newline/>
1126+ <field name="break_onchange_commercial_margin" invisible="1"/>
1127+ <field name="break_onchange_markup_rate" invisible="1"/>
1128+ <field name="break_onchange_discount" invisible="1"/>
1129+ </group>
1130 </xpath>
1131- <xpath expr="//field[@name='product_uom_qty']" position="attributes">
1132- <attribute name="on_change">product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id,False,True,parent.date_order,product_packaging,parent.fiscal_position,False,price_unit,discount,context)</attribute>
1133+ <xpath expr="//field[@name='order_line']/form//field[@name='product_uom_qty']" position="attributes">
1134+ <attribute name="context_markup">{'discount': discount, 'price_unit': price_unit, 'markup_rate': markup_rate, 'commercial_margin': commercial_margin}</attribute>
1135 </xpath>
1136 </field>
1137 </record>
1138@@ -90,12 +94,11 @@
1139 <!-- Add Markup in Sales Orders form's Sale order lines tree -->
1140 <record model="ir.ui.view" id="sale_markup_sale_order_form_line_tree">
1141 <field name="name">sale.order.markup.view.form</field>
1142- <field name="type">form</field>
1143 <field name="model">sale.order</field>
1144 <field name="inherit_id" ref="sale.view_order_form" />
1145 <field name="arch" type="xml">
1146 <xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']"
1147- position="after">
1148+ position="after">
1149 <field name="cost_price"
1150 groups="base.group_sale_manager"/>
1151 <field name="markup_rate"
1152
1153=== added directory 'sale_markup/tests'
1154=== added file 'sale_markup/tests/__init__.py'
1155--- sale_markup/tests/__init__.py 1970-01-01 00:00:00 +0000
1156+++ sale_markup/tests/__init__.py 2014-06-10 15:19:47 +0000
1157@@ -0,0 +1,21 @@
1158+# -*- coding: utf-8 -*-
1159+##############################################################################
1160+#
1161+# Author: Yannick Vaucher
1162+# Copyright 2014 Camptocamp SA
1163+#
1164+# This program is free software: you can redistribute it and/or modify
1165+# it under the terms of the GNU Affero General Public License as
1166+# published by the Free Software Foundation, either version 3 of the
1167+# License, or (at your option) any later version.
1168+#
1169+# This program is distributed in the hope that it will be useful,
1170+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1171+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1172+# GNU Affero General Public License for more details.
1173+#
1174+# You should have received a copy of the GNU Affero General Public License
1175+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1176+#
1177+##############################################################################
1178+from . import test_sale_markup
1179
1180=== added file 'sale_markup/tests/test_sale_markup.py'
1181--- sale_markup/tests/test_sale_markup.py 1970-01-01 00:00:00 +0000
1182+++ sale_markup/tests/test_sale_markup.py 2014-06-10 15:19:47 +0000
1183@@ -0,0 +1,197 @@
1184+# -*- coding: utf-8 -*-
1185+##############################################################################
1186+#
1187+# Author: Yannick Vaucher
1188+# Copyright 2014 Camptocamp SA
1189+#
1190+# This program is free software: you can redistribute it and/or modify
1191+# it under the terms of the GNU Affero General Public License as
1192+# published by the Free Software Foundation, either version 3 of the
1193+# License, or (at your option) any later version.
1194+#
1195+# This program is distributed in the hope that it will be useful,
1196+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1197+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1198+# GNU Affero General Public License for more details.
1199+#
1200+# You should have received a copy of the GNU Affero General Public License
1201+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1202+#
1203+##############################################################################
1204+import openerp.tests.common as common
1205+from openerp.addons import get_module_resource
1206+
1207+
1208+def _trigger_on_changes(self, cr, uid, sale_order, view_values, changed_values):
1209+ triggered_on_changes = []
1210+ while [i for i in changed_values.keys() if i not in triggered_on_changes]:
1211+ res_values = {}
1212+ for field in view_values.keys():
1213+ if field in triggered_on_changes:
1214+ continue
1215+ triggered_on_changes.append(field)
1216+ if field == 'product_id':
1217+ res_values = self.SaleOrderLine.product_id_change(
1218+ cr, uid, False,
1219+ sale_order.pricelist_id.id,
1220+ view_values.get('product_id'),
1221+ qty=view_values.get('product_uom_qty'),
1222+ uom=False,
1223+ qty_uos=view_values.get('product_uos_qty'),
1224+ uos=False,
1225+ name='sol_test_1',
1226+ partner_id=sale_order.partner_id.id,
1227+ lang=False,
1228+ update_tax=True,
1229+ date_order=sale_order.date_order,
1230+ packaging=False,
1231+ fiscal_position=sale_order.fiscal_position.id,
1232+ flag=False,
1233+ context=view_values)
1234+ break
1235+ elif field == 'price_unit':
1236+ res_values = self.SaleOrderLine.onchange_price_unit(cr, uid, False, context=view_values)
1237+ break
1238+ elif field == 'discount':
1239+ res_values = self.SaleOrderLine.onchange_discount(cr, uid, False, context=view_values)
1240+ break
1241+ elif field == 'commercial_margin':
1242+ res_values = self.SaleOrderLine.onchange_commercial_margin(cr, uid, False, context=view_values)
1243+ break
1244+ elif field == 'markup_rate':
1245+ res_values = self.SaleOrderLine.onchange_markup_rate(cr, uid, False, context=view_values)
1246+ break
1247+ if res_values:
1248+ changed_values.update(res_values['value'])
1249+ view_values.update(res_values['value'])
1250+ return view_values
1251+
1252+class test_sale_markup(common.TransactionCase):
1253+ """ Test the wizard for delivery carrier label generation """
1254+
1255+ def setUp(self):
1256+ super(test_sale_markup, self).setUp()
1257+ cr, uid = self.cr, self.uid
1258+
1259+ self.SaleOrder = self.registry('sale.order')
1260+ self.SaleOrderLine = self.registry('sale.order.line')
1261+ self.Product = self.registry('product.product')
1262+ self.product_33 = self.Product.browse(
1263+ cr, uid, self.ref('product.product_product_33'))
1264+ self.ResPartner = self.registry('res.partner')
1265+ self.partner_12 = self.ResPartner.browse(
1266+ cr, uid, self.ref('base.res_partner_12'))
1267+
1268+ def test_00_create_sale_order(self):
1269+ """ Check markup computing in sale order
1270+
1271+ And check each on_changes
1272+ """
1273+ cr, uid = self.cr, self.uid
1274+
1275+ so_data = {'partner_id': self.partner_12.id,
1276+ }
1277+ res = self.SaleOrder.onchange_partner_id(cr, uid, False, self.partner_12.id)
1278+ so_data.update(res['value'])
1279+
1280+ so_1_id = self.SaleOrder.create(cr, uid, so_data)
1281+ so_1 = self.SaleOrder.browse(cr, uid, so_1_id)
1282+
1283+ ctx = {'product_id': self.product_33.id,
1284+ 'product_uom': self.ref('product.product_uom_unit'),
1285+ 'discount': 0.0,
1286+ 'product_uom_qty': 1,
1287+ 'product_uos_qty': 1,
1288+ 'sequence': 10,
1289+ 'state': 'draft',
1290+ 'type': 'make_to_stock',
1291+ 'price_unit': 0.0,
1292+ 'order_id': so_1_id,
1293+ }
1294+
1295+ # I set product A on sale order and trigger the on_change on product.
1296+ res = self.SaleOrderLine.product_id_change(
1297+ cr, uid, False,
1298+ so_1.pricelist_id.id,
1299+ ctx.get('product_id'),
1300+ qty=ctx.get('product_uom_qty'),
1301+ uom=False,
1302+ qty_uos=ctx.get('product_uos_qty'),
1303+ uos=False,
1304+ name='sol_test_1',
1305+ partner_id=so_1.partner_id.id,
1306+ lang=False,
1307+ update_tax=True,
1308+ date_order=so_1.date_order,
1309+ packaging=False,
1310+ fiscal_position=so_1.fiscal_position.id,
1311+ flag=False,
1312+ context=ctx)
1313+
1314+ ctx.update(res['value'])
1315+ res = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
1316+ # cost_price should be set and equal to product cost price.
1317+ assert abs(res.get('cost_price') - self.Product.get_cost_field(cr, uid, self.product_33.id)[self.product_33.id]) < 0.01
1318+
1319+ # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
1320+ commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
1321+ assert abs(ctx.get('commercial_margin') - commercial_margin) < 0.01, "Commercial margin is %s instead of %s after update of product_id" % (ctx.get('commercial_margin'), commercial_margin)
1322+ # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
1323+ markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
1324+ assert abs(ctx.get('markup_rate') - markup_rate) < 0.01, "Markup rate is %s instead of %s after update of product_id" % (ctx.get('markup_rate'), markup_rate)
1325+
1326+ # I add 1 percent to discount and trigger the on_change on discount.
1327+ ctx['discount'] = 1.0
1328+ res = self.SaleOrderLine.onchange_discount(cr, uid, False, context=ctx)
1329+ ctx.update(res['value'])
1330+ ctx = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
1331+
1332+ # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
1333+ commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
1334+ assert abs(ctx.get('commercial_margin') - commercial_margin) < 0.01, "Commercial margin is %s instead of %s after update of discount" % (ctx.get('commercial_margin'), commercial_margin)
1335+
1336+ # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
1337+ markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
1338+ assert abs(ctx.get('markup_rate') - markup_rate) < 0.01, "Markup rate is %s instead of %s after update of discount" % (ctx.get('markup_rate'), markup_rate)
1339+
1340+ # I change the markup rate to 20.0 and trigger the on_change on markup_rate.
1341+ ctx['markup_rate'] = 20.0
1342+ res = self.SaleOrderLine.onchange_markup_rate(cr, uid, False, context=ctx)
1343+ ctx.update(res['value'])
1344+ ctx = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
1345+
1346+ # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
1347+ commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
1348+ assert abs(ctx.get('commercial_margin') - commercial_margin) < 0.01, "Commercial margin is %s instead of %s after update of markup_rate" % (ctx.get('commercial_margin'), commercial_margin)
1349+
1350+ # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
1351+ markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
1352+ assert abs(ctx.get('markup_rate') - markup_rate) < 0.01, "Markup rate is %s instead of %s after update of markup_rate" % (ctx.get('markup_rate'), markup_rate)
1353+
1354+
1355+ # I change the price unit to 2000.0 and trigger the on_change on price_unit.
1356+ ctx['price_unit'] = 2000.0
1357+ res = self.SaleOrderLine.onchange_price_unit(cr, uid, False, context=ctx)
1358+ ctx.update(res['value'])
1359+ ctx = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
1360+
1361+ # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
1362+ commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
1363+ assert abs(ctx.get('commercial_margin') - commercial_margin) < 0.01, "Commercial margin is %s instead of %s after update of price_unit" % (ctx.get('commercial_margin'), commercial_margin)
1364+
1365+ # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
1366+ markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
1367+ assert abs(ctx.get('markup_rate') - markup_rate) < 0.01, "Markup rate is %s instead of %s after update of price_unit" % (ctx.get('markup_rate'), markup_rate)
1368+
1369+ sol_data = ctx
1370+
1371+ # I create the sale order line for the sale order.
1372+ sol_1_id = self.SaleOrderLine.create(
1373+ cr, uid,
1374+ sol_data
1375+ )
1376+
1377+ so_1.refresh()
1378+ assert so_1.markup_rate
1379+ # as we have only one line it should be equal to our last line markup
1380+ assert abs(so_1.markup_rate - markup_rate) < 0.01

Subscribers

People subscribed via source and target branches

to all changes: