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