← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~camptocamp/margin-analysis/7.0-cost-price-store-improv-jge into lp:margin-analysis

 

Joël Grand-Guillaume @ camptocamp has proposed merging lp:~camptocamp/margin-analysis/7.0-cost-price-store-improv-jge into lp:margin-analysis.

Commit message:
[ADD] Module to adapt the standard OpenERP report to cost_price field
[IMP] Make the cost_price a stored field
[IMP] Adapt the product_price_history module to hide value information and provide a view for inventory valuation
[IMP] Strongly improve the computation of BoM cost_price by including all bom trees in the update
[ADD] New module to make the glue between product_price_history and product_cost_incl_bom
[ADD] Strong tests cases

Requested reviews:
  Maxime Chambreuil (http://www.savoirfairelinux.com) (max3903)
  Margin Analysis Core Editors (margin-analysis-core-editors)

For more details, see:
https://code.launchpad.net/~camptocamp/margin-analysis/7.0-cost-price-store-improv-jge/+merge/198551

Hi,


This is the result of quite lots of efforts to provide a whole set of modules to deal with costing and margin problematic. The goal was to provide the proper level of modularity to allow people to benefit from a simple to a complex system to handle those problematics.

Problem summary
===============

 1) The cost price of products need to be computed with different method: standard or average
 2) The products with BoM want to be computed real time based on their components (cost of their components)
 3) Margin of products with BoM need to be computed based on a cost price incl. BoM
 4) For complex cases, we need to store an historic value of cost price per company (by date, by company)
 5) The standard reporting available in "Reporting->Warehouse" must work regardless we install or not the historic prices module.
 6) Inventory valuation made by OpenERP for the 31th of december, but made on the 20th of march for example, give the proper value for the stock level, but not for the cost price. If historic price module is installed we want to have the proper value in a report.
 7) We cannot have a function field in reporting view based on SQL view and use group by (technically impossible)
 8) The average price computation is made when receiving product, we need to avoid overriding this part of code.

The proposed solution here
==========================
The following modules:
 - product_get_cost_field
 - product_cost_incl_bom
 - product_stock_cost_field_report
Gives:
 - A stored function field cost_price that compute the cost of product based on their bom (very much improved here, as we can update various bom in various tree of bom)
 - Override the standard SQL view of OpenERP to use the cost_price field instead of standard_price

This is the "simple" solution. Now if you install :
 - product_price_history
 - product_cost_incl_bom_price_history
You have:
 * Hide the price and value in all standard OpenERP Report
 * Provide a new view for inventory valuation that display the historical value and stock level of a given date. This view avoid product with quantity = 0
 * The cost_price field is now also an historic field and value are stored by company and by date 

The product_historical_margin use the cost_price field and will work regardless you installed product_price_history or not.

The MP contain also a whole set of tests to ensure the computation are right.

Thanks in advance for the review.

Regards,

Joël
-- 
https://code.launchpad.net/~camptocamp/margin-analysis/7.0-cost-price-store-improv-jge/+merge/198551
Your team Margin Analysis Core Editors is requested to review the proposed merge of lp:~camptocamp/margin-analysis/7.0-cost-price-store-improv-jge into lp:margin-analysis.
=== modified file 'product_cost_incl_bom/__init__.py'
--- product_cost_incl_bom/__init__.py	2012-05-29 07:06:32 +0000
+++ product_cost_incl_bom/__init__.py	2013-12-11 12:22:52 +0000
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
 #
-#    Author: Alexandre Fayolle
+#    Author: Alexandre Fayolle, Guewen Baconnier, Joel Grand-Guillaume
 #    Copyright 2012 Camptocamp SA
 #
 #    This program is free software: you can redistribute it and/or modify

=== modified file 'product_cost_incl_bom/__openerp__.py'
--- product_cost_incl_bom/__openerp__.py	2013-12-10 15:00:42 +0000
+++ product_cost_incl_bom/__openerp__.py	2013-12-11 12:22:52 +0000
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
 #
-#    Author:  Alexandre Fayolle
+#    Author:  Alexandre Fayolle, Guewen Baconnier, Joel Grand-Guillaume
 #    Copyright 2012 Camptocamp SA
 #
 #    This program is free software: you can redistribute it and/or modify
@@ -18,24 +18,55 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
+<<<<<<< TREE
 {'name': 'Product Cost incl. BOM',
  'version': '0.1',
  'author': 'Camptocamp',
+=======
+{'name' : 'Product Cost incl. BOM',
+ 'version' : '1.0',
+ 'author' : 'Camptocamp',
+>>>>>>> MERGE-SOURCE
  'maintainer': 'Camptocamp',
  'category': 'Products',
  'complexity': "normal",  # easy, normal, expert
  'depends': ['product_get_cost_field',
              'mrp'],
  'description': """
-  Compute product cost price by recursively summing parts cost prices according to product BOM. It takes into
-  account the BoM costing (cost per cycle and so...). If no BOM define for a product, the cost_price is always
-  equal to the standard_price field of the product, so we always have a value to base our reporting on.
+ Compute product cost price by recursively summing parts cost prices according to product BOM. It takes into
+ account the BoM costing (cost per cycle and so...). If no BOM define for a product, the cost_price is always
+ equal to the standard_price field of the product, so we always have a value to base our reporting on.
+
+ The computed value is stored in the DB and can be used in 3rd party report.
+
+ It makes a quite complex computation to include correct computation of such use case having
+ such a hierarchy of products:
+
+            - Table A
+                - 2x Plank 20.-
+                - 4x Wood leg 10.-
+            - Table B
+                - 3x Plank 20.-
+                - 4x Red wood leg
+            - Red wood leg
+                - 1x Wood leg 10.-
+                - 1x Red paint pot 10.-
+            - Chair
+                - 1x Plank
+                - 4x Wood leg
+            - Table and Chair
+                - 1x Table Z
+                - 4x Chair Z
+Changing the price of Wood leg will update the price of Table A, Table B, Red wood leg, 
+Table & Chair products.
 """,
  'website': 'http://www.camptocamp.com/',
- 'init_xml': [],
- 'update_xml': [],
- 'demo_xml': [],
- 'tests': [],
+ 'data': [],
+ 'demo': [],
+ 'test': [
+    'test/cost_price_update.yml',
+    'test/cost_price_update_by_bom.yml',
+ ],
  'installable': True,
  'auto_install': False,
  'license': 'AGPL-3',

=== modified file 'product_cost_incl_bom/product_cost_incl_bom.py'
--- product_cost_incl_bom/product_cost_incl_bom.py	2013-12-10 15:03:48 +0000
+++ product_cost_incl_bom/product_cost_incl_bom.py	2013-12-11 12:22:52 +0000
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
 #
-#    Author: Alexandre Fayolle
+#    Author: Alexandre Fayolle, Guewen Baconnier, Joel Grand-Guillaume
 #    Copyright 2012 Camptocamp SA
 #
 #    This program is free software: you can redistribute it and/or modify
@@ -19,6 +19,7 @@
 #
 ##############################################################################
 
+<<<<<<< TREE
 from openerp.osv import orm, fields
 import decimal_precision as dp
 import logging
@@ -26,13 +27,114 @@
 
 
 class Product(orm.Model):
+=======
+from __future__ import division
+
+import logging
+from itertools import chain
+from openerp.osv import orm, fields
+import openerp.addons.decimal_precision as dp
+
+_logger = logging.getLogger(__name__)
+
+
+def topological_sort(data):
+    """ Topological sort on a dict expressing dependencies.
+
+    Recipe from:
+    http://code.activestate.com/recipes/578272-topological-sort/
+    Slightly modified, adapted for Python 2.6
+
+    Dependencies are expressed as a dictionary whose keys are items
+    and whose values are a set of dependent items. Output is a list of
+    sets in topological order. The first set consists of items with no
+    dependences, each subsequent set consists of items that depend upon
+    items in the preceeding sets.
+
+    >>> print '\\n'.join(repr(sorted(x)) for x in toposort2({
+    ...     2: set([11]),
+    ...     9: set([11,8]),
+    ...     10: set([11,3]),
+    ...     11: set([7,5]),
+    ...     8: set([7,3]),
+    ...     }) )
+    [3, 5, 7]
+    [8, 11]
+    [2, 9, 10]
+
+    """
+    # Ignore self dependencies.
+    for k, v in data.items():
+        v.discard(k)
+    # Find all items that don't depend on anything.
+    extra_items_in_deps = set.union(*data.itervalues()) - set(data.iterkeys())
+    # Add empty dependences where needed
+    data.update(dict((item, set()) for item in extra_items_in_deps))
+    while True:
+        ordered = set(item for item, dep in data.iteritems() if not dep)
+        if not ordered:
+            break
+        yield ordered
+        data = dict((item, (dep - ordered))
+                    for item, dep in data.iteritems()
+                    if item not in ordered)
+    assert not data, \
+        "Cyclic dependencies exist among these items " \
+        ":\n%s" % '\n'.join(repr(x) for x in data.iteritems())
+
+
+class product_product(orm.Model):
+>>>>>>> MERGE-SOURCE
     _inherit = 'product.product'
 
-    def _compute_purchase_price(self, cursor, user, ids,
-                                context=None):
-        '''
-        Compute the purchase price, taking into account sub products and routing
-        '''
+    def _compute_purchase_price(self, cr, uid, ids, context=None):
+        """ Compute the purchase price of products
+
+        Take into account the sub products (bills of materials) and the
+        routing.
+
+        As an example, we have such a hierarchy of products::
+
+            - Table A
+                - 2x Plank 20.-
+                - 4x Wood leg 10.-
+            - Table B
+                - 3x Plank 20.-
+                - 4x Red wood leg
+            - Red wood leg
+                - 1x Wood leg 10.-
+                - 1x Red paint pot 10.-
+            - Chair
+                - 1x Plank
+                - 4x Wood leg
+            - Table and Chair
+                - 1x Table Z
+                - 4x Chair Z
+
+        When we update the ``standard_price`` of a "Wood leg", all cost
+        prices of the products upper in the tree must be computed again.
+        Here, that is: "Table A", "Red wood leg", "Chair", "Table B".
+        The price of all theses products are computed at the same
+        time in this function, the effect is that we should take:
+
+        1. to not read the cost price of a product in the browse_record,
+           if it is computed here because it has likely changed, but use
+           the new cost instead
+        2. compute the prices in a topological order, so we start at the
+           leaves of the tree thus we know the prices of all the sub
+           products when we go up the tree
+
+        The topological sort, in this example, when we modify the "Wood
+        leg", will returns successively 3 generators::
+
+            [set('Wood plank', 'Red Paint Pot', 'Wood Leg')],
+            [set('Table A', 'Red wood leg')],
+            [set('Table B')]]
+
+        The first set having no dependencies and the subsequent sets
+        having items that depend upon items in the preceding set.
+
+        """
         if context is None:
             context = {}
         product_uom = context.get('product_uom')
@@ -41,6 +143,7 @@
         bom_obj = self.pool.get('mrp.bom')
         uom_obj = self.pool.get('product.uom')
 
+<<<<<<< TREE
         res = {}
         ids = ids or []
 
@@ -67,10 +170,86 @@
                                            qty=sub_product_dict['product_qty'],
                                            to_uom_id=sub_product.uom_po_id.id)
                 price += std_price * qty
+=======
+        computed = {}
+        if not ids:
+            return computed
+        _logger.debug("_compute_purchase_price with ids %s" % ids)
+
+        # keep a map between id and browse_record because we use
+        # the ids in the dependency tree
+        depends = dict((product_id, set()) for product_id in ids)
+        product_bom = {}
+        for product_id in ids:
+            bom_id = bom_obj._bom_find(cr, uid, product_id,
+                                       product_uom=product_uom,
+                                       properties=bom_properties)
+            if not bom_id:  # no BoM: use standard_price
+                continue
+            bom = bom_obj.browse(cr, uid, bom_id, context=context)
+            subproducts, routes = bom_obj._bom_explode(cr, uid, bom,
+                                                       factor=1,
+                                                       properties=bom_properties,
+                                                       addthis=True)
+            # set the dependencies of "product_id"
+            depends[product_id].update([sp['product_id'] for sp in
+                                        subproducts])
+            product_bom[product_id] = {
+                'bom': bom,
+                'subproducts': subproducts
+            }
+
+        # eagerly read all the dependencies products
+        sub_read = self.read(cr, uid,
+                             list(chain.from_iterable(depends.itervalues())),
+                             ['cost_price', 'uom_po_id'], context=context)
+        subproduct_costs = dict((p['id'], p) for p in sub_read)
+
+        ordered = list(chain.from_iterable(topological_sort(depends)))
+
+        # extract all the products not in a bom and get their costs
+        # first, avoid to read them one by one
+        no_bom_ids = [p_id for p_id in ordered if
+                      p_id not in product_bom and
+                      p_id in ids]
+        costs = super(product_product, self)._compute_purchase_price(
+            cr, uid, no_bom_ids, context=context)
+        computed.update(costs)
+
+        for product_id in ordered:
+            if not product_id in ids:
+                # the product is a dependency so it appears in the
+                # topological sort, but the cost price should not be
+                # recomputed
+                continue
+            if product_id not in product_bom:
+                # already computed with ``super``
+                continue
+
+            cost = 0.
+            subproduct_infos = product_bom[product_id]['subproducts']
+            for subproduct_info in subproduct_infos:
+                subproduct_id = subproduct_info['product_id']
+                subproduct = subproduct_costs[subproduct_id]
+                # The cost price could have been recomputed in an
+                # earlier iteration.  Thanks to the topological sort,
+                # the subproducts are always computed before their
+                # parents
+                subcost = computed.get(subproduct_id, subproduct['cost_price'])
+                qty = uom_obj._compute_qty(
+                    cr, uid,
+                    from_uom_id=subproduct_info['product_uom'],
+                    qty=subproduct_info['product_qty'],
+                    to_uom_id=subproduct['uom_po_id'][0])
+                cost += subcost * qty
+
+            bom = product_bom[product_id]['bom']
+>>>>>>> MERGE-SOURCE
             if bom.routing_id:
                 for wline in bom.routing_id.workcenter_lines:
                     wc = wline.workcenter_id
                     cycle = wline.cycle_nbr
+<<<<<<< TREE
                     hour = (wc.time_start + wc.time_stop + cycle * wc.time_cycle) *  (wc.time_efficiency or 1.0)
                     price += wc.costs_cycle * cycle + wc.costs_hour * hour
             price /= bom.product_qty
@@ -85,21 +264,137 @@
             res.update(standard_prices)
         return res
 
+=======
+                    hour = ((wc.time_start + wc.time_stop +
+                             cycle * wc.time_cycle) *
+                            (wc.time_efficiency or 1.0))
+                    cost += wc.costs_cycle * cycle + wc.costs_hour * hour
+            cost /= bom.product_qty
+
+            cost = uom_obj._compute_price(
+                cr, uid, bom.product_uom.id,
+                cost, bom.product_id.uom_id.id)
+            computed[product_id] = cost
+
+        return computed
+
+>>>>>>> MERGE-SOURCE
     def _cost_price(self, cr, uid, ids, field_name, arg, context=None):
+<<<<<<< TREE
         if context is None:
             context = {}
         res = self._compute_purchase_price(cr, uid, ids, context=context)
         _logger.debug("Get cost field _cost_price %s,"
                       " arg: %s, context: %s, result:%s",
                       field_name, arg, context, res)
+=======
+        return self._compute_purchase_price(cr, uid, ids, context=context)
+
+    def _get_bom_product(self, cr, uid, ids, context=None):
+        """ return ids of modified product and ids of all product that use
+        as sub-product one of this ids.
+
+        Example::
+
+            BoM:
+                Product A
+                    - Product B
+                    - Product C
+
+        => If we change standard_price of product B, we want to update
+        Product A as well...
+
+        """
+        def _get_parent_bom(bom_record):
+            """ Recursively find the parent bom of all the impacted products
+            and return a list of bom ids
+
+            """
+            bom_result = []
+            if bom_record.bom_id:
+                bom_result.append(bom_record.bom_id.id)
+                bom_result.extend(_get_parent_bom(bom_record.bom_id))
+            return bom_result
+
+        res = []
+        bom_obj = self.pool.get('mrp.bom')
+        bom_ids = bom_obj.search(cr, uid, [('product_id', 'in', ids)],
+                                 context=context)
+        if not bom_ids:
+            return ids
+
+        boms = bom_obj.browse(cr, uid, bom_ids, context=context)
+        parent_bom_ids = set(chain.from_iterable(_get_parent_bom(bom) for
+                                                 bom in boms))
+        bom_ids = set(bom_ids)
+        product_ids = set(ids)
+        # product ids from the other BoMs
+        bom_product_ids = self._get_product_id_from_bom(
+            cr, uid, list(parent_bom_ids), context=context)
+        product_ids.update(bom_product_ids)
+        # recurse in the other BoMs to find all the product ids
+        recurs_ids = self._get_bom_product(cr, uid,
+                                           bom_product_ids,
+                                           context=context)
+        product_ids.update(recurs_ids)
+        return list(product_ids)
+
+    def _get_product(self, cr, uid, ids, context=None):
+        """ Return all product impacted from a change in a bom, that means
+        current product and all parent that is composed by it.
+
+        """
+        bom_obj = self.pool.get('mrp.bom')
+        prod_obj = self.pool.get('product.product')
+        res = set()
+        for bom in bom_obj.read(cr, uid, ids, ['product_id'], context=context):
+            res.add(bom['product_id'][0])
+        final_res = prod_obj._get_bom_product(cr, uid,
+                                              list(res),
+                                              context=context)
+        _logger.debug("trigger on mrp.bom model for product ids %s", final_res)
+        return final_res
+
+    def _get_product_id_from_bom(self, cr, uid, ids, context=None):
+        """ Return a list of product ids from bom """
+        bom_obj = self.pool.get('mrp.bom')
+        res = set()
+        for bom in bom_obj.read(cr, uid, ids, ['product_id'], context=context):
+            res.add(bom['product_id'][0])
+        return list(res)
+
+    def _get_product_from_template2(self, cr, uid, ids, context=None):
+        prod_obj = self.pool.get('product.product')
+        res = prod_obj._get_product_from_template(cr, uid, ids, context=context)
+>>>>>>> MERGE-SOURCE
         return res
 
+    # Trigger on product.product is set to None, otherwise do not trigg
+    # on product creation !
+    _cost_price_triggers = {
+        'product.product': (_get_bom_product, None, 5),
+        'product.template': (_get_product_from_template2,
+                             ['standard_price'], 5),
+        'mrp.bom': (_get_product,
+                    ['bom_id',
+                     'bom_lines',
+                     'product_id',
+                     'product_uom',
+                     'product_qty',
+                     'product_uos',
+                     'product_uos_qty',
+                     ],
+                    10)
+    }
+
     _columns = {
-        'cost_price': fields.function(_cost_price,
-                                      method=True,
-                                      string='Cost Price (incl. BoM)',
-                                      digits_compute=dp.get_precision('Sale Price'),
-                                      help="The cost price is the standard price or, if the product has a bom, "
-                                      "the sum of all standard price of its components. it take also care of the "
-                                      "bom costing like cost per cylce.")
-        }
+        'cost_price': fields.function(
+            _cost_price,
+            store=_cost_price_triggers,
+            string='Cost Price (incl. BoM)',
+            digits_compute=dp.get_precision('Sale Price'),
+            help="The cost price is the standard price or, if the "
+                 "product has a bom, the sum of all standard price "
+                 "of its components. it take also care of the bom "
+                 "costing like cost per cycle.")
+    }

=== added directory 'product_cost_incl_bom/test'
=== added file 'product_cost_incl_bom/test/cost_price_update.yml'
--- product_cost_incl_bom/test/cost_price_update.yml	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom/test/cost_price_update.yml	2013-12-11 12:22:52 +0000
@@ -0,0 +1,50 @@
+-
+  Test the following with user admin
+-
+  !context
+    uid: 'base.user_root'
+-
+  Create a wine A product
+-
+  !record {model: product.product, id: product_product_a}:
+    categ_id: product.product_category_1
+    name: Wine A01
+    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 updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 50.0, "01 The standard_price has not been recorded correctly"
+    assert product.cost_price == 50.0, "01 The cost_price has not been recorded correctly"
+-
+  Modify product A set new prices
+-
+  !python {model: product.product}: |
+    self.write(cr, uid, ref('product_product_a'), {'standard_price': 70})
+-
+  Test price are update accordingly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 70.0, "02 The standard_price has not been recorded correctly"
+    assert product.cost_price == 70.0, "02 The cost_price has not been recorded correctly"
+-
+  Modify product A using the template object and set new prices
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    prod_tmpl_id = product.product_tmpl_id.id
+    self.pool.get('product.template').write(cr, uid, [prod_tmpl_id], {'standard_price':100})
+-
+  Test cost_price is update accordingly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 100.0, "03 The standard_price has not been recorded correctly"
+    assert product.cost_price == 100.0, "03 The cost_price has not been recorded correctly"

=== added file 'product_cost_incl_bom/test/cost_price_update_by_bom.yml'
--- product_cost_incl_bom/test/cost_price_update_by_bom.yml	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom/test/cost_price_update_by_bom.yml	2013-12-11 12:22:52 +0000
@@ -0,0 +1,437 @@
+-
+  Test the following with user admin
+-
+  !context
+    uid: 'base.user_root'
+-
+  Create a table A product
+-
+  !record {model: product.product, id: product_product_table_a}:
+    categ_id: product.product_category_1
+    name: Table 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
+-
+  Create a table B product
+-
+  !record {model: product.product, id: product_product_table_b}:
+    categ_id: product.product_category_1
+    name: Table B
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 80.0
+    list_price: 120.0
+-
+  Create a wood plank product
+-
+  !record {model: product.product, id: product_product_plank_a}:
+    categ_id: product.product_category_1
+    name: Wood plank
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 20.0
+    list_price: 15.0
+-
+  Create a red paint pot product
+-
+  !record {model: product.product, id: product_product_red_paint_a}:
+    categ_id: product.product_category_1
+    name: Red Paint Pot
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 10.0
+    list_price: 15.0
+-
+  Create a wood table leg product
+-
+  !record {model: product.product, id: product_product_w_leg_a}:
+    categ_id: product.product_category_1
+    name: Table wood Leg
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 10.0
+    list_price: 15.0
+-
+  Create a red wood table leg product
+-
+  !record {model: product.product, id: product_product_red_w_leg_a}:
+    categ_id: product.product_category_1
+    name: Red table wood Leg
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 15.0
+    list_price: 20.0
+-
+  Create a Chair product
+-
+  !record {model: product.product, id: product_product_chair_a}:
+    categ_id: product.product_category_1
+    name: Chair
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 23.0
+    list_price: 33.0
+-
+  Create a table and Chair product
+-
+  !record {model: product.product, id: product_product_table_chair_a}:
+    categ_id: product.product_category_1
+    name: Table and Chair
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 150.0
+    list_price: 200.0
+-
+  Create a multilevel bom product
+-
+  !record {model: product.product, id: product_product_multilevel}:
+    categ_id: product.product_category_1
+    name: Multilevel bom product
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 100
+    list_price: 200.0
+-
+  Create a Level A product
+-
+  !record {model: product.product, id: product_product_multilevel_a}:
+    categ_id: product.product_category_1
+    name: Level A bom product
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 100
+    list_price: 200.0
+-
+  Create a Level B bom product
+-
+  !record {model: product.product, id: product_product_multilevel_b}:
+    categ_id: product.product_category_1
+    name: Level B bom product
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 100
+    list_price: 200.0
+-
+  Create a Level C bom product
+-
+  !record {model: product.product, id: product_product_multilevel_c}:
+    categ_id: product.product_category_1
+    name: Level C bom product
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: 0
+    standard_price: 100
+    list_price: 200.0
+-
+  Test the prices of table A and B are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_a'))
+    assert product.standard_price == 50.0, "01a The standard_price has not been recorded correctly"
+    assert product.cost_price == 50.0, "01a The cost_price has not been recorded correctly"
+    product = self.browse(cr, uid, ref('product_product_table_b'))
+    assert product.standard_price == 80.0, "01b The standard_price has not been recorded correctly"
+    assert product.cost_price == 80.0, "01b The cost_price has not been recorded correctly"
+# 
+# The BoM structure will be the following
+#  - Table A
+#     - 2x Plank 20.-
+#     - 4x Wood leg 10.-
+#  - Table B
+#     - 3x Plank 20.-
+#     - 4x Red wood leg
+#  - Red wood leg
+#     - 1x Wood leg 10.-
+#     - 1x Red paint pot 10.-
+#   - Chair
+#     - 1x Plank
+#     - 4x Wood leg
+#   - Table and Chair
+#     - 1x Table
+#     - 4x Chair
+#   - Multilvel-bom-product
+#     - 1x Level A
+#        - 1x Level B
+#          - 1x Level C
+
+-
+  I create Bill of Materials for the table A
+-
+  !record {model: mrp.bom, id: mrp_bom_table_a}:
+    company_id: base.main_company
+    name: Table A
+    product_id: product_product_table_a
+    product_qty: 1.0
+    type: normal
+    bom_lines:
+      - company_id: base.main_company
+        name: Plank
+        product_id: product_product_plank_a
+        product_qty: 2.0
+      - company_id: base.main_company
+        name: Wood Leg
+        product_id: product_product_w_leg_a
+        product_qty: 4.0
+-
+  Test the prices of table A are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_a'))
+    assert product.standard_price == 50.0, "02 The standard_price has not been recorded correctly"
+    assert product.cost_price == 80.0, "02 The cost_price has not been recorded correctly"
+-
+  I create Bill of Materials for Red wood leg
+-
+  !record {model: mrp.bom, id: mrp_bom_red_leg}:
+    company_id: base.main_company
+    name: Red wood leg
+    product_id: product_product_red_w_leg_a
+    product_qty: 1.0
+    type: normal
+    bom_lines:
+      - company_id: base.main_company
+        name: Paint pot
+        product_id: product_product_red_paint_a
+        product_qty: 1.0
+      - company_id: base.main_company
+        name: Wood Leg
+        product_id: product_product_w_leg_a
+        product_qty: 1.0
+-
+  Test the prices of Red Wood leg are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_red_w_leg_a'))
+    assert product.standard_price == 15.0, "03 The standard_price has not been recorded correctly"
+    assert product.cost_price == 20.0, "03 The cost_price has not been recorded correctly"
+-
+  I create Bill of Materials for the table B
+-
+  !record {model: mrp.bom, id: mrp_bom_table_b}:
+    company_id: base.main_company
+    name: Table B
+    product_id: product_product_table_b
+    product_qty: 1.0
+    type: normal
+-
+  Add the plank
+-
+  !record {model: mrp.bom, id: mrp_bom_table_b_plank}:
+    company_id: base.main_company
+    name: Plank
+    product_id: product_product_plank_a
+    product_qty: 3.0
+    bom_id: mrp_bom_table_b
+-
+  Add the Red wood leg
+-
+  !record {model: mrp.bom, id: mrp_bom_table_b_red_w_leg}:
+    company_id: base.main_company
+    name: Red Wood Leg
+    product_id: product_product_red_w_leg_a
+    product_qty: 4.0
+    bom_id: mrp_bom_table_b
+-
+  Test the prices of Table B are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_b'))
+    assert product.standard_price == 80.0, "04 The standard_price has not been recorded correctly"
+    assert product.cost_price == 140.0, "04 The cost_price has not been recorded correctly"
+-
+  I modify Bill of Materials for the table B by adding 4 wood leg
+-
+  !record {model: mrp.bom, id: mrp_bom_table_b_w_leg}:
+    company_id: base.main_company
+    name: Wood Leg
+    product_id: product_product_w_leg_a
+    product_qty: 4.0
+    bom_id: mrp_bom_table_b
+-
+  Test the prices of Table B are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_b'))
+    assert product.standard_price == 80.0, "05 The standard_price has not been recorded correctly"
+
+    assert product.cost_price == 180.0, "05 The cost_price has not been recorded correctly"
+-
+  I modify Bill of Materials for the table B by removing 4 wood leg
+-
+  !python {model: mrp.bom}: |
+    self.unlink(cr, uid, [ref('mrp_bom_table_b_w_leg')])
+-
+  Test the prices of Table B are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_b'))
+    assert product.standard_price == 80.0, "06 The standard_price has not been recorded correctly"
+    assert product.cost_price == 140.0, "06 The cost_price has not been recorded correctly"
+-
+  I modify Bill of Materials for the table B by changing plank qty
+-
+  !record {model: mrp.bom, id: mrp_bom_table_b_plank}:
+    company_id: base.main_company
+    name: Plank
+    product_id: product_product_plank_a
+    product_qty: 1.0
+    bom_id: mrp_bom_table_b
+-
+  Test the prices of Table B are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_b'))
+    assert product.standard_price == 80.0, "07 The standard_price has not been recorded correctly"
+    assert product.cost_price == 100.0, "07 The cost_price has not been recorded correctly"
+-
+  I create Bill of Materials for the Chair
+-
+  !record {model: mrp.bom, id: mrp_bom_chair_a}:
+    company_id: base.main_company
+    name: Chair
+    product_id: product_product_chair_a
+    product_qty: 1.0
+    type: normal
+    bom_lines:
+      - company_id: base.main_company
+        name: Plank
+        product_id: product_product_plank_a
+        product_qty: 1.0
+      - company_id: base.main_company
+        name: Wood Leg
+        product_id: product_product_w_leg_a
+        product_qty: 4.0
+-
+  Test the prices of chair are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_chair_a'))
+    assert product.standard_price == 23.0, "08 The standard_price has not been recorded correctly"
+    assert product.cost_price == 60.0, "08 The cost_price has not been recorded correctly"
+-
+  I create Bill of Materials for the table and Chair
+-
+  !record {model: mrp.bom, id: mrp_bom_table_chair_a}:
+    company_id: base.main_company
+    name: Table and Chair
+    product_id: product_product_table_chair_a
+    product_qty: 1.0
+    type: normal
+    bom_lines:
+      - company_id: base.main_company
+        name: Table
+        product_id: product_product_table_a
+        product_qty: 1.0
+      - company_id: base.main_company
+        name: Chair
+        product_id: product_product_chair_a
+        product_qty: 4.0
+-
+  Test the prices of table and chair are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_chair_a'))
+    assert product.standard_price == 150.0, "09 The standard_price has not been recorded correctly"
+    assert product.cost_price == 320.0, "09 The cost_price has not been recorded correctly"
+-
+  Modify the wood table leg product by changing std price
+-
+  !record {model: product.product, id: product_product_w_leg_a}:
+    standard_price: 5.0
+-
+  Test the prices of wood leg are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_w_leg_a'))
+    assert product.standard_price == 5.0, "10 The standard_price has not been recorded correctly"
+    assert product.cost_price == 5.0, "10 The cost_price has not been recorded correctly"
+-
+  Test the prices of Red Wood leg are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_red_w_leg_a'))
+    assert product.standard_price == 15.0, "11 The standard_price has not been recorded correctly"
+-
+  Test the prices of table A are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_a'))
+    assert product.standard_price == 50.0, "12 The standard_price has not been recorded correctly"
+    assert product.cost_price == 60.0, "12 The cost_price has not been recorded correctly"
+-
+  Test the prices of Table B are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_b'))
+    assert product.standard_price == 80.0, "13 The standard_price has not been recorded correctly"
+    assert product.cost_price == 80.0, "13 The cost_price has not been recorded correctly"
+-
+  Test the prices of chair are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_chair_a'))
+    assert product.standard_price == 23.0, "14 The standard_price has not been recorded correctly"
+    assert product.cost_price == 40.0, "14 The cost_price has not been recorded correctly"
+-
+  Test the prices of table and chair are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_table_chair_a'))
+    assert product.standard_price == 150.0, "15 The standard_price has not been recorded correctly"
+    assert product.cost_price == 220.0, "15 The cost_price has not been recorded correctly"
+-
+  I create Bill of Materials for Multilevel bom product
+-
+  !record {model: mrp.bom, id: mrp_bom_multilevel}:
+    company_id: base.main_company
+    name: Multilevel bom product
+    product_id: product_product_multilevel
+    product_qty: 1.0
+    type: normal
+    bom_lines:
+      - company_id: base.main_company
+        name: Level A Bom product
+        product_id: product_product_multilevel_a
+        product_qty: 1.0
+        bom_lines:
+          - company_id: base.main_company
+            name: Level B Bom product
+            product_id: product_product_multilevel_b
+            product_qty: 1.0
+            bom_lines:
+            - company_id: base.main_company
+              name: Level C Bom product
+              product_id: product_product_multilevel_c
+              product_qty: 1.0
+-
+  Test the prices of multilevel bom product are updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_multilevel'))
+    assert product.standard_price == 100.0, "16 The standard_price has not been recorded correctly"
+    assert product.cost_price == 100.0, "16 The cost_price has not been recorded correctly"

=== added directory 'product_cost_incl_bom_price_history'
=== added file 'product_cost_incl_bom_price_history/__init__.py'
--- product_cost_incl_bom_price_history/__init__.py	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom_price_history/__init__.py	2013-12-11 12:22:52 +0000
@@ -0,0 +1,23 @@
+# -*- 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 product

=== added file 'product_cost_incl_bom_price_history/__openerp__.py'
--- product_cost_incl_bom_price_history/__openerp__.py	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom_price_history/__openerp__.py	2013-12-11 12:22:52 +0000
@@ -0,0 +1,59 @@
+# -*- 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/>
+#
+##############################################################################
+
+{
+    "name" : "Product Cost incl. BoM and Price History",
+    "version" : "1.2",
+    "author" : "Camptocamp",
+    "category" : "Generic Modules/Inventory Control",
+    "depends" : ["product_cost_incl_bom",
+                 "product_price_history",
+                 ],
+    "description": """
+Product Cost incl. BoM and Price History
+========================================
+
+This module make the glue between product_cost_incl_bom and product_price_history and allow
+to have your cost price computed from the component of the BoM, while having it also
+historized by company.
+
+It display now this value for the inventory valuation provided by product_price_history module.
+
+Technically speaking, the way function field store the values computed for
+the cost_price to store the proper value per date and company in the history table.
+
+""",
+    'demo': [
+    ],
+    'data': [
+    ],
+    'test': [
+        'test/price_historization.yml',
+        'test/cost_price_update.yml',
+        'test/price_controlling_multicompany.yml',
+    ],
+    'installable': True,
+    'auto_install': True,
+    'active': False,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'product_cost_incl_bom_price_history/product.py'
--- product_cost_incl_bom_price_history/product.py	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom_price_history/product.py	2013-12-11 12:22:52 +0000
@@ -0,0 +1,221 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author: Joel Grand-Guillaume
+#    Copyright 2013 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
+import decimal_precision as dp
+import openerp
+from openerp.addons.product_price_history.product_price_history import PRODUCT_FIELD_HISTORIZE
+from openerp import SUPERUSER_ID
+import time
+import datetime
+import logging
+_logger = logging.getLogger(__name__)
+
+# Add the field cost_price to the list of historized field per company
+openerp.addons.product_price_history.product_price_history.PRODUCT_FIELD_HISTORIZE.append('cost_price')
+
+
+class product_product(orm.Model):
+    _inherit = 'product.product'
+
+    def _set_field_name_values(self, cr, uid, ids, field_name, context):
+        field_flag = False
+        field_dict = {}
+        prod_tpl_obj = self.pool.get('product.template')
+        if self._log_access:
+            cr.execute('select id,write_date from '+
+                       self._table+' where id IN %s', (tuple(ids),))
+            res = cr.fetchall()
+            for r in res:
+                if r[1]:
+                    field_dict.setdefault(r[0], [])
+                    res_date = time.strptime((r[1])[:19], '%Y-%m-%d %H:%M:%S')
+                    write_date = datetime.datetime.fromtimestamp(time.mktime(res_date))
+                    for i in self.pool._store_function.get(self._name, []):
+                        if i[5]:
+                            up_write_date = write_date + datetime.timedelta(hours=i[5])
+                            if datetime.datetime.now() < up_write_date:
+                                if i[1] in fields:
+                                    field_dict[r[0]].append(i[1])
+                                    if not field_flag:
+                                        field_flag = True
+        if self._columns[field_name]._multi:
+            raise ValueError('multi is not supported on the cost_price field')
+        # use admin user for accessing objects having rules defined on store fields
+        result = self._columns[field_name].get(cr, self, ids, field_name, uid, context=context)
+        for r in result.keys():
+            if field_flag:
+                if r in field_dict.keys():
+                    if field_name in field_dict[r]:
+                        result.pop(r)
+        for id, value in result.items():
+            tpl_id = self.read(cr, uid, id,
+                               ['product_tmpl_id'],
+                               context=context)['product_tmpl_id']
+            _logger.debug("set price history: %s, product_tpl_id: %s, context: %s",
+                              value,
+                              tpl_id,
+                              context)
+            prod_tpl_obj._log_price_change(cr, uid, id,
+                                           field_name,
+                                           value,
+                                           context=context)
+        return True
+
+    def _store_set_values(self, cr, uid, ids, fields, context):
+        """
+        Override the method to have the proper computation in case of cost_price history.
+        Calls the fields.function's "implementation function" for all ``fields``, 
+        on records with ``ids`` (taking care of respecting ``multi`` attributes), 
+        and stores the resulting values in the database directly.
+        """
+        if not ids:
+            return True
+        if 'cost_price' in fields:
+            fields = list(set(fields))            
+            fields.remove('cost_price')
+            self._set_field_name_values(cr, uid, ids, 'cost_price', context)
+        _logger.debug("call _store_set_values, ids %s, fields: %s",
+                              ids,
+                              fields)
+        res = super(product_product, self)._store_set_values(cr,
+                                                             uid,
+                                                             ids,
+                                                             fields,
+                                                             context)
+        return res
+
+    def _cost_price(self, cr, uid, ids, field_name, arg, context=None):
+        res = super(product_product, self)._cost_price(cr, uid, ids,
+                                                       field_name,
+                                                       arg,
+                                                       context=context)
+        return res
+    
+    def _get_product2(self, cr, uid, ids, context=None):
+        mrp_obj = self.pool.get('mrp.bom')
+        res = mrp_obj._get_product(cr, uid, ids, context=context)
+        return res
+        
+    def _get_bom_product2(self, cr, uid, ids, context=None):
+        prod_obj = self.pool.get('product.product')
+        res = prod_obj._get_bom_product(cr, uid, ids, context=context)
+        return res
+        
+    def _get_product_from_template2(self, cr, uid, ids, context=None):
+        prod_obj = self.pool.get('product.product')
+        return prod_obj._get_product_from_template(cr, uid, 
+                                                   ids,
+                                                   context=context)
+    
+    def _read_flat(self, cr, uid, ids, fields, 
+                   context=None, load='_classic_read'):
+        if context is None:
+            context = {}
+        if fields:
+            fields.append('id')
+        pt_obj = self.pool.get('product.template')
+        results = super(product_product, self)._read_flat(cr, uid, ids,
+                                                       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 = pt_obj._get_transaction_company_id(cr, 
+                                                            uid,
+                                                            context=context)
+            if context.get('to_date'):
+                date_crit = context['to_date']
+            # 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_names=price_fields,
+                                                            context=context)
+            for result in results:
+                dict_value = prod_prices[result['id']]
+                result.update(dict_value)
+        return results
+    
+    def _product_value(self, cr, uid, ids, field_names=None, 
+                       arg=False, context=None):
+        """ Override the method to use cost_price instead of standard_price.
+        @return: Dictionary of values
+        """
+        if context is None:
+            context = {}
+        res = {}
+        for id in ids:
+            res[id] = 0.0
+        products = self.read(cr, uid, ids, 
+                          ['id','qty_available','cost_price'],
+                          context=context)
+        _logger.debug("product value get, result :%s, context: %s",
+                      products, context)
+        for product in products:
+            res[product['id']] = product['qty_available'] * product['cost_price']
+        return res
+
+    # Trigger on product.product is set to None, otherwise do not trigg
+    # on product creation !
+    _cost_price_triggers = {
+        'product.product': (_get_bom_product2, None, 10),
+        'product.template': (_get_product_from_template2, ['standard_price'], 10),
+        'mrp.bom': (_get_product2, 
+                   [
+                     'bom_id',
+                     'bom_lines',
+                     'product_id',
+                     'product_uom',
+                     'product_qty',
+                     'product_uos',
+                     'product_uos_qty',
+                   ],
+                   10)
+    }
+
+    _columns = {
+        'cost_price': fields.function(_cost_price,
+              store=_cost_price_triggers,
+              string='Cost Price (incl. BoM)',
+              digits_compute=dp.get_precision('Sale Price'),
+              help="The cost price is the standard price or, if the product has a bom, "
+              "the sum of all standard price of its components. it take also care of the "
+              "bom costing like cost per cylce."),
+        'value_available': fields.function(_product_value,
+            type='float', digits_compute=dp.get_precision('Product Price'),
+            group_operator="sum",
+            string='Value',
+            help="Current value of products available.\n"
+                 "This is using the product historize price incl. BoM."
+                 "In a context with a single Stock Location, this includes "
+                 "goods stored at this Location, or any of its children."),
+        }
+
+    

=== added directory 'product_cost_incl_bom_price_history/test'
=== added file 'product_cost_incl_bom_price_history/test/cost_price_update.yml'
--- product_cost_incl_bom_price_history/test/cost_price_update.yml	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom_price_history/test/cost_price_update.yml	2013-12-11 12:22:52 +0000
@@ -0,0 +1,51 @@
+-
+  Test the following with user admin
+-
+  !context
+    uid: 'base.user_root'
+-
+  Create a wine A product
+-
+  !record {model: product.product, id: product_product_a}:
+    categ_id: product.product_category_1
+    name: Wine A01
+    cost_method: standard
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+    company_id: False
+    standard_price: 50.0
+    list_price: 75.0
+-
+  Test the prices are updated correctly on creation
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'), context=context)
+    assert product.standard_price == 50.0, "01 The standard_price has not been recorded correctly"
+    assert product.cost_price == 50.0, "01 The cost_price has not been recorded correctly"
+-
+  Modify product A set new prices
+-
+  !python {model: product.product}: |
+    self.write(cr, uid, ref('product_product_a'), {'standard_price':70.0}, context=context)
+-
+  Test price are update accordingly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 70.0, "02 The standard_price has not been recorded correctly"
+    assert product.cost_price == 70.0, "02 The cost_price has not been recorded correctly"
+-
+  Modify product A using the template object and set new prices
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    prod_tmpl_id = product.product_tmpl_id.id
+    self.pool.get('product.template').write(cr, uid, [prod_tmpl_id], {'standard_price':100.0}, context=context)
+-
+  Test cost_price is update accordingly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 100.0, "03 The standard_price has not been recorded correctly"
+    assert product.cost_price == 100.0, "03 The cost_price has not been recorded correctly"
+

=== added file 'product_cost_incl_bom_price_history/test/price_controlling_multicompany.yml'
--- product_cost_incl_bom_price_history/test/price_controlling_multicompany.yml	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom_price_history/test/price_controlling_multicompany.yml	2013-12-11 12:22:52 +0000
@@ -0,0 +1,58 @@
+-
+  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.cost_price == 50.0, "The cost_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: 'product_price_history.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.cost_price == 70.0, "The cost_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.cost_price == 50.0, "The cost_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_cost_incl_bom_price_history/test/price_historization.yml'
--- product_cost_incl_bom_price_history/test/price_historization.yml	1970-01-01 00:00:00 +0000
+++ product_cost_incl_bom_price_history/test/price_historization.yml	2013-12-11 12:22:52 +0000
@@ -0,0 +1,54 @@
+-
+  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({'to_date':time.strftime('%Y-01-01 %H:%M:%S')})
+    self.write(cr, uid, ref('product_price_history.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({'to_date':time.strftime('%Y-01-01 %H:%M:%S')})
+    product = self.browse(cr, uid, ref('product_price_history.product_product_k_avg_01'), context=ctx)
+    assert product.standard_price == 70.0, "The standard_price has not been recorded correctly for first company"
+    assert product.cost_price == 70.0, "The cost_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"
+-
+  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({'to_date':time.strftime('%Y-01-03 %H:%M:%S')})
+    self.write(cr, uid, ref('product_price_history.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({'to_date':time.strftime('%Y-01-02 %H:%M:%S')})
+    product = self.browse(cr, uid, ref('product_price_history.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.cost_price == 70.0, "The cost_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({'to_date':time.strftime('%Y-01-03 %H:%M:%S')})
+    product = self.browse(cr, uid, ref('product_price_history.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.cost_price == 80.0, "The cost_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"

=== modified file 'product_get_cost_field/__openerp__.py'
--- product_get_cost_field/__openerp__.py	2013-10-10 12:05:11 +0000
+++ product_get_cost_field/__openerp__.py	2013-12-11 12:22:52 +0000
@@ -19,28 +19,33 @@
 #
 ##############################################################################
 {'name' : 'Product Cost field',
- 'version' : '0.1',
+ 'version' : '1.0',
  'author' : 'Camptocamp',
  'maintainer': 'Camptocamp',
  'category': 'Products',
  'complexity': "normal",  # easy, normal, expert
- 'depends' : ['product',
+ 'depends' : [
+    'product',
               ],
  'description': """
- Provides an overridable method on product which compute the cost_price field of a product. 
- By default it just return the value of standard_price field, but using the product_cost_incl_bom
- module, it will return the costing from the bom.
-
- As it is a generic module, you can also setup your own way of computing the cost_price for your 
- product.
-
- All our modules to compute margin are based on it, so you'll ba able to use them in your own way.
+Provides an overridable method on product which compute the cost_price field of a product. 
+By default it just return the value of standard_price field, but using the product_cost_incl_bom
+module, it will return the costing from the bom.
+
+As it is a generic module, you can also setup your own way of computing the cost_price for your 
+product.
+
+All our modules to compute margin are based on it, so you'll ba able to use them in your own way.
+
  """,
  'website': 'http://www.camptocamp.com/',
- 'init_xml': [],
- 'update_xml': ['product_view.xml'],
- 'demo_xml': [],
- 'tests': [],
+ 'data': [
+    'product_view.xml'
+ ],
+ 'demo': [],
+ 'test': [
+    'test/cost_price_update.yml',
+ ],
  'installable': True,
  'auto_install': False,
  'license': 'AGPL-3',

=== modified file 'product_get_cost_field/product_get_cost_field.py'
--- product_get_cost_field/product_get_cost_field.py	2013-12-10 15:03:48 +0000
+++ product_get_cost_field/product_get_cost_field.py	2013-12-11 12:22:52 +0000
@@ -22,34 +22,71 @@
 
 from openerp.osv import orm, fields
 import decimal_precision as dp
+<<<<<<< TREE
 _logger = logging.getLogger(__name__)
 
 
 class Product(orm.Model):
+=======
+_logger = logging.getLogger(__name__)
+
+
+class product_product(orm.Model):
+>>>>>>> MERGE-SOURCE
     _inherit = 'product.product'
 
-    def _compute_purchase_price(self, cr, uid, ids,
-                                context=None):
+    def _compute_purchase_price(self, cr, uid, ids, context=None):
         res = {}
         if isinstance(ids, (int, long)):
+<<<<<<< TREE
             ids = [ids]
         for product in self.browse(cr, uid, ids, context=context):
             res[product.id] = product.standard_price
+=======
+            ids = [ids]
+        for product in self.read(cr, uid, ids,
+                                 ['id','standard_price'],
+                                 context=context):
+            res[product['id']] = product['standard_price']
+>>>>>>> MERGE-SOURCE
         return res
 
     def _cost_price(self, cr, uid, ids, field_name, arg, context=None):
         if context is None:
             context = {}
+<<<<<<< TREE
         res = self._compute_purchase_price(cr, uid, ids, context=context)
         _logger.debug("get cost field _cost_price %s, arg: %s, "
                       "context: %s, result:%s",
                       field_name, arg, context, res)
         return res
+=======
+        return self._compute_purchase_price(cr, uid, ids, context=context)
+>>>>>>> MERGE-SOURCE
 
     def get_cost_field(self, cr, uid, ids, context=None):
-        return self._cost_price(cr, uid, ids, '', [], context=context)
+<<<<<<< TREE
+        return self._cost_price(cr, uid, ids, '', [], context=context)
+=======
+        return self._cost_price(cr, uid, ids, '', [], context=context)
+
+    def _get_product_from_template(self, cr, uid, ids, context=None):
+        prod_obj = self.pool.get('product.product')
+        prod_ids = prod_obj.search(cr, uid,
+                                   [('product_tmpl_id','in',ids)],
+                                   context=context)
+        return prod_ids
+
+    # Trigger on product.product is set to None, otherwise do not trigg
+    # on product creation !
+    _cost_price_triggers = {
+        'product.product': (lambda self, cr, uid, ids, context=None: ids, None, 10),
+        'product.template': (_get_product_from_template, ['standard_price'], 10),
+    }
+>>>>>>> MERGE-SOURCE
 
     _columns = {
+<<<<<<< TREE
         'cost_price': fields.function(
             _cost_price,
             method=True,
@@ -58,3 +95,13 @@
             help="The cost price is the standard price unless you install the "
                  "product_cost_incl_bom module.")
     }
+=======
+        'cost_price': fields.function(
+            _cost_price,
+            store=_cost_price_triggers,
+            string='Cost Price',
+            digits_compute=dp.get_precision('Sale Price'),
+            help="The cost price is the standard price unless you install the "
+                 "product_cost_incl_bom module.")
+    }
+>>>>>>> MERGE-SOURCE

=== added directory 'product_get_cost_field/test'
=== added file 'product_get_cost_field/test/cost_price_update.yml'
--- product_get_cost_field/test/cost_price_update.yml	1970-01-01 00:00:00 +0000
+++ product_get_cost_field/test/cost_price_update.yml	2013-12-11 12:22:52 +0000
@@ -0,0 +1,51 @@
+-
+  Test the following with user admin
+-
+  !context
+    uid: 'base.user_root'
+-
+  Create a wine A product
+-
+  !record {model: product.product, id: product_product_a}:
+    categ_id: product.product_category_1
+    name: Wine A01
+    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 updated correctly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 50.0, "01 The standard_price has not been recorded correctly"
+    assert product.cost_price == 50.0, "01 The cost_price has not been recorded correctly"
+-
+  Modify product A set new prices
+-
+  !python {model: product.product}: |
+    self.write(cr, uid, ref('product_product_a'), {'standard_price':70})
+-
+  Test price are update accordingly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 70.0, "02 The standard_price has not been recorded correctly"
+    assert product.cost_price == 70.0, "02 The cost_price has not been recorded correctly"
+-
+  Modify product A using the template object and set new prices
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    prod_tmpl_id = product.product_tmpl_id.id
+    self.pool.get('product.template').write(cr, uid, [prod_tmpl_id], {'standard_price':100})
+-
+  Test cost_price is update accordingly
+-
+  !python {model: product.product}: |
+    product = self.browse(cr, uid, ref('product_product_a'))
+    assert product.standard_price == 100.0, "03 The standard_price has not been recorded correctly"
+    assert product.cost_price == 100.0, "03 The cost_price has not been recorded correctly"
+

=== modified file 'product_historical_margin/test/basic_historical_margin.yml'
--- product_historical_margin/test/basic_historical_margin.yml	2013-12-10 15:01:26 +0000
+++ product_historical_margin/test/basic_historical_margin.yml	2013-12-11 12:22:52 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 -
   Create a Customer for SO
 -
@@ -127,3 +128,134 @@
   !python {model: product.product}: |
      value = ((150 - 100) * 10 + (200 - 100) * 10 )
      assert self.browse(cr, uid, ref("product_product_a_avg_01")).margin_absolute == value,"Margin for product Wine A is wrongly computed"
+=======
+-
+  Create a Customer for SO
+-
+  !record {model: res.partner, id: res_partner_customer01}:
+    name: Customer 3
+    customer: 1
+-
+  Create a wine product A with an avg price of 100
+-
+  !record {model: product.product, id: product_product_a_avg_01}:
+    categ_id: product.product_category_1
+    cost_method: standard
+    name: Wine A
+    standard_price: 100.0
+    list_price: 150.0
+    cost_method: average
+    uom_id: product.product_uom_unit
+    uom_po_id: product.product_uom_unit
+-
+  Create a shop
+-
+  !record {model: sale.shop, id: default_shop}:
+    name: Default test shop
+    company_id: base.main_company
+    payment_default_id: account.account_payment_term_net
+-
+  In order to test process of the Sale Order, I create sale order
+-
+  !record {model: sale.order, id: sale_order_test1}:
+    partner_id: res_partner_customer01
+    note: Invoice Manual
+    order_policy: manual
+    pricelist_id: product.list0
+    shop_id: default_shop
+    order_line: 
+      - product_id: product_product_a_avg_01
+        product_uom_qty: 10
+        price_unit: 150.0
+-
+  I confirm the Quotation with "Manual" order policy.
+-
+  !workflow {model: sale.order, action: order_confirm, ref: sale_order_test1}
+-
+  I check that Invoice should not created.
+-
+  !python {model: sale.order}: |
+    sale_order = self.browse(cr, uid, ref("sale_order_test1"))
+    assert len(sale_order.invoice_ids) == False, "Invoice should not created."
+-
+  I create advance invoice where type is 'Invoice all the Sale Order'.
+-
+  !python {model: sale.advance.payment.inv}: |
+    ctx = context.copy()
+    ctx.update({"active_model": 'sale.order', "active_ids": [ref("sale_order_test1")], "active_id":ref("sale_order_test1")})
+    pay_id = self.create(cr, uid, {'advance_payment_method': 'all'})
+    self.create_invoices(cr, uid, [pay_id], context=ctx)
+-
+  I check Invoice which made advance where type is 'Invoice all the Sale Order'.
+-
+  !python {model: sale.order}: |
+    order = self.browse(cr, uid, ref('sale_order_test1'))
+    assert order.invoice_ids, "Invoice should be created after make invoice where type is 'Invoice all the Sale Order'."
+-
+  I open the Invoice.
+-
+  !python {model: sale.order}: |
+    import netsvc
+    wf_service = netsvc.LocalService("workflow")
+    so = self.browse(cr, uid, ref("sale_order_test1"))
+    for invoice in so.invoice_ids:
+      wf_service.trg_validate(uid, 'account.invoice', invoice.id, 'invoice_open', cr)
+-
+  I check that margin of products is computed correctly
+-
+  !python {model: product.product}: |
+     value = 150 * 10 - 100 * 10
+     assert self.browse(cr, uid, ref("product_product_a_avg_01")).margin_absolute == value,"Margin for product Wine A is wrongly computed"
+-
+  In order to test process of the Sale Order, I create a second sale order
+-
+  !record {model: sale.order, id: sale_order_test2}:
+    partner_id: res_partner_customer01
+    note: Invoice Manual
+    order_policy: manual
+    pricelist_id: product.list0
+    shop_id: default_shop
+    order_line: 
+      - product_id: product_product_a_avg_01
+        product_uom_qty: 10
+        price_unit: 200
+-
+  I confirm the Quotation with "Manual" order policy.
+-
+  !workflow {model: sale.order, action: order_confirm, ref: sale_order_test2}
+-
+  I check that Invoice should not created.
+-
+  !python {model: sale.order}: |
+    sale_order = self.browse(cr, uid, ref("sale_order_test2"))
+    assert len(sale_order.invoice_ids) == False, "Invoice should not created."
+-
+  I create advance invoice where type is 'Invoice all the Sale Order'.
+-
+  !python {model: sale.advance.payment.inv}: |
+    ctx = context.copy()
+    ctx.update({"active_model": 'sale.order', "active_ids": [ref("sale_order_test2")], "active_id":ref("sale_order_test2")})
+    pay_id = self.create(cr, uid, {'advance_payment_method': 'all'})
+    self.create_invoices(cr, uid, [pay_id], context=ctx)
+-
+  I check Invoice which made advance where type is 'Invoice all the Sale Order'.
+-
+  !python {model: sale.order}: |
+    order = self.browse(cr, uid, ref('sale_order_test2'))
+    assert order.invoice_ids, "Invoice should be created after make invoice where type is 'Invoice all the Sale Order'."
+-
+  I open the Invoice.
+-
+  !python {model: sale.order}: |
+    import netsvc
+    wf_service = netsvc.LocalService("workflow")
+    so = self.browse(cr, uid, ref("sale_order_test2"))
+    for invoice in so.invoice_ids:
+      wf_service.trg_validate(uid, 'account.invoice', invoice.id, 'invoice_open', cr)
+-
+  I check that margin of products is computed correctly
+-
+  !python {model: product.product}: |
+     value = ((150 - 100) * 10 + (200 - 100) * 10 )
+     assert self.browse(cr, uid, ref("product_product_a_avg_01")).margin_absolute == value,"Margin for product Wine A is wrongly computed"
+>>>>>>> MERGE-SOURCE

=== modified file 'product_price_history/__openerp__.py'
--- product_price_history/__openerp__.py	2013-11-25 13:52:59 +0000
+++ product_price_history/__openerp__.py	2013-12-11 12:22:52 +0000
@@ -1,79 +1,167 @@
-# -*- 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/>
-#
-##############################################################################
-
-{
-    "name" : "Product Price History",
-    "version" : "1.2",
-    "author" : "Camptocamp",
-    "category" : "Generic Modules/Inventory Control",
-    "depends" : ["product",
-                 "purchase",
-                 ],
-    "description": """
-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:
+<<<<<<< TREE
+# -*- 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/>
+#
+##############################################################################
+
+{
+    "name" : "Product Price History",
+    "version" : "1.2",
+    "author" : "Camptocamp",
+    "category" : "Generic Modules/Inventory Control",
+    "depends" : ["product",
+                 "purchase",
+                 ],
+    "description": """
+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:
+=======
+# -*- 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/>
+#
+##############################################################################
+
+{
+    "name" : "Product Price History",
+    "version" : "1.2",
+    "author" : "Camptocamp",
+    "category" : "Generic Modules/Inventory Control",
+    "depends" : ["product",
+                 "purchase",
+                 ],
+    "description": """
+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.
+
+As the prices are now historized, some information aren't revelant anymore in some report. This
+module also hide the price information in those view to avoid having wrong informations. Instead
+you'll find a view that allow you to retrive the product price and stock at a given date to value
+your inventory properly.
+
+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': [
+        'wizard/historic_prices_view.xml',
+        'product_price_history_view.xml',
+        'report_stock_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:
+>>>>>>> MERGE-SOURCE

=== modified file 'product_price_history/demo/product_price_history_purchase_demo.yml'
--- product_price_history/demo/product_price_history_purchase_demo.yml	2013-11-05 11:49:51 +0000
+++ product_price_history/demo/product_price_history_purchase_demo.yml	2013-12-11 12:22:52 +0000
@@ -1,189 +1,382 @@
--
-  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)]"
+<<<<<<< TREE
+-
+  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)]"
+=======
+-
+  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
+    company_id: False
+-
+  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)]"
+>>>>>>> MERGE-SOURCE

=== modified file 'product_price_history/product_price_history.py'
--- product_price_history/product_price_history.py	2013-12-04 10:24:03 +0000
+++ product_price_history/product_price_history.py	2013-12-11 12:22:52 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- coding: utf-8 -*-
 ##############################################################################
 #
@@ -236,3 +237,302 @@
     _defaults = {
         'company_id': _get_default_company,
         }
+=======
+# -*- 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/>
+#
+##############################################################################
+
+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('to_date'):
+            result = context.get('to_date')
+        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_names=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_names is None:
+            field_names = PRODUCT_FIELD_HISTORIZE
+        select = ("SELECT DISTINCT ON (product_id, name) "
+                  "datetime, product_id, name, amount ")
+        table = "FROM product_price_history "
+        where = ("WHERE product_id IN %s "
+                 "AND company_id = %s "
+                 "AND name IN %s ")
+        args = [tuple(ids), company_id, tuple(field_names)]
+        if datetime:
+            where += "AND datetime <= %s "
+            args.append(datetime)
+        # at end, sort by ID desc if several entries are created
+        # on the same datetime
+        order = ("ORDER BY product_id, name, datetime DESC, id DESC ")
+        cr.execute(select + table + where + order, args)
+        for id in ids:
+            res[id] = dict.fromkeys(field_names, 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_product(orm.Model):
+    _inherit = "product.product"
+    
+    def _product_value(self, cr, uid, ids,
+                       field_names=None, arg=False, context=None):
+        """ Comute the value of product using qty_available and historize 
+        values for the price.
+        @return: Dictionary of values
+        """
+        if context is None:
+            context = {}
+        res = {}
+        for id in ids:
+            res[id] = 0.0
+        products = self.read(cr, uid, ids, 
+                          ['id','qty_available','standard_price'],
+                          context=context)
+        _logger.debug("product value get, result :%s, context: %s",
+                      products, context)
+        for product in products:
+            res[product['id']] = product['qty_available'] * product['standard_price']
+        return res
+
+    _columns = {
+        'value_available': fields.function(_product_value,
+            type='float', digits_compute=dp.get_precision('Product Price'),
+            group_operator="sum",
+            string='Value',
+            help="Current value of products available.\n"
+                 "This is using the product historize price."
+                 "In a context with a single Stock Location, this includes "
+                 "goods stored at this Location, or any of its children."),
+    }
+
+
+class product_template(orm.Model):
+
+    _inherit = "product.template"
+
+    def _log_all_price_changes(self, cr, uid, product, values, context=None):
+        """
+        For each field to historize, call the _log_price_change method
+        @param: values dict of vals used by write and create od product
+        @param: int product ID
+        """
+        for field_name in PRODUCT_FIELD_HISTORIZE:
+            if values.get(field_name): 
+                amount = values[field_name]
+                self._log_price_change(cr, uid, product, field_name, 
+                                       amount, context=context)
+        return True
+
+    def _log_price_change(self, cr, uid, product, field_name, amount, context=None):
+        """
+        On change of price create a price_history
+        :param int product value of new product or product_id
+        """
+        res = True
+        price_history = self.pool.get('product.price.history')
+        company = self._get_transaction_company_id(cr, uid,
+                context=context)
+        data = {
+            'product_id': product,
+            'amount': amount,
+            'name': field_name,
+            'company_id': company
+            }
+        p_prices = price_history._get_historic_price(cr, uid, [product],
+                                                     company,
+                                                     field_names=[field_name],
+                                                     context=context)
+        
+        if p_prices[product].get(field_name) != amount:
+            _logger.debug("Log price change (product id: %s): %s, field: %s",
+                          product, amount, field_name)
+            res = price_history.create(cr, uid, data, context=context)
+        return res
+
+    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.read(cr, uid, uid,
+                                ['company_id'],
+                                context=context)['company_id'][0]
+        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_all_price_changes(cr, uid, res, values, context=context)
+        return res
+
+    def _read_flat(self, cr, uid, ids, fields,
+                   context=None, load='_classic_read'):
+        if context is None:
+            context = {}
+        if fields:
+            fields.append('id')
+        results = super(product_template, self)._read_flat(cr, uid, ids,
+                                                           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
+            p_history = self.pool.get('product.price.history')
+            company_id = self._get_transaction_company_id(cr, uid,
+                                                          context=context)
+            if context.get('to_date'):
+                date_crit = context['to_date']
+            # if fields is empty we read all price fields
+            if not fields:
+                p_fields = PRODUCT_FIELD_HISTORIZE
+            # Otherwise we filter on price fields asked in read
+            else:
+                p_fields = [f for f in PRODUCT_FIELD_HISTORIZE if f in fields]
+            prod_prices = p_history._get_historic_price(cr, uid, ids,
+                                                        company_id,
+                                                        datetime=date_crit,
+                                                        field_names=p_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 isinstance(ids, (int, long)):
+            ids = [ids]
+        if any([f in PRODUCT_FIELD_HISTORIZE for f in values]):
+            for id in ids:
+                self._log_all_price_changes(cr, uid, id, values,
+                                       context=context)
+        return super(product_template, self).write(cr, uid, ids, values,
+                                                   context=context)
+
+    def unlink(self, cr, uid, ids, context=None):
+        if isinstance(ids, (int, long)):
+            ids = [ids]
+        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 = {
+        'company_id': fields.many2one('res.company', 'Company',
+                                      required=True),
+    }
+
+    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,
+        }
+>>>>>>> MERGE-SOURCE

=== modified file 'product_price_history/product_price_history_view.xml'
--- product_price_history/product_price_history_view.xml	2013-11-25 13:51:14 +0000
+++ product_price_history/product_price_history_view.xml	2013-12-11 12:22:52 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
   <data>
@@ -88,3 +89,95 @@
 
   </data>
 </openerp>
+=======
+<?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="qty_available" sum="Total Stock"/>
+          <field name="list_price"/>
+          <field name="standard_price"/>
+          <field name="value_available" sum="Total Value"/>
+          <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"
+            id="menu_product_price_history_action_form"
+            parent="stock.next_id_61" sequence="2"/>
+    
+  </data>
+</openerp>
+>>>>>>> MERGE-SOURCE

=== added file 'product_price_history/report_stock_view.xml'
--- product_price_history/report_stock_view.xml	1970-01-01 00:00:00 +0000
+++ product_price_history/report_stock_view.xml	2013-12-11 12:22:52 +0000
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+<data>
+
+    <record id="view_stock_tree" model="ir.ui.view">
+        <field name="name">report.stock.move.tree</field>
+        <field name="model">report.stock.move</field>
+        <field name="inherit_id" ref="stock.view_stock_tree" />
+        <field name="arch" type="xml">
+            <field name="value"  sum="Total value" position="replace">
+                <field name="value"  sum="Total value" invisible="1" />
+            </field>
+        </field>
+    </record>
+
+    <record id="view_stock_inventory_tree" model="ir.ui.view">
+        <field name="name">report.stock.inventory.tree</field>
+        <field name="model">report.stock.inventory</field>
+        <field name="inherit_id" ref="stock.view_stock_inventory_tree" />
+        <field name="arch" type="xml">
+            <field name="value"  sum="Total value" position="replace">
+                <field name="value"  sum="Total value" invisible="1" />
+            </field>
+        </field>
+    </record>
+
+</data>
+</openerp>

=== modified file 'product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml'
--- product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml	2013-11-05 11:49:51 +0000
+++ product_price_history/test/avg_price_computation_mutlicompanies_multicurrencies.yml	2013-12-11 12:22:52 +0000
@@ -1,186 +1,380 @@
--
-  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"
+<<<<<<< TREE
+-
+  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"
+=======
+-
+  Setup the multi company rules for company, share them between company
+-
+  !record {model: ir.rule, id: base.multi_company_default_rule}:
+    domain_force: "[(1,'=',1)]"
+-
+  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"
+>>>>>>> MERGE-SOURCE

=== modified file 'product_price_history/wizard/historic_prices.py'
--- product_price_history/wizard/historic_prices.py	2013-12-03 10:38:20 +0000
+++ product_price_history/wizard/historic_prices.py	2013-12-11 12:22:52 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # -*- coding: utf-8 -*-
 ##############################################################################
 #
@@ -68,3 +69,167 @@
             'view_id': product_view_id[1],
             'search_view_id': filter_id,
             }
+=======
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author: Alexandre Fayolle, Joel Grand-Guillaume
+#    Copyright 2013 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
+from openerp.tools import mute_logger
+
+class historic_prices(orm.TransientModel):
+    _name = 'historic.prices'
+    _description = 'Product historical prices'
+
+
+    def _default_location(self, cr, uid, ids, context=None):
+        try:
+            ir_model = self.pool.get('ir.model.data')
+            location = ir_model.get_object(cr,
+                                           uid,
+                                           'stock',
+                                           'stock_location_stock')
+            with mute_logger('openerp.osv.orm'):
+                location.check_access_rule('read', context=context)
+            location_id = location.id
+        except (ValueError, orm.except_orm), e:
+            return False
+        return location_id or False
+
+    _columns = {
+        'location_id': fields.many2one('stock.location', 
+            'Location',
+            required=True,
+            help='The location where you want the valuation (this will '
+                 'include all the child locations.'),
+        'to_date': fields.datetime(
+            'Date',
+            help='Date at which the analysis need to be done.'),
+        }
+    _defaults = {
+        'location_id': _default_location,
+    }
+
+    def _get_product_qty(self, cr, uid, context=None):
+        """Return all product ids that have a qty at the given location for 
+        the given date in the context. Use SQL for performance here.
+        """
+        if context == None:
+            context = {}
+        location_id = context.get('location')
+        location_obj = self.pool.get('stock.location')
+        child_location_ids = location_obj.search(cr, uid,
+                                                 [
+                                                    ('location_id',
+                                                     'child_of',
+                                                     [location_id])
+                                                 ])
+        location_ids = child_location_ids or [location_id]
+        stop_date = context.get('to_date', time.strftime('%Y-%m-%d %H:%M:%S'))
+        sql_req = """
+                    SELECT pp.id,
+                           SUM(qty) AS qty
+                      FROM (SELECT p.id AS product_id,
+                                   t.id AS template_id,
+                                   s_in.qty
+                              FROM (SELECT SUM(product_qty) AS qty,
+                                           product_id
+                                      FROM stock_move
+                                     WHERE location_id NOT IN %(location_ids)s
+                                       AND location_dest_id IN %(location_ids)s
+                                       AND state = 'done'
+                                       AND date <= %(stop_date)s
+                                     GROUP BY product_id) AS s_in
+                             INNER JOIN product_product p
+                                ON p.id = s_in.product_id
+                             INNER JOIN product_template t
+                                ON t.id = p.product_tmpl_id
+                             UNION
+                            SELECT p.id AS product_id,
+                                   t.id AS template_id,
+                                   -s_out.qty AS qty
+                              FROM (SELECT SUM(product_qty) AS qty,
+                                           product_id
+                                      FROM stock_move
+                                     WHERE location_id IN %(location_ids)s
+                                       AND location_dest_id NOT IN %(location_ids)s
+                                       AND state = 'done'
+                                       AND date <= %(stop_date)s
+                                     GROUP BY product_id) AS s_out
+                             INNER JOIN product_product p
+                                ON p.id = s_out.product_id
+                             INNER JOIN product_template t
+                                ON t.id = p.product_tmpl_id) AS in_out
+                     INNER JOIN product_template pt
+                        ON pt.id = in_out.template_id
+                     INNER JOIN product_product pp
+                        ON pp.id = in_out.product_id
+                     WHERE pt.type = 'product'
+                       AND pp.active = true
+                     GROUP BY pp.id
+                    HAVING SUM(qty) <> 0"""
+        cr.execute(sql_req,
+                   {'location_ids': tuple(location_ids),
+                    'stop_date': stop_date,}
+                    )
+        res = dict(cr.fetchall())
+        return res.keys()
+
+    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()
+        ctx.update(
+            location=wiz.get('location_id')[0],
+        )
+        if wiz.get('to_date'):
+            ctx.update(
+                to_date=wiz.get('to_date')
+                )
+        displayed_ids = self._get_product_qty(cr, uid, context=ctx)
+        domain = [('id','in',displayed_ids)]
+        d_obj = self.pool.get('ir.model.data')
+        filter_ids = d_obj.get_object_reference(cr, uid, 'product',
+                                               'product_search_form_view')
+        product_view_id = d_obj.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],
+            'domain': domain,
+            'search_view_id': filter_id,
+            }
+>>>>>>> MERGE-SOURCE

=== modified file 'product_price_history/wizard/historic_prices_view.xml'
--- product_price_history/wizard/historic_prices_view.xml	2013-11-05 11:49:51 +0000
+++ product_price_history/wizard/historic_prices_view.xml	2013-12-11 12:22:52 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
   <data>
@@ -34,3 +35,42 @@
 
   </data>
 </openerp>
+=======
+<?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"/>
+            <field name="location_id"/>
+          </group>
+          <footer>
+            <button name="action_open_window" string="Ok" 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">Inventory Valuation</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>
+>>>>>>> MERGE-SOURCE

=== modified file 'product_standard_margin/product_std_margin.py'
--- product_standard_margin/product_std_margin.py	2013-11-27 15:26:22 +0000
+++ product_standard_margin/product_std_margin.py	2013-12-11 12:22:52 +0000
@@ -75,16 +75,26 @@
             return res
         for product in ids:
             res[product] = {'margin_absolute': 0, 'margin_relative': 0}
+<<<<<<< TREE
         for product in self.browse(cursor, user, ids, context=context):
             cost = product.cost_price
+=======
+        for product in self.read(cursor, user, ids, ['id','cost_price'], context=context):
+            cost = product['cost_price']
+>>>>>>> MERGE-SOURCE
             sale = self._amount_tax_excluded(cursor, user,
-                    [product.id], context=context)[product.id]
-            res[product.id]['standard_margin'] = sale - cost
+                    [product['id']], context=context)[product['id']]
+            res[product['id']]['standard_margin'] = sale - cost
             if sale == 0:
+<<<<<<< TREE
                 _logger.debug("Sale price for product ID %d is 0, cannot compute margin rate...", product.id)
                 res[product.id]['standard_margin_rate'] = 999.
+=======
+                _logger.debug("Sale price for product ID %d is 0, cannot compute margin rate...", product['id'])
+                res[product['id']]['standard_margin_rate'] = 999.
+>>>>>>> MERGE-SOURCE
             else:
-                res[product.id]['standard_margin_rate'] = (sale - cost) / sale * 100
+                res[product['id']]['standard_margin_rate'] = (sale - cost) / sale * 100
         return res
 
     _columns = {

=== added directory 'product_stock_cost_field_report'
=== added file 'product_stock_cost_field_report/__init__.py'
--- product_stock_cost_field_report/__init__.py	1970-01-01 00:00:00 +0000
+++ product_stock_cost_field_report/__init__.py	2013-12-11 12:22:52 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author: Joel Grand-Guillaume
+#    Copyright 2013 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 . import report_stock_move

=== added file 'product_stock_cost_field_report/__openerp__.py'
--- product_stock_cost_field_report/__openerp__.py	1970-01-01 00:00:00 +0000
+++ product_stock_cost_field_report/__openerp__.py	2013-12-11 12:22:52 +0000
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author:  Joel Grand-Guillaume
+#    Copyright 2013 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/>.
+#
+##############################################################################
+{'name' : 'Product Cost field Report',
+ 'version' : '1.0',
+ 'author' : 'Camptocamp',
+ 'maintainer': 'Camptocamp',
+ 'category': 'Products',
+ 'complexity': "normal",  # easy, normal, expert
+ 'depends' : [
+        'product_get_cost_field',
+        'stock',
+              ],
+ 'description': """
+This module override the reporting view of OpenERP to replace the standard_price field used
+by the new cost_price one. This way all reporting of OpenERP will now take this field into 
+account and display the correct result.
+
+We're tlaking here about the reporting found under : Reporting -> Warehouse
+ """,
+ 'website': 'http://www.camptocamp.com/',
+ 'data': [],
+ 'demo': [],
+ 'test': [],
+ 'installable': True,
+ 'auto_install': True,
+ 'license': 'AGPL-3',
+ 'application': False
+ }

=== added file 'product_stock_cost_field_report/report_stock_move.py'
--- product_stock_cost_field_report/report_stock_move.py	1970-01-01 00:00:00 +0000
+++ product_stock_cost_field_report/report_stock_move.py	2013-12-11 12:22:52 +0000
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Author:  Joel Grand-Guillaume
+#    Copyright 2013 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 import tools
+from openerp.osv import orm, fields
+
+
+class report_stock_move(orm.Model):
+    _inherit = "report.stock.move"
+
+    def init(self, cr):
+        """
+        Override the SQL view tpo replace standard_price by cost_price
+        """
+        tools.drop_view_if_exists(cr, 'report_stock_move')
+        cr.execute("""
+            CREATE OR REPLACE view report_stock_move AS (
+                SELECT
+                        min(sm.id) as id, 
+                        date_trunc('day', sm.date) as date,
+                        to_char(date_trunc('day',sm.date), 'YYYY') as year,
+                        to_char(date_trunc('day',sm.date), 'MM') as month,
+                        to_char(date_trunc('day',sm.date), 'YYYY-MM-DD') as day,
+                        avg(date(sm.date)-date(sm.create_date)) as day_diff,
+                        avg(date(sm.date_expected)-date(sm.create_date)) as day_diff1,
+                        avg(date(sm.date)-date(sm.date_expected)) as day_diff2,
+                        sm.location_id as location_id,
+                        sm.picking_id as picking_id,
+                        sm.company_id as company_id,
+                        sm.location_dest_id as location_dest_id,
+                        sum(sm.product_qty) as product_qty,
+                        sum(
+                            (CASE WHEN sp.type in ('out') THEN
+                                     (sm.product_qty * pu.factor / pu2.factor)
+                                  ELSE 0.0 
+                            END)
+                        ) as product_qty_out,
+                        sum(
+                            (CASE WHEN sp.type in ('in') THEN
+                                     (sm.product_qty * pu.factor / pu2.factor)
+                                  ELSE 0.0 
+                            END)
+                        ) as product_qty_in,
+                        sm.partner_id as partner_id,
+                        sm.product_id as product_id,
+                        sm.state as state,
+                        sm.product_uom as product_uom,
+                        pt.categ_id as categ_id ,
+                        coalesce(sp.type, 'other') as type,
+                        sp.stock_journal_id AS stock_journal,
+
+                        -- *** BEGIN of changes ***
+                        sum(
+                            (CASE WHEN sp.type in ('in') THEN
+                                     (sm.product_qty * pu.factor / pu2.factor) * pp.cost_price
+                                  ELSE 0.0 
+                            END)
+                            -
+                            (CASE WHEN sp.type in ('out') THEN
+                                     (sm.product_qty * pu.factor / pu2.factor) * pp.cost_price
+                                  ELSE 0.0 
+                            END)
+                        ) as value
+                        -- *** END OF CHANGES ***
+                    FROM
+                        stock_move sm
+                        LEFT JOIN stock_picking sp ON (sm.picking_id=sp.id)
+                        LEFT JOIN product_product pp ON (sm.product_id=pp.id)
+                        LEFT JOIN product_uom pu ON (sm.product_uom=pu.id)
+                          LEFT JOIN product_uom pu2 ON (sm.product_uom=pu2.id)
+                        LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
+                    GROUP BY
+                        coalesce(sp.type, 'other'), date_trunc('day', sm.date), sm.partner_id,
+                        sm.state, sm.product_uom, sm.date_expected,
+                        -- *** BEGIN of changes ***
+                        sm.product_id, pp.cost_price, sm.picking_id,
+                        -- *** END OF CHANGES ***
+                        sm.company_id, sm.location_id, sm.location_dest_id, pu.factor, pt.categ_id, sp.stock_journal_id,
+                        year, month, day
+               )
+        """)
+
+
+class report_stock_inventory(orm.Model):
+    _inherit = "report.stock.inventory"
+
+    def init(self, cr):
+        """
+        Override the SQL view tpo replace standard_price by cost_price
+        """
+        tools.drop_view_if_exists(cr, 'report_stock_inventory')
+        cr.execute("""
+CREATE OR REPLACE view report_stock_inventory AS (
+    (SELECT
+        min(m.id) as id, m.date as date,
+        to_char(m.date, 'YYYY') as year,
+        to_char(m.date, 'MM') as month,
+        m.partner_id as partner_id, m.location_id as location_id,
+        m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, l.scrap_location as scrap_location,
+        m.company_id,
+        m.state as state, m.prodlot_id as prodlot_id,
+        -- *** BEGIN of changes ***
+        coalesce(sum(-pp.cost_price * m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as value,
+        -- *** END OF CHANGES ***
+        coalesce(sum(-m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as product_qty
+    FROM
+        stock_move m
+            LEFT JOIN stock_picking p ON (m.picking_id=p.id)
+            LEFT JOIN product_product pp ON (m.product_id=pp.id)
+                LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
+                LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
+                LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
+            LEFT JOIN product_uom u ON (m.product_uom=u.id)
+            LEFT JOIN stock_location l ON (m.location_id=l.id)
+            WHERE m.state != 'cancel'
+    GROUP BY
+        m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id,  m.location_dest_id,
+        m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM')
+) UNION ALL (
+    SELECT
+        -m.id as id, m.date as date,
+        to_char(m.date, 'YYYY') as year,
+        to_char(m.date, 'MM') as month,
+        m.partner_id as partner_id, m.location_dest_id as location_id,
+        m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type, l.scrap_location as scrap_location,
+        m.company_id,
+        m.state as state, m.prodlot_id as prodlot_id,
+        -- *** BEGIN of changes ***
+        coalesce(sum(pp.cost_price * m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as value,
+        -- *** END OF CHANGES ***
+        coalesce(sum(m.product_qty * pu.factor / pu2.factor)::decimal, 0.0) as product_qty
+    FROM
+        stock_move m
+            LEFT JOIN stock_picking p ON (m.picking_id=p.id)
+            LEFT JOIN product_product pp ON (m.product_id=pp.id)
+                LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
+                LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
+                LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
+            LEFT JOIN product_uom u ON (m.product_uom=u.id)
+            LEFT JOIN stock_location l ON (m.location_dest_id=l.id)
+            WHERE m.state != 'cancel'
+    GROUP BY
+        m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
+        m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM')
+    )
+);
+        """)
+