Merge lp:~camptocamp/openerp-product-attributes/port-add-product_multi_company_7.0-bis-jge into lp:~product-core-editors/openerp-product-attributes/7.0
- port-add-product_multi_company_7.0-bis-jge
- Merge into 7.0
Status: | Superseded |
---|---|
Proposed branch: | lp:~camptocamp/openerp-product-attributes/port-add-product_multi_company_7.0-bis-jge |
Merge into: | lp:~product-core-editors/openerp-product-attributes/7.0 |
Diff against target: |
1626 lines (+1238/-196) 21 files modified
base_custom_attributes/custom_attributes.py (+18/-3) base_custom_attributes/custom_attributes_view.xml (+7/-4) base_custom_attributes/security/ir.model.access.csv (+3/-0) product_multi_company/i18n/en_US.po (+0/-32) product_multi_company/i18n/es.po (+0/-36) product_multi_company/i18n/fr.po (+0/-33) product_multi_company/i18n/product_multi_company.pot (+0/-32) product_price_history/__init__.py (+4/-4) product_price_history/__openerp__.py (+55/-15) product_price_history/demo/product_price_history_purchase_demo.yml (+189/-0) product_price_history/i18n/product_price_history.pot (+210/-0) product_price_history/product_price_history.py (+216/-37) product_price_history/product_price_history_view.xml (+90/-0) product_price_history/security/ir.model.access.csv (+7/-0) product_price_history/security/product_price_history_security.xml (+13/-0) product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml (+186/-0) product_price_history/test/price_controlling_multicompany.yml (+55/-0) product_price_history/test/price_historization.yml (+55/-0) product_price_history/wizard/__init__.py (+24/-0) product_price_history/wizard/historic_prices.py (+70/-0) product_price_history/wizard/historic_prices_view.xml (+36/-0) |
To merge this branch: | bzr merge lp:~camptocamp/openerp-product-attributes/port-add-product_multi_company_7.0-bis-jge |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guewen Baconnier @ Camptocamp | code review | Approve | |
Pedro Manuel Baeza | code review and test | Approve | |
Yannick Vaucher @ Camptocamp | Needs Fixing | ||
Maxime Chambreuil (http://www.savoirfairelinux.com) | code review | Pending | |
Review via email: mp+192872@code.launchpad.net |
This proposal supersedes a proposal from 2013-10-16.
This proposal has been superseded by a proposal from 2013-12-04.
Commit message
Description of the change
Hi,
I propose here a brand new version of product_
It uses another table to store and historize the prices instead of using ir.property. You can also easily override the field to historize in other module to add your own.
I provided here a whole set of tests and demo data to ensure no regression and show how to setup OpenERP in a complex multi-company, multi-currency context. With this module, you can have a same shared product between company, with each of them their own average price computed. You can even have the standard_price recorded in USD for company 1 and CHF for company 2 for example.
See description of the module for more details.
Note that this module replace the old one. I prefered to update this module rather than making another one (with another name). So, If you think it's better to call it differently, please just say it !
Thanks for the review,
Joël
Maxime Chambreuil (http://www.savoirfairelinux.com) (max3903) wrote : Posted in a previous version of this proposal | # |
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : Posted in a previous version of this proposal | # |
You're right Maxime, I agree with you. Since this module was originally put here by you, I don't wanted to mode it from here. But if you're alright, let's move it !
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : Posted in a previous version of this proposal | # |
Just resumbit on the proper branch after using bzr-replay
Pedro Manuel Baeza (pedro.baeza) wrote : Posted in a previous version of this proposal | # |
Hi, Joel, thank you very much for the module. Indeed, it seems to be quite interesting, but I see here two concepts put together in the same module:
- On one hand, you have prices historization, what is a very good feature, but nothing to do with multi-company. Besides, this feature can be also interesting for single-company.
- On the other hand, multi-company feature of this module is about prices, but not products in general, so I would rename the module to product_
I know that I'm asking too much, but if you want, we can work together to make these changes and get two very useful modules.
Regards.
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : Posted in a previous version of this proposal | # |
Hi Pedro,
Thanks for your remarks ! This module bing 2 main behaviors: Having price by company (like ir.property, but faster) + historization... So difficult to chose...
For historization:
I agree with your remarks. I was wondering about changing the name of this module because of the historization concept, very useful for others needs than multi-company. The thing is here the way it's implemented. To have this historization (that work on single company), you'll also need the ir.rule on price_type to make it correct in multi-company context. As well the table price_history adds the company_id. In case you're in a single company, nothing to do, it just work out of the box but just provide historization. In a multi-company context, it adds historization + different price per company (in different currency if needed).
For allowing a product to be visible/selectable by companies:
I think IMO that this is another need. By default, you define ir.rule to share product or not between company. If a product has a company_id (with default ir.rule), he's only part of that company. If is has not, it is shared for all company. Providing a o2m to deal with that at product level seems interesting, but not required and can easily be added in another module what do you think ?
Conclusion/
- I suggest another module to implement the "product to be visible/selectable by companies with a one2many" and that also adapt the default ir.rule provided by OpenERP on product.product
- I suggest to rename this module: product_
Other opinions ?
Maxime Chambreuil (http://www.savoirfairelinux.com) (max3903) wrote : Posted in a previous version of this proposal | # |
lgtm
Ronald Portier (Therp) (rportier1962) wrote : Posted in a previous version of this proposal | # |
Looks very promising!
One remark:
please rename 'test/average_
Pedro Manuel Baeza (pedro.baeza) wrote : Posted in a previous version of this proposal | # |
I have added the other product_
But for this module, do you agree to rename and reallocate it in the proper place?
Regards.
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : Posted in a previous version of this proposal | # |
Hi,
I changed the name of the test file according to your remarks. I'm also push this MP back to openerp-
Regards,
- 221. By Yannick Vaucher @ Camptocamp
-
[UPD]
- 222. By Yannick Vaucher @ Camptocamp
-
[MRG] update from lp:~camptocamp/openerp-product-attributes/7.0-fix-1245875-yvr
- 223. By Yannick Vaucher @ Camptocamp
-
[MRG] update from lp:~camptocamp/openerp-product-attributes/7.0-base_custom_attributes-wiz-imp-yvr
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote : | # |
Need po files
Otherwise lgtm
Romain Deheele - Camptocamp (romaindeheele) wrote : | # |
Hi Joel,
Are you aware about :
lp:~camptocamp/openerp-product-attributes/7.0-add-product_multi_company_new-jge
and two last commits?
Romain
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote : | # |
Romain
I setted
https:/
as Abandonned branch
Can you check and add your fixes again?
Romain Deheele - Camptocamp (romaindeheele) wrote : | # |
I add my fixes,
Romain
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
Hi, Joël and Romain,
Thank you very much for the effort you are making to get this module in the best way. I have tested it and view the code, and these are my remarks:
- We agreed about renaming to product_
- You can put in the description that is multi-company aware and avoid it on the module name.
- Put product_
- Create at least the pot translation template file.
- Rename 'price.history' model to 'product.
- In security/
- It would be very interesting to have any screen to query past prices.
Regards.
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote : | # |
Just for the records of the MP
On Thu, Oct 31, 2013 at 5:37 PM, Pedro Manuel Baeza Romero <
<email address hidden>> wrote:
> Well, product_
>
> However, I swear that I have seen this term a lot of times on software.
>
> Regards.
>
>
> 2013/10/31 Ray Carnes <email address hidden>
>
>> Also in Australia, Canada (English speaking part) and the USA (and the
>> other 57 countries where English is the official
>> language), people would also see this as an ugly non-word.
>>
>> (No offence intended).
>>
>> Ray.
>>
>> -----Original Message-----
>> From: Openerp-community [mailto:
>> <email address hidden>] On Behalf Of
>> Martin Collins
>> Sent: Thursday, October 31, 2013 9:28 AM
>> To: <email address hidden>
>> Subject: Re: [Openerp-community] [Merge]
>> lp:~camptocamp/openerp-product-attributes/port-add-product_multi_company_7.0-bis-jge
>> into lp:openerp-product-attributes
>>
>> On 2013-10-31 09:33, Pedro Manuel Baeza wrote:
>> >
>> > - We agreed about renaming to product_
>>
>> To an Englishman like me 'historization' is a very ugly non-word. How
>> about just 'history'.
>>
>> Martin
>>
>
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Hi there,
So, I made the requested changes :
* Rename module
* Add .pot file
* Include right management changes
* Create a demo folder for demo files
* Add to view to display product historic prices (one in reporting, one from product)
Everything tested and all green.
Can you update your review based on those modifications please ?
Thanks !
Regards,
Joël
- 224. By Joël Grand-Guillaume @ camptocamp
-
[REN] Rename module and model to product_
price_history
[ADD] View from product form to display historical prices
[ADD] View to reporting to display the product list at a given date for prices analysis
[FIX] Adapt tests and security - 225. By Joël Grand-Guillaume @ camptocamp
-
[ADD] .pot file for translation
- 226. By Joël Grand-Guillaume @ camptocamp
-
[DEL] .pyc file.. GRrrr.
- 227. By Joël Grand-Guillaume @ camptocamp
-
[MRG] Last fix from Romain suggested in MP
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
Hi, Joël
Thanks again for your extensive work. The module works like a charm. I have seen one minor thing, but I approve it anyway:
- In the module description, the lines of the bulleted list are split in the middle, not respecting the indentation (maybe you have to put two spaces to align to the beginning of the text?).
Regards.
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Thanks for pointing that out, it's fixed now !
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
Made some changes, approve.
- 228. By Joël Grand-Guillaume @ camptocamp
-
[DOC] Make a nice description of the module.
- 229. By Joël Grand-Guillaume @ camptocamp
-
[IMP] Add company_id on price.type object to allow manager to verify that the setup is correct regarding price.type in a multi-company context
- 230. By Joël Grand-Guillaume @ camptocamp
-
[MRG] From trunk
- 231. By Joël Grand-Guillaume @ camptocamp
-
[FIX] Security rule on price.type
- 232. By Joël Grand-Guillaume @ camptocamp
-
[FIX] When prices are read from a function field, OpenERP replace the current uid. That lead to reading the price of the company of the admin user instead of the current one. This fix that by retrieving the real user from context in that case
- 233. By Guewen Baconnier @ Camptocamp
-
[MRG] from lp:~camptocamp/openerp-product-attributes/7.0-attribute-set-create-1256023
- 234. By Joël Grand-Guillaume @ camptocamp
-
[MRG] [IMP] in CRUD operation, we need to check the proper company_id with wich we work. ir.cron and function field may replace the uid used, so f..
- 235. By Joël Grand-Guillaume @ camptocamp
-
[MRG] [IMP] Change the variable name of date historic given through context to use 'to_date' as it is used in stock reporting => this allow to have the...
- 236. By Joël Grand-Guillaume @ camptocamp
-
[IMP] Refactor the log_price method to let it be used in other modulesmore easily
Unmerged revisions
- 236. By Joël Grand-Guillaume @ camptocamp
-
[IMP] Refactor the log_price method to let it be used in other modulesmore easily
- 235. By Joël Grand-Guillaume @ camptocamp
-
[MRG] [IMP] Change the variable name of date historic given through context to use 'to_date' as it is used in stock reporting => this allow to have the...
- 234. By Joël Grand-Guillaume @ camptocamp
-
[MRG] [IMP] in CRUD operation, we need to check the proper company_id with wich we work. ir.cron and function field may replace the uid used, so f..
- 233. By Guewen Baconnier @ Camptocamp
-
[MRG] from lp:~camptocamp/openerp-product-attributes/7.0-attribute-set-create-1256023
- 232. By Joël Grand-Guillaume @ camptocamp
-
[FIX] When prices are read from a function field, OpenERP replace the current uid. That lead to reading the price of the company of the admin user instead of the current one. This fix that by retrieving the real user from context in that case
- 231. By Joël Grand-Guillaume @ camptocamp
-
[FIX] Security rule on price.type
- 230. By Joël Grand-Guillaume @ camptocamp
-
[MRG] From trunk
- 229. By Joël Grand-Guillaume @ camptocamp
-
[IMP] Add company_id on price.type object to allow manager to verify that the setup is correct regarding price.type in a multi-company context
- 228. By Joël Grand-Guillaume @ camptocamp
-
[DOC] Make a nice description of the module.
- 227. By Joël Grand-Guillaume @ camptocamp
-
[MRG] Last fix from Romain suggested in MP
Preview Diff
1 | === modified file 'base_custom_attributes/custom_attributes.py' |
2 | --- base_custom_attributes/custom_attributes.py 2013-11-13 08:38:19 +0000 |
3 | +++ base_custom_attributes/custom_attributes.py 2013-12-04 10:24:54 +0000 |
4 | @@ -246,6 +246,21 @@ |
5 | } |
6 | |
7 | def create(self, cr, uid, vals, context=None): |
8 | + if vals.get('field_id'): |
9 | + field_obj = self.pool.get('ir.model.fields') |
10 | + field = field_obj.browse(cr, uid, vals['field_id'], context=context) |
11 | + if vals.get('serialized'): |
12 | + raise orm.except_orm( |
13 | + _('Error'), |
14 | + _("Can't create a serialized attribute on " |
15 | + "an existing ir.model.fields (%s)") % field.name) |
16 | + if field.state != 'manual': |
17 | + # the ir.model.fields already exists and we want to map |
18 | + # an attribute on it. We can't change the field so we |
19 | + # won't add the ttype, relation and so on. |
20 | + return super(attribute_attribute, self).create(cr, uid, vals, |
21 | + context=context) |
22 | + |
23 | if vals.get('relation_model_id'): |
24 | relation = self.pool.get('ir.model').read( |
25 | cr, uid, [vals.get('relation_model_id')], ['model'])[0]['model'] |
26 | @@ -352,9 +367,9 @@ |
27 | } |
28 | |
29 | def create(self, cr, uid, vals, context=None): |
30 | - for attribute in vals['attribute_ids']: |
31 | - if vals.get('attribute_set_id') and attribute[2] and \ |
32 | - not attribute[2].get('attribute_set_id'): |
33 | + for attribute in vals.get('attribute_ids', []): |
34 | + if (vals.get('attribute_set_id') and attribute[2] and |
35 | + not attribute[2].get('attribute_set_id')): |
36 | attribute[2]['attribute_set_id'] = vals['attribute_set_id'] |
37 | return super(attribute_group, self).create(cr, uid, vals, context) |
38 | |
39 | |
40 | === modified file 'base_custom_attributes/custom_attributes_view.xml' |
41 | --- base_custom_attributes/custom_attributes_view.xml 2013-11-12 08:02:42 +0000 |
42 | +++ base_custom_attributes/custom_attributes_view.xml 2013-12-04 10:24:54 +0000 |
43 | @@ -286,11 +286,14 @@ |
44 | <field name="name">attribute.option.wizard</field> |
45 | <field name="model">attribute.option.wizard</field> |
46 | <field name="arch" type="xml"> |
47 | - <form string="Options Wizard" col="6"> |
48 | - <field name="attribute_id" invisible="1" colspan="2"/> |
49 | + <form string="Options Wizard" version="7.0"> |
50 | + <field name="attribute_id" invisible="1"/> |
51 | <separator string="options_placeholder"/> |
52 | - <button special="cancel" string="Cancel" icon="gtk-cancel"/> |
53 | - <button name="validate" string="Validate" type="object" icon="gtk-convert"/> |
54 | + <footer> |
55 | + <button name="validate" string="Validate" type="object" icon="gtk-convert" class="oe_highlight"/> |
56 | + or |
57 | + <button string="Cancel" class="oe_link" special="cancel"/> |
58 | + </footer> |
59 | </form> |
60 | </field> |
61 | </record> |
62 | |
63 | === modified file 'base_custom_attributes/security/ir.model.access.csv' |
64 | --- base_custom_attributes/security/ir.model.access.csv 2013-07-23 14:39:16 +0000 |
65 | +++ base_custom_attributes/security/ir.model.access.csv 2013-12-04 10:24:54 +0000 |
66 | @@ -1,12 +1,15 @@ |
67 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
68 | +access_base_custom_attributes_attribute_set_salemanager,base_custom_attributes_attribute_set,base_custom_attributes.model_attribute_set,base.group_sale_manager,1,1,1,1 |
69 | access_base_custom_attributes_attribute_group_salemanager,base_custom_attributes_attribute_group,base_custom_attributes.model_attribute_group,base.group_sale_manager,1,1,1,1 |
70 | access_base_custom_attributes_attribute_attribute_salemanager,base_custom_attributes_product_attribute,base_custom_attributes.model_attribute_attribute,base.group_sale_manager,1,1,1,1 |
71 | access_base_custom_attributes_attribute_option_salemanager,base_custom_attributes_attribute_option,base_custom_attributes.model_attribute_option,base.group_sale_manager,1,1,1,1 |
72 | access_base_custom_attributes_attribute_location_salemanager,base_custom_attributes_attribute_location,base_custom_attributes.model_attribute_location,base.group_sale_manager,1,1,1,1 |
73 | +access_base_custom_attributes_attribute_set_manager,base_custom_attributes_attribute_set,base_custom_attributes.model_attribute_set,base.group_no_one,1,1,1,1 |
74 | access_base_custom_attributes_attribute_group_manager,base_custom_attributes_attribute_group,base_custom_attributes.model_attribute_group,base.group_no_one,1,1,1,1 |
75 | access_base_custom_attributes_attribute_attribute_manager,base_custom_attributes_attribute_attribute,base_custom_attributes.model_attribute_attribute,base.group_no_one,1,1,1,1 |
76 | access_base_custom_attributes_attribute_option_manager,base_custom_attributes_attribute_option,base_custom_attributes.model_attribute_option,base.group_no_one,1,1,1,1 |
77 | access_base_custom_attributes_attribute_location_manager,base_custom_attributes_attribute_location,base_custom_attributes.model_attribute_location,base.group_no_one,1,1,1,1 |
78 | +access_base_custom_attributes_attribute_set_user,base_custom_attributes_attribute_set,base_custom_attributes.model_attribute_set,base.group_user,1,0,0,0 |
79 | access_base_custom_attributes_attribute_group_user,base_custom_attributes_attribute_group,base_custom_attributes.model_attribute_group,base.group_user,1,0,0,0 |
80 | access_base_custom_attributes_attribute_attribute_user,base_custom_attributes_attribute_attribute,base_custom_attributes.model_attribute_attribute,base.group_user,1,0,0,0 |
81 | access_base_custom_attributes_attribute_option_user,base_custom_attributes_attribute_option,base_custom_attributes.model_attribute_option,base.group_user,1,0,0,0 |
82 | |
83 | === removed directory 'product_multi_company/i18n' |
84 | === removed file 'product_multi_company/i18n/en_US.po' |
85 | --- product_multi_company/i18n/en_US.po 2010-12-29 07:42:35 +0000 |
86 | +++ product_multi_company/i18n/en_US.po 1970-01-01 00:00:00 +0000 |
87 | @@ -1,32 +0,0 @@ |
88 | -# Translation of OpenERP Server. |
89 | -# This file contains the translation of the following modules: |
90 | -# * product_multi_company |
91 | -# |
92 | -msgid "" |
93 | -msgstr "" |
94 | -"Project-Id-Version: OpenERP Server 6.0.0-rc1\n" |
95 | -"Report-Msgid-Bugs-To: support@openerp.com\n" |
96 | -"POT-Creation-Date: 2010-12-28 12:10:13+0000\n" |
97 | -"PO-Revision-Date: 2010-12-28 12:10:13+0000\n" |
98 | -"Last-Translator: <>\n" |
99 | -"Language-Team: \n" |
100 | -"MIME-Version: 1.0\n" |
101 | -"Content-Type: text/plain; charset=UTF-8\n" |
102 | -"Content-Transfer-Encoding: \n" |
103 | -"Plural-Forms: \n" |
104 | - |
105 | -#. module: product_multi_company |
106 | -#: model:ir.model,name:product_multi_company.model_product_template |
107 | -msgid "Product Template" |
108 | -msgstr "" |
109 | - |
110 | -#. module: product_multi_company |
111 | -#: constraint:product.template:0 |
112 | -msgid "Error: The default UOM and the purchase UOM must be in the same category." |
113 | -msgstr "" |
114 | - |
115 | -#. module: product_multi_company |
116 | -#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo |
117 | -msgid "pricelist.partnerinfo" |
118 | -msgstr "" |
119 | - |
120 | |
121 | === removed file 'product_multi_company/i18n/es.po' |
122 | --- product_multi_company/i18n/es.po 2012-12-05 05:42:11 +0000 |
123 | +++ product_multi_company/i18n/es.po 1970-01-01 00:00:00 +0000 |
124 | @@ -1,36 +0,0 @@ |
125 | -# Spanish translation for openobject-addons |
126 | -# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 |
127 | -# This file is distributed under the same license as the openobject-addons package. |
128 | -# FIRST AUTHOR <EMAIL@ADDRESS>, 2011. |
129 | -# |
130 | -msgid "" |
131 | -msgstr "" |
132 | -"Project-Id-Version: openobject-addons\n" |
133 | -"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" |
134 | -"POT-Creation-Date: 2010-12-28 12:10+0000\n" |
135 | -"PO-Revision-Date: 2011-08-27 14:01+0000\n" |
136 | -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
137 | -"Language-Team: Spanish <es@li.org>\n" |
138 | -"MIME-Version: 1.0\n" |
139 | -"Content-Type: text/plain; charset=UTF-8\n" |
140 | -"Content-Transfer-Encoding: 8bit\n" |
141 | -"X-Launchpad-Export-Date: 2012-12-05 05:41+0000\n" |
142 | -"X-Generator: Launchpad (build 16335)\n" |
143 | - |
144 | -#. module: product_multi_company |
145 | -#: model:ir.model,name:product_multi_company.model_product_template |
146 | -msgid "Product Template" |
147 | -msgstr "Plantilla de producto" |
148 | - |
149 | -#. module: product_multi_company |
150 | -#: constraint:product.template:0 |
151 | -msgid "" |
152 | -"Error: The default UOM and the purchase UOM must be in the same category." |
153 | -msgstr "" |
154 | -"Error: La UdM por defecto y la UdM de compra deben estar en la misma " |
155 | -"categoría." |
156 | - |
157 | -#. module: product_multi_company |
158 | -#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo |
159 | -msgid "pricelist.partnerinfo" |
160 | -msgstr "listaprecios.infoempresa" |
161 | |
162 | === removed file 'product_multi_company/i18n/fr.po' |
163 | --- product_multi_company/i18n/fr.po 2012-12-05 05:42:11 +0000 |
164 | +++ product_multi_company/i18n/fr.po 1970-01-01 00:00:00 +0000 |
165 | @@ -1,33 +0,0 @@ |
166 | -# Translation of OpenERP Server. |
167 | -# This file contains the translation of the following modules: |
168 | -# * product_multi_company |
169 | -# |
170 | -msgid "" |
171 | -msgstr "" |
172 | -"Project-Id-Version: OpenERP Server 6.0.0-rc1\n" |
173 | -"Report-Msgid-Bugs-To: support@openerp.com\n" |
174 | -"POT-Creation-Date: 2010-12-28 12:10+0000\n" |
175 | -"PO-Revision-Date: 2011-02-15 17:25+0000\n" |
176 | -"Last-Translator: <>\n" |
177 | -"Language-Team: \n" |
178 | -"MIME-Version: 1.0\n" |
179 | -"Content-Type: text/plain; charset=UTF-8\n" |
180 | -"Content-Transfer-Encoding: 8bit\n" |
181 | -"X-Launchpad-Export-Date: 2012-12-05 05:41+0000\n" |
182 | -"X-Generator: Launchpad (build 16335)\n" |
183 | - |
184 | -#. module: product_multi_company |
185 | -#: model:ir.model,name:product_multi_company.model_product_template |
186 | -msgid "Product Template" |
187 | -msgstr "" |
188 | - |
189 | -#. module: product_multi_company |
190 | -#: constraint:product.template:0 |
191 | -msgid "" |
192 | -"Error: The default UOM and the purchase UOM must be in the same category." |
193 | -msgstr "" |
194 | - |
195 | -#. module: product_multi_company |
196 | -#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo |
197 | -msgid "pricelist.partnerinfo" |
198 | -msgstr "" |
199 | |
200 | === removed file 'product_multi_company/i18n/product_multi_company.pot' |
201 | --- product_multi_company/i18n/product_multi_company.pot 2010-12-29 07:42:35 +0000 |
202 | +++ product_multi_company/i18n/product_multi_company.pot 1970-01-01 00:00:00 +0000 |
203 | @@ -1,32 +0,0 @@ |
204 | -# Translation of OpenERP Server. |
205 | -# This file contains the translation of the following modules: |
206 | -# * product_multi_company |
207 | -# |
208 | -msgid "" |
209 | -msgstr "" |
210 | -"Project-Id-Version: OpenERP Server 6.0.0-rc1\n" |
211 | -"Report-Msgid-Bugs-To: support@openerp.com\n" |
212 | -"POT-Creation-Date: 2010-12-28 12:10:13+0000\n" |
213 | -"PO-Revision-Date: 2010-12-28 12:10:13+0000\n" |
214 | -"Last-Translator: <>\n" |
215 | -"Language-Team: \n" |
216 | -"MIME-Version: 1.0\n" |
217 | -"Content-Type: text/plain; charset=UTF-8\n" |
218 | -"Content-Transfer-Encoding: \n" |
219 | -"Plural-Forms: \n" |
220 | - |
221 | -#. module: product_multi_company |
222 | -#: model:ir.model,name:product_multi_company.model_product_template |
223 | -msgid "Product Template" |
224 | -msgstr "" |
225 | - |
226 | -#. module: product_multi_company |
227 | -#: constraint:product.template:0 |
228 | -msgid "Error: The default UOM and the purchase UOM must be in the same category." |
229 | -msgstr "" |
230 | - |
231 | -#. module: product_multi_company |
232 | -#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo |
233 | -msgid "pricelist.partnerinfo" |
234 | -msgstr "" |
235 | - |
236 | |
237 | === renamed directory 'product_multi_company' => 'product_price_history' |
238 | === modified file 'product_price_history/__init__.py' |
239 | --- product_multi_company/__init__.py 2010-12-29 07:42:35 +0000 |
240 | +++ product_price_history/__init__.py 2013-12-04 10:24:54 +0000 |
241 | @@ -2,7 +2,8 @@ |
242 | ############################################################################## |
243 | # |
244 | # OpenERP, Open Source Management Solution |
245 | -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
246 | +# Copyright 2013 Camptocamp SA |
247 | +# Author: Joel Grand-Guillaume |
248 | # |
249 | # This program is free software: you can redistribute it and/or modify |
250 | # it under the terms of the GNU Affero General Public License as |
251 | @@ -19,6 +20,5 @@ |
252 | # |
253 | ############################################################################## |
254 | |
255 | -import product_multi_company |
256 | - |
257 | -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
258 | \ No newline at end of file |
259 | +from . import product_price_history |
260 | +from . import wizard |
261 | \ No newline at end of file |
262 | |
263 | === modified file 'product_price_history/__openerp__.py' |
264 | --- product_multi_company/__openerp__.py 2013-01-21 06:49:06 +0000 |
265 | +++ product_price_history/__openerp__.py 2013-12-04 10:24:54 +0000 |
266 | @@ -1,8 +1,9 @@ |
267 | # -*- coding: utf-8 -*- |
268 | ############################################################################## |
269 | -# |
270 | +# |
271 | # OpenERP, Open Source Management Solution |
272 | -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
273 | +# Copyright 2013 Camptocamp SA |
274 | +# Author: Joel Grand-Guillaume |
275 | # |
276 | # This program is free software: you can redistribute it and/or modify |
277 | # it under the terms of the GNU Affero General Public License as |
278 | @@ -15,25 +16,64 @@ |
279 | # GNU Affero General Public License for more details. |
280 | # |
281 | # You should have received a copy of the GNU Affero General Public License |
282 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
283 | +# along with this program. If not, see <http://www.gnu.org/licenses/> |
284 | # |
285 | ############################################################################## |
286 | |
287 | { |
288 | - "name" : "Product multi company ", |
289 | - "version" : "1.1", |
290 | - "author" : "OpenERP SA", |
291 | + "name" : "Product Price History", |
292 | + "version" : "1.2", |
293 | + "author" : "Camptocamp", |
294 | "category" : "Generic Modules/Inventory Control", |
295 | - "depends" : [ "product"], |
296 | - "init_xml" : [], |
297 | - "demo_xml" : [], |
298 | + "depends" : ["product", |
299 | + "purchase", |
300 | + ], |
301 | "description": """ |
302 | - This module updates the definitions of standard price, public price and seller price with property fields. |
303 | - """, |
304 | - 'update_xml': [], |
305 | - 'test':[], |
306 | - 'installable': False, |
307 | +Product Price History |
308 | +===================== |
309 | + |
310 | +This module allows you to: |
311 | + |
312 | +* Record various prices of a same product for different companies. This |
313 | + way, every company can have its own costs (average or standard) and |
314 | + sale prices. |
315 | +* Historize the prices in a way that you'll then be able to retrieve the |
316 | + cost (or sale) price at a given date. |
317 | + |
318 | +Note that to benefit those values in stock report (or any other view that is based on SQL), |
319 | +you'll have to adapt it to include this new historized table. Especially true for stock |
320 | +valuation. |
321 | + |
322 | +This module also contains demo data and various tests to ensure it works well. It shows |
323 | +how to configure OpenERP properly when you have various company, each of them having |
324 | +their product setup in average price and using different currencies. The goal is to share |
325 | +the products between all companies, keeping the right price for each of them. |
326 | + |
327 | +Technically, this module updates the definition of field standard_price, list_price |
328 | +of the product and will make them stored in an external table. We override the read, |
329 | +write and create methods to achieve that and don't used ir.property for performance |
330 | +and historization purpose. |
331 | + |
332 | +You may want to also use the module analytic_multicurrency from `bzr branch lp:account-analytic/7.0` |
333 | +in order to have a proper computation in analytic line as well (standard_price will be converted |
334 | +in company currency with this module when computing cost of analytic line). |
335 | +""", |
336 | + 'demo': [ |
337 | + 'demo/product_price_history_purchase_demo.yml', |
338 | + ], |
339 | + 'data': [ |
340 | + 'product_price_history_view.xml', |
341 | + 'wizard/historic_prices_view.xml', |
342 | + 'security/ir.model.access.csv', |
343 | + 'security/product_price_history_security.xml', |
344 | + ], |
345 | + 'test': [ |
346 | + 'test/price_controlling_multicompany.yml', |
347 | + 'test/avg_price_computation_mutlicompanies_multicurrencies.yml', |
348 | + 'test/price_historization.yml', |
349 | + ], |
350 | + 'installable': True, |
351 | 'active': False, |
352 | } |
353 | |
354 | -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
355 | \ No newline at end of file |
356 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
357 | |
358 | === added directory 'product_price_history/demo' |
359 | === added file 'product_price_history/demo/product_price_history_purchase_demo.yml' |
360 | --- product_price_history/demo/product_price_history_purchase_demo.yml 1970-01-01 00:00:00 +0000 |
361 | +++ product_price_history/demo/product_price_history_purchase_demo.yml 2013-12-04 10:24:54 +0000 |
362 | @@ -0,0 +1,189 @@ |
363 | +- |
364 | + Create a Partner for second company and second company user |
365 | +- |
366 | + !record {model: res.partner, id: res_partner_company_01}: |
367 | + name: Second Company Partner test |
368 | +- |
369 | + !record {model: res.partner, id: res_partner_user_second_company_01}: |
370 | + name: Second Company User Partner test |
371 | +- |
372 | + Create a second company hanlded in CHF |
373 | +- |
374 | + !record {model: res.company, id: res_company_01}: |
375 | + name: Second Company test |
376 | + partner_id: res_partner_company_01 |
377 | + currency_id: base.CHF |
378 | +- |
379 | + Create a user for second company |
380 | +- |
381 | + !record {model: res.users, id: res_users_second_company_01}: |
382 | + login: user second company |
383 | + password: user second company |
384 | + partner_id: res_partner_user_second_company_01 |
385 | + company_id: res_company_01 |
386 | + company_ids: [res_company_01,base.main_company] |
387 | + groups_id: |
388 | + - base.group_user |
389 | + - base.group_sale_manager |
390 | + - purchase.group_purchase_manager |
391 | + - stock.group_stock_user |
392 | +- |
393 | + Create a second set of price type in USD for the second company |
394 | +- |
395 | + !record {model: product.price.type, id: list_price_second_cmp}: |
396 | + name: Public Price USD |
397 | + field: list_price |
398 | + currency_id: base.USD |
399 | + company_id: res_company_01 |
400 | +- |
401 | + !record {model: product.price.type, id: standard_price_second_cmp}: |
402 | + name: Cost Price USD |
403 | + field: standard_price |
404 | + currency_id: base.USD |
405 | + company_id: res_company_01 |
406 | +- |
407 | + Ensure the price type of first company is in EUR |
408 | +- |
409 | + !record {model: product.price.type, id: product.list_price}: |
410 | + name: Public Price EUR |
411 | + field: list_price |
412 | + currency_id: base.EUR |
413 | + company_id: base.main_company |
414 | +- |
415 | + !record {model: product.price.type, id: product.standard_price}: |
416 | + name: Cost Price EUR |
417 | + field: standard_price |
418 | + currency_id: base.EUR |
419 | + company_id: base.main_company |
420 | +- |
421 | + Set the admin user to first company |
422 | +- |
423 | + !record {model: res.users, id: base.user_root}: |
424 | + company_id: base.main_company |
425 | +- |
426 | + Set the currency rate of CHF, EUR and USD (reference EUR) |
427 | +- |
428 | + !record {model: res.currency.rate, id: base.rateCHF}: |
429 | + rate: 1.3086 |
430 | + currency_id: base.CHF |
431 | + name: !eval time.strftime('%Y-%m-%d') |
432 | +- |
433 | + !record {model: res.currency.rate, id: base.rateUSD}: |
434 | + rate: 1.2086 |
435 | + currency_id: base.USD |
436 | + name: !eval time.strftime('%Y-%m-%d') |
437 | +- |
438 | + !record {model: res.currency.rate, id: base.rateEUR}: |
439 | + rate: 1.0 |
440 | + currency_id: base.EUR |
441 | + name: !eval time.strftime('%Y-%m-%d') |
442 | +- |
443 | + Ensure the first Company and pricelists are in EUR (rate 1.0) |
444 | +- |
445 | + !record {model: res.company, id: base.main_company}: |
446 | + currency_id: base.EUR |
447 | +- |
448 | + !record {model: product.pricelist, id: purchase.list0}: |
449 | + name: Purchase Pricelist EUR |
450 | + currency_id: base.EUR |
451 | + company_id: base.main_company |
452 | +- |
453 | + !record {model: product.pricelist, id: product.list0}: |
454 | + name: Public Pricelist EUR |
455 | + currency_id: base.EUR |
456 | + company_id: base.main_company |
457 | +- |
458 | + Create a sale pricelist in CHF for second company (also in CHF) |
459 | +- |
460 | + !record {model: product.pricelist, id: product_pricelist_salechf}: |
461 | + name: Public Pricelist CHF |
462 | + type: sale |
463 | + company_id: res_company_01 |
464 | + currency_id: base.CHF |
465 | +- |
466 | + !record {model: product.pricelist.version, id: product_pricelist_version_salechf}: |
467 | + pricelist_id: product_pricelist_salechf |
468 | + name: Default Public Pricelist Version |
469 | +- |
470 | + !record {model: product.pricelist.item, id: product_pricelist_item_salechf}: |
471 | + price_version_id: product_pricelist_version_salechf |
472 | + base: !eval ref('list_price_second_cmp') |
473 | + name: Default Public Pricelist Line |
474 | +- |
475 | + Create a purchase pricelist in CHF for second company (also in CHF) |
476 | +- |
477 | + !record {model: product.pricelist, id: product_pricelist_purchchf}: |
478 | + name: Purchase Pricelist CHF |
479 | + type: purchase |
480 | + company_id: res_company_01 |
481 | + currency_id: base.CHF |
482 | +- |
483 | + !record {model: product.pricelist.version, id: product_pricelist_version_purchchf}: |
484 | + pricelist_id: product_pricelist_purchchf |
485 | + name: Default Purchase Pricelist Version |
486 | +- |
487 | + !record {model: product.pricelist.item, id: product_pricelist_item_puchchf}: |
488 | + price_version_id: product_pricelist_version_purchchf |
489 | + base: !eval ref('standard_price_second_cmp') |
490 | + name: Default Purchase Pricelist Line |
491 | +- |
492 | + Create a stock location for first and second company |
493 | +- |
494 | + !record {model: stock.location, id: location_stock_01}: |
495 | + name: Stock first company EUR |
496 | + usage: internal |
497 | + company_id: base.main_company |
498 | +- |
499 | + !record {model: stock.location, id: location_stock_02}: |
500 | + name: Stock second company CHF |
501 | + usage: internal |
502 | + company_id: res_company_01 |
503 | +- |
504 | + Create a warehouse for first and second company |
505 | +- |
506 | + !record {model: stock.warehouse, id: wh_stock_01}: |
507 | + name: Warehouse for first company EUR |
508 | + lot_output_id: location_stock_01 |
509 | + lot_stock_id: location_stock_01 |
510 | + lot_input_id: location_stock_01 |
511 | + company_id: base.main_company |
512 | +- |
513 | + !record {model: stock.warehouse, id: wh_stock_02}: |
514 | + name: Warehouse for second company CHF |
515 | + lot_output_id: location_stock_02 |
516 | + lot_stock_id: location_stock_02 |
517 | + lot_input_id: location_stock_02 |
518 | + company_id: res_company_01 |
519 | +- |
520 | + Create a Supplier for PO |
521 | +- |
522 | + !record {model: res.partner, id: res_partner_supplier_01}: |
523 | + name: Supplier 1 |
524 | + supplier: 1 |
525 | +- |
526 | + Create a wine product J owned by both company |
527 | +- |
528 | + !record {model: product.product, id: product_product_j_avg_01}: |
529 | + categ_id: product.product_category_1 |
530 | + cost_method: average |
531 | + name: Wine J |
532 | + type: product |
533 | + uom_id: product.product_uom_unit |
534 | + uom_po_id: product.product_uom_unit |
535 | + company_id: False |
536 | +- |
537 | + Create a wine product K owned by both company |
538 | +- |
539 | + !record {model: product.product, id: product_product_k_avg_01}: |
540 | + categ_id: product.product_category_1 |
541 | + cost_method: average |
542 | + name: Wine K |
543 | + type: product |
544 | + uom_id: product.product_uom_unit |
545 | + uom_po_id: product.product_uom_unit |
546 | + company_id: False |
547 | +- |
548 | + Setup the multi company rules for product, share them between company |
549 | +- |
550 | + !record {model: ir.rule, id: product.product_comp_rule}: |
551 | + domain_force: "[(1,'=',1)]" |
552 | |
553 | === added directory 'product_price_history/i18n' |
554 | === added file 'product_price_history/i18n/product_price_history.pot' |
555 | --- product_price_history/i18n/product_price_history.pot 1970-01-01 00:00:00 +0000 |
556 | +++ product_price_history/i18n/product_price_history.pot 2013-12-04 10:24:54 +0000 |
557 | @@ -0,0 +1,210 @@ |
558 | +# Translation of OpenERP Server. |
559 | +# This file contains the translation of the following modules: |
560 | +# * product_price_history |
561 | +# |
562 | +msgid "" |
563 | +msgstr "" |
564 | +"Project-Id-Version: OpenERP Server 7.0\n" |
565 | +"Report-Msgid-Bugs-To: \n" |
566 | +"POT-Creation-Date: 2013-11-05 11:39+0000\n" |
567 | +"PO-Revision-Date: 2013-11-05 11:39+0000\n" |
568 | +"Last-Translator: <>\n" |
569 | +"Language-Team: \n" |
570 | +"MIME-Version: 1.0\n" |
571 | +"Content-Type: text/plain; charset=UTF-8\n" |
572 | +"Content-Transfer-Encoding: \n" |
573 | +"Plural-Forms: \n" |
574 | + |
575 | +#. module: product_price_history |
576 | +#: model:stock.location,name:product_price_history.location_stock_02 |
577 | +msgid "Stock second company CHF" |
578 | +msgstr "" |
579 | + |
580 | +#. module: product_price_history |
581 | +#: view:product.price.history:0 |
582 | +msgid "Group By..." |
583 | +msgstr "" |
584 | + |
585 | +#. module: product_price_history |
586 | +#: model:product.pricelist,name:product_price_history.product_pricelist_purchchf |
587 | +msgid "Purchase Pricelist CHF" |
588 | +msgstr "" |
589 | + |
590 | +#. module: product_price_history |
591 | +#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_product_id |
592 | +#: view:product.price.history:0 |
593 | +#: field:product.price.history,product_id:0 |
594 | +msgid "Product" |
595 | +msgstr "" |
596 | + |
597 | +#. module: product_price_history |
598 | +#: model:product.price.type,name:product_price_history.standard_price_second_cmp |
599 | +msgid "Cost Price USD" |
600 | +msgstr "" |
601 | + |
602 | +#. module: product_price_history |
603 | +#: view:historic.prices:0 |
604 | +msgid "Compute prices" |
605 | +msgstr "" |
606 | + |
607 | +#. module: product_price_history |
608 | +#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_company_id |
609 | +#: model:ir.model.fields,field_description:product_price_history.field_product_price_type_company_id |
610 | +#: view:product.price.history:0 |
611 | +#: field:product.price.history,company_id:0 |
612 | +#: field:product.price.type,company_id:0 |
613 | +msgid "Company" |
614 | +msgstr "" |
615 | + |
616 | +#. module: product_price_history |
617 | +#: code:addons/product_price_history/wizard/historic_prices.py:63 |
618 | +#, python-format |
619 | +msgid "Historical Prices" |
620 | +msgstr "" |
621 | + |
622 | +#. module: product_price_history |
623 | +#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_name |
624 | +#: field:product.price.history,name:0 |
625 | +msgid "Field name" |
626 | +msgstr "" |
627 | + |
628 | +#. module: product_price_history |
629 | +#: model:product.pricelist.version,name:product_price_history.product_pricelist_version_salechf |
630 | +msgid "Default Public Pricelist Version" |
631 | +msgstr "" |
632 | + |
633 | +#. module: product_price_history |
634 | +#: model:ir.actions.act_window,name:product_price_history.action_product_historic_prices_view |
635 | +#: model:ir.ui.menu,name:product_price_history.menu_action_product_historic_prices_tree |
636 | +msgid "Product Historical Prices" |
637 | +msgstr "" |
638 | + |
639 | +#. module: product_price_history |
640 | +#: field:historic.prices,to_date:0 |
641 | +#: model:ir.model.fields,field_description:product_price_history.field_historic_prices_to_date |
642 | +#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_datetime |
643 | +#: view:product.price.history:0 |
644 | +#: field:product.price.history,datetime:0 |
645 | +msgid "Date" |
646 | +msgstr "" |
647 | + |
648 | +#. module: product_price_history |
649 | +#: model:ir.actions.act_window,name:product_price_history.act_product_prices_history_open |
650 | +#: model:ir.actions.act_window,name:product_price_history.action_price_history |
651 | +#: model:ir.ui.menu,name:product_price_history.menu_product_price_history_action_form |
652 | +msgid "Prices History" |
653 | +msgstr "" |
654 | + |
655 | +#. module: product_price_history |
656 | +#: model:product.pricelist.version,name:product_price_history.product_pricelist_version_purchchf |
657 | +msgid "Default Purchase Pricelist Version" |
658 | +msgstr "" |
659 | + |
660 | +#. module: product_price_history |
661 | +#: view:product.price.history:0 |
662 | +msgid "Search Prices History" |
663 | +msgstr "" |
664 | + |
665 | +#. module: product_price_history |
666 | +#: view:product.price.history:0 |
667 | +msgid "Name" |
668 | +msgstr "" |
669 | + |
670 | +#. module: product_price_history |
671 | +#: view:historic.prices:0 |
672 | +msgid "Historical prices" |
673 | +msgstr "" |
674 | + |
675 | +#. module: product_price_history |
676 | +#: model:res.company,overdue_msg:product_price_history.res_company_01 |
677 | +msgid "Dear Sir/Madam,\n" |
678 | +"\n" |
679 | +"Our records indicate that some payments on your account are still due. Please find details below.\n" |
680 | +"If the amount has already been paid, please disregard this notice. Otherwise, please forward us the total amount stated below.\n" |
681 | +"If you have any queries regarding your account, Please contact us.\n" |
682 | +"\n" |
683 | +"Thank you in advance for your cooperation.\n" |
684 | +"Best Regards," |
685 | +msgstr "" |
686 | + |
687 | +#. module: product_price_history |
688 | +#: model:ir.model,name:product_price_history.model_product_price_history |
689 | +msgid "product.price.history" |
690 | +msgstr "" |
691 | + |
692 | +#. module: product_price_history |
693 | +#: view:product.price.history:0 |
694 | +msgid "Price field" |
695 | +msgstr "" |
696 | + |
697 | +#. module: product_price_history |
698 | +#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_amount |
699 | +#: field:product.price.history,amount:0 |
700 | +msgid "Amount" |
701 | +msgstr "" |
702 | + |
703 | +#. module: product_price_history |
704 | +#: model:product.price.type,name:product_price_history.list_price_second_cmp |
705 | +msgid "Public Price USD" |
706 | +msgstr "" |
707 | + |
708 | +#. module: product_price_history |
709 | +#: model:stock.location,name:product_price_history.location_stock_01 |
710 | +msgid "Stock first company EUR" |
711 | +msgstr "" |
712 | + |
713 | +#. module: product_price_history |
714 | +#: model:product.pricelist,name:product_price_history.product_pricelist_salechf |
715 | +msgid "Public Pricelist CHF" |
716 | +msgstr "" |
717 | + |
718 | +#. module: product_price_history |
719 | +#: model:product.template,name:product_price_history.product_product_k_avg_01_product_template |
720 | +msgid "Wine K" |
721 | +msgstr "" |
722 | + |
723 | +#. module: product_price_history |
724 | +#: model:product.template,name:product_price_history.product_product_j_avg_01_product_template |
725 | +msgid "Wine J" |
726 | +msgstr "" |
727 | + |
728 | +#. module: product_price_history |
729 | +#: model:ir.model,name:product_price_history.model_product_template |
730 | +msgid "Product Template" |
731 | +msgstr "" |
732 | + |
733 | +#. module: product_price_history |
734 | +#: view:product.price.history:0 |
735 | +msgid "Historic Prices" |
736 | +msgstr "" |
737 | + |
738 | +#. module: product_price_history |
739 | +#: help:historic.prices,to_date:0 |
740 | +msgid "Date at which the analysis need to be done. Note that the date is understood as this day at midnight, so you may want to specify the day after ! No date is the last value." |
741 | +msgstr "" |
742 | + |
743 | +#. module: product_price_history |
744 | +#: view:product.product:0 |
745 | +msgid "Product Historic Prices" |
746 | +msgstr "" |
747 | + |
748 | +#. module: product_price_history |
749 | +#: model:ir.model,name:product_price_history.model_historic_prices |
750 | +msgid "Product historical prices" |
751 | +msgstr "" |
752 | + |
753 | +#. module: product_price_history |
754 | +#: view:historic.prices:0 |
755 | +msgid "Cancel" |
756 | +msgstr "" |
757 | + |
758 | +#. module: product_price_history |
759 | +#: view:historic.prices:0 |
760 | +msgid "or" |
761 | +msgstr "" |
762 | + |
763 | +#. module: product_price_history |
764 | +#: model:ir.model,name:product_price_history.model_product_price_type |
765 | +msgid "Price Type" |
766 | +msgstr "" |
767 | + |
768 | |
769 | === renamed file 'product_multi_company/product_multi_company.py' => 'product_price_history/product_price_history.py' |
770 | --- product_multi_company/product_multi_company.py 2010-12-29 08:08:51 +0000 |
771 | +++ product_price_history/product_price_history.py 2013-12-04 10:24:54 +0000 |
772 | @@ -1,8 +1,9 @@ |
773 | # -*- coding: utf-8 -*- |
774 | ############################################################################## |
775 | -# |
776 | +# |
777 | # OpenERP, Open Source Management Solution |
778 | -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
779 | +# Copyright 2013 Camptocamp SA |
780 | +# Author: Joel Grand-Guillaume |
781 | # |
782 | # This program is free software: you can redistribute it and/or modify |
783 | # it under the terms of the GNU Affero General Public License as |
784 | @@ -15,45 +16,223 @@ |
785 | # GNU Affero General Public License for more details. |
786 | # |
787 | # You should have received a copy of the GNU Affero General Public License |
788 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
789 | +# along with this program. If not, see <http://www.gnu.org/licenses/> |
790 | # |
791 | ############################################################################## |
792 | |
793 | -from osv import osv, fields |
794 | - |
795 | -class product_template(osv.osv): |
796 | +import logging |
797 | +import time |
798 | +from openerp.osv import orm, fields |
799 | +import openerp.addons.decimal_precision as dp |
800 | +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT |
801 | + |
802 | +# All field name of product that will be historize |
803 | +PRODUCT_FIELD_HISTORIZE = ['standard_price', 'list_price'] |
804 | + |
805 | +_logger = logging.getLogger(__name__) |
806 | + |
807 | +class product_price_history(orm.Model): |
808 | + # TODO : Create good index for select |
809 | + |
810 | + _name = 'product.price.history' |
811 | + _order = 'datetime, company_id asc' |
812 | + |
813 | + _columns = { |
814 | + 'name': fields.char('Field name', size=32, required=True), |
815 | + 'company_id': fields.many2one('res.company', 'Company', |
816 | + required=True), |
817 | + 'product_id': fields.many2one('product.template', 'Product', |
818 | + required=True), |
819 | + 'datetime': fields.datetime('Date'), |
820 | + 'amount': fields.float('Amount', |
821 | + digits_compute=dp.get_precision('Product Price')), |
822 | + } |
823 | + |
824 | + def _get_default_company(self, cr, uid, context=None): |
825 | + company = self.pool.get('res.company') |
826 | + return company._company_default_get(cr, uid, |
827 | + 'product.template', |
828 | + context=context) |
829 | + |
830 | + def _get_default_date(self, cr, uid, context=None): |
831 | + if context is None: |
832 | + context = {} |
833 | + if context.get('to_date'): |
834 | + result = context.get('to_date') |
835 | + else: |
836 | + result = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) |
837 | + return result |
838 | + |
839 | + _defaults = { |
840 | + 'company_id': _get_default_company, |
841 | + 'datetime': _get_default_date, |
842 | + } |
843 | + |
844 | + def _get_historic_price(self, cr, uid, ids, company_id, |
845 | + datetime=False, field_name=None, |
846 | + context=None): |
847 | + """ Use SQL for performance. Return a dict like: |
848 | + {product_id:{'standard_price': Value, 'list_price': Value}} |
849 | + If no value found, return 0.0 for each field and products. |
850 | + """ |
851 | + res = {} |
852 | + if not ids: |
853 | + return res |
854 | + if field_name is None: |
855 | + field_name = PRODUCT_FIELD_HISTORIZE |
856 | + if not datetime: |
857 | + datetime = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) |
858 | + sql_wh_clause = """SELECT DISTINCT ON (product_id, name) |
859 | + datetime, product_id, name, amount |
860 | + FROM product_price_history |
861 | + WHERE product_id IN %s |
862 | + AND datetime <= %s |
863 | + AND company_id = %s |
864 | + AND name IN %s |
865 | + ORDER BY product_id, name, datetime DESC""" |
866 | + cr.execute(sql_wh_clause, (tuple(ids), datetime, |
867 | + company_id, tuple(field_name))) |
868 | + for id in ids: |
869 | + res[id] = dict.fromkeys(field_name, 0.0) |
870 | + result = cr.dictfetchall() |
871 | + for line in result: |
872 | + data = {line['name']: line['amount']} |
873 | + res[line['product_id']].update(data) |
874 | + _logger.debug("Result of price history is : %s, company_id: %s", res, company_id) |
875 | + return res |
876 | + |
877 | + |
878 | +class product_template(orm.Model): |
879 | + |
880 | _inherit = "product.template" |
881 | - _description = "Product Template" |
882 | - _columns={ |
883 | - 'list_price': fields.property('product.template', |
884 | - type='float', |
885 | - string='Public Price', |
886 | - method=True, |
887 | - view_load=True, |
888 | - required=True, |
889 | - help="Base price for computing the customer price. Sometimes called the catalog price."), |
890 | - 'standard_price': fields.property('product.template', |
891 | - type='float', |
892 | - string='Standard Price', |
893 | - method=True, |
894 | - view_load=True, |
895 | - required=True, |
896 | - help="Product's cost for accounting stock valuation. It is the base price for the supplier price."), |
897 | - } |
898 | -product_template() |
899 | - |
900 | -class pricelist_partnerinfo(osv.osv): |
901 | - _inherit = 'pricelist.partnerinfo' |
902 | - _description = "Pricelist Partner" |
903 | + |
904 | + def _log_all_price_changes(self, cr, uid, product, values, context=None): |
905 | + """ |
906 | + For each field to historize, call the _log_price_change method |
907 | + @param: values dict of vals used by write and create od product |
908 | + @param: int product ID |
909 | + """ |
910 | + for field_name in PRODUCT_FIELD_HISTORIZE: |
911 | + if values.get(field_name): |
912 | + amount = values[field_name] |
913 | + self._log_price_change(cr, uid, product, field_name, |
914 | + amount, context=context) |
915 | + return True |
916 | + |
917 | + def _log_price_change(self, cr, uid, product, field_name, amount, context=None): |
918 | + """ |
919 | + On change of price create a price_history |
920 | + :param int product value of new product or product_id |
921 | + """ |
922 | + price_history = self.pool.get('product.price.history') |
923 | + data = { |
924 | + 'product_id': product, |
925 | + 'amount': amount, |
926 | + 'name': field_name, |
927 | + 'company_id': self._get_transaction_company_id(cr, uid, |
928 | + context=context) |
929 | + } |
930 | + return price_history.create(cr, uid, data, context=context) |
931 | + |
932 | + def _get_transaction_company_id(self, cr, uid, context=None): |
933 | + """As it may happend that OpenERP force the uid to 1 to bypass |
934 | + rule (in function field), we may sometimes read the price of the company |
935 | + of user id 1 instead of the good one. Because we found the real uid and company_id |
936 | + in the context in that case, I return this one. It also allow other module to |
937 | + give the proper company_id in the context (like it's done in product_standard_margin |
938 | + for example. If company_id not in context, take the one from uid.""" |
939 | + res = uid |
940 | + if context == None: |
941 | + context = {} |
942 | + if context.get('company_id'): |
943 | + res = context.get('company_id') |
944 | + else: |
945 | + user_obj = self.pool.get('res.users') |
946 | + res = user_obj.browse(cr, uid, uid, |
947 | + context=context).company_id.id |
948 | + return res |
949 | + |
950 | + def create(self, cr, uid, values, context=None): |
951 | + """Add the historization at product creation.""" |
952 | + res = super(product_template, self).create(cr, uid, values, |
953 | + context=context) |
954 | + self._log_all_price_changes(cr, uid, res, values, context=context) |
955 | + return res |
956 | + |
957 | + def read(self, cr, uid, ids, fields=None, context=None, |
958 | + load='_classic_read'): |
959 | + """Override the read to take price values from the related |
960 | + price history table.""" |
961 | + if context is None: |
962 | + context = {} |
963 | + if fields: |
964 | + fields.append('id') |
965 | + results = super(product_template, self).read( |
966 | + cr, uid, ids, fields=fields, context=context, load=load) |
967 | + # Note if fields is empty => read all, so look at history table |
968 | + if not fields or any([f in PRODUCT_FIELD_HISTORIZE for f in fields]): |
969 | + date_crit = False |
970 | + price_history = self.pool.get('product.price.history') |
971 | + company_id = self._get_transaction_company_id(cr, uid, context=context) |
972 | + if context.get('to_date'): |
973 | + date_crit = context['to_date'] |
974 | + # if fields is empty we read all price fields |
975 | + if not fields: |
976 | + price_fields = PRODUCT_FIELD_HISTORIZE |
977 | + # Otherwise we filter on price fields asked in read |
978 | + else: |
979 | + price_fields = [f for f in PRODUCT_FIELD_HISTORIZE if f in fields] |
980 | + prod_prices = price_history._get_historic_price(cr, uid, ids, |
981 | + company_id, |
982 | + datetime=date_crit, |
983 | + field_name=price_fields, |
984 | + context=context) |
985 | + for result in results: |
986 | + dict_value = prod_prices[result['id']] |
987 | + result.update(dict_value) |
988 | + return results |
989 | + |
990 | + def write(self, cr, uid, ids, values, context=None): |
991 | + """Create an entry in the history table for every modified price |
992 | + of every products with current datetime (or given one in context)""" |
993 | + if any([f in PRODUCT_FIELD_HISTORIZE for f in values]): |
994 | + for product in self.browse(cr, uid, ids, context=context): |
995 | + self._log_all_price_changes(cr, uid, product.id, values, |
996 | + context=context) |
997 | + return super(product_template, self).write(cr, uid, ids, values, |
998 | + context=context) |
999 | + |
1000 | + def unlink(self, cr, uid, ids, context=None): |
1001 | + price_history = self.pool.get('product.price.history') |
1002 | + history_ids = price_history.search(cr, uid, |
1003 | + [('product_id', 'in', ids)], |
1004 | + context=context) |
1005 | + price_history.unlink(cr, uid, history_ids, context=context) |
1006 | + res = super(product_template, self).unlink(cr, uid, ids, |
1007 | + context=context) |
1008 | + return res |
1009 | + |
1010 | + |
1011 | +class price_type(orm.Model): |
1012 | + """ |
1013 | + The price type is used to points which field in the product form |
1014 | + is a price and in which currency is this price expressed. |
1015 | + Here, we add the company field to allow having various price type for |
1016 | + various company, may be even in different currency. |
1017 | + """ |
1018 | + |
1019 | + _inherit = "product.price.type" |
1020 | + |
1021 | _columns = { |
1022 | - 'price': fields.property('pricelist.partnerinfo', |
1023 | - type='float', |
1024 | - string='Seller Price', |
1025 | - method=True, |
1026 | - view_load=True, |
1027 | - required=True, |
1028 | - help="This price will be considered as a price for the supplier UoM if any or the default Unit of Measure of the product otherwise"), |
1029 | + 'company_id': fields.many2one('res.company', 'Company', |
1030 | + required=True), |
1031 | } |
1032 | -pricelist_partnerinfo() |
1033 | |
1034 | -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
1035 | + def _get_default_company(self, cr, uid, context=None): |
1036 | + company = self.pool.get('res.company') |
1037 | + return company._company_default_get(cr, uid, |
1038 | + 'product.price.type', |
1039 | + context=context) |
1040 | + _defaults = { |
1041 | + 'company_id': _get_default_company, |
1042 | + } |
1043 | |
1044 | === added file 'product_price_history/product_price_history_view.xml' |
1045 | --- product_price_history/product_price_history_view.xml 1970-01-01 00:00:00 +0000 |
1046 | +++ product_price_history/product_price_history_view.xml 2013-12-04 10:24:54 +0000 |
1047 | @@ -0,0 +1,90 @@ |
1048 | +<?xml version="1.0" encoding="utf-8"?> |
1049 | +<openerp> |
1050 | + <data> |
1051 | + |
1052 | + <record id="product_price_type_view" model="ir.ui.view"> |
1053 | + <field name="name">product.price.type.form</field> |
1054 | + <field name="model">product.price.type</field> |
1055 | + <field name="inherit_id" ref="product.product_price_type_view"/> |
1056 | + <field name="arch" type="xml"> |
1057 | + <field name="currency_id" groups="base.group_multi_currency" position="after"> |
1058 | + <field name="company_id" groups="base.group_multi_company"/> |
1059 | + </field> |
1060 | + </field> |
1061 | + </record> |
1062 | + |
1063 | + <record id="view_product_price_history" model="ir.ui.view"> |
1064 | + <field name="name">product.product.price.history.tree</field> |
1065 | + <field name="model">product.product</field> |
1066 | + <field name="arch" type="xml"> |
1067 | + <tree string="Product Historic Prices" create="false"> |
1068 | + <field name="default_code"/> |
1069 | + <field name="name"/> |
1070 | + <field name="categ_id" invisible="1"/> |
1071 | + <field name="variants" groups="product.group_product_variant"/> |
1072 | + <field name="type"/> |
1073 | + <field name="state" groups="base.group_extended"/> |
1074 | + <field name="list_price"/> |
1075 | + <field name="standard_price"/> |
1076 | + <field name="company_id" groups="base.group_multi_company" invisible="1"/> |
1077 | + </tree> |
1078 | + </field> |
1079 | + </record> |
1080 | + |
1081 | + <record id="view_product_price_history_from_product" model="ir.ui.view"> |
1082 | + <field name="name">product.price.history.tree</field> |
1083 | + <field name="model">product.price.history</field> |
1084 | + <field name="arch" type="xml"> |
1085 | + <tree string="Historic Prices" create="false"> |
1086 | + <field name="datetime"/> |
1087 | + <field name="name"/> |
1088 | + <field name="company_id" groups="base.group_multi_company" invisible="1"/> |
1089 | + <field name="product_id"/> |
1090 | + <field name="amount"/> |
1091 | + </tree> |
1092 | + </field> |
1093 | + </record> |
1094 | + |
1095 | + <record id="view_product_price_history_filter" model="ir.ui.view"> |
1096 | + <field name="name">product.price.history.filter</field> |
1097 | + <field name="model">product.price.history</field> |
1098 | + <field name="arch" type="xml"> |
1099 | + <search string="Search Prices History"> |
1100 | + <field name="name" string="Price field"/> |
1101 | + <field name="product_id"/> |
1102 | + <field name="company_id" groups="base.group_multi_company"/> |
1103 | + <group expand="0" string="Group By..."> |
1104 | + <filter string="Date" icon="terp-go-month" domain="[]" context="{'group_by':'datetime'}"/> |
1105 | + <filter string="Product" domain="[]" context="{'group_by':'product_id'}"/> |
1106 | + <filter string="Name" domain="[]" context="{'group_by':'name'}"/> |
1107 | + <filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/> |
1108 | + </group> |
1109 | + </search> |
1110 | + |
1111 | + </field> |
1112 | + </record> |
1113 | + |
1114 | + <record id="action_price_history" model="ir.actions.act_window"> |
1115 | + <field name="name">Prices History</field> |
1116 | + <field name="type">ir.actions.act_window</field> |
1117 | + <field name="res_model">product.price.history</field> |
1118 | + <field name="view_type">form</field> |
1119 | + <field name="view_id" ref="view_product_price_history_from_product"/> |
1120 | + <field name="search_view_id" ref="view_product_price_history_filter"/> |
1121 | + </record> |
1122 | + |
1123 | + <act_window |
1124 | + context="{'search_default_product_id': [active_id], 'default_product_id': active_id}" |
1125 | + id="act_product_prices_history_open" |
1126 | + name="Prices History" |
1127 | + res_model="product.price.history" |
1128 | + src_model="product.product"/> |
1129 | + |
1130 | + <menuitem action="action_price_history" |
1131 | + groups="base.group_no_one" |
1132 | + id="menu_product_price_history_action_form" |
1133 | + parent="product.prod_config_main" sequence="2"/> |
1134 | + |
1135 | + |
1136 | + </data> |
1137 | +</openerp> |
1138 | |
1139 | === added directory 'product_price_history/security' |
1140 | === added file 'product_price_history/security/ir.model.access.csv' |
1141 | --- product_price_history/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 |
1142 | +++ product_price_history/security/ir.model.access.csv 2013-12-04 10:24:54 +0000 |
1143 | @@ -0,0 +1,7 @@ |
1144 | +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
1145 | +"access_product_price_history_group_user","product_price_history user","model_product_price_history","base.group_user",1,0,0,0 |
1146 | +"access_product_price_history_group_sale_manager","product_price_history sale manager","model_product_price_history","base.group_sale_manager",1,1,1,1 |
1147 | +"access_product_price_history_stock_manager","product_price_history stock manager","model_product_price_history","stock.group_stock_manager",1,1,1,1 |
1148 | +"access_product_price_history_stock_user","product_price_history stock user","model_product_price_history","stock.group_stock_user",1,1,0,0 |
1149 | +"access_product_price_history_purchase_user","product_price_history purchase user","model_product_price_history","purchase.group_purchase_user",1,0,0,0 |
1150 | +"access_product_price_history_purchase_manager","product_price_history purchase manager","model_product_price_history","purchase.group_purchase_manager",1,1,1,1 |
1151 | |
1152 | === added file 'product_price_history/security/product_price_history_security.xml' |
1153 | --- product_price_history/security/product_price_history_security.xml 1970-01-01 00:00:00 +0000 |
1154 | +++ product_price_history/security/product_price_history_security.xml 2013-12-04 10:24:54 +0000 |
1155 | @@ -0,0 +1,13 @@ |
1156 | +<?xml version="1.0" encoding="utf-8"?> |
1157 | +<openerp> |
1158 | +<data noupdate="1"> |
1159 | + |
1160 | + <record id="product_price_type_comp_rule" model="ir.rule"> |
1161 | + <field name="name" >Product Price type multi-company</field> |
1162 | + <field name="model_id" ref="model_product_price_type"/> |
1163 | + <field name="global" eval="True"/> |
1164 | + <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> |
1165 | + </record> |
1166 | + |
1167 | +</data> |
1168 | +</openerp> |
1169 | |
1170 | === added directory 'product_price_history/test' |
1171 | === added file 'product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml' |
1172 | --- product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml 1970-01-01 00:00:00 +0000 |
1173 | +++ product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml 2013-12-04 10:24:54 +0000 |
1174 | @@ -0,0 +1,186 @@ |
1175 | +- |
1176 | + Test the following with user admin from first company (EUR) |
1177 | +- |
1178 | + !context |
1179 | + uid: 'base.user_root' |
1180 | +- |
1181 | + Create a purchase order for first company EUR |
1182 | +- |
1183 | + !record {model: purchase.order, id: purchase_order_lcost_01}: |
1184 | + partner_id: res_partner_supplier_01 |
1185 | + invoice_method: order |
1186 | + location_id: location_stock_01 |
1187 | + pricelist_id: purchase.list0 |
1188 | + company_id: base.main_company |
1189 | + order_line: |
1190 | + - product_id: product_product_j_avg_01 |
1191 | + price_unit: 100 |
1192 | + product_qty: 15.0 |
1193 | +- |
1194 | + I confirm the order where invoice control is 'Bases on order'. |
1195 | +- |
1196 | + !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_01} |
1197 | +- |
1198 | + Reception is ready to process, make it and check moves value |
1199 | +- |
1200 | + !python {model: stock.partial.picking}: | |
1201 | + pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_01")).picking_ids |
1202 | + partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]}) |
1203 | + self.do_partial(cr, uid, [partial_id]) |
1204 | + picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0] |
1205 | + for move in picking.move_lines: |
1206 | + if move.product_id.name == 'Wine J': |
1207 | + assert move.price_unit == 100.0,"Technical field price_unit of Wine J stock move should record the purchase price" |
1208 | +- |
1209 | + I check that purchase order is shipped. |
1210 | +- |
1211 | + !python {model: purchase.order}: | |
1212 | + assert self.browse(cr, uid, ref("purchase_order_lcost_01")).shipped == True,"Purchase order should be delivered" |
1213 | +- |
1214 | + I check that avg price of products is computed correctly |
1215 | +- |
1216 | + !python {model: product.product}: | |
1217 | + xchg_rate_chf = 1.0 |
1218 | + # computed as : (100 * 15 / 15) * Exchnge rate of 1.3086 |
1219 | + value_a = round(100.0 * xchg_rate_chf, 2) |
1220 | + assert self.browse(cr, uid, ref("product_product_j_avg_01")).standard_price == value_a,"Avg price for product Wine J for first company is wrongly computed" |
1221 | +- |
1222 | + Create a second purchase order for first company EUR |
1223 | +- |
1224 | + !record {model: purchase.order, id: purchase_order_lcost_01bis}: |
1225 | + partner_id: res_partner_supplier_01 |
1226 | + invoice_method: order |
1227 | + location_id: location_stock_01 |
1228 | + pricelist_id: purchase.list0 |
1229 | + company_id: base.main_company |
1230 | + order_line: |
1231 | + - product_id: product_product_j_avg_01 |
1232 | + price_unit: 200 |
1233 | + product_qty: 15.0 |
1234 | +- |
1235 | + I confirm the order where invoice control is 'Bases on order'. |
1236 | +- |
1237 | + !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_01bis} |
1238 | +- |
1239 | + Reception is ready for process, make it and check moves value |
1240 | +- |
1241 | + !python {model: stock.partial.picking}: | |
1242 | + pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_01bis")).picking_ids |
1243 | + partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]}) |
1244 | + self.do_partial(cr, uid, [partial_id]) |
1245 | + picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0] |
1246 | + for move in picking.move_lines: |
1247 | + if move.product_id.name == 'Wine J': |
1248 | + assert move.price_unit == 200.0,"Technical field price_unit of Wine J stock move should record the purchase price" |
1249 | +- |
1250 | + I check that purchase order is shipped. |
1251 | +- |
1252 | + !python {model: purchase.order}: | |
1253 | + assert self.browse(cr, uid, ref("purchase_order_lcost_01bis")).shipped == True,"Purchase order should be delivered" |
1254 | +- |
1255 | + I check that avg price of products is computed correctly |
1256 | +- |
1257 | + !python {model: product.product}: | |
1258 | + xchg_rate_chf = 1.0 |
1259 | + # Value in stock in EUR |
1260 | + value_a = round(100.0 * xchg_rate_chf, 2) |
1261 | + # computed as : (value_a * 15 + (200 * xchg_rate_chf) * 15) / 30 |
1262 | + value_abis = round((value_a * 15 + (200 * xchg_rate_chf) * 15) / 30, 2) |
1263 | + assert self.browse(cr, uid, ref("product_product_j_avg_01")).standard_price == value_abis,"Avg price for product Wine J for first company is wrongly computed" |
1264 | +- |
1265 | + Test the following with user admin from second company (CHF) |
1266 | +- |
1267 | + !context |
1268 | + uid: 'res_users_second_company_01' |
1269 | +- |
1270 | + Create a purchase order for second company CHF |
1271 | +- |
1272 | + !record {model: purchase.order, id: purchase_order_lcost_02}: |
1273 | + partner_id: res_partner_supplier_01 |
1274 | + invoice_method: manual |
1275 | + location_id: location_stock_02 |
1276 | + pricelist_id: product_pricelist_purchchf |
1277 | + company_id: res_company_01 |
1278 | + order_line: |
1279 | + - product_id: product_product_j_avg_01 |
1280 | + price_unit: 50 |
1281 | + product_qty: 15.0 |
1282 | +- |
1283 | + I confirm the order where invoice control is 'Bases on order'. |
1284 | +- |
1285 | + !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_02} |
1286 | +- |
1287 | + Reception is ready for process, make it and check moves value |
1288 | +- |
1289 | + !python {model: stock.partial.picking}: | |
1290 | + pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_02")).picking_ids |
1291 | + partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]}) |
1292 | + self.do_partial(cr, uid, [partial_id]) |
1293 | + picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0] |
1294 | + for move in picking.move_lines: |
1295 | + if move.product_id.name == 'Wine J': |
1296 | + assert move.price_unit == 50.0,"Technical field price_unit of Wine J stock move should record the purchase price" |
1297 | +- |
1298 | + I check that purchase order is shipped. |
1299 | +- |
1300 | + !python {model: purchase.order}: | |
1301 | + assert self.browse(cr, uid, ref("purchase_order_lcost_02")).shipped == True,"Purchase order should be delivered" |
1302 | +- |
1303 | + I check that avg price of products is computed correctly |
1304 | +- |
1305 | + !python {model: product.product}: | |
1306 | + # value in USD stored |
1307 | + value_a = round(50 * (1.2086 / 1.3086), 2) |
1308 | + assert self.browse(cr, uid, ref("product_product_j_avg_01")).standard_price == value_a,"Avg price for product Wine J for second company is wrongly computed" |
1309 | +- |
1310 | + Create a second purchase order for second company CHF |
1311 | +- |
1312 | + !record {model: purchase.order, id: purchase_order_lcost_02bis}: |
1313 | + partner_id: res_partner_supplier_01 |
1314 | + invoice_method: manual |
1315 | + location_id: location_stock_02 |
1316 | + pricelist_id: product_pricelist_purchchf |
1317 | + company_id: res_company_01 |
1318 | + order_line: |
1319 | + - product_id: product_product_j_avg_01 |
1320 | + price_unit: 100 |
1321 | + product_qty: 15.0 |
1322 | +- |
1323 | + I confirm the order where invoice control is 'Bases on order'. |
1324 | +- |
1325 | + !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_02bis} |
1326 | +- |
1327 | + Reception is ready for process, make it and check moves value |
1328 | +- |
1329 | + !python {model: stock.partial.picking}: | |
1330 | + pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_02bis")).picking_ids |
1331 | + partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]}) |
1332 | + self.do_partial(cr, uid, [partial_id]) |
1333 | + picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0] |
1334 | + for move in picking.move_lines: |
1335 | + if move.product_id.name == 'Wine J': |
1336 | + assert move.price_unit == 100.0,"Technical field price_unit of Wine J stock move should record the purchase price" |
1337 | +- |
1338 | + I check that purchase order is shipped. |
1339 | +- |
1340 | + !python {model: purchase.order}: | |
1341 | + assert self.browse(cr, uid, ref("purchase_order_lcost_02bis")).shipped == True,"Purchase order should be delivered" |
1342 | +- |
1343 | + I check that avg price of products is computed correctly |
1344 | +- |
1345 | + !python {model: product.product}: | |
1346 | + # Value in stock in USD for first entry (compute as to_currency / from_currency) |
1347 | + value_a = round(50 * (1.2086 / 1.3086), 2) |
1348 | + # Value in stock in USD for second entry (compute as to_currency / from_currency) |
1349 | + value_b = round(100 * (1.2086 / 1.3086), 2) |
1350 | + # computed as : (value_a * 15 + value_b * 15) / 30 |
1351 | + value_abis = round((value_a * 15 + value_b * 15) / 30, 2) |
1352 | + assert self.browse(cr, uid, ref("product_product_j_avg_01")).standard_price == value_abis,"Avg price for product Wine J for second company is wrongly computed" |
1353 | +- |
1354 | + I Check that I get all entries in the price history table |
1355 | +- |
1356 | + !python {model: product.price.history}: | |
1357 | + num_j = self.search(cr, uid, [('product_id','=',ref("product_product_j_avg_01")),('name','=','standard_price')]) |
1358 | + # 4 PO, 4 updates of standard_price |
1359 | + right_number_j = 4 |
1360 | + assert len(num_j) == right_number_j,"The number of value in the price history table is correct for product J" |
1361 | |
1362 | === added file 'product_price_history/test/price_controlling_multicompany.yml' |
1363 | --- product_price_history/test/price_controlling_multicompany.yml 1970-01-01 00:00:00 +0000 |
1364 | +++ product_price_history/test/price_controlling_multicompany.yml 2013-12-04 10:24:54 +0000 |
1365 | @@ -0,0 +1,55 @@ |
1366 | +- |
1367 | + Test the following with user admin from first company (EUR) |
1368 | +- |
1369 | + !context |
1370 | + uid: 'base.user_root' |
1371 | +- |
1372 | + Create a wine A product owned by both company and set price for first company (EUR) |
1373 | +- |
1374 | + !record {model: product.product, id: product_product_a_avg_01}: |
1375 | + categ_id: product.product_category_1 |
1376 | + name: Wine A |
1377 | + cost_method: standard |
1378 | + uom_id: product.product_uom_unit |
1379 | + uom_po_id: product.product_uom_unit |
1380 | + company_id: 0 |
1381 | + standard_price: 50.0 |
1382 | + list_price: 75.0 |
1383 | +- |
1384 | + Test the prices are those set for the first company (EUR) |
1385 | +- |
1386 | + !python {model: product.product}: | |
1387 | + product = self.browse(cr, uid, ref('product_product_a_avg_01')) |
1388 | + assert product.standard_price == 50.0, "The standard_price has not been recorded correctly for first company" |
1389 | + assert product.list_price == 75.0, "The list_price has not been recorded correctly for first company" |
1390 | +- |
1391 | + Test the following with user second_user from second company (CHF) |
1392 | +- |
1393 | + !context |
1394 | + uid: 'res_users_second_company_01' |
1395 | +- |
1396 | + Modify product A owned by both company and set price in USD for second company (CHF) |
1397 | +- |
1398 | + !python {model: product.product}: | |
1399 | + self.write(cr, uid, ref('product_product_a_avg_01'), {'standard_price':70,'list_price':90}) |
1400 | +- |
1401 | + Test the USD prices are those set for the second company (CHF) |
1402 | +- |
1403 | + !python {model: product.product}: | |
1404 | + product = self.browse(cr, uid, ref('product_product_a_avg_01')) |
1405 | + assert product.standard_price == 70.0, "The standard_price has not been recorded correctly for first company" |
1406 | + assert product.list_price == 90.0, "The list_price has not been recorded correctly for first company" |
1407 | +- |
1408 | + Test the following with user admin from first company (EUR) |
1409 | +- |
1410 | + !context |
1411 | + uid: 'base.user_root' |
1412 | +- |
1413 | + Test the prices are still the same for first company (EUR) |
1414 | +- |
1415 | + !python {model: product.product}: | |
1416 | + product = self.browse(cr, uid, ref('product_product_a_avg_01')) |
1417 | + assert product.standard_price == 50.0, "The standard_price has not been recorded correctly for first company" |
1418 | + assert product.list_price == 75.0, "The list_price has not been recorded correctly for first company" |
1419 | + |
1420 | + |
1421 | |
1422 | === added file 'product_price_history/test/price_historization.yml' |
1423 | --- product_price_history/test/price_historization.yml 1970-01-01 00:00:00 +0000 |
1424 | +++ product_price_history/test/price_historization.yml 2013-12-04 10:24:54 +0000 |
1425 | @@ -0,0 +1,55 @@ |
1426 | +- |
1427 | + Test the following with user admin from first company (EUR) |
1428 | +- |
1429 | + !context |
1430 | + uid: 'base.user_root' |
1431 | +- |
1432 | + Modify product K at first day of year and set price in EUR for first company (EUR) |
1433 | +- |
1434 | + !python {model: product.product}: | |
1435 | + import time |
1436 | + ctx = context |
1437 | + ctx.update({'to_date':time.strftime('%Y-01-01 %H:%M:%S')}) |
1438 | + print ctx |
1439 | + self.write(cr, uid, ref('product_product_k_avg_01'), {'standard_price':70,'list_price':90}, context=ctx) |
1440 | +- |
1441 | + Test the EUR prices are those just set on the first day of year |
1442 | +- |
1443 | + !python {model: product.product}: | |
1444 | + import time |
1445 | + ctx = context |
1446 | + ctx.update({'to_date':time.strftime('%Y-01-01 %H:%M:%S')}) |
1447 | + product = self.browse(cr, uid, ref('product_product_k_avg_01'), context=ctx) |
1448 | + assert product.standard_price == 70.0, "The standard_price has not been recorded correctly for first company" |
1449 | + assert product.list_price == 90.0, "The list_price has not been recorded correctly for first company" |
1450 | +- |
1451 | + Modify product K at 3rd day of year and set price in EUR for first company (EUR) |
1452 | +- |
1453 | + !python {model: product.product}: | |
1454 | + import time |
1455 | + ctx = context |
1456 | + ctx.update({'to_date':time.strftime('%Y-01-03 %H:%M:%S')}) |
1457 | + print ctx |
1458 | + self.write(cr, uid, ref('product_product_k_avg_01'), {'standard_price':80,'list_price':100}, context=ctx) |
1459 | +- |
1460 | + Test the EUR prices 2nd day of year |
1461 | +- |
1462 | + !python {model: product.product}: | |
1463 | + import time |
1464 | + ctx = context |
1465 | + ctx.update({'to_date':time.strftime('%Y-01-02 %H:%M:%S')}) |
1466 | + print ctx |
1467 | + product = self.browse(cr, uid, ref('product_product_k_avg_01'),context=ctx) |
1468 | + assert product.standard_price == 70.0, "The standard_price has not been retrieved correctly for first company" |
1469 | + assert product.list_price == 90.0, "The list_price has not been retrieved correctly for first company" |
1470 | +- |
1471 | + Test the EUR prices 3rd day of year |
1472 | +- |
1473 | + !python {model: product.product}: | |
1474 | + import time |
1475 | + ctx = context |
1476 | + ctx.update({'to_date':time.strftime('%Y-01-03 %H:%M:%S')}) |
1477 | + print ctx |
1478 | + product = self.browse(cr, uid, ref('product_product_k_avg_01'),context=ctx) |
1479 | + assert product.standard_price == 80.0, "The standard_price has not been retrieved correctly for first company" |
1480 | + assert product.list_price == 100.0, "The list_price has not been retrieved correctly for first company" |
1481 | |
1482 | === added directory 'product_price_history/wizard' |
1483 | === added file 'product_price_history/wizard/__init__.py' |
1484 | --- product_price_history/wizard/__init__.py 1970-01-01 00:00:00 +0000 |
1485 | +++ product_price_history/wizard/__init__.py 2013-12-04 10:24:54 +0000 |
1486 | @@ -0,0 +1,24 @@ |
1487 | +# -*- coding: utf-8 -*- |
1488 | +############################################################################## |
1489 | +# |
1490 | +# OpenERP, Open Source Management Solution |
1491 | +# Copyright 2013 Camptocamp SA |
1492 | +# Author: Joel Grand-Guillaume |
1493 | +# |
1494 | +# This program is free software: you can redistribute it and/or modify |
1495 | +# it under the terms of the GNU Affero General Public License as |
1496 | +# published by the Free Software Foundation, either version 3 of the |
1497 | +# License, or (at your option) any later version. |
1498 | +# |
1499 | +# This program is distributed in the hope that it will be useful, |
1500 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1501 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1502 | +# GNU Affero General Public License for more details. |
1503 | +# |
1504 | +# You should have received a copy of the GNU Affero General Public License |
1505 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1506 | +# |
1507 | +############################################################################## |
1508 | + |
1509 | +from . import historic_prices |
1510 | + |
1511 | |
1512 | === added file 'product_price_history/wizard/historic_prices.py' |
1513 | --- product_price_history/wizard/historic_prices.py 1970-01-01 00:00:00 +0000 |
1514 | +++ product_price_history/wizard/historic_prices.py 2013-12-04 10:24:54 +0000 |
1515 | @@ -0,0 +1,70 @@ |
1516 | +# -*- coding: utf-8 -*- |
1517 | +############################################################################## |
1518 | +# |
1519 | +# Author: Alexandre Fayolle, Joel Grand-Guillaume |
1520 | +# Copyright 2012 Camptocamp SA |
1521 | +# |
1522 | +# This program is free software: you can redistribute it and/or modify |
1523 | +# it under the terms of the GNU Affero General Public License as |
1524 | +# published by the Free Software Foundation, either version 3 of the |
1525 | +# License, or (at your option) any later version. |
1526 | +# |
1527 | +# This program is distributed in the hope that it will be useful, |
1528 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1529 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1530 | +# GNU Affero General Public License for more details. |
1531 | +# |
1532 | +# You should have received a copy of the GNU Affero General Public License |
1533 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1534 | +# |
1535 | +############################################################################## |
1536 | +from openerp.osv import orm, fields |
1537 | +from openerp.tools.translate import _ |
1538 | +import time |
1539 | + |
1540 | + |
1541 | +class historic_prices(orm.TransientModel): |
1542 | + _name = 'historic.prices' |
1543 | + _description = 'Product historical prices' |
1544 | + |
1545 | + _columns = { |
1546 | + 'to_date': fields.date( |
1547 | + 'Date', |
1548 | + help='Date at which the analysis need to be done. ' |
1549 | + 'Note that the date is understood as this day at midnight, so you may want to ' |
1550 | + 'specify the day after ! No date is the last value.'), |
1551 | + } |
1552 | + |
1553 | + def action_open_window(self, cr, uid, ids, context=None): |
1554 | + """ |
1555 | + Open the historical prices view |
1556 | + """ |
1557 | + if context is None: |
1558 | + context = {} |
1559 | + user_obj = self.pool.get('res.users') |
1560 | + wiz = self.read(cr, uid, ids, [], context=context)[0] |
1561 | + ctx = context.copy() |
1562 | + if wiz.get('to_date'): |
1563 | + ctx.update( |
1564 | + to_date=wiz.get('to_date') |
1565 | + ) |
1566 | + data_pool = self.pool.get('ir.model.data') |
1567 | + filter_ids = data_pool.get_object_reference(cr, uid, 'product', |
1568 | + 'product_search_form_view') |
1569 | + product_view_id = data_pool.get_object_reference(cr, uid, |
1570 | + 'product_price_history', |
1571 | + 'view_product_price_history') |
1572 | + if filter_ids: |
1573 | + filter_id = filter_ids[1] |
1574 | + else: |
1575 | + filter_id = 0 |
1576 | + return { |
1577 | + 'type': 'ir.actions.act_window', |
1578 | + 'name': _('Historical Prices'), |
1579 | + 'context': ctx, |
1580 | + 'view_type': 'form', |
1581 | + 'view_mode': 'tree', |
1582 | + 'res_model': 'product.product', |
1583 | + 'view_id': product_view_id[1], |
1584 | + 'search_view_id': filter_id, |
1585 | + } |
1586 | |
1587 | === added file 'product_price_history/wizard/historic_prices_view.xml' |
1588 | --- product_price_history/wizard/historic_prices_view.xml 1970-01-01 00:00:00 +0000 |
1589 | +++ product_price_history/wizard/historic_prices_view.xml 2013-12-04 10:24:54 +0000 |
1590 | @@ -0,0 +1,36 @@ |
1591 | +<?xml version="1.0" encoding="utf-8"?> |
1592 | +<openerp> |
1593 | + <data> |
1594 | + <record id="view_historical_prices" model="ir.ui.view"> |
1595 | + <field name="name">historic.prices.form</field> |
1596 | + <field name="model">historic.prices</field> |
1597 | + <field name="arch" type="xml"> |
1598 | + <form string="Historical prices" version="7.0"> |
1599 | + <group> |
1600 | + <field name="to_date"/> |
1601 | + </group> |
1602 | + <footer> |
1603 | + <button name="action_open_window" string="Compute prices" type="object" icon="gtk-execute" class="oe_highlight"/> |
1604 | + or |
1605 | + <button string="Cancel" class="oe_link" special="cancel" /> |
1606 | + </footer> |
1607 | + </form> |
1608 | + </field> |
1609 | + </record> |
1610 | + |
1611 | + <record id="action_product_historic_prices_view" model="ir.actions.act_window"> |
1612 | + <field name="name">Product Historical Prices</field> |
1613 | + <field name="res_model">historic.prices</field> |
1614 | + <field name="view_type">form</field> |
1615 | + <field name="view_mode">tree,form</field> |
1616 | + <field name="view_id" ref="view_historical_prices"/> |
1617 | + <field name="target">new</field> |
1618 | + </record> |
1619 | + |
1620 | + <menuitem action="action_product_historic_prices_view" |
1621 | + id="menu_action_product_historic_prices_tree" |
1622 | + parent="stock.next_id_61" /> |
1623 | + |
1624 | + |
1625 | + </data> |
1626 | +</openerp> |
Thanks Joël.
I would put this module in the Multi-company project: /launchpad. net/multi- company
https:/