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

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

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

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'sale_markup/__openerp__.py'
--- sale_markup/__openerp__.py 2013-11-14 08:16:22 +0000
+++ sale_markup/__openerp__.py 2014-06-10 15:19:47 +0000
@@ -23,19 +23,28 @@
23 'author' : 'Camptocamp',23 'author' : 'Camptocamp',
24 'maintainer': 'Camptocamp',24 'maintainer': 'Camptocamp',
25 'category': 'version',25 'category': 'version',
26 'complexity': "normal", # easy, normal, expert26 'complexity': "normal",
27 'depends' : ['base',27 'depends' : [
28 'product_get_cost_field',28 'base',
29 'mrp',29 'product_get_cost_field',
30 'sale',30 'mrp',
31 'sale_floor_price'],31 'sale',
32 'description': """display the product and sale markup in the appropriate views""",32 'web_context_tunnel',
33 'sale_line_watcher',
34 ],
35 'description': """
36Markup rate on product and sales
37================================
38
39Display the product and sale markup in the appropriate views
40""",
33 'website': 'http://www.camptocamp.com/',41 'website': 'http://www.camptocamp.com/',
34 'init_xml': [],42 'data': [
35 'update_xml': ['sale_view.xml', 'product_view.xml'],43 'sale_view.xml',
36 'demo_xml': [],44 'product_view.xml',
37 'tests': [],45 ],
38 'installable': False,46 'test': [],
47 'installable': True,
39 'auto_install': False,48 'auto_install': False,
40 'license': 'AGPL-3',49 'license': 'AGPL-3',
41 'application': True}50 'application': True}
4251
=== added file 'sale_markup/i18n/fr.po'
--- sale_markup/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ sale_markup/i18n/fr.po 2014-06-10 15:19:47 +0000
@@ -0,0 +1,76 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * sale_markup
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2014-04-03 14:11+0000\n"
10"PO-Revision-Date: 2014-04-03 16:32+0100\n"
11"Last-Translator: Yannick Vaucher <yannick.vaucher@camptocamp.com>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: 8bit\n"
16"Plural-Forms: \n"
17
18#. module: sale_markup
19#: field:product.product,cost_price:0
20msgid "Cost Price"
21msgstr "Coût de revient"
22
23#. module: sale_markup
24#: field:sale.order.line,cost_price:0
25msgid "Historical Cost Price"
26msgstr "Coût de revient historique"
27
28#. module: sale_markup
29#: field:product.product,commercial_margin:0
30#: field:sale.order.line,commercial_margin:0
31msgid "Margin"
32msgstr "Marge"
33
34#. module: sale_markup
35#: help:product.product,commercial_margin:0
36msgid "Margin is [ sale_price - cost_price ] (not based on historical values)"
37msgstr "La marge est calculée ainsi [ prix_vente - prix_achat ] (n'est pas basé sur l'historique des valeurs)"
38
39#. module: sale_markup
40#: help:sale.order.line,commercial_margin:0
41msgid "Margin is [ sale_price - cost_price ], changing it will update the discount"
42msgstr "La marge est calculée ainsi [ prix_vente - prix_achat ], changer la marge adapte la remise."
43
44#. module: sale_markup
45#: help:sale.order.line,markup_rate:0
46msgid "Markup is [ margin / sale_price ], changing it will update the discount"
47msgstr "Le taux de marque est calculé ainsi [ marge / prix_vente ], changer le taux de marque adapte la remise."
48
49#. module: sale_markup
50#: view:sale.order:0
51#: field:sale.order,markup_rate:0
52#: field:sale.order.line,markup_rate:0
53#: field:product.product,markup_rate:0
54msgid "Markup"
55msgstr "Taux de marque"
56
57#. module: sale_markup
58#: help:product.product,markup_rate:0
59msgid "Markup is [ margin / sale_price ] (not based on historical values)"
60msgstr "Le taux de marque est [ marge / prix_vente ] (n'est pas basé sur l'historique des valeurs)"
61
62#. module: sale_markup
63#: help:product.product,cost_price:0
64msgid "The cost price is the standard price unless you install the product_cost_incl_bom module."
65msgstr "Le coût de revient est le prix standard à moins que le module product_cost_incl_bom ne soit installé."
66
67#. module: sale_markup
68#: help:sale.order.line,cost_price:0
69msgid "The cost price of the product at the time of the creation of the sale order"
70msgstr "Le coût de revient du produit au moment de la création de la commande client."
71
72#. module: sale_markup
73#: view:sale.order:0
74msgid "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)"
75msgstr "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)"
76
077
=== added file 'sale_markup/i18n/sale_markup.pot'
--- sale_markup/i18n/sale_markup.pot 1970-01-01 00:00:00 +0000
+++ sale_markup/i18n/sale_markup.pot 2014-06-10 15:19:47 +0000
@@ -0,0 +1,76 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * sale_markup
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2014-04-03 14:11+0000\n"
10"PO-Revision-Date: 2014-04-03 14:11+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: sale_markup
19#: field:product.product,cost_price:0
20msgid "Cost Price"
21msgstr ""
22
23#. module: sale_markup
24#: field:sale.order.line,cost_price:0
25msgid "Historical Cost Price"
26msgstr ""
27
28#. module: sale_markup
29#: field:product.product,commercial_margin:0
30#: field:sale.order.line,commercial_margin:0
31msgid "Margin"
32msgstr ""
33
34#. module: sale_markup
35#: help:product.product,commercial_margin:0
36msgid "Margin is [ sale_price - cost_price ] (not based on historical values)"
37msgstr ""
38
39#. module: sale_markup
40#: help:sale.order.line,commercial_margin:0
41msgid "Margin is [ sale_price - cost_price ], changing it will update the discount"
42msgstr ""
43
44#. module: sale_markup
45#: help:sale.order.line,markup_rate:0
46msgid "Markup is [ margin / sale_price ], changing it will update the discount"
47msgstr ""
48
49#. module: sale_markup
50#: view:sale.order:0
51#: field:sale.order,markup_rate:0
52#: field:sale.order.line,markup_rate:0
53#: field:product.product,markup_rate:0
54msgid "Markup"
55msgstr ""
56
57#. module: sale_markup
58#: help:product.product,markup_rate:0
59msgid "Markup is [ margin / sale_price ] (not based on historical values)"
60msgstr ""
61
62#. module: sale_markup
63#: help:product.product,cost_price:0
64msgid "The cost price is the standard price unless you install the product_cost_incl_bom module."
65msgstr ""
66
67#. module: sale_markup
68#: help:sale.order.line,cost_price:0
69msgid "The cost price of the product at the time of the creation of the sale order"
70msgstr ""
71
72#. module: sale_markup
73#: view:sale.order:0
74msgid "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)"
75msgstr ""
76
077
=== modified file 'sale_markup/product_markup.py'
--- sale_markup/product_markup.py 2013-04-30 12:33:48 +0000
+++ sale_markup/product_markup.py 2014-06-10 15:19:47 +0000
@@ -1,10 +1,8 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2##############################################################################2##############################################################################
3#3#
4# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)4# Author: Yannick Vaucher, Joel Grand-Guillaume
5# All Right Reserved5# Copyright 2011 Camptocamp SA
6#
7# Author : Yannick Vaucher, Joel Grand-Guillaume
8#6#
9# This program is free software: you can redistribute it and/or modify7# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU Affero General Public License as8# it under the terms of the GNU Affero General Public License as
@@ -21,80 +19,98 @@
21#19#
22##############################################################################20##############################################################################
2321
24from osv import fields22from openerp.osv import orm, fields
25from osv.orm import Model23from openerp.addons import decimal_precision as dp
26import decimal_precision as dp24
2725
2826class Product(orm.Model):
29class Product(Model):
30 _inherit = 'product.product'27 _inherit = 'product.product'
3128
32 def _convert_to_foreign_currency(self, cursor, user, pricelist, amount_dict, context=None):29 def _convert_to_foreign_currency(self, cr, uid, pricelist,
33 if context is None:30 amount_dict, context=None):
34 context = {}31 """
32 Apply purchase pricelist
33 """
35 if not pricelist:34 if not pricelist:
36 return amount_dict35 return amount_dict
37 pricelist_obj = self.pool.get('product.pricelist')36 pricelist_obj = self.pool['product.pricelist']
38 currency_obj = self.pool.get('res.currency')37 currency_obj = self.pool['res.currency']
39 user_obj = self.pool.get('res.users')38 user_obj = self.pool['res.users']
40 pricelist = pricelist_obj.browse(cursor, user, pricelist)39 pricelist = pricelist_obj.browse(cr, uid, pricelist, context=context)
41 company_currency_id = user_obj.browse(cursor, user, user).company_id.currency_id.id40 user = user_obj.browse(cr, uid, uid, context=context)
42 converted_price = {}41 company_currency_id = user.company_id.currency_id.id
42 converted_prices = {}
43 for product_id, amount in amount_dict.iteritems():43 for product_id, amount in amount_dict.iteritems():
44 converted_price[product_id] = currency_obj.compute(cursor, user,44 converted_prices[product_id] = currency_obj.compute(
45 company_currency_id,45 cr, uid, company_currency_id, pricelist.currency_id.id, amount,
46 pricelist.currency_id.id,46 round=False)
47 amount,47 return converted_prices
48 round=False)48
49 return converted_price49 @staticmethod
5050 def _compute_markup(sale_price, purchase_price):
51 def compute_markup(self, cursor, user, ids,51 """
52 product_uom = None,52 Return markup as a rate
53 pricelist = None,53
54 sale_price = None,54 Markup = SP - PP / SP
55 properties = None,55
56 context = None):56 Where SP = Sale price
57 '''57 PP = Purchase price
58 """
59 if not sale_price:
60 return 0.0
61 return (sale_price - purchase_price) / sale_price * 100
62
63 def compute_markup(self, cr, uid, ids,
64 product_uom=None, pricelist=None, sale_price=None,
65 properties=None, context=None):
66 """
58 compute markup67 compute markup
5968
60 If properties, pricelist and sale_price arguments are set, it69 If properties, pricelist and sale_price arguments are set, it
61 will be used to compute all results70 will be used to compute all results
62 '''71 """
63 properties = properties or []72 properties = properties or []
64 pricelist = pricelist or []73 pricelist = pricelist or []
65 if context is None:74 if context is None:
66 context = {}75 context = {}
67 if isinstance(ids, (int, long)):76 if isinstance(ids, (int, long)):
68 ids = [ids]77 ids = [ids]
69 res = {}78 res = {}
7079
71 # cost_price_context will be used by product_get_cost_field if it is installed80 # cost_price_context will be used by product_get_cost_field if it is
72 cost_price_context = context.copy().update({'produc_uom': product_uom,81 # installed
73 'properties': properties})82 cost_price_context = context.copy()
74 purchase_prices = self.get_cost_field(cursor, user, ids, cost_price_context)83 cost_price_context.update({
84 'product_uom': product_uom,
85 'properties': properties})
86 purchase_prices = self.get_cost_field(cr, uid, ids, cost_price_context)
75 # if purchase prices failed returned a dict of default values87 # if purchase prices failed returned a dict of default values
76 if not purchase_prices: return dict([(id, {'commercial_margin': 0.0,88 if not purchase_prices:
77 'markup_rate': 0.0,89 return dict([(id, {'commercial_margin': 0.0,
78 'cost_price': 0.0,}) for id in ids])90 'markup_rate': 0.0,
91 'cost_price': 0.0,
92 }) for id in ids])
7993
80 purchase_price = self._convert_to_foreign_currency(cursor, user, pricelist, purchase_prices)94 purchase_prices = self._convert_to_foreign_currency(cr, uid, pricelist,
81 for pr in self.browse(cursor, user, ids):95 purchase_prices,
96 context=context)
97 for pr in self.browse(cr, uid, ids, context=context):
82 res[pr.id] = {}98 res[pr.id] = {}
83 if sale_price is None:99 if sale_price is None:
84 catalog_price = pr.list_price100 catalog_price = pr.list_price
85 else:101 else:
86 catalog_price = sale_price102 catalog_price = sale_price
87103
88 res[pr.id]['commercial_margin'] = catalog_price - purchase_prices[pr.id]104 res[pr.id].update({
89105 'commercial_margin': catalog_price - purchase_prices[pr.id],
90 res[pr.id]['markup_rate'] = (catalog_price and106 'markup_rate': self._compute_markup(catalog_price,
91 (catalog_price - purchase_prices[pr.id]) / catalog_price * 100 or 0.0)107 purchase_prices[pr.id]),
92108 'cost_price': purchase_prices[pr.id]
93 res[pr.id]['cost_price'] = purchase_prices[pr.id]109 })
94110
95 return res111 return res
96112
97 def _get_bom_product(self,cursor, user, ids, context=None):113 def _get_bom_product(self, cr, uid, ids, context=None):
98 """return ids of modified product and ids of all product that use114 """return ids of modified product and ids of all product that use
99 as sub-product one of this ids. Ex:115 as sub-product one of this ids. Ex:
100 BoM :116 BoM :
@@ -103,63 +119,61 @@
103 - Product C119 - Product C
104 => If we change standard_price of product B, we want to update Product120 => If we change standard_price of product B, we want to update Product
105 A as well..."""121 A as well..."""
106 if context is None:
107 context = {}
108 def _get_parent_bom(bom_record):122 def _get_parent_bom(bom_record):
109 """Recursvely find the parent bom"""123 """Recursvely find the parent bom"""
110 result=[]124 result = []
111 if bom_record.bom_id:125 if bom_record.bom_id:
112 result.append(bom_record.bom_id.id)126 result.append(bom_record.bom_id.id)
113 result.extend(_get_parent_bom(bom_record.bom_id))127 result.extend(_get_parent_bom(bom_record.bom_id))
114 return result128 return result
115 res = []129 res = []
116 bom_obj = self.pool.get('mrp.bom')130 bom_obj = self.pool['mrp.bom']
117 bom_ids = bom_obj.search(cursor, user, [('product_id','in',ids)])131 bom_ids = bom_obj.search(cr, uid, [('product_id', 'in', ids)],
118 for bom in bom_obj.browse(cursor, user, bom_ids):132 context=context)
133 for bom in bom_obj.browse(cr, uid, bom_ids, context=context):
119 res += _get_parent_bom(bom)134 res += _get_parent_bom(bom)
120 final_bom_ids = list(set(res + bom_ids))135 final_bom_ids = list(set(res + bom_ids))
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,
137 context=context)))
122138
123 def _get_product(self, cursor, user, ids, context = None):139 def _get_product(self, cr, uid, ids, context=None):
124 if context is None:140 bom_obj = self.pool['mrp.bom']
125 context = {}
126 bom_obj = self.pool.get('mrp.bom')
127141
128 res = {}142 res = {}
129 for bom in bom_obj.browse(cursor, user, ids, context=context):143 for bom in bom_obj.browse(cr, uid, ids, context=context):
130 res[bom.product_id.id] = True144 res[bom.product_id.id] = True
131 return res.keys()145 return res.keys()
132146
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,
134 '''148 context=None):
149 """
135 method for product function field on multi 'markup'150 method for product function field on multi 'markup'
136 '''151 """
137 if context is None:152 return self.compute_markup(cr, uid, ids, context=context)
138 context = {}
139 res = self.compute_markup(cursor, user, ids, context=context)
140 return res
141153
142 _store_cfg = {'product.product': (_get_bom_product, ['list_price' ,'standard_price'], 20),154 _store_cfg = {'product.product': (_get_bom_product,
155 ['list_price', 'standard_price'], 20),
143 'mrp.bom': (_get_product,156 'mrp.bom': (_get_product,
144 ['bom_id', 'bom_lines', 'product_id', 'product_uom',157 ['bom_id', 'bom_lines', 'product_id',
145 'product_qty', 'product_uos', 'product_uos_qty',158 'product_uom', 'product_qty', 'product_uos',
146 'property_ids'], 20)}159 'product_uos_qty', 'property_ids'], 20)
147160 }
148
149161
150 _columns = {162 _columns = {
151 'commercial_margin' : fields.function(_compute_all_markup,163 'commercial_margin': fields.function(
152 method=True,164 _compute_all_markup,
153 string='Margin',165 string='Margin',
154 digits_compute=dp.get_precision('Sale Price'),166 digits_compute=dp.get_precision('Sale Price'),
155 store =_store_cfg,167 store=_store_cfg,
156 multi ='markup',168 multi='markup',
157 help='Margin is [ sale_price - cost_price ] (not based on historical values)'),169 help='Margin is [ sale_price - cost_price ] (not based on '
158 'markup_rate' : fields.function(_compute_all_markup,170 'historical values)'),
159 method=True,171 'markup_rate': fields.function(
160 string='Markup rate (%)',172 _compute_all_markup,
161 digits_compute=dp.get_precision('Sale Price'),173 string='Markup',
162 store=_store_cfg,174 digits_compute=dp.get_precision('Sale Price'),
163 multi='markup',175 store=_store_cfg,
164 help='Markup rate is [ margin / sale_price ] (not based on historical values)'),176 multi='markup',
165 }177 help='Markup is [ margin / sale_price ] (not based on '
178 'historical values)'),
179 }
166180
=== modified file 'sale_markup/product_view.xml'
--- sale_markup/product_view.xml 2012-05-15 15:56:19 +0000
+++ sale_markup/product_view.xml 2014-06-10 15:19:47 +0000
@@ -3,28 +3,26 @@
3 <data>3 <data>
4 <record model="ir.ui.view" id="sale_markup_product_form">4 <record model="ir.ui.view" id="sale_markup_product_form">
5 <field name="name">product.markup.view.form</field>5 <field name="name">product.markup.view.form</field>
6 <field name="type">form</field>
7 <field name="model">product.product</field>6 <field name="model">product.product</field>
8 <field name="inherit_id" ref="product.product_normal_form_view" />7 <field name="inherit_id" ref="product.product_normal_form_view" />
9 <field name="arch" type="xml">8 <field name="arch" type="xml">
10 <field name="list_price" position="after">9 <field name="list_price" position="after">
11 <field name="markup_rate"10 <field name="markup_rate"
12 groups="sale.group_sale_manager"/>11 groups="base.group_sale_manager"/>
13 <field name="commercial_margin"12 <field name="commercial_margin"
14 groups="sale.group_sale_manager"/>13 groups="base.group_sale_manager"/>
15 </field>14 </field>
16 </field>15 </field>
17 </record>16 </record>
1817
19 <record model="ir.ui.view" id="sale_markup_product_tree">18 <record model="ir.ui.view" id="sale_markup_product_tree">
20 <field name="name">product.markup.view.tree</field>19 <field name="name">product.markup.view.tree</field>
21 <field name="type">tree</field>
22 <field name="model">product.product</field>20 <field name="model">product.product</field>
23 <field name="inherit_id" ref="product.product_product_tree_view" />21 <field name="inherit_id" ref="product.product_product_tree_view" />
24 <field name="arch" type="xml">22 <field name="arch" type="xml">
25 <field name="standard_price" position="after">23 <field name="standard_price" position="after">
26 <field name="markup_rate"24 <field name="markup_rate"
27 groups="sale.group_sale_manager"/>25 groups="base.group_sale_manager"/>
28 </field>26 </field>
29 </field>27 </field>
30 </record>28 </record>
3129
=== modified file 'sale_markup/sale_markup.py'
--- sale_markup/sale_markup.py 2012-07-12 13:56:17 +0000
+++ sale_markup/sale_markup.py 2014-06-10 15:19:47 +0000
@@ -1,10 +1,8 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2##############################################################################2##############################################################################
3#3#
4# Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)4# Author: Yannick Vaucher, Joel Grand-Guillaume
5# All Right Reserved5# Copyright 2011 Camptocamp SA
6#
7# Author : Yannick Vaucher, Joel Grand-Guillaume
8#6#
9# This program is free software: you can redistribute it and/or modify7# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU Affero General Public License as8# it under the terms of the GNU Affero General Public License as
@@ -22,202 +20,383 @@
22##############################################################################20##############################################################################
2321
24from openerp.osv.orm import Model, fields22from openerp.osv.orm import Model, fields
25import decimal_precision as dp23from openerp.addons import decimal_precision as dp
24from openerp.tools import float_compare
25
2626
27def _prec(obj, cr, uid, mode=None):27def _prec(obj, cr, uid, mode=None):
28 # This function use orm cache it should be efficient28 # This function use orm cache it should be efficient
29 mode = mode or 'Sale Price'29 mode = mode or 'Sale Price'
30 return obj.pool.get('decimal.precision').precision_get(cr, uid, mode)30 return obj.pool['decimal.precision'].precision_get(cr, uid, mode)
31
3132
32class SaleOrder(Model):33class SaleOrder(Model):
33 _inherit = 'sale.order'34 _inherit = 'sale.order'
3435
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):
36 '''Calculate the markup rate based on sums'''37 """Calculate the markup rate based on sums"""
3738
38 if context is None:39 res = dict.fromkeys(ids, {})
39 context = {}40
40 res = {}41 for sale_order in self.browse(cr, user, ids):
41 res = super(SaleOrder, self)._amount_all(cursor, user, ids, field_name, arg, context)
42
43 for sale_order in self.browse(cursor, user, ids):
44 cost_sum = 0.042 cost_sum = 0.0
45 sale_sum = 0.043 sale_sum = 0.0
46 for line in sale_order.order_line:44 for line in sale_order.order_line:
47 cost_sum += line.cost_price45 cost_sum += line.cost_price
48 sale_sum += line.price_unit * (100 - line.discount) / 100.046 sale_sum += line.price_unit * (100 - line.discount) / 100.0
49 res[sale_order.id]['markup_rate'] = sale_sum and (sale_sum - cost_sum) / sale_sum * 100 or 0.047 markup_rate = ((sale_sum - cost_sum) / sale_sum * 100 if sale_sum
48 else 0.0)
49 res[sale_order.id]['markup_rate'] = markup_rate
50 return res50 return res
5151
52
53 def _get_order(self, cr, uid, ids, context=None):52 def _get_order(self, cr, uid, ids, context=None):
54 if context is None:53 sale_order_line_obj = self.pool['sale.order.line']
55 context = {}54 sale_order_lines = sale_order_line_obj.browse(cr, uid, ids,
55 context=context)
56 result = set()56 result = set()
57 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):57 for line in sale_order_lines:
58 result.add(line.order_id.id)58 result.add(line.order_id.id)
59 return list(result)59 return list(result)
6060
61 _store_sums = {61 _store_sums = {
62 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),62 'sale.order': (lambda self, cr, uid, ids, c=None: ids,
63 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty',63 ['order_line'], 10),
64 'product_id','commercial_margin', 'markup_rate'], 10)}64 'sale.order.line': (_get_order,
6565 ['price_unit', 'tax_id', 'discount',
6666 'product_uom_qty', 'product_id', 'order_id',
67 _columns = {'markup_rate': fields.function(_amount_all,67 'commercial_margin', 'markup_rate'], 10)
68 method = True,68 }
69 string = 'Markup Rate',69
70 digits_compute=dp.get_precision('Sale Price'),70 _columns = {
71 store = _store_sums,71 'markup_rate': fields.function(
72 multi='sums')}72 _amount_markup,
73 string='Markup',
74 digits_compute=dp.get_precision('Sale Price'),
75 store=_store_sums,
76 multi='sums_markup'),
77 }
7378
7479
75class SaleOrderLine(Model):80class SaleOrderLine(Model):
7681
77 _inherit = 'sale.order.line'82 _inherit = 'sale.order.line'
7883
79 _columns = {'commercial_margin': fields.float('Margin',84 def _get_break(self, cr, uid, ids, field_name, arg, context=None):
80 digits_compute=dp.get_precision('Sale Price'),85 return dict.fromkeys(ids, False)
81 help='Margin is [ sale_price - cost_price ],'86
82 ' changing it will update the discount'),87 _columns = {
83 'markup_rate': fields.float('Markup Rate (%)',88 'commercial_margin': fields.float(
84 digits_compute=dp.get_precision('Sale Price'),89 'Margin',
85 help='Margin rate is [ margin / sale_price ],'90 digits_compute=dp.get_precision('Sale Price'),
86 'changing it will update the discount'),91 help='Margin is [ sale_price - cost_price ], changing it will '
87 'cost_price': fields.float('Historical Cost Price',92 'update the discount'),
88 digits_compute=dp.get_precision('Sale Price'),93 'markup_rate': fields.float(
89 help="The cost price of the product at the time of the creation of the sale order"),94 'Markup',
90 }95 digits_compute=dp.get_precision('Sale Price'),
91 96 help='Markup is [ margin / sale_price ], changing it will '
9297 'update the discount'),
9398 'cost_price': fields.float(
94 def onchange_price_unit(self, cursor, uid, ids, price_unit, product_id, discount,99 'Historical Cost Price',
95 product_uom, pricelist, **kwargs):100 digits_compute=dp.get_precision('Sale Price'),
96 '''101 help='The cost price of the product at the time of the creation '
97 If price unit change, compute the new markup rate.102 'of the sale order'),
98 '''103 # boolean fields to skip onchange loop
99 res = super(SaleOrderLine,self).onchange_price_unit(cursor, uid, ids,104 'break_onchange_discount': fields.function(
100 price_unit,105 _get_break,
101 product_id,106 string='Break onchange', type='boolean'),
102 discount,107 'break_onchange_markup_rate': fields.function(
103 product_uom,108 _get_break,
104 pricelist)109 string='Break onchange', type='boolean'),
105110 'break_onchange_commercial_margin': fields.function(
111 _get_break,
112 string='Break onchange', type='boolean'),
113 }
114
115 def onchange_price_unit(self, cr, uid, ids, context=None, **kwargs):
116 """
117 If price unit changes, compute the new markup rate and
118 commercial margin
119
120 context arguments:
121 price_unit
122 product_id
123 discount
124 product_uom
125 pricelist
126 markup_rate
127 commercial_margin
128
129 Will change:
130 markup_rate
131 commercial_margin
132 """
133 if context is None:
134 context = {}
135 res = super(SaleOrderLine, self
136 ).onchange_price_unit(cr, uid, ids, context=context)
137 product_id = context.get('product_id')
106 if product_id:138 if product_id:
107 product_obj = self.pool.get('product.product')139 price_unit = context.get('price_unit')
108 if res['value'].has_key('price_unit'):140 discount = context.get('discount')
141 product_uom = context.get('product_uom')
142 pricelist = context.get('pricelist')
143 product_obj = self.pool['product.product']
144 markup = context.get('markup_rate', 0.0)
145 margin = context.get('commercial_margin', 0.0)
146 if not 'value' in res:
147 res['value'] = {}
148 if 'price_unit' in res['value']:
109 price_unit = res['value']['price_unit']149 price_unit = res['value']['price_unit']
110 sale_price = price_unit * (100 - discount) / 100.0150 sale_price = price_unit * (100 - discount) / 100.0
111 markup_res = product_obj.compute_markup(cursor, uid,151 markup_res = product_obj.compute_markup(cr, uid,
112 product_id,152 product_id,
113 product_uom,153 product_uom,
114 pricelist,154 pricelist,
115 sale_price)[product_id]155 sale_price)[product_id]
116156
117157 new_margin = round(markup_res['commercial_margin'],
118 res['value']['commercial_margin'] = round(markup_res['commercial_margin'], _prec(self, cursor, uid))158 _prec(self, cr, uid))
119 res['value']['markup_rate'] = round(markup_res['markup_rate'], _prec(self, cursor, uid))159 if not float_compare(margin, new_margin,
160 precision_digits=_prec(self, cr, uid)) == 0:
161 res['value'].update({
162 'commercial_margin': new_margin,
163 'break_onchange_commercial_margin': True,
164 })
165 new_markup = round(markup_res['markup_rate'],
166 _prec(self, cr, uid))
167 if not float_compare(markup, new_markup,
168 precision_digits=_prec(self, cr, uid)) == 0:
169 res['value'].update({
170 'markup_rate': new_markup,
171 'break_onchange_markup_rate': True,
172 })
120 return res173 return res
121174
122175 def onchange_discount(self, cr, uid, ids, context=None, **kwargs):
123176 """
124 def onchange_discount(self, cursor, uid, ids,177 If discount changes, compute the new markup rate and commercial margin.
125 price_unit, product_id, discount, product_uom, pricelist, **kwargs):178
126 '''179 context arguments:
127 If discount change, compute the new markup rate180 product_id
128 '''181 price_unit
129 res = super(SaleOrderLine,self).onchange_discount(cursor, uid, ids,182 discount
130 price_unit,183 product_uom
131 product_id,184 pricelist
132 discount,185 markup_rate
133 product_uom,186 commercial_margin
134 pricelist)187 break_onchange
135 188
136 if product_id:189 Will change:
137 product_obj = self.pool.get('product.product')190 markup_rate
138 if res['value'].has_key('price_unit'):191 commercial_margin
192 """
193 if context is None:
194 context = {}
195 res = super(SaleOrderLine, self
196 ).onchange_discount(cr, uid, ids, context=context)
197 product_id = context.get('product_id')
198 if context.get('break_onchange'):
199 res['value']['break_onchange_discount'] = False
200 elif product_id:
201 price_unit = context.get('price_unit')
202 discount = context.get('discount')
203 product_uom = context.get('product_uom')
204 pricelist = context.get('pricelist')
205 markup = context.get('markup_rate', 0.0)
206 margin = context.get('commercial_margin', 0.0)
207
208 product_obj = self.pool['product.product']
209 if not 'value' in res:
210 res['value'] = {}
211 if 'price_unit' in res['value']:
139 price_unit = res['value']['price_unit']212 price_unit = res['value']['price_unit']
140 if res['value'].has_key('discount'):213 if 'discount' in res['value']:
141 discount = res['value']['discount']214 discount = res['value']['discount']
142 sale_price = price_unit * (100 - discount) / 100.0215 sale_price = price_unit * (100 - discount) / 100.0
143 markup_res = product_obj.compute_markup(cursor, uid,216 markup_res = product_obj.compute_markup(cr, uid,
144 product_id,217 product_id,
145 product_uom,218 product_uom,
146 pricelist,219 pricelist,
147 sale_price)[product_id]220 sale_price)[product_id]
148221
149222 new_margin = round(markup_res['commercial_margin'],
150 res['value']['commercial_margin'] = round(markup_res['commercial_margin'] , _prec(self, cursor, uid))223 _prec(self, cr, uid))
151 res['value']['markup_rate'] = round(markup_res['markup_rate'], _prec(self, cursor, uid))224 if not float_compare(margin, new_margin,
225 precision_digits=_prec(self, cr, uid)) == 0:
226 res['value'].update({
227 'commercial_margin': new_margin,
228 'break_onchange_commercial_margin': True,
229 })
230 new_markup = round(markup_res['markup_rate'],
231 _prec(self, cr, uid))
232 if not float_compare(markup, new_markup,
233 precision_digits=_prec(self, cr, uid)) == 0:
234 res['value'].update({
235 'markup_rate': new_markup,
236 'break_onchange_markup_rate': True,
237 })
152 return res238 return res
153239
154240 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
155 def product_id_change(self, cursor, uid, ids, pricelist, product, qty=0,241 uom=False, qty_uos=0, uos=False, name='',
156 uom=False, qty_uos=0, uos=False, name='', partner_id=False,242 partner_id=False, lang=False, update_tax=True,
157 lang=False, update_tax=True, date_order=False, packaging=False,243 date_order=False, packaging=False,
158 fiscal_position=False, flag=False, discount=None, price_unit=None, context=None):244 fiscal_position=False, flag=False,
159 '''245 context=None):
246 """
160 Overload method247 Overload method
161 If product change, compute the new markup.248 If product changes, compute the new markup, cost_price and
162 Added params : - price_unit,249 commercial_margin.
250 Added params : - price_unit
163 - discount251 - discount
164 - properties252 - markup_rate
165 '''253 - commercial_margin
254
255 Will change:
256 markup_rate
257 commercial_margin
258 """
166 if context is None:259 if context is None:
167 context = {}260 context = {}
168 discount = discount or 0.0261 res = super(SaleOrderLine, self
169 price_unit = price_unit or 0.0262 ).product_id_change(cr, uid, ids, pricelist, product, qty,
170 res = {}263 uom, qty_uos, uos, name, partner_id,
171 res = super(SaleOrderLine, self).product_id_change(cursor, uid, ids, pricelist, product, qty,264 lang, update_tax, date_order,
172 uom, qty_uos, uos, name, partner_id,265 packaging, fiscal_position, flag,
173 lang, update_tax, date_order, packaging,266 context=context)
174 fiscal_position, flag, context)
175267
176 if product:268 if product:
177 if res['value'].has_key('price_unit'):269 price_unit = context.get('price_unit', 0.0)
270 discount = context.get('discount', 0.0)
271 markup = context.get('markup_rate', 0.0)
272 margin = context.get('commercial_margin', 0.0)
273
274 if not 'value' in res:
275 res['value'] = {}
276 if 'price_unit' in res['value']:
178 price_unit = res['value']['price_unit']277 price_unit = res['value']['price_unit']
179 sale_price = price_unit * (100 - discount) / 100.0278 sale_price = price_unit * (100 - discount) / 100.0
180279
181 product_obj = self.pool.get('product.product')280 product_obj = self.pool['product.product']
182 markup_res = product_obj.compute_markup(cursor, uid,281 markup_res = product_obj.compute_markup(cr, uid,
183 product,282 product,
184 uom,283 uom,
185 pricelist,284 pricelist,
186 sale_price)[product]285 sale_price)[product]
187286
188 res['value']['commercial_margin'] = round(markup_res['commercial_margin'], _prec(self, cursor, uid))287 res['value']['cost_price'] = round(markup_res['cost_price'],
189 res['value']['markup_rate'] = round(int(markup_res['markup_rate'] * 100) / 100.0, _prec(self, cursor, uid))288 _prec(self, cr, uid))
190 res['value']['cost_price'] = round(markup_res['cost_price'], _prec(self, cursor, uid))289 new_margin = round(markup_res['commercial_margin'],
290 _prec(self, cr, uid))
291 if not float_compare(margin, new_margin,
292 precision_digits=_prec(self, cr, uid)) == 0:
293 res['value'].update({
294 'commercial_margin': new_margin,
295 'break_onchange_commercial_margin': True,
296 })
297 new_markup = round(int(markup_res['markup_rate'] * 100) / 100.0,
298 _prec(self, cr, uid))
299 if not float_compare(markup, new_markup,
300 precision_digits=_prec(self, cr, uid)) == 0:
301 res['value'].update({
302 'markup_rate': new_markup,
303 'break_onchange_markup_rate': True,
304 })
191305
192 return res306 return res
193307
194308 def onchange_markup_rate(self, cr, uid, ids, context=None):
195 def onchange_markup_rate(self, cursor, uid, ids,309 """ If markup rate changes compute the discount
196 markup, cost_price, price_unit, context=None):310
197 ''' If markup rate change compute the discount '''311 context arguments:
312 markup_rate
313 commercial_margin
314 cost_price
315 price_unit
316 break_onchange
317
318 Will change:
319 discount
320 commercial_margin
321 """
198 if context is None:322 if context is None:
199 context = {}323 context = {}
200 res = {}324 res = {'value': {}}
201 res['value'] = {}325 if context.get('break_onchange'):
326 res['value']['break_onchange_markup_rate'] = False
327 return res
328
329 markup = context.get('markup_rate')
330 price_unit = context.get('price_unit')
202 markup = markup / 100.0331 markup = markup / 100.0
203 if not price_unit or markup == 1: return {'value': {}}332 if price_unit and not markup == 1:
204 discount = 1 + cost_price / (markup - 1) / price_unit333 cost_price = context.get('cost_price')
205 sale_price = price_unit * (1 - discount)334 margin = context.get('commercial_margin')
206 res['value']['discount'] = round(discount * 100, _prec(self, cursor, uid))335 discount = context.get('discount')
207 res['value']['commercial_margin'] = round(sale_price - cost_price, _prec(self, cursor, uid))336
337 new_discount = 1 + cost_price / (markup - 1) / price_unit
338 sale_price = price_unit * (1 - new_discount)
339
340 new_discount = round(new_discount * 100, _prec(self, cr, uid))
341 if not float_compare(discount, new_discount,
342 precision_digits=_prec(self, cr, uid)) == 0:
343 res['value'].update({
344 'discount': new_discount,
345 'break_onchange_discount': True,
346 })
347
348 new_margin = round(sale_price - cost_price, _prec(self, cr, uid))
349 if not float_compare(margin, new_margin,
350 precision_digits=_prec(self, cr, uid)) == 0:
351 res['value'].update({
352 'commercial_margin': new_margin,
353 'break_onchange_commercial_margin': True,
354 })
208 return res355 return res
209356
210357 def onchange_commercial_margin(self, cr, uid, ids, context=None):
211 def onchange_commercial_margin(self, cursor, uid, ids,358 """ If commercial margin changes compute the discount
212 margin, cost_price, price_unit, context=None):359
213 ''' If markup rate change compute the discount '''360 context arguments:
361 commercial_margin
362 markup_rate
363 discount
364 cost_price
365 price_unit
366 break_onchange
367
368 Will change:
369 discount
370 markup_rate
371 """
214 if context is None:372 if context is None:
215 context = {}373 context = {}
216 res = {}374 res = {'value': {}}
217 res['value'] = {}375 price_unit = context.get('price_unit')
218 if not price_unit: return {'value': {}}376 if context.get('break_onchange'):
219 discount = 1 - ((cost_price + margin) / price_unit)377 res['value']['break_onchange_commercial_margin'] = False
220 sale_price = price_unit * (1 - discount)378 elif price_unit:
221 res['value']['discount'] = round(discount * 100, _prec(self, cursor, uid))379 margin = context.get('commercial_margin')
222 res['value']['markup_rate'] = round(margin / (sale_price or 1.0) * 100, _prec(self, cursor, uid))380 markup = context.get('markup_rate')
381 cost_price = context.get('cost_price')
382 discount = context.get('discount')
383
384 new_discount = 1 - ((cost_price + margin) / price_unit)
385 sale_price = price_unit * (1 - new_discount)
386
387 new_discount = round(new_discount * 100, _prec(self, cr, uid))
388 if not float_compare(discount, new_discount,
389 precision_digits=_prec(self, cr, uid)) == 0:
390 res['value'].update({
391 'discount': new_discount,
392 'break_onchange_discount': True,
393 })
394 new_markup = round(margin / (sale_price or 1.0) * 100,
395 _prec(self, cr, uid))
396 if not float_compare(markup, new_markup,
397 precision_digits=_prec(self, cr, uid)) == 0:
398 res['value'].update({
399 'markup_rate': new_markup,
400 'break_onchange_markup_rate': True,
401 })
223 return res402 return res
224403
=== modified file 'sale_markup/sale_view.xml'
--- sale_markup/sale_view.xml 2012-07-23 12:01:18 +0000
+++ sale_markup/sale_view.xml 2014-06-10 15:19:47 +0000
@@ -7,13 +7,15 @@
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 -->
8 <record model="ir.ui.view" id="sale_markup_sale_order_view">8 <record model="ir.ui.view" id="sale_markup_sale_order_view">
9 <field name="name">sale.order.markup.view.form</field>9 <field name="name">sale.order.markup.view.form</field>
10 <field name="type">form</field>
11 <field name="model">sale.order</field>10 <field name="model">sale.order</field>
12 <field name="inherit_id" ref="sale.view_order_form" />11 <field name="inherit_id" ref="sale.view_order_form" />
13 <field name="arch" type="xml">12 <field name="arch" type="xml">
14 <xpath expr="//button[@name='button_dummy']" position="before">13 <xpath expr="//group[@name='sale_total']" position="after">
15 <field name="markup_rate"14 <group class="oe_subtotal_footer oe_right" colspan="2" name="sale_markup"
16 groups="base.group_sale_manager"/>15 groups="base.group_sale_manager">
16 <field name="markup_rate"
17 groups="base.group_sale_manager"/>
18 </group>
17 </xpath>19 </xpath>
18 </field>20 </field>
19 </record>21 </record>
@@ -21,7 +23,6 @@
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 -->
22 <record model="ir.ui.view" id="sale_markup_sale_order_tree">24 <record model="ir.ui.view" id="sale_markup_sale_order_tree">
23 <field name="name">sale.order.markup.view.tree</field>25 <field name="name">sale.order.markup.view.tree</field>
24 <field name="type">tree</field>
25 <field name="model">sale.order</field>26 <field name="model">sale.order</field>
26 <field name="inherit_id" ref="sale.view_order_tree" />27 <field name="inherit_id" ref="sale.view_order_tree" />
27 <field name="arch" type="xml">28 <field name="arch" type="xml">
@@ -34,7 +35,6 @@
3435
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">
36 <field name="name">sale.order.line.markup.view.tree</field>37 <field name="name">sale.order.line.markup.view.tree</field>
37 <field name="type">tree</field>
38 <field name="model">sale.order.line</field>38 <field name="model">sale.order.line</field>
39 <field name="inherit_id" ref="sale.view_order_line_tree" />39 <field name="inherit_id" ref="sale.view_order_line_tree" />
40 <field name="arch" type="xml">40 <field name="arch" type="xml">
@@ -52,37 +52,41 @@
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 -->
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">
54 <field name="name">sale.order.markup.view.form2</field>54 <field name="name">sale.order.markup.view.form2</field>
55 <field name="type">form</field>
56 <field name="model">sale.order</field>55 <field name="model">sale.order</field>
57 <field name="inherit_id" ref="sale.view_order_form" />56 <field name="inherit_id" ref="sale.view_order_form" />
58 <field name="arch" type="xml">57 <field name="arch" type="xml">
59 <xpath expr="//field[@name='product_id']"58 <xpath expr="//field[@name='order_line']/form//field[@name='product_id']"
60 position="replace">59 position="attributes">
61 <field colspan="4"60 <attribute name="context_markup">{'discount': discount, 'price_unit': price_unit, 'markup_rate': markup_rate, 'commercial_margin': commercial_margin}</attribute>
62 name="product_id"61 </xpath>
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']"
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">
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>
66 </xpath>65 </xpath>
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']"
68 position="attributes">67 position="attributes">
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>
70 </xpath>69 </xpath>
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']"
72 position="after">71 position="after">
73 <separator string="Markup" colspan="5" groups="base.group_sale_manager"/>72 <group string="Markup" groups="base.group_sale_manager">
74 <field name="commercial_margin"73 <field name="commercial_margin"
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}"
75 on_change="onchange_commercial_margin(context)"
76 groups="base.group_sale_manager"/>76 groups="base.group_sale_manager"/>
77 <field name="markup_rate"77 <field name="markup_rate"
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}"
79 on_change="onchange_markup_rate(context)"
79 groups="base.group_sale_manager"/>80 groups="base.group_sale_manager"/>
80 <field name="cost_price"81 <field name="cost_price"
81 groups="base.group_sale_manager" invisible="1"/>82 groups="base.group_sale_manager" invisible="1"/>
82 <newline/>83 <field name="break_onchange_commercial_margin" invisible="1"/>
84 <field name="break_onchange_markup_rate" invisible="1"/>
85 <field name="break_onchange_discount" invisible="1"/>
86 </group>
83 </xpath>87 </xpath>
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">
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>
86 </xpath>90 </xpath>
87 </field>91 </field>
88 </record>92 </record>
@@ -90,12 +94,11 @@
90 <!-- Add Markup in Sales Orders form's Sale order lines tree -->94 <!-- Add Markup in Sales Orders form's Sale order lines tree -->
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">
92 <field name="name">sale.order.markup.view.form</field>96 <field name="name">sale.order.markup.view.form</field>
93 <field name="type">form</field>
94 <field name="model">sale.order</field>97 <field name="model">sale.order</field>
95 <field name="inherit_id" ref="sale.view_order_form" />98 <field name="inherit_id" ref="sale.view_order_form" />
96 <field name="arch" type="xml">99 <field name="arch" type="xml">
97 <xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']"100 <xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']"
98 position="after">101 position="after">
99 <field name="cost_price"102 <field name="cost_price"
100 groups="base.group_sale_manager"/>103 groups="base.group_sale_manager"/>
101 <field name="markup_rate"104 <field name="markup_rate"
102105
=== added directory 'sale_markup/tests'
=== added file 'sale_markup/tests/__init__.py'
--- sale_markup/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ sale_markup/tests/__init__.py 2014-06-10 15:19:47 +0000
@@ -0,0 +1,21 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Yannick Vaucher
5# Copyright 2014 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21from . import test_sale_markup
022
=== added file 'sale_markup/tests/test_sale_markup.py'
--- sale_markup/tests/test_sale_markup.py 1970-01-01 00:00:00 +0000
+++ sale_markup/tests/test_sale_markup.py 2014-06-10 15:19:47 +0000
@@ -0,0 +1,197 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Yannick Vaucher
5# Copyright 2014 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21import openerp.tests.common as common
22from openerp.addons import get_module_resource
23
24
25def _trigger_on_changes(self, cr, uid, sale_order, view_values, changed_values):
26 triggered_on_changes = []
27 while [i for i in changed_values.keys() if i not in triggered_on_changes]:
28 res_values = {}
29 for field in view_values.keys():
30 if field in triggered_on_changes:
31 continue
32 triggered_on_changes.append(field)
33 if field == 'product_id':
34 res_values = self.SaleOrderLine.product_id_change(
35 cr, uid, False,
36 sale_order.pricelist_id.id,
37 view_values.get('product_id'),
38 qty=view_values.get('product_uom_qty'),
39 uom=False,
40 qty_uos=view_values.get('product_uos_qty'),
41 uos=False,
42 name='sol_test_1',
43 partner_id=sale_order.partner_id.id,
44 lang=False,
45 update_tax=True,
46 date_order=sale_order.date_order,
47 packaging=False,
48 fiscal_position=sale_order.fiscal_position.id,
49 flag=False,
50 context=view_values)
51 break
52 elif field == 'price_unit':
53 res_values = self.SaleOrderLine.onchange_price_unit(cr, uid, False, context=view_values)
54 break
55 elif field == 'discount':
56 res_values = self.SaleOrderLine.onchange_discount(cr, uid, False, context=view_values)
57 break
58 elif field == 'commercial_margin':
59 res_values = self.SaleOrderLine.onchange_commercial_margin(cr, uid, False, context=view_values)
60 break
61 elif field == 'markup_rate':
62 res_values = self.SaleOrderLine.onchange_markup_rate(cr, uid, False, context=view_values)
63 break
64 if res_values:
65 changed_values.update(res_values['value'])
66 view_values.update(res_values['value'])
67 return view_values
68
69class test_sale_markup(common.TransactionCase):
70 """ Test the wizard for delivery carrier label generation """
71
72 def setUp(self):
73 super(test_sale_markup, self).setUp()
74 cr, uid = self.cr, self.uid
75
76 self.SaleOrder = self.registry('sale.order')
77 self.SaleOrderLine = self.registry('sale.order.line')
78 self.Product = self.registry('product.product')
79 self.product_33 = self.Product.browse(
80 cr, uid, self.ref('product.product_product_33'))
81 self.ResPartner = self.registry('res.partner')
82 self.partner_12 = self.ResPartner.browse(
83 cr, uid, self.ref('base.res_partner_12'))
84
85 def test_00_create_sale_order(self):
86 """ Check markup computing in sale order
87
88 And check each on_changes
89 """
90 cr, uid = self.cr, self.uid
91
92 so_data = {'partner_id': self.partner_12.id,
93 }
94 res = self.SaleOrder.onchange_partner_id(cr, uid, False, self.partner_12.id)
95 so_data.update(res['value'])
96
97 so_1_id = self.SaleOrder.create(cr, uid, so_data)
98 so_1 = self.SaleOrder.browse(cr, uid, so_1_id)
99
100 ctx = {'product_id': self.product_33.id,
101 'product_uom': self.ref('product.product_uom_unit'),
102 'discount': 0.0,
103 'product_uom_qty': 1,
104 'product_uos_qty': 1,
105 'sequence': 10,
106 'state': 'draft',
107 'type': 'make_to_stock',
108 'price_unit': 0.0,
109 'order_id': so_1_id,
110 }
111
112 # I set product A on sale order and trigger the on_change on product.
113 res = self.SaleOrderLine.product_id_change(
114 cr, uid, False,
115 so_1.pricelist_id.id,
116 ctx.get('product_id'),
117 qty=ctx.get('product_uom_qty'),
118 uom=False,
119 qty_uos=ctx.get('product_uos_qty'),
120 uos=False,
121 name='sol_test_1',
122 partner_id=so_1.partner_id.id,
123 lang=False,
124 update_tax=True,
125 date_order=so_1.date_order,
126 packaging=False,
127 fiscal_position=so_1.fiscal_position.id,
128 flag=False,
129 context=ctx)
130
131 ctx.update(res['value'])
132 res = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
133 # cost_price should be set and equal to product cost price.
134 assert abs(res.get('cost_price') - self.Product.get_cost_field(cr, uid, self.product_33.id)[self.product_33.id]) < 0.01
135
136 # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
137 commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
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)
139 # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
140 markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
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)
142
143 # I add 1 percent to discount and trigger the on_change on discount.
144 ctx['discount'] = 1.0
145 res = self.SaleOrderLine.onchange_discount(cr, uid, False, context=ctx)
146 ctx.update(res['value'])
147 ctx = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
148
149 # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
150 commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
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)
152
153 # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
154 markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
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)
156
157 # I change the markup rate to 20.0 and trigger the on_change on markup_rate.
158 ctx['markup_rate'] = 20.0
159 res = self.SaleOrderLine.onchange_markup_rate(cr, uid, False, context=ctx)
160 ctx.update(res['value'])
161 ctx = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
162
163 # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
164 commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
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)
166
167 # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
168 markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
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)
170
171
172 # I change the price unit to 2000.0 and trigger the on_change on price_unit.
173 ctx['price_unit'] = 2000.0
174 res = self.SaleOrderLine.onchange_price_unit(cr, uid, False, context=ctx)
175 ctx.update(res['value'])
176 ctx = _trigger_on_changes(self, cr, uid, so_1, ctx, res['value'])
177
178 # commercial_margin should be updated and equal to price_unit * (1 - (discount / 100.0)) - cost_price
179 commercial_margin = (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0)) - ctx.get('cost_price'))
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)
181
182 # markup_rate should be updated and equal to commercial_margin / (price_unit * (1 - (discount / 100.0)))
183 markup_rate = (ctx.get('commercial_margin') / (ctx.get('price_unit') * (1 - (ctx.get('discount') / 100.0))) * 100.0)
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)
185
186 sol_data = ctx
187
188 # I create the sale order line for the sale order.
189 sol_1_id = self.SaleOrderLine.create(
190 cr, uid,
191 sol_data
192 )
193
194 so_1.refresh()
195 assert so_1.markup_rate
196 # as we have only one line it should be equal to our last line markup
197 assert abs(so_1.markup_rate - markup_rate) < 0.01

Subscribers

People subscribed via source and target branches

to all changes: