openerp-community team mailing list archive
-
openerp-community team
-
Mailing list archive
-
Message #04140
lp:~camptocamp/openerp-product-attributes/product_price_history_fixes-jge into lp:openerp-product-attributes
Joël Grand-Guillaume @ camptocamp has proposed merging lp:~camptocamp/openerp-product-attributes/product_price_history_fixes-jge into lp:openerp-product-attributes.
Requested reviews:
Product Core Editors (product-core-editors)
For more details, see:
https://code.launchpad.net/~camptocamp/openerp-product-attributes/product_price_history_fixes-jge/+merge/197225
Hi,
In product_price_history CRUD operations, we need to check the proper company_id with which we work. ir.cron and function field may replace the uid used by "1" admin, then the company is wrong.
This little MP correct that and look at the context to see if a specific user should be used to read the price. This will allow other module to deal with that, like product_historical_margin does.
Regards
--
https://code.launchpad.net/~camptocamp/openerp-product-attributes/product_price_history_fixes-jge/+merge/197225
Your team OpenERP Community is subscribed to branch lp:openerp-product-attributes.
=== modified file 'base_custom_attributes/custom_attributes.py'
--- base_custom_attributes/custom_attributes.py 2013-11-13 08:38:19 +0000
+++ base_custom_attributes/custom_attributes.py 2013-11-29 15:46:08 +0000
@@ -246,6 +246,21 @@
}
def create(self, cr, uid, vals, context=None):
+ if vals.get('field_id'):
+ field_obj = self.pool.get('ir.model.fields')
+ field = field_obj.browse(cr, uid, vals['field_id'], context=context)
+ if vals.get('serialized'):
+ raise orm.except_orm(
+ _('Error'),
+ _("Can't create a serialized attribute on "
+ "an existing ir.model.fields (%s)") % field.name)
+ if field.state != 'manual':
+ # the ir.model.fields already exists and we want to map
+ # an attribute on it. We can't change the field so we
+ # won't add the ttype, relation and so on.
+ return super(attribute_attribute, self).create(cr, uid, vals,
+ context=context)
+
if vals.get('relation_model_id'):
relation = self.pool.get('ir.model').read(
cr, uid, [vals.get('relation_model_id')], ['model'])[0]['model']
@@ -352,9 +367,9 @@
}
def create(self, cr, uid, vals, context=None):
- for attribute in vals['attribute_ids']:
- if vals.get('attribute_set_id') and attribute[2] and \
- not attribute[2].get('attribute_set_id'):
+ for attribute in vals.get('attribute_ids', []):
+ if (vals.get('attribute_set_id') and attribute[2] and
+ not attribute[2].get('attribute_set_id')):
attribute[2]['attribute_set_id'] = vals['attribute_set_id']
return super(attribute_group, self).create(cr, uid, vals, context)
=== modified file 'base_custom_attributes/custom_attributes_view.xml'
--- base_custom_attributes/custom_attributes_view.xml 2013-11-12 08:02:42 +0000
+++ base_custom_attributes/custom_attributes_view.xml 2013-11-29 15:46:08 +0000
@@ -286,11 +286,14 @@
<field name="name">attribute.option.wizard</field>
<field name="model">attribute.option.wizard</field>
<field name="arch" type="xml">
- <form string="Options Wizard" col="6">
- <field name="attribute_id" invisible="1" colspan="2"/>
+ <form string="Options Wizard" version="7.0">
+ <field name="attribute_id" invisible="1"/>
<separator string="options_placeholder"/>
- <button special="cancel" string="Cancel" icon="gtk-cancel"/>
- <button name="validate" string="Validate" type="object" icon="gtk-convert"/>
+ <footer>
+ <button name="validate" string="Validate" type="object" icon="gtk-convert" class="oe_highlight"/>
+ or
+ <button string="Cancel" class="oe_link" special="cancel"/>
+ </footer>
</form>
</field>
</record>
=== modified file 'base_custom_attributes/security/ir.model.access.csv'
--- base_custom_attributes/security/ir.model.access.csv 2013-07-23 14:39:16 +0000
+++ base_custom_attributes/security/ir.model.access.csv 2013-11-29 15:46:08 +0000
@@ -1,12 +1,15 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+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
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
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
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
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
+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
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
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
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
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
+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
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
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
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
=== removed directory 'product_multi_company/i18n'
=== removed file 'product_multi_company/i18n/en_US.po'
--- product_multi_company/i18n/en_US.po 2010-12-29 07:42:35 +0000
+++ product_multi_company/i18n/en_US.po 1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
-# Translation of OpenERP Server.
-# This file contains the translation of the following modules:
-# * product_multi_company
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: OpenERP Server 6.0.0-rc1\n"
-"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
-"POT-Creation-Date: 2010-12-28 12:10:13+0000\n"
-"PO-Revision-Date: 2010-12-28 12:10:13+0000\n"
-"Last-Translator: <>\n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_product_template
-msgid "Product Template"
-msgstr ""
-
-#. module: product_multi_company
-#: constraint:product.template:0
-msgid "Error: The default UOM and the purchase UOM must be in the same category."
-msgstr ""
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo
-msgid "pricelist.partnerinfo"
-msgstr ""
-
=== removed file 'product_multi_company/i18n/es.po'
--- product_multi_company/i18n/es.po 2012-12-05 05:42:11 +0000
+++ product_multi_company/i18n/es.po 1970-01-01 00:00:00 +0000
@@ -1,36 +0,0 @@
-# Spanish translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2010-12-28 12:10+0000\n"
-"PO-Revision-Date: 2011-08-27 14:01+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Spanish <es@xxxxxx>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-12-05 05:41+0000\n"
-"X-Generator: Launchpad (build 16335)\n"
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_product_template
-msgid "Product Template"
-msgstr "Plantilla de producto"
-
-#. module: product_multi_company
-#: constraint:product.template:0
-msgid ""
-"Error: The default UOM and the purchase UOM must be in the same category."
-msgstr ""
-"Error: La UdM por defecto y la UdM de compra deben estar en la misma "
-"categoría."
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo
-msgid "pricelist.partnerinfo"
-msgstr "listaprecios.infoempresa"
=== removed file 'product_multi_company/i18n/fr.po'
--- product_multi_company/i18n/fr.po 2012-12-05 05:42:11 +0000
+++ product_multi_company/i18n/fr.po 1970-01-01 00:00:00 +0000
@@ -1,33 +0,0 @@
-# Translation of OpenERP Server.
-# This file contains the translation of the following modules:
-# * product_multi_company
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: OpenERP Server 6.0.0-rc1\n"
-"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
-"POT-Creation-Date: 2010-12-28 12:10+0000\n"
-"PO-Revision-Date: 2011-02-15 17:25+0000\n"
-"Last-Translator: <>\n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-12-05 05:41+0000\n"
-"X-Generator: Launchpad (build 16335)\n"
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_product_template
-msgid "Product Template"
-msgstr ""
-
-#. module: product_multi_company
-#: constraint:product.template:0
-msgid ""
-"Error: The default UOM and the purchase UOM must be in the same category."
-msgstr ""
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo
-msgid "pricelist.partnerinfo"
-msgstr ""
=== removed file 'product_multi_company/i18n/product_multi_company.pot'
--- product_multi_company/i18n/product_multi_company.pot 2010-12-29 07:42:35 +0000
+++ product_multi_company/i18n/product_multi_company.pot 1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
-# Translation of OpenERP Server.
-# This file contains the translation of the following modules:
-# * product_multi_company
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: OpenERP Server 6.0.0-rc1\n"
-"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
-"POT-Creation-Date: 2010-12-28 12:10:13+0000\n"
-"PO-Revision-Date: 2010-12-28 12:10:13+0000\n"
-"Last-Translator: <>\n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_product_template
-msgid "Product Template"
-msgstr ""
-
-#. module: product_multi_company
-#: constraint:product.template:0
-msgid "Error: The default UOM and the purchase UOM must be in the same category."
-msgstr ""
-
-#. module: product_multi_company
-#: model:ir.model,name:product_multi_company.model_pricelist_partnerinfo
-msgid "pricelist.partnerinfo"
-msgstr ""
-
=== renamed directory 'product_multi_company' => 'product_price_history'
=== modified file 'product_price_history/__init__.py'
--- product_multi_company/__init__.py 2010-12-29 07:42:35 +0000
+++ product_price_history/__init__.py 2013-11-29 15:46:08 +0000
@@ -2,7 +2,8 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+# Copyright 2013 Camptocamp SA
+# Author: Joel Grand-Guillaume
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -19,6 +20,5 @@
#
##############################################################################
-import product_multi_company
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
+from . import product_price_history
+from . import wizard
\ No newline at end of file
=== modified file 'product_price_history/__openerp__.py'
--- product_multi_company/__openerp__.py 2013-01-21 06:49:06 +0000
+++ product_price_history/__openerp__.py 2013-11-29 15:46:08 +0000
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
-#
+#
# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+# Copyright 2013 Camptocamp SA
+# Author: Joel Grand-Guillaume
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -15,25 +16,64 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
{
- "name" : "Product multi company ",
- "version" : "1.1",
- "author" : "OpenERP SA",
+ "name" : "Product Price History",
+ "version" : "1.2",
+ "author" : "Camptocamp",
"category" : "Generic Modules/Inventory Control",
- "depends" : [ "product"],
- "init_xml" : [],
- "demo_xml" : [],
+ "depends" : ["product",
+ "purchase",
+ ],
"description": """
- This module updates the definitions of standard price, public price and seller price with property fields.
- """,
- 'update_xml': [],
- 'test':[],
- 'installable': False,
+Product Price History
+=====================
+
+This module allows you to:
+
+* Record various prices of a same product for different companies. This
+ way, every company can have its own costs (average or standard) and
+ sale prices.
+* Historize the prices in a way that you'll then be able to retrieve the
+ cost (or sale) price at a given date.
+
+Note that to benefit those values in stock report (or any other view that is based on SQL),
+you'll have to adapt it to include this new historized table. Especially true for stock
+valuation.
+
+This module also contains demo data and various tests to ensure it works well. It shows
+how to configure OpenERP properly when you have various company, each of them having
+their product setup in average price and using different currencies. The goal is to share
+the products between all companies, keeping the right price for each of them.
+
+Technically, this module updates the definition of field standard_price, list_price
+of the product and will make them stored in an external table. We override the read,
+write and create methods to achieve that and don't used ir.property for performance
+and historization purpose.
+
+You may want to also use the module analytic_multicurrency from `bzr branch lp:account-analytic/7.0`
+in order to have a proper computation in analytic line as well (standard_price will be converted
+in company currency with this module when computing cost of analytic line).
+""",
+ 'demo': [
+ 'demo/product_price_history_purchase_demo.yml',
+ ],
+ 'data': [
+ 'product_price_history_view.xml',
+ 'wizard/historic_prices_view.xml',
+ 'security/ir.model.access.csv',
+ 'security/product_price_history_security.xml',
+ ],
+ 'test': [
+ 'test/price_controlling_multicompany.yml',
+ 'test/avg_price_computation_mutlicompanies_multicurrencies.yml',
+ 'test/price_historization.yml',
+ ],
+ 'installable': True,
'active': False,
}
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added directory 'product_price_history/demo'
=== added file 'product_price_history/demo/product_price_history_purchase_demo.yml'
--- product_price_history/demo/product_price_history_purchase_demo.yml 1970-01-01 00:00:00 +0000
+++ product_price_history/demo/product_price_history_purchase_demo.yml 2013-11-29 15:46:08 +0000
@@ -0,0 +1,189 @@
+-
+ Create a Partner for second company and second company user
+-
+ !record {model: res.partner, id: res_partner_company_01}:
+ name: Second Company Partner test
+-
+ !record {model: res.partner, id: res_partner_user_second_company_01}:
+ name: Second Company User Partner test
+-
+ Create a second company hanlded in CHF
+-
+ !record {model: res.company, id: res_company_01}:
+ name: Second Company test
+ partner_id: res_partner_company_01
+ currency_id: base.CHF
+-
+ Create a user for second company
+-
+ !record {model: res.users, id: res_users_second_company_01}:
+ login: user second company
+ password: user second company
+ partner_id: res_partner_user_second_company_01
+ company_id: res_company_01
+ company_ids: [res_company_01,base.main_company]
+ groups_id:
+ - base.group_user
+ - base.group_sale_manager
+ - purchase.group_purchase_manager
+ - stock.group_stock_user
+-
+ Create a second set of price type in USD for the second company
+-
+ !record {model: product.price.type, id: list_price_second_cmp}:
+ name: Public Price USD
+ field: list_price
+ currency_id: base.USD
+ company_id: res_company_01
+-
+ !record {model: product.price.type, id: standard_price_second_cmp}:
+ name: Cost Price USD
+ field: standard_price
+ currency_id: base.USD
+ company_id: res_company_01
+-
+ Ensure the price type of first company is in EUR
+-
+ !record {model: product.price.type, id: product.list_price}:
+ name: Public Price EUR
+ field: list_price
+ currency_id: base.EUR
+ company_id: base.main_company
+-
+ !record {model: product.price.type, id: product.standard_price}:
+ name: Cost Price EUR
+ field: standard_price
+ currency_id: base.EUR
+ company_id: base.main_company
+-
+ Set the admin user to first company
+-
+ !record {model: res.users, id: base.user_root}:
+ company_id: base.main_company
+-
+ Set the currency rate of CHF, EUR and USD (reference EUR)
+-
+ !record {model: res.currency.rate, id: base.rateCHF}:
+ rate: 1.3086
+ currency_id: base.CHF
+ name: !eval time.strftime('%Y-%m-%d')
+-
+ !record {model: res.currency.rate, id: base.rateUSD}:
+ rate: 1.2086
+ currency_id: base.USD
+ name: !eval time.strftime('%Y-%m-%d')
+-
+ !record {model: res.currency.rate, id: base.rateEUR}:
+ rate: 1.0
+ currency_id: base.EUR
+ name: !eval time.strftime('%Y-%m-%d')
+-
+ Ensure the first Company and pricelists are in EUR (rate 1.0)
+-
+ !record {model: res.company, id: base.main_company}:
+ currency_id: base.EUR
+-
+ !record {model: product.pricelist, id: purchase.list0}:
+ name: Purchase Pricelist EUR
+ currency_id: base.EUR
+ company_id: base.main_company
+-
+ !record {model: product.pricelist, id: product.list0}:
+ name: Public Pricelist EUR
+ currency_id: base.EUR
+ company_id: base.main_company
+-
+ Create a sale pricelist in CHF for second company (also in CHF)
+-
+ !record {model: product.pricelist, id: product_pricelist_salechf}:
+ name: Public Pricelist CHF
+ type: sale
+ company_id: res_company_01
+ currency_id: base.CHF
+-
+ !record {model: product.pricelist.version, id: product_pricelist_version_salechf}:
+ pricelist_id: product_pricelist_salechf
+ name: Default Public Pricelist Version
+-
+ !record {model: product.pricelist.item, id: product_pricelist_item_salechf}:
+ price_version_id: product_pricelist_version_salechf
+ base: !eval ref('list_price_second_cmp')
+ name: Default Public Pricelist Line
+-
+ Create a purchase pricelist in CHF for second company (also in CHF)
+-
+ !record {model: product.pricelist, id: product_pricelist_purchchf}:
+ name: Purchase Pricelist CHF
+ type: purchase
+ company_id: res_company_01
+ currency_id: base.CHF
+-
+ !record {model: product.pricelist.version, id: product_pricelist_version_purchchf}:
+ pricelist_id: product_pricelist_purchchf
+ name: Default Purchase Pricelist Version
+-
+ !record {model: product.pricelist.item, id: product_pricelist_item_puchchf}:
+ price_version_id: product_pricelist_version_purchchf
+ base: !eval ref('standard_price_second_cmp')
+ name: Default Purchase Pricelist Line
+-
+ Create a stock location for first and second company
+-
+ !record {model: stock.location, id: location_stock_01}:
+ name: Stock first company EUR
+ usage: internal
+ company_id: base.main_company
+-
+ !record {model: stock.location, id: location_stock_02}:
+ name: Stock second company CHF
+ usage: internal
+ company_id: res_company_01
+-
+ Create a warehouse for first and second company
+-
+ !record {model: stock.warehouse, id: wh_stock_01}:
+ name: Warehouse for first company EUR
+ lot_output_id: location_stock_01
+ lot_stock_id: location_stock_01
+ lot_input_id: location_stock_01
+ company_id: base.main_company
+-
+ !record {model: stock.warehouse, id: wh_stock_02}:
+ name: Warehouse for second company CHF
+ lot_output_id: location_stock_02
+ lot_stock_id: location_stock_02
+ lot_input_id: location_stock_02
+ company_id: res_company_01
+-
+ Create a Supplier for PO
+-
+ !record {model: res.partner, id: res_partner_supplier_01}:
+ name: Supplier 1
+ supplier: 1
+-
+ Create a wine product J owned by both company
+-
+ !record {model: product.product, id: product_product_j_avg_01}:
+ categ_id: product.product_category_1
+ cost_method: average
+ name: Wine J
+ type: product
+ uom_id: product.product_uom_unit
+ uom_po_id: product.product_uom_unit
+ company_id: False
+-
+ Create a wine product K owned by both company
+-
+ !record {model: product.product, id: product_product_k_avg_01}:
+ categ_id: product.product_category_1
+ cost_method: average
+ name: Wine K
+ type: product
+ uom_id: product.product_uom_unit
+ uom_po_id: product.product_uom_unit
+ company_id: False
+-
+ Setup the multi company rules for product, share them between company
+-
+ !record {model: ir.rule, id: product.product_comp_rule}:
+ domain_force: "[(1,'=',1)]"
=== added directory 'product_price_history/i18n'
=== added file 'product_price_history/i18n/product_price_history.pot'
--- product_price_history/i18n/product_price_history.pot 1970-01-01 00:00:00 +0000
+++ product_price_history/i18n/product_price_history.pot 2013-11-29 15:46:08 +0000
@@ -0,0 +1,210 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * product_price_history
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-11-05 11:39+0000\n"
+"PO-Revision-Date: 2013-11-05 11:39+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: product_price_history
+#: model:stock.location,name:product_price_history.location_stock_02
+msgid "Stock second company CHF"
+msgstr ""
+
+#. module: product_price_history
+#: view:product.price.history:0
+msgid "Group By..."
+msgstr ""
+
+#. module: product_price_history
+#: model:product.pricelist,name:product_price_history.product_pricelist_purchchf
+msgid "Purchase Pricelist CHF"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_product_id
+#: view:product.price.history:0
+#: field:product.price.history,product_id:0
+msgid "Product"
+msgstr ""
+
+#. module: product_price_history
+#: model:product.price.type,name:product_price_history.standard_price_second_cmp
+msgid "Cost Price USD"
+msgstr ""
+
+#. module: product_price_history
+#: view:historic.prices:0
+msgid "Compute prices"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_company_id
+#: model:ir.model.fields,field_description:product_price_history.field_product_price_type_company_id
+#: view:product.price.history:0
+#: field:product.price.history,company_id:0
+#: field:product.price.type,company_id:0
+msgid "Company"
+msgstr ""
+
+#. module: product_price_history
+#: code:addons/product_price_history/wizard/historic_prices.py:63
+#, python-format
+msgid "Historical Prices"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_name
+#: field:product.price.history,name:0
+msgid "Field name"
+msgstr ""
+
+#. module: product_price_history
+#: model:product.pricelist.version,name:product_price_history.product_pricelist_version_salechf
+msgid "Default Public Pricelist Version"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.actions.act_window,name:product_price_history.action_product_historic_prices_view
+#: model:ir.ui.menu,name:product_price_history.menu_action_product_historic_prices_tree
+msgid "Product Historical Prices"
+msgstr ""
+
+#. module: product_price_history
+#: field:historic.prices,to_date:0
+#: model:ir.model.fields,field_description:product_price_history.field_historic_prices_to_date
+#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_datetime
+#: view:product.price.history:0
+#: field:product.price.history,datetime:0
+msgid "Date"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.actions.act_window,name:product_price_history.act_product_prices_history_open
+#: model:ir.actions.act_window,name:product_price_history.action_price_history
+#: model:ir.ui.menu,name:product_price_history.menu_product_price_history_action_form
+msgid "Prices History"
+msgstr ""
+
+#. module: product_price_history
+#: model:product.pricelist.version,name:product_price_history.product_pricelist_version_purchchf
+msgid "Default Purchase Pricelist Version"
+msgstr ""
+
+#. module: product_price_history
+#: view:product.price.history:0
+msgid "Search Prices History"
+msgstr ""
+
+#. module: product_price_history
+#: view:product.price.history:0
+msgid "Name"
+msgstr ""
+
+#. module: product_price_history
+#: view:historic.prices:0
+msgid "Historical prices"
+msgstr ""
+
+#. module: product_price_history
+#: model:res.company,overdue_msg:product_price_history.res_company_01
+msgid "Dear Sir/Madam,\n"
+"\n"
+"Our records indicate that some payments on your account are still due. Please find details below.\n"
+"If the amount has already been paid, please disregard this notice. Otherwise, please forward us the total amount stated below.\n"
+"If you have any queries regarding your account, Please contact us.\n"
+"\n"
+"Thank you in advance for your cooperation.\n"
+"Best Regards,"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model,name:product_price_history.model_product_price_history
+msgid "product.price.history"
+msgstr ""
+
+#. module: product_price_history
+#: view:product.price.history:0
+msgid "Price field"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model.fields,field_description:product_price_history.field_product_price_history_amount
+#: field:product.price.history,amount:0
+msgid "Amount"
+msgstr ""
+
+#. module: product_price_history
+#: model:product.price.type,name:product_price_history.list_price_second_cmp
+msgid "Public Price USD"
+msgstr ""
+
+#. module: product_price_history
+#: model:stock.location,name:product_price_history.location_stock_01
+msgid "Stock first company EUR"
+msgstr ""
+
+#. module: product_price_history
+#: model:product.pricelist,name:product_price_history.product_pricelist_salechf
+msgid "Public Pricelist CHF"
+msgstr ""
+
+#. module: product_price_history
+#: model:product.template,name:product_price_history.product_product_k_avg_01_product_template
+msgid "Wine K"
+msgstr ""
+
+#. module: product_price_history
+#: model:product.template,name:product_price_history.product_product_j_avg_01_product_template
+msgid "Wine J"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model,name:product_price_history.model_product_template
+msgid "Product Template"
+msgstr ""
+
+#. module: product_price_history
+#: view:product.price.history:0
+msgid "Historic Prices"
+msgstr ""
+
+#. module: product_price_history
+#: help:historic.prices,to_date:0
+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."
+msgstr ""
+
+#. module: product_price_history
+#: view:product.product:0
+msgid "Product Historic Prices"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model,name:product_price_history.model_historic_prices
+msgid "Product historical prices"
+msgstr ""
+
+#. module: product_price_history
+#: view:historic.prices:0
+msgid "Cancel"
+msgstr ""
+
+#. module: product_price_history
+#: view:historic.prices:0
+msgid "or"
+msgstr ""
+
+#. module: product_price_history
+#: model:ir.model,name:product_price_history.model_product_price_type
+msgid "Price Type"
+msgstr ""
+
=== renamed file 'product_multi_company/product_multi_company.py' => 'product_price_history/product_price_history.py'
--- product_multi_company/product_multi_company.py 2010-12-29 08:08:51 +0000
+++ product_price_history/product_price_history.py 2013-11-29 15:46:08 +0000
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
-#
+#
# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+# Copyright 2013 Camptocamp SA
+# Author: Joel Grand-Guillaume
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -15,45 +16,212 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
-from osv import osv, fields
-
-class product_template(osv.osv):
+import logging
+import time
+from openerp.osv import orm, fields
+import openerp.addons.decimal_precision as dp
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+
+# All field name of product that will be historize
+PRODUCT_FIELD_HISTORIZE = ['standard_price', 'list_price']
+
+_logger = logging.getLogger(__name__)
+
+class product_price_history(orm.Model):
+ # TODO : Create good index for select
+
+ _name = 'product.price.history'
+ _order = 'datetime, company_id asc'
+
+ _columns = {
+ 'name': fields.char('Field name', size=32, required=True),
+ 'company_id': fields.many2one('res.company', 'Company',
+ required=True),
+ 'product_id': fields.many2one('product.template', 'Product',
+ required=True),
+ 'datetime': fields.datetime('Date'),
+ 'amount': fields.float('Amount',
+ digits_compute=dp.get_precision('Product Price')),
+ }
+
+ def _get_default_company(self, cr, uid, context=None):
+ company = self.pool.get('res.company')
+ return company._company_default_get(cr, uid,
+ 'product.template',
+ context=context)
+
+ def _get_default_date(self, cr, uid, context=None):
+ if context is None:
+ context = {}
+ if context.get('date_for_history'):
+ result = context.get('date_for_history')
+ else:
+ result = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ return result
+
+ _defaults = {
+ 'company_id': _get_default_company,
+ 'datetime': _get_default_date,
+ }
+
+ def _get_historic_price(self, cr, uid, ids, company_id,
+ datetime=False, field_name=None,
+ context=None):
+ """ Use SQL for performance. Return a dict like:
+ {product_id:{'standard_price': Value, 'list_price': Value}}
+ If no value found, return 0.0 for each field and products.
+ """
+ res = {}
+ if not ids:
+ return res
+ if field_name is None:
+ field_name = PRODUCT_FIELD_HISTORIZE
+ if not datetime:
+ datetime = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ sql_wh_clause = """SELECT DISTINCT ON (product_id, name)
+ datetime, product_id, name, amount
+ FROM product_price_history
+ WHERE product_id IN %s
+ AND datetime <= %s
+ AND company_id = %s
+ AND name IN %s
+ ORDER BY product_id, name, datetime DESC"""
+ cr.execute(sql_wh_clause, (tuple(ids), datetime,
+ company_id, tuple(field_name)))
+ for id in ids:
+ res[id] = dict.fromkeys(field_name, 0.0)
+ result = cr.dictfetchall()
+ for line in result:
+ data = {line['name']: line['amount']}
+ res[line['product_id']].update(data)
+ _logger.debug("Result of price history is : %s, company_id: %s", res, company_id)
+ return res
+
+
+class product_template(orm.Model):
+
_inherit = "product.template"
- _description = "Product Template"
- _columns={
- 'list_price': fields.property('product.template',
- type='float',
- string='Public Price',
- method=True,
- view_load=True,
- required=True,
- help="Base price for computing the customer price. Sometimes called the catalog price."),
- 'standard_price': fields.property('product.template',
- type='float',
- string='Standard Price',
- method=True,
- view_load=True,
- required=True,
- help="Product's cost for accounting stock valuation. It is the base price for the supplier price."),
- }
-product_template()
-
-class pricelist_partnerinfo(osv.osv):
- _inherit = 'pricelist.partnerinfo'
- _description = "Pricelist Partner"
+
+ def _log_price_change(self, cr, uid, product, values, context=None):
+ """
+ On change of price create a price_history
+ :param product value of new product or product_id
+ """
+ price_history = self.pool.get('product.price.history')
+ for field_name in PRODUCT_FIELD_HISTORIZE:
+ if values.get(field_name):
+ data = {
+ 'product_id': product,
+ 'amount': values[field_name],
+ 'name': field_name,
+ 'company_id': self._get_transaction_company_id(cr, uid,
+ context=context)
+ }
+ price_history.create(cr, uid, data, context=context)
+
+ def _get_transaction_company_id(self, cr, uid, context=None):
+ """As it may happend that OpenERP force the uid to 1 to bypass
+ rule (in function field), we may sometimes read the price of the company
+ of user id 1 instead of the good one. Because we found the real uid and company_id
+ in the context in that case, I return this one. It also allow other module to
+ give the proper company_id in the context (like it's done in product_standard_margin
+ for example. If company_id not in context, take the one from uid."""
+ res = uid
+ if context == None:
+ context = {}
+ if context.get('company_id'):
+ res = context.get('company_id')
+ else:
+ user_obj = self.pool.get('res.users')
+ res = user_obj.browse(cr, uid, uid,
+ context=context).company_id.id
+ return res
+
+ def create(self, cr, uid, values, context=None):
+ """Add the historization at product creation."""
+ res = super(product_template, self).create(cr, uid, values,
+ context=context)
+ self._log_price_change(cr, uid, res, values, context=context)
+ return res
+
+ def read(self, cr, uid, ids, fields=None, context=None,
+ load='_classic_read'):
+ """Override the read to take price values from the related
+ price history table."""
+ if context is None:
+ context = {}
+ if fields:
+ fields.append('id')
+ results = super(product_template, self).read(
+ cr, uid, ids, fields=fields, context=context, load=load)
+ # Note if fields is empty => read all, so look at history table
+ if not fields or any([f in PRODUCT_FIELD_HISTORIZE for f in fields]):
+ date_crit = False
+ price_history = self.pool.get('product.price.history')
+ company_id = self._get_transaction_company_id(cr, uid, context=context)
+ if context.get('date_for_history'):
+ date_crit = context['date_for_history']
+ # if fields is empty we read all price fields
+ if not fields:
+ price_fields = PRODUCT_FIELD_HISTORIZE
+ # Otherwise we filter on price fields asked in read
+ else:
+ price_fields = [f for f in PRODUCT_FIELD_HISTORIZE if f in fields]
+ prod_prices = price_history._get_historic_price(cr, uid, ids,
+ company_id,
+ datetime=date_crit,
+ field_name=price_fields,
+ context=context)
+ for result in results:
+ dict_value = prod_prices[result['id']]
+ result.update(dict_value)
+ return results
+
+ def write(self, cr, uid, ids, values, context=None):
+ """Create an entry in the history table for every modified price
+ of every products with current datetime (or given one in context)"""
+ if any([f in PRODUCT_FIELD_HISTORIZE for f in values]):
+ for product in self.browse(cr, uid, ids, context=context):
+ self._log_price_change(cr, uid, product.id, values,
+ context=context)
+ return super(product_template, self).write(cr, uid, ids, values,
+ context=context)
+
+ def unlink(self, cr, uid, ids, context=None):
+ price_history = self.pool.get('product.price.history')
+ history_ids = price_history.search(cr, uid,
+ [('product_id', 'in', ids)],
+ context=context)
+ price_history.unlink(cr, uid, history_ids, context=context)
+ res = super(product_template, self).unlink(cr, uid, ids,
+ context=context)
+ return res
+
+
+class price_type(orm.Model):
+ """
+ The price type is used to points which field in the product form
+ is a price and in which currency is this price expressed.
+ Here, we add the company field to allow having various price type for
+ various company, may be even in different currency.
+ """
+
+ _inherit = "product.price.type"
+
_columns = {
- 'price': fields.property('pricelist.partnerinfo',
- type='float',
- string='Seller Price',
- method=True,
- view_load=True,
- required=True,
- 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"),
+ 'company_id': fields.many2one('res.company', 'Company',
+ required=True),
}
-pricelist_partnerinfo()
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+ def _get_default_company(self, cr, uid, context=None):
+ company = self.pool.get('res.company')
+ return company._company_default_get(cr, uid,
+ 'product.price.type',
+ context=context)
+ _defaults = {
+ 'company_id': _get_default_company,
+ }
=== added file 'product_price_history/product_price_history_view.xml'
--- product_price_history/product_price_history_view.xml 1970-01-01 00:00:00 +0000
+++ product_price_history/product_price_history_view.xml 2013-11-29 15:46:08 +0000
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="product_price_type_view" model="ir.ui.view">
+ <field name="name">product.price.type.form</field>
+ <field name="model">product.price.type</field>
+ <field name="inherit_id" ref="product.product_price_type_view"/>
+ <field name="arch" type="xml">
+ <field name="currency_id" groups="base.group_multi_currency" position="after">
+ <field name="company_id" groups="base.group_multi_company"/>
+ </field>
+ </field>
+ </record>
+
+ <record id="view_product_price_history" model="ir.ui.view">
+ <field name="name">product.product.price.history.tree</field>
+ <field name="model">product.product</field>
+ <field name="arch" type="xml">
+ <tree string="Product Historic Prices" create="false">
+ <field name="default_code"/>
+ <field name="name"/>
+ <field name="categ_id" invisible="1"/>
+ <field name="variants" groups="product.group_product_variant"/>
+ <field name="type"/>
+ <field name="state" groups="base.group_extended"/>
+ <field name="list_price"/>
+ <field name="standard_price"/>
+ <field name="company_id" groups="base.group_multi_company" invisible="1"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_product_price_history_from_product" model="ir.ui.view">
+ <field name="name">product.price.history.tree</field>
+ <field name="model">product.price.history</field>
+ <field name="arch" type="xml">
+ <tree string="Historic Prices" create="false">
+ <field name="datetime"/>
+ <field name="name"/>
+ <field name="company_id" groups="base.group_multi_company" invisible="1"/>
+ <field name="product_id"/>
+ <field name="amount"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_product_price_history_filter" model="ir.ui.view">
+ <field name="name">product.price.history.filter</field>
+ <field name="model">product.price.history</field>
+ <field name="arch" type="xml">
+ <search string="Search Prices History">
+ <field name="name" string="Price field"/>
+ <field name="product_id"/>
+ <field name="company_id" groups="base.group_multi_company"/>
+ <group expand="0" string="Group By...">
+ <filter string="Date" icon="terp-go-month" domain="[]" context="{'group_by':'datetime'}"/>
+ <filter string="Product" domain="[]" context="{'group_by':'product_id'}"/>
+ <filter string="Name" domain="[]" context="{'group_by':'name'}"/>
+ <filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
+ </group>
+ </search>
+
+ </field>
+ </record>
+
+ <record id="action_price_history" model="ir.actions.act_window">
+ <field name="name">Prices History</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">product.price.history</field>
+ <field name="view_type">form</field>
+ <field name="view_id" ref="view_product_price_history_from_product"/>
+ <field name="search_view_id" ref="view_product_price_history_filter"/>
+ </record>
+
+ <act_window
+ context="{'search_default_product_id': [active_id], 'default_product_id': active_id}"
+ id="act_product_prices_history_open"
+ name="Prices History"
+ res_model="product.price.history"
+ src_model="product.product"/>
+
+ <menuitem action="action_price_history"
+ groups="base.group_no_one"
+ id="menu_product_price_history_action_form"
+ parent="product.prod_config_main" sequence="2"/>
+
+
+ </data>
+</openerp>
=== added directory 'product_price_history/security'
=== added file 'product_price_history/security/ir.model.access.csv'
--- product_price_history/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ product_price_history/security/ir.model.access.csv 2013-11-29 15:46:08 +0000
@@ -0,0 +1,7 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_product_price_history_group_user","product_price_history user","model_product_price_history","base.group_user",1,0,0,0
+"access_product_price_history_group_sale_manager","product_price_history sale manager","model_product_price_history","base.group_sale_manager",1,1,1,1
+"access_product_price_history_stock_manager","product_price_history stock manager","model_product_price_history","stock.group_stock_manager",1,1,1,1
+"access_product_price_history_stock_user","product_price_history stock user","model_product_price_history","stock.group_stock_user",1,1,0,0
+"access_product_price_history_purchase_user","product_price_history purchase user","model_product_price_history","purchase.group_purchase_user",1,0,0,0
+"access_product_price_history_purchase_manager","product_price_history purchase manager","model_product_price_history","purchase.group_purchase_manager",1,1,1,1
=== added file 'product_price_history/security/product_price_history_security.xml'
--- product_price_history/security/product_price_history_security.xml 1970-01-01 00:00:00 +0000
+++ product_price_history/security/product_price_history_security.xml 2013-11-29 15:46:08 +0000
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+<data noupdate="1">
+
+ <record id="product_price_type_comp_rule" model="ir.rule">
+ <field name="name" >Product Price type multi-company</field>
+ <field name="model_id" ref="model_product_price_type"/>
+ <field name="global" eval="True"/>
+ <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
+ </record>
+
+</data>
+</openerp>
=== added directory 'product_price_history/test'
=== added file 'product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml'
--- product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml 1970-01-01 00:00:00 +0000
+++ product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml 2013-11-29 15:46:08 +0000
@@ -0,0 +1,186 @@
+-
+ Test the following with user admin from first company (EUR)
+-
+ !context
+ uid: 'base.user_root'
+-
+ Create a purchase order for first company EUR
+-
+ !record {model: purchase.order, id: purchase_order_lcost_01}:
+ partner_id: res_partner_supplier_01
+ invoice_method: order
+ location_id: location_stock_01
+ pricelist_id: purchase.list0
+ company_id: base.main_company
+ order_line:
+ - product_id: product_product_j_avg_01
+ price_unit: 100
+ product_qty: 15.0
+-
+ I confirm the order where invoice control is 'Bases on order'.
+-
+ !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_01}
+-
+ Reception is ready to process, make it and check moves value
+-
+ !python {model: stock.partial.picking}: |
+ pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_01")).picking_ids
+ partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
+ self.do_partial(cr, uid, [partial_id])
+ picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0]
+ for move in picking.move_lines:
+ if move.product_id.name == 'Wine J':
+ assert move.price_unit == 100.0,"Technical field price_unit of Wine J stock move should record the purchase price"
+-
+ I check that purchase order is shipped.
+-
+ !python {model: purchase.order}: |
+ assert self.browse(cr, uid, ref("purchase_order_lcost_01")).shipped == True,"Purchase order should be delivered"
+-
+ I check that avg price of products is computed correctly
+-
+ !python {model: product.product}: |
+ xchg_rate_chf = 1.0
+ # computed as : (100 * 15 / 15) * Exchnge rate of 1.3086
+ value_a = round(100.0 * xchg_rate_chf, 2)
+ 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"
+-
+ Create a second purchase order for first company EUR
+-
+ !record {model: purchase.order, id: purchase_order_lcost_01bis}:
+ partner_id: res_partner_supplier_01
+ invoice_method: order
+ location_id: location_stock_01
+ pricelist_id: purchase.list0
+ company_id: base.main_company
+ order_line:
+ - product_id: product_product_j_avg_01
+ price_unit: 200
+ product_qty: 15.0
+-
+ I confirm the order where invoice control is 'Bases on order'.
+-
+ !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_01bis}
+-
+ Reception is ready for process, make it and check moves value
+-
+ !python {model: stock.partial.picking}: |
+ pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_01bis")).picking_ids
+ partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
+ self.do_partial(cr, uid, [partial_id])
+ picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0]
+ for move in picking.move_lines:
+ if move.product_id.name == 'Wine J':
+ assert move.price_unit == 200.0,"Technical field price_unit of Wine J stock move should record the purchase price"
+-
+ I check that purchase order is shipped.
+-
+ !python {model: purchase.order}: |
+ assert self.browse(cr, uid, ref("purchase_order_lcost_01bis")).shipped == True,"Purchase order should be delivered"
+-
+ I check that avg price of products is computed correctly
+-
+ !python {model: product.product}: |
+ xchg_rate_chf = 1.0
+ # Value in stock in EUR
+ value_a = round(100.0 * xchg_rate_chf, 2)
+ # computed as : (value_a * 15 + (200 * xchg_rate_chf) * 15) / 30
+ value_abis = round((value_a * 15 + (200 * xchg_rate_chf) * 15) / 30, 2)
+ 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"
+-
+ Test the following with user admin from second company (CHF)
+-
+ !context
+ uid: 'res_users_second_company_01'
+-
+ Create a purchase order for second company CHF
+-
+ !record {model: purchase.order, id: purchase_order_lcost_02}:
+ partner_id: res_partner_supplier_01
+ invoice_method: manual
+ location_id: location_stock_02
+ pricelist_id: product_pricelist_purchchf
+ company_id: res_company_01
+ order_line:
+ - product_id: product_product_j_avg_01
+ price_unit: 50
+ product_qty: 15.0
+-
+ I confirm the order where invoice control is 'Bases on order'.
+-
+ !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_02}
+-
+ Reception is ready for process, make it and check moves value
+-
+ !python {model: stock.partial.picking}: |
+ pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_02")).picking_ids
+ partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
+ self.do_partial(cr, uid, [partial_id])
+ picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0]
+ for move in picking.move_lines:
+ if move.product_id.name == 'Wine J':
+ assert move.price_unit == 50.0,"Technical field price_unit of Wine J stock move should record the purchase price"
+-
+ I check that purchase order is shipped.
+-
+ !python {model: purchase.order}: |
+ assert self.browse(cr, uid, ref("purchase_order_lcost_02")).shipped == True,"Purchase order should be delivered"
+-
+ I check that avg price of products is computed correctly
+-
+ !python {model: product.product}: |
+ # value in USD stored
+ value_a = round(50 * (1.2086 / 1.3086), 2)
+ 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"
+-
+ Create a second purchase order for second company CHF
+-
+ !record {model: purchase.order, id: purchase_order_lcost_02bis}:
+ partner_id: res_partner_supplier_01
+ invoice_method: manual
+ location_id: location_stock_02
+ pricelist_id: product_pricelist_purchchf
+ company_id: res_company_01
+ order_line:
+ - product_id: product_product_j_avg_01
+ price_unit: 100
+ product_qty: 15.0
+-
+ I confirm the order where invoice control is 'Bases on order'.
+-
+ !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lcost_02bis}
+-
+ Reception is ready for process, make it and check moves value
+-
+ !python {model: stock.partial.picking}: |
+ pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lcost_02bis")).picking_ids
+ partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
+ self.do_partial(cr, uid, [partial_id])
+ picking = self.pool.get('stock.picking').browse(cr, uid, [pick_ids[0].id])[0]
+ for move in picking.move_lines:
+ if move.product_id.name == 'Wine J':
+ assert move.price_unit == 100.0,"Technical field price_unit of Wine J stock move should record the purchase price"
+-
+ I check that purchase order is shipped.
+-
+ !python {model: purchase.order}: |
+ assert self.browse(cr, uid, ref("purchase_order_lcost_02bis")).shipped == True,"Purchase order should be delivered"
+-
+ I check that avg price of products is computed correctly
+-
+ !python {model: product.product}: |
+ # Value in stock in USD for first entry (compute as to_currency / from_currency)
+ value_a = round(50 * (1.2086 / 1.3086), 2)
+ # Value in stock in USD for second entry (compute as to_currency / from_currency)
+ value_b = round(100 * (1.2086 / 1.3086), 2)
+ # computed as : (value_a * 15 + value_b * 15) / 30
+ value_abis = round((value_a * 15 + value_b * 15) / 30, 2)
+ 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"
+-
+ I Check that I get all entries in the price history table
+-
+ !python {model: product.price.history}: |
+ num_j = self.search(cr, uid, [('product_id','=',ref("product_product_j_avg_01")),('name','=','standard_price')])
+ # 4 PO, 4 updates of standard_price
+ right_number_j = 4
+ assert len(num_j) == right_number_j,"The number of value in the price history table is correct for product J"
=== added file 'product_price_history/test/price_controlling_multicompany.yml'
--- product_price_history/test/price_controlling_multicompany.yml 1970-01-01 00:00:00 +0000
+++ product_price_history/test/price_controlling_multicompany.yml 2013-11-29 15:46:08 +0000
@@ -0,0 +1,55 @@
+-
+ Test the following with user admin from first company (EUR)
+-
+ !context
+ uid: 'base.user_root'
+-
+ Create a wine A product owned by both company and set price for first company (EUR)
+-
+ !record {model: product.product, id: product_product_a_avg_01}:
+ categ_id: product.product_category_1
+ name: Wine A
+ cost_method: standard
+ uom_id: product.product_uom_unit
+ uom_po_id: product.product_uom_unit
+ company_id: 0
+ standard_price: 50.0
+ list_price: 75.0
+-
+ Test the prices are those set for the first company (EUR)
+-
+ !python {model: product.product}: |
+ product = self.browse(cr, uid, ref('product_product_a_avg_01'))
+ assert product.standard_price == 50.0, "The standard_price has not been recorded correctly for first company"
+ assert product.list_price == 75.0, "The list_price has not been recorded correctly for first company"
+-
+ Test the following with user second_user from second company (CHF)
+-
+ !context
+ uid: 'res_users_second_company_01'
+-
+ Modify product A owned by both company and set price in USD for second company (CHF)
+-
+ !python {model: product.product}: |
+ self.write(cr, uid, ref('product_product_a_avg_01'), {'standard_price':70,'list_price':90})
+-
+ Test the USD prices are those set for the second company (CHF)
+-
+ !python {model: product.product}: |
+ product = self.browse(cr, uid, ref('product_product_a_avg_01'))
+ assert product.standard_price == 70.0, "The standard_price has not been recorded correctly for first company"
+ assert product.list_price == 90.0, "The list_price has not been recorded correctly for first company"
+-
+ Test the following with user admin from first company (EUR)
+-
+ !context
+ uid: 'base.user_root'
+-
+ Test the prices are still the same for first company (EUR)
+-
+ !python {model: product.product}: |
+ product = self.browse(cr, uid, ref('product_product_a_avg_01'))
+ assert product.standard_price == 50.0, "The standard_price has not been recorded correctly for first company"
+ assert product.list_price == 75.0, "The list_price has not been recorded correctly for first company"
+
+
=== added file 'product_price_history/test/price_historization.yml'
--- product_price_history/test/price_historization.yml 1970-01-01 00:00:00 +0000
+++ product_price_history/test/price_historization.yml 2013-11-29 15:46:08 +0000
@@ -0,0 +1,55 @@
+-
+ Test the following with user admin from first company (EUR)
+-
+ !context
+ uid: 'base.user_root'
+-
+ Modify product K at first day of year and set price in EUR for first company (EUR)
+-
+ !python {model: product.product}: |
+ import time
+ ctx = context
+ ctx.update({'date_for_history':time.strftime('%Y-01-01 %H:%M:%S')})
+ print ctx
+ self.write(cr, uid, ref('product_product_k_avg_01'), {'standard_price':70,'list_price':90}, context=ctx)
+-
+ Test the EUR prices are those just set on the first day of year
+-
+ !python {model: product.product}: |
+ import time
+ ctx = context
+ ctx.update({'date_for_history':time.strftime('%Y-01-01 %H:%M:%S')})
+ product = self.browse(cr, uid, ref('product_product_k_avg_01'), context=ctx)
+ assert product.standard_price == 70.0, "The standard_price has not been recorded correcdate_for_historytly for first company"
+ assert product.list_price == 90.0, "The list_price has not been recorded correctly for first company"
+-
+ Modify product K at 3rd day of year and set price in EUR for first company (EUR)
+-
+ !python {model: product.product}: |
+ import time
+ ctx = context
+ ctx.update({'date_for_history':time.strftime('%Y-01-03 %H:%M:%S')})
+ print ctx
+ self.write(cr, uid, ref('product_product_k_avg_01'), {'standard_price':80,'list_price':100}, context=ctx)
+-
+ Test the EUR prices 2nd day of year
+-
+ !python {model: product.product}: |
+ import time
+ ctx = context
+ ctx.update({'date_for_history':time.strftime('%Y-01-02 %H:%M:%S')})
+ print ctx
+ product = self.browse(cr, uid, ref('product_product_k_avg_01'),context=ctx)
+ assert product.standard_price == 70.0, "The standard_price has not been retrieved correctly for first company"
+ assert product.list_price == 90.0, "The list_price has not been retrieved correctly for first company"
+-
+ Test the EUR prices 3rd day of year
+-
+ !python {model: product.product}: |
+ import time
+ ctx = context
+ ctx.update({'date_for_history':time.strftime('%Y-01-03 %H:%M:%S')})
+ print ctx
+ product = self.browse(cr, uid, ref('product_product_k_avg_01'),context=ctx)
+ assert product.standard_price == 80.0, "The standard_price has not been retrieved correctly for first company"
+ assert product.list_price == 100.0, "The list_price has not been retrieved correctly for first company"
=== added directory 'product_price_history/wizard'
=== added file 'product_price_history/wizard/__init__.py'
--- product_price_history/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ product_price_history/wizard/__init__.py 2013-11-29 15:46:08 +0000
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright 2013 Camptocamp SA
+# Author: Joel Grand-Guillaume
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import historic_prices
+
=== added file 'product_price_history/wizard/historic_prices.py'
--- product_price_history/wizard/historic_prices.py 1970-01-01 00:00:00 +0000
+++ product_price_history/wizard/historic_prices.py 2013-11-29 15:46:08 +0000
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Alexandre Fayolle, Joel Grand-Guillaume
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from openerp.osv import orm, fields
+from openerp.tools.translate import _
+import time
+
+
+class historic_prices(orm.TransientModel):
+ _name = 'historic.prices'
+ _description = 'Product historical prices'
+
+ _columns = {
+ 'to_date': fields.date(
+ 'Date',
+ help='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.'),
+ }
+
+ def action_open_window(self, cr, uid, ids, context=None):
+ """
+ Open the historical prices view
+ """
+ if context is None:
+ context = {}
+ user_obj = self.pool.get('res.users')
+ wiz = self.read(cr, uid, ids, [], context=context)[0]
+ ctx = context.copy()
+ if wiz.get('to_date'):
+ ctx.update(
+ date_for_history=wiz.get('to_date')
+ )
+ data_pool = self.pool.get('ir.model.data')
+ filter_ids = data_pool.get_object_reference(cr, uid, 'product',
+ 'product_search_form_view')
+ product_view_id = data_pool.get_object_reference(cr, uid,
+ 'product_price_history',
+ 'view_product_price_history')
+ if filter_ids:
+ filter_id = filter_ids[1]
+ else:
+ filter_id = 0
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': _('Historical Prices'),
+ 'context': ctx,
+ 'view_type': 'form',
+ 'view_mode': 'tree',
+ 'res_model': 'product.product',
+ 'view_id': product_view_id[1],
+ 'search_view_id': filter_id,
+ }
=== added file 'product_price_history/wizard/historic_prices_view.xml'
--- product_price_history/wizard/historic_prices_view.xml 1970-01-01 00:00:00 +0000
+++ product_price_history/wizard/historic_prices_view.xml 2013-11-29 15:46:08 +0000
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <record id="view_historical_prices" model="ir.ui.view">
+ <field name="name">historic.prices.form</field>
+ <field name="model">historic.prices</field>
+ <field name="arch" type="xml">
+ <form string="Historical prices" version="7.0">
+ <group>
+ <field name="to_date"/>
+ </group>
+ <footer>
+ <button name="action_open_window" string="Compute prices" type="object" icon="gtk-execute" class="oe_highlight"/>
+ or
+ <button string="Cancel" class="oe_link" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_product_historic_prices_view" model="ir.actions.act_window">
+ <field name="name">Product Historical Prices</field>
+ <field name="res_model">historic.prices</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="view_historical_prices"/>
+ <field name="target">new</field>
+ </record>
+
+ <menuitem action="action_product_historic_prices_view"
+ id="menu_action_product_historic_prices_tree"
+ parent="stock.next_id_61" />
+
+
+ </data>
+</openerp>