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

Proposed by Joël Grand-Guillaume @ camptocamp
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
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.

Description of the change

Hi,

I propose here a brand new version of product_multi_company to manage prices (standard_price and list_price) by company.

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

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

Thanks Joël.

I would put this module in the Multi-company project:
https://launchpad.net/multi-company

Revision history for this message
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 !

Revision history for this message
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

Revision history for this message
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_price_multi_company or even better, expand the features to allow a product to be visible/selectable by companies with a one2many to select allowed companies, and let the name product_multi_company.

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.

Revision history for this message
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/Suggestions:

- 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_price_historization_by_company and re-change" the destination branch for the original one : https://launchpad.net/openerp-product-attributes.

Other opinions ?

Revision history for this message
Maxime Chambreuil (http://www.savoirfairelinux.com) (max3903) wrote : Posted in a previous version of this proposal

lgtm

review: Approve (code review)
Revision history for this message
Ronald Portier (Therp) (rportier1962) wrote : Posted in a previous version of this proposal

Looks very promising!

One remark:

please rename 'test/average_price_computation_mutlicompany_currency.yml': s/mutli/multi/

Revision history for this message
Pedro Manuel Baeza (pedro.baeza) wrote : Posted in a previous version of this proposal

I have added the other product_multi_company module to my agenda!

But for this module, do you agree to rename and reallocate it in the proper place?

Regards.

Revision history for this message
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-product-attributes project.

Regards,

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

Need po files

Otherwise lgtm

review: Needs Fixing
Revision history for this message
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

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

Romain

I setted
https://code.launchpad.net/~camptocamp/openerp-product-attributes/7.0-add-product_multi_company_new-jge
as Abandonned branch

Can you check and add your fixes again?

Revision history for this message
Romain Deheele - Camptocamp (romaindeheele) wrote :

I add my fixes,

Romain

Revision history for this message
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_price_historization_by_company or it can be only product_price_historization, are we?
- You can put in the description that is multi-company aware and avoid it on the module name.
- Put product_multi_company_purchase_demo.yml on test folder and add it to tests on manifest file.
- Create at least the pot translation template file.
- Rename 'price.history' model to 'product.price.history' to be consistent.
- In security/ir.model.access.csv, there are references to groups installed by "mrp", "sale" or "hr"modules, but these modules are not declared as dependences. BTW, it doesn't make any sense to have it, because you already have enough permissions with groups like base.group_sale_manager or base.group_user.
- It would be very interesting to have any screen to query past prices.

Regards.

review: Needs Fixing (code review and test)
Revision history for this message
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_price_history then ;)
>
> 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:openerp-community-bounces+rcarnes=
>> <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_price_historization_by_company
>>
>> To an Englishman like me 'historization' is a very ugly non-word. How
>> about just 'history'.
>>
>> Martin
>>
>

Revision history for this message
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

Revision history for this message
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.

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

Thanks for pointing that out, it's fixed now !

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

Made some changes, approve.

review: Approve (code review)
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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>

Subscribers

People subscribed via source and target branches