← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations into lp:stock-logistic-warehouse

 

Laetitia Gangloff (Acsone) has proposed merging lp:~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations into lp:stock-logistic-warehouse.

Requested reviews:
  Stock and Logistic Core Editors (stock-logistic-core-editors)

For more details, see:
https://code.launchpad.net/~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations/+merge/224774

Add exhaustive parameter for fill inventory.
And encapsulate in try catch to confirm even if there is no product to add
-- 
https://code.launchpad.net/~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations/+merge/224774
Your team Stock and Logistic Core Editors is requested to review the proposed merge of lp:~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations into lp:stock-logistic-warehouse.
=== added directory 'stock_inventory_hierarchical'
=== added file 'stock_inventory_hierarchical/__init__.py'
--- stock_inventory_hierarchical/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/__init__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+# This package-wide list keeps the names of the field that must be
+# propagated from root inventories to their children.
+# Add field names in the Model's definition.
+PARENT_VALUES = []
+
+import hierarchical_inventory
+# Bring the main exception into the package's scope for easier reuse
+from .exceptions import HierarchicalInventoryException

=== added file 'stock_inventory_hierarchical/__openerp__.py'
--- stock_inventory_hierarchical/__openerp__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/__openerp__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name": "Hierarchical Physical Inventory",
+    "version": "1.1",
+    "depends": ["stock"],
+    "author": "Numérigraphe",
+    "category": "Warehouse Management",
+    "description": """
+Hierarchical structure for Physical Inventories and sub-Inventories
+===================================================================
+
+This module adds a parent-child relationship between Physical Inventories, to
+help users manage complex inventories.
+Using several inventories, you can distribute the counting to several persons
+and still keep a clear overview of global Inventory's status.
+
+OpenERP will make sure the status of the Inventory and it's Sub-Inventories are
+consistent.
+""",
+    "data": ["hierarchical_inventory_view.xml"],
+    "test": ["test/hierarchical_inventory_test.yml"],
+    "demo": ["hierarchical_inventory_demo.xml"],
+    "images": [
+        "inventory_form.png",
+        "inventory_form_actions.png",
+    ],
+}

=== added file 'stock_inventory_hierarchical/exceptions.py'
--- stock_inventory_hierarchical/exceptions.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/exceptions.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import orm
+
+
+class HierarchicalInventoryException(orm.except_orm):
+    """The operation is not possible for a hierarchical inventory"""
+    pass

=== added file 'stock_inventory_hierarchical/hierarchical_inventory.py'
--- stock_inventory_hierarchical/hierarchical_inventory.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU 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 _
+
+from .exceptions import HierarchicalInventoryException
+
+# Add the date to the list of fields we must propagate to children inventories
+from . import PARENT_VALUES
+PARENT_VALUES.append('date')
+
+
+class HierarchicalInventory(orm.Model):
+    _inherit = 'stock.inventory'
+
+    _parent_store = True
+    _parent_order = 'date, name'
+    _order = 'parent_left'
+
+    def name_get(self, cr, uid, ids, context=None):
+        """Show the parent inventory's name in the name of the children
+
+        :param dict context: the ``inventory_display`` key can be
+                             used to select the short version of the
+                             inventory name (without the direct parent),
+                             when set to ``'short'``. The default is
+                             the long version."""
+        if context is None:
+            context = {}
+        if context.get('inventory_display') == 'short':
+            # Short name context: just do the usual stuff
+            return super(HierarchicalInventory, self).name_get(
+                cr, uid, ids, context=context)
+        if isinstance(ids, (list, tuple)) and not len(ids):
+            return []
+        if isinstance(ids, (long, int)):
+            ids = [ids]
+        reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context)
+        res = []
+        for record in reads:
+            name = record['name']
+            if record['parent_id']:
+                name = record['parent_id'][1] + ' / ' + name
+            res.append((record['id'], name))
+        return res
+
+    def name_search(self, cr, uid, name='', args=None, operator='ilike',
+                    context=None, limit=100):
+        """Enable search on value returned by name_get ("parent / child")"""
+        if not args:
+            args = []
+        if not context:
+            context = {}
+        if name:
+            # Make sure name_search is symmetric to name_get
+            name = name.split(' / ')[-1]
+            ids = self.search(cr, uid, [('name', operator, name)] + args,
+                              limit=limit, context=context)
+        else:
+            ids = self.search(cr, uid, args, limit=limit, context=context)
+        return self.name_get(cr, uid, ids, context=context)
+
+    def _complete_name(self, cr, uid, ids, field_name, arg, context=None):
+        """Function-field wrapper to get the complete name from name_get"""
+        res = self.name_get(cr, uid, ids, context=context)
+        return dict(res)
+
+    def _progress_rate(self, cr, uid, ids, field_name, arg, context=None):
+        """Rate of (sub)inventories done/total"""
+        rates = {}
+        for current_id in ids:
+            nb = self.search(
+                cr, uid, [('parent_id', 'child_of', current_id)],
+                context=context, count=True)
+            if not nb:
+                # No inventory, consider it's 0% done
+                rates[current_id] = 0
+                continue
+            nb_done = self.search(
+                cr, uid, [('parent_id', 'child_of', current_id),
+                          ('state', '=', 'done')],
+                context=context, count=True)
+            rates[current_id] = 100 * nb_done / nb
+        return rates
+
+    _columns = {
+        # name_get() only changes the default name of the record, not the
+        # content of the field "name" so we add another field for that
+        'complete_name': fields.function(
+            _complete_name, type="char",
+            string='Complete reference'),
+        'parent_id': fields.many2one(
+            'stock.inventory', 'Parent', ondelete='cascade', readonly=True,
+            states={'draft': [('readonly', False)]}),
+        'inventory_ids': fields.one2many(
+            'stock.inventory', 'parent_id', 'List of Sub-inventories',
+            readonly=True, states={'draft': [('readonly', False)]}),
+        'parent_left': fields.integer('Parent Left', select=1),
+        'parent_right': fields.integer('Parent Right', select=1),
+        'progress_rate': fields.function(
+            _progress_rate, string='Progress', type='float'),
+        }
+
+    _constraints = [
+        (orm.Model._check_recursion,
+         'Error: You can not create recursive inventories.',
+         ['parent_id']),
+    ]
+
+    def create(self, cr, uid, vals, context=None):
+        """Copy selected values from parent to child"""
+        if vals and vals.get('parent_id'):
+            existing_fields = self.fields_get_keys(cr, uid, context=context)
+            parent_values = self.read(cr, uid, [vals['parent_id']],
+                                      PARENT_VALUES, context=context)
+            vals = vals.copy()
+            vals.update({field: parent_values[0][field]
+                         for field in PARENT_VALUES
+                         if field in existing_fields})
+        return super(HierarchicalInventory, self).create(
+            cr, uid, vals, context=context)
+
+    def write(self, cr, uid, ids, vals, context=None):
+        """Copy selected values from parent to children"""
+        if context is None:
+            context = {}
+
+        values = super(HierarchicalInventory, self).write(
+            cr, uid, ids, vals, context=context)
+        if not vals or context.get('norecurse', False):
+            return values
+
+        # filter the fields we want to propagate
+        children_values = {
+            field: vals[field] for field in PARENT_VALUES if field in vals
+        }
+        if not children_values:
+            return values
+
+        if not isinstance(ids, list):
+            ids = [ids]
+        # The context disables recursion - children are already included
+        return self.write(
+            cr, uid, self.search(cr, uid, [('parent_id', 'child_of', ids)]),
+            children_values, context=dict(context, norecurse=True))
+
+    def action_cancel_inventory(self, cr, uid, ids, context=None):
+        """Cancel inventory only if all the parents are canceled"""
+        inventories = self.browse(cr, uid, ids, context=context)
+        for inventory in inventories:
+            while inventory.parent_id:
+                inventory = inventory.parent_id
+                if inventory.state != 'cancel':
+                    raise HierarchicalInventoryException(
+                        _('Warning'),
+                        _('One of the parent Inventories is not canceled.'))
+        return super(HierarchicalInventory,
+                     self).action_cancel_inventory(cr, uid, ids,
+                                                   context=context)
+
+    def action_confirm(self, cr, uid, ids, context=None):
+        """Confirm inventory only if all the children are confirmed"""
+        children_count = self.search(
+            cr, uid, [('parent_id', 'child_of', ids),
+                      ('state', 'not in', ['confirm', 'done'])],
+            context=context, count=True)
+        if children_count > 1:
+            raise HierarchicalInventoryException(
+                _('Warning'),
+                _('Some Sub-inventories are not confirmed.'))
+        return super(HierarchicalInventory, self).action_confirm(
+            cr, uid, ids, context=context)
+
+    def action_done(self, cr, uid, ids, context=None):
+        """Perform validation only if all the children states are 'done'."""
+        children_count = self.search(cr, uid, [('parent_id', 'child_of', ids),
+                                               ('state', '!=', 'done')],
+                                     context=context, count=True)
+        if children_count > 1:
+            raise HierarchicalInventoryException(
+                _('Warning'),
+                _('Some Sub-inventories are not validated.'))
+        return super(HierarchicalInventory, self).action_done(
+            cr, uid, ids, context=context)

=== added file 'stock_inventory_hierarchical/hierarchical_inventory_demo.xml'
--- stock_inventory_hierarchical/hierarchical_inventory_demo.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory_demo.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data noupdate="0">
+        <!-- Example Inventory with Sub-Inventories. -->
+        <record id="stock_inventory_parent0" model="stock.inventory">
+            <field name="name">Main Inventory</field>
+        </record>
+        <record id="child_1_id" model="stock.inventory">
+            <field name="name">Sub-Inventory 1</field>
+            <field name="parent_id" ref="stock_inventory_parent0" />
+        </record>
+        <record id="child_2_id" model="stock.inventory">
+            <field name="name">Sub-Inventory 2</field>
+            <field name="parent_id" ref="stock_inventory_parent0" />
+        </record>
+    </data>
+</openerp>

=== added file 'stock_inventory_hierarchical/hierarchical_inventory_view.xml'
--- stock_inventory_hierarchical/hierarchical_inventory_view.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory_view.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <!-- Add parent_id and number of Sub-inventories to form view -->
+        <record model="ir.ui.view" id="stock_inventory_hierarchical_tree_view">
+            <field name="name">hierarchical.inventory.tree</field>
+            <field name="model">stock.inventory</field>
+            <field name="inherit_id" ref="stock.view_inventory_tree" />
+            <field name="field_parent">inventory_ids</field>
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='name']" position="replace">
+                    <field name="complete_name" string="Reference"/>
+                </xpath>
+                <xpath expr="//field[@name='state']" position="after">
+                    <field name="progress_rate" widget="progressbar" />
+                </xpath>
+            </field>
+        </record>
+
+        <!-- Add the parent_id filter to search view  -->
+        <record model="ir.ui.view" id="view_inventory_subinventories_filter">
+            <field name="name">hierarchical.inventory.filter</field>
+            <field name="model">stock.inventory</field>
+            <field name="inherit_id" ref="stock.view_inventory_filter" />
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='name']" position="before">
+                    <filter icon="terp-check" name="main_inventories" string="Main inventories" domain="[('parent_id', '=', False)]" help="Only select inventories that have no parents." />
+                    <separator orientation="vertical"/>
+                </xpath>
+                <xpath expr="//field[@name='date']" position="after">
+                    <field name="parent_id" />
+                </xpath>
+            </field>
+        </record>
+        <!-- Show main inventories by default -->
+        <record id="stock.action_inventory_form" model="ir.actions.act_window">
+            <field name="context">{'full':'1', 'search_default_main_inventories':1}</field>
+        </record>
+
+        <record model="ir.ui.view" id="stock_inventory_hierarchical_form_view">
+            <field name="name">hierarchical.inventory.form</field>
+            <field name="model">stock.inventory</field>
+            <field name="inherit_id" ref="stock.view_inventory_form" />
+            <field name="arch" type="xml">
+                <xpath expr="/form//field[@name='name']" position="after">
+                    <field name="parent_id"/>
+                </xpath>
+                <xpath expr="/form//field[@name='date']" position="attributes">
+                    <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
+                </xpath>
+                <xpath
+                    expr="//page[@string='General Information']"
+                    position="after">
+                    <page string="Sub-inventories">
+                        <field name="inventory_ids" nolabel="1" context="{'default_parent_id': active_id}">
+                            <tree>
+                                <field name="name" />
+                                <field name="state" />
+                                <field name="progress_rate" widget="progressbar" />
+                            </tree>
+                        </field>
+                    </page>
+                </xpath>
+            </field>
+        </record>
+
+        <!-- Open the children of the current Inventory in a distinct list
+              to let users work in a normal window instead of a popup -->
+        <act_window id="action_view_sub_inventory"
+            name="View Sub-inventories"
+            res_model="stock.inventory"
+            src_model="stock.inventory"
+            view_mode="tree,form"
+            view_type="form"
+            domain="[('parent_id', 'child_of', active_id),('id', '!=', active_id)]"
+            context="{'full':1, 'search_default_main_inventories':0}"/>
+    </data>
+</openerp>

=== added directory 'stock_inventory_hierarchical/i18n'
=== added file 'stock_inventory_hierarchical/i18n/fr.po'
--- stock_inventory_hierarchical/i18n/fr.po	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/i18n/fr.po	2014-06-27 09:18:58 +0000
@@ -0,0 +1,112 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* stock_inventory_hierarchical
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.4\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2013-09-25 13:43+0000\n"
+"PO-Revision-Date: 2013-09-25 13:43+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: stock_inventory_hierarchical
+#: field:stock.inventory,complete_name:0
+msgid "Complete reference"
+msgstr "Réference complète"
+
+#. module: stock_inventory_hierarchical
+#: field:stock.inventory,progress_rate:0
+msgid "Done"
+msgstr "Terminé"
+
+#. module: stock_inventory_hierarchical
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:108
+#: constraint:stock.inventory:0
+#, python-format
+msgid "Error: You can not create recursive inventories."
+msgstr "Erreur : Vous ne pouvez pas créer d'inventaire récursifs."
+
+#. module: stock_inventory_hierarchical
+#: model:ir.model,name:stock_inventory_hierarchical.model_stock_inventory
+msgid "Gestion des stocks"
+msgstr "Gestion des stocks"
+
+#. module: stock_inventory_hierarchical
+#: field:stock.inventory,inventory_ids:0
+msgid "List of Sub-inventories"
+msgstr "Liste des sous-inventaires"
+
+#. module: stock_inventory_hierarchical
+#: view:stock.inventory:0
+msgid "Main inventories"
+msgstr "Inventaires principaux"
+
+#. module: stock_inventory_hierarchical
+#: view:stock.inventory:0
+msgid "Number of Sub-inventories"
+msgstr "Nombre de sous-inventaires"
+
+#. module: stock_inventory_hierarchical
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:180
+#, python-format
+msgid "One of the parent Inventories is not canceled."
+msgstr "Un des inventaires pères n'est pas annulé."
+
+#. module: stock_inventory_hierarchical
+#: constraint:stock.inventory:0
+msgid "Other Physical inventories are being conducted using the same Locations."
+msgstr "Certains emplacements sont déjà dans un autre inventaire."
+
+#. module: stock_inventory_hierarchical
+#: field:stock.inventory,parent_id:0
+msgid "Parent"
+msgstr "Parent"
+
+#. module: stock_inventory_hierarchical
+#: field:stock.inventory,parent_left:0
+msgid "Parent Left"
+msgstr "Parent gauche"
+
+#. module: stock_inventory_hierarchical
+#: field:stock.inventory,parent_right:0
+msgid "Parent Right"
+msgstr "Parent droit"
+
+#. module: stock_inventory_hierarchical
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:188
+#, python-format
+msgid "Some Sub-inventories are not confirmed."
+msgstr "Certains sous-inventaires ne sont pas confirmés."
+
+#. module: stock_inventory_hierarchical
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
+#, python-format
+msgid "Some Sub-inventories are not validated."
+msgstr "Certains sous-inventaires ne sont pas terminés."
+
+#. module: stock_inventory_hierarchical
+#: model:ir.actions.act_window,name:stock_inventory_hierarchical.action_view_sub_inventory
+#: view:stock.inventory:0
+msgid "View Sub-inventories"
+msgstr "Voir les sous-inventaires"
+
+#. module: stock_inventory_hierarchical
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:130
+#, python-format
+msgid "Sub-inventory: %s"
+msgstr "Sous-inventaire : %s"
+
+#. module: stock_inventory_hierarchical
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:180
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:188
+#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
+#, python-format
+msgid "Warning"
+msgstr "Attention"
+

=== added directory 'stock_inventory_hierarchical/images'
=== added file 'stock_inventory_hierarchical/images/inventory_form.png'
Binary files stock_inventory_hierarchical/images/inventory_form.png	1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical/images/inventory_form.png	2014-06-27 09:18:58 +0000 differ
=== added file 'stock_inventory_hierarchical/images/inventory_form_actions.png'
Binary files stock_inventory_hierarchical/images/inventory_form_actions.png	1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical/images/inventory_form_actions.png	2014-06-27 09:18:58 +0000 differ
=== added directory 'stock_inventory_hierarchical/static'
=== added directory 'stock_inventory_hierarchical/static/src'
=== added directory 'stock_inventory_hierarchical/static/src/img'
=== added file 'stock_inventory_hierarchical/static/src/img/icon.png'
Binary files stock_inventory_hierarchical/static/src/img/icon.png	1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical/static/src/img/icon.png	2014-06-27 09:18:58 +0000 differ
=== added directory 'stock_inventory_hierarchical/test'
=== added file 'stock_inventory_hierarchical/test/hierarchical_inventory_test.yml'
--- stock_inventory_hierarchical/test/hierarchical_inventory_test.yml	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/test/hierarchical_inventory_test.yml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,96 @@
+-
+  In this file, i check rules about hierarchical inventories.
+  Children date must be the same of parent date for each state,
+  the state of parent and children can change only if conditions are correct.
+-
+  Check if date of children are the same as the parent's.
+-
+  !python {model: stock.inventory}: |
+    parent_date = self.read(
+      cr, uid, [ref('stock_inventory_parent0')], ['date'])[0]['date']
+    child_1_date = self.read(
+      cr, uid, [ref('child_1_id')], ['date'])[0]['date']
+    assert child_1_date == parent_date, "Date are different: %s - %s" % (parent_date, child_1_date)
+
+    child_2_date = self.read(
+      cr, uid, [ref('child_2_id')], ['date'])[0]['date']
+    assert child_2_date == parent_date, "Date are different: %s - %s" % (parent_date, child_2_date)
+
+-
+  Check if children cannot be canceled if the parent was not canceled.
+  I'll try to cancel both children inventory while parent inventory having "draft" state.
+  After, i'll verify the state of each inventory.
+-
+  !python {model: stock.inventory}: |
+    from stock_inventory_hierarchical import HierarchicalInventoryException
+    try:
+      self.action_cancel_inventory(cr, uid, [ref('child_1_id')])
+    except HierarchicalInventoryException as e:
+      log("Good ! The Inventory could not be canceled: %s" % e)
+    try:
+      self.action_cancel_inventory(cr, uid, [ref('child_2_id')])
+    except HierarchicalInventoryException as e:
+      log("Good ! The Inventory could not be canceled: %s" % e)
+    child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
+    assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
+    child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
+    assert child_2_state == 'draft', "Child inventory 2 have '%s' state. It should be 'draft'" % child_2_state
+
+-
+  Check if children inventory have confirm state before confirm parent inventory.
+  To check this, i'll try to confirm parent inventory when children inventory having "draft" state,
+  and i'll check if state is still 'draft'.
+-
+  !python {model: stock.inventory}: |
+    from stock_inventory_hierarchical import HierarchicalInventoryException
+    try:
+      self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
+    except HierarchicalInventoryException as e:
+      log("Good, the inventory could not be confirmed: %s", e)
+    parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
+    assert parent_state == 'draft', "Parent inventory have '%s' state. It should be 'draft'" % parent_state
+
+-
+  In order, i'll confirm the children inventories, and the parent inventory after.
+-
+  !python {model: stock.inventory}: |
+    self.action_confirm(cr, uid, [ref('child_1_id')])
+    child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
+    assert child_1_state == 'confirm', "Child inventory 1 have '%s' state. It should be 'confirm'" % child_1_state
+
+    self.action_confirm(cr, uid, [ref('child_2_id')])
+    child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
+    assert child_2_state == 'confirm', "Child inventory 2 have '%s' state. It should be 'confirm'" % child_2_state
+
+    self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
+    parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
+    assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
+
+-
+  Check if children inventory have done state before validate parent inventory.
+  I'll try to validate parent inventory before children.
+-
+  !python {model: stock.inventory}: |
+    from stock_inventory_hierarchical import HierarchicalInventoryException
+    try:
+      self.action_done(cr, uid, [ref('stock_inventory_parent0')])
+    except HierarchicalInventoryException as e:
+      log("Good, the inventory could not be validated: %s", e)
+    parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
+    assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
+
+-
+  Now, i'll validate all children inventory before validate the parent.
+-
+  !python {model: stock.inventory}: |
+    self.action_done(cr, uid, [ref('child_1_id')])
+    child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
+    assert child_1_state == 'done', "Child inventory 1 have '%s' state. It should be 'done'" % child_1_state
+
+    self.action_done(cr, uid, [ref('child_2_id')])
+    child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
+    assert child_2_state == 'done', "Child inventory 2 have '%s' state. It should be 'done'" % child_2_state
+
+    self.action_done(cr, uid, [ref('stock_inventory_parent0')])
+    parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
+    assert parent_state == 'done', "Parent inventory have '%s' state. It should be 'done'" % parent_state

=== added directory 'stock_inventory_hierarchical_location'
=== added file 'stock_inventory_hierarchical_location/__init__.py'
--- stock_inventory_hierarchical_location/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/__init__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import inventory_hierarchical_location
+from . import wizard

=== added file 'stock_inventory_hierarchical_location/__openerp__.py'
--- stock_inventory_hierarchical_location/__openerp__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/__openerp__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name": "Exhaustive and hierarchical Stock Inventories",
+    "version": "1.1",
+    "depends": ["stock_inventory_hierarchical", "stock_inventory_location"],
+    "auto_install": True,
+    "author": u"Numérigraphe",
+    "category": "Hidden",
+    "description": """
+Make exhaustive Inventories aware of their Sub-Inventories.
+===========================================================
+
+This module allows an inventory to contain a general Location,
+and it's sub-inventories to contain some of it's sub-Locations.
+It will prevent you from setting the Inventories and sub-Inventories
+in inconsistent status.
+
+This module will be installed automatically if the modules
+"stock_inventory_location" and "stock_inventory_hierarchical" are both
+installed.
+You must keep this module installed to ensure proper functioning.
+
+    """,
+    "data": [
+        "inventory_hierarchical_location_view.xml",
+        "wizard/generate_inventory_view.xml",
+    ],
+    "test": ["tests/inventory_hierarchical_location_test.yml"],
+    "demo": ["inventory_hierarchical_location_demo.xml"],
+}

=== added directory 'stock_inventory_hierarchical_location/i18n'
=== added file 'stock_inventory_hierarchical_location/i18n/fr.po'
--- stock_inventory_hierarchical_location/i18n/fr.po	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/i18n/fr.po	2014-06-27 09:18:58 +0000
@@ -0,0 +1,127 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* stock_inventory_hierarchical_location
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.4\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2013-09-25 13:55+0000\n"
+"PO-Revision-Date: 2013-09-25 13:55+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: stock_inventory_hierarchical_location
+#: model:ir.actions.act_window,name:stock_inventory_hierarchical_location.action_view_stock_inventory_missing_location
+msgid "Confirm missing location"
+msgstr "Confirmer les emplacements manquants"
+
+#. module: stock_inventory_hierarchical_location
+#: view:stock.inventory.missing.location:0
+msgid "Confirm missing locations"
+msgstr "Confirmer les emplacements manquants"
+
+#. module: stock_inventory_hierarchical_location
+#: model:ir.model,name:stock_inventory_hierarchical_location.model_stock_inventory_uninventoried_locations
+msgid "Confirm the uninventoried Locations."
+msgstr "Confirmer les emplacements non inventoriés."
+
+#. module: stock_inventory_hierarchical_location
+#: view:stock.inventory.missing.location:0
+msgid "Do you want to continue ?"
+msgstr "Voulez-vous continuer ?"
+
+#. module: stock_inventory_hierarchical_location
+#: constraint:stock.inventory:0
+msgid "Error! You can not create recursive inventories."
+msgstr "Erreur! Vous ne pouvez pas créer un inventaire récursif."
+
+#. module: stock_inventory_hierarchical_location
+#: model:ir.model,name:stock_inventory_hierarchical_location.model_stock_inventory
+msgid "Gestion des stocks"
+msgstr "Gestion des stocks"
+
+#. module: stock_inventory_hierarchical_location
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:70
+#, python-format
+msgid "Location missing for inventory \"%s\"."
+msgstr "Emplacement manquants dans l'inventaire \"%s\"."
+
+#. module: stock_inventory_hierarchical_location
+#: view:stock.inventory:0
+msgid "Locations"
+msgstr "Emplacements"
+
+#. module: stock_inventory_hierarchical_location
+#: field:stock.inventory.missing.location,location_ids:0
+msgid "Missing location"
+msgstr "Emplacements manquants"
+
+#. module: stock_inventory_hierarchical_location
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
+#, python-format
+msgid "One of the parent inventories is not open."
+msgstr "Un des inventaire parent n'est pas ouvert."
+
+#. module: stock_inventory_hierarchical_location
+#: view:stock.inventory:0
+msgid "Open Inventory"
+msgstr "Ouvrir l'inventaire"
+
+#. module: stock_inventory_hierarchical_location
+#: constraint:stock.inventory:0
+msgid "Other Physical inventories are being conducted using the same Locations."
+msgstr "Erreur: certains emplacements sont déjà dans un autre inventaire."
+
+#. module: stock_inventory_hierarchical_location
+#: model:ir.model,name:stock_inventory_hierarchical_location.model_stock_inventory_missing_location
+msgid "Search on inventory tree for missing declared locations."
+msgstr "Recherche dans les inventaires les emplacements absents."
+
+#. module: stock_inventory_hierarchical_location
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:122
+#, python-format
+msgid "Some Sub-inventories are not confirmed."
+msgstr "Au moins un sous-inventaire n'est pas confirmé."
+
+#. module: stock_inventory_hierarchical_location
+#: view:stock.inventory.missing.location:0
+msgid "This is the list of missing locations."
+msgstr "Voici la liste des emplacements manquants."
+
+#. module: stock_inventory_hierarchical_location
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:58
+#, python-format
+msgid "This location is not declared on the parent inventory\n"
+"It cannot be added."
+msgstr "Cet emplacement n'est pas déclaré dans l'inventaire parent\n"
+"Vous ne pouvez pas l'ajouter."
+
+#. module: stock_inventory_hierarchical_location
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:70
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:122
+#, python-format
+msgid "Warning !"
+msgstr "Attention !"
+
+#. module: stock_inventory_hierarchical_location
+#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:57
+#, python-format
+msgid "Warning: Wrong location"
+msgstr "Attention: mauvais emplacement"
+
+#. module: stock_inventory_hierarchical_location
+#: view:stock.inventory.missing.location:0
+msgid "_Cancel"
+msgstr "_Annuler"
+
+#. module: stock_inventory_hierarchical_location
+#: view:stock.inventory.missing.location:0
+msgid "_Confirm missing locations"
+msgstr "_Confirmer les emplacements manquants"
+

=== added file 'stock_inventory_hierarchical_location/inventory_hierarchical_location.py'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import orm
+from openerp.tools.translate import _
+
+from stock_inventory_hierarchical import HierarchicalInventoryException
+
+# Add the date to the list of fields we must propagate to children inventories
+from stock_inventory_hierarchical import PARENT_VALUES
+PARENT_VALUES.append('exhaustive')
+
+
+class HierarchicalExhInventory(orm.Model):
+    """Add hierarchical structure features to exhaustive Inventories"""
+    _inherit = 'stock.inventory'
+
+    def action_open(self, cr, uid, ids, context=None):
+        """Open only if all the parents are Open."""
+        for inventory in self.browse(cr, uid, ids, context=context):
+            while inventory.parent_id:
+                inventory = inventory.parent_id
+                if inventory.state != 'open':
+                    raise HierarchicalInventoryException(
+                        _('Warning'),
+                        _('One of the parent inventories is not open.'))
+        return super(HierarchicalExhInventory, self).action_open(
+            cr, uid, ids, context=context)
+
+    def get_missing_locations(self, cr, uid, ids, context=None):
+        """Extend the list of inventories with their children"""
+        ids = self.search(
+            cr, uid, [('parent_id', 'child_of', ids)], context=context)
+        missing_ids = set(
+            super(HierarchicalExhInventory, self).get_missing_locations(
+                cr, uid, ids, context=context))
+        # Find the locations already included in sub-inventories
+        inventories = self.browse(cr, uid, ids, context=context)
+        subinv_location_ids = [sub.location_id.id
+                               for i in inventories
+                                   for sub in i.inventory_ids]
+        # Extend to the children locations
+        subinv_location_ids = set(self.pool['stock.location'].search(
+            cr, uid, [
+                ('location_id', 'child_of', subinv_location_ids),
+                ('usage', '=', 'internal')], context=context))
+        return list(missing_ids - subinv_location_ids)
+
+    # TODO v8: probably only keep the state "done"
+    def confirm_missing_locations(self, cr, uid, ids, context=None):
+        """Do something only if children state are confirm or done."""
+        children_count = self.search(
+            cr, uid, [('parent_id', 'child_of', ids),
+                      ('id', 'not in', ids),
+                      ('state', 'not in', ['confirm', 'done'])],
+            context=context, count=True)
+        if children_count > 0:
+            raise HierarchicalInventoryException(
+                _('Warning'),
+                _('Some Sub-inventories are not confirmed.'))
+        return super(HierarchicalExhInventory,
+                     self).confirm_missing_locations(
+            cr, uid, ids, context=context)
+
+    def onchange_location_id(self, cr, uid, ids, location_id, context=None):
+        """Check if location is a child of parent inventory location"""
+        loc_obj = self.pool['stock.location']
+        for inventory in self.browse(cr, uid, ids, context=context):
+            if inventory.parent_id:
+                allowed_location_ids = loc_obj.search(
+                    cr, uid, [('location_id', 'child_of',
+                               inventory.parent_id.location_id.id)],
+                    context=context)
+                if location_id not in allowed_location_ids:
+                    return {
+                        'location_id': False,
+                        'warning': {
+                            'title': _('Warning: Wrong location'),
+                            'message': _("This location is not declared on "
+                                         "the parent inventory\n"
+                                         "It cannot be added.")}
+                    }
+        return {}

=== added file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data noupdate="0">
+
+        <!-- Record inventories we can use in the tests. -->
+        <!-- We need them in the demo data because test data is rolled back
+            whenever an exception is raised. -->
+
+        <!-- Record a hierarchical exhaustive inventory -->
+        <record id="parent_inventory" model="stock.inventory">
+            <field name="name">Hierarchical exhaustive inventory</field>
+            <field name="state">draft</field>
+            <field name="date">2020-04-15 00:00:00</field>
+            <field name="exhaustive">True</field>
+            <field name="location_id" model="stock.location" ref="stock.stock_location_stock"/>
+        </record>
+        <record id="child_1_id" model="stock.inventory">
+            <field name="name">Team A</field>
+            <field name="parent_id" ref="parent_inventory"/>
+            <field name="location_id" model="stock.location" ref="stock.stock_location_14"/>
+        </record>
+        <record id="child_2_id" model="stock.inventory">
+            <field name="name">Team B</field>
+            <field name="parent_id" ref="parent_inventory"/>
+            <field name="location_id" model="stock.location" ref="stock.stock_location_components"/>
+        </record>
+    </data>
+</openerp>

=== added file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record model="ir.ui.view" id="stock_inventory_hierarchical_location_form_view">
+            <field name="name">hierarchical.inventory.location.form</field>
+            <field name="model">stock.inventory</field>
+            <field name="inherit_id" ref="stock.view_inventory_form" />
+            <field name="arch" type="xml">
+
+                 <xpath expr="/form//field[@name='exhaustive']" position="attributes">
+                    <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
+                </xpath>
+
+                <xpath expr="/form//field[@name='location_id']" position="attributes">
+                    <attribute name="on_change">onchange_location_id(location_id)</attribute>
+                </xpath>
+
+            </field>
+        </record>
+
+        <record model="ir.ui.view" id="stock_ihl_exhautive_form_view">
+            <field name="name">hierarchical.inventory.location.exhautive.form</field>
+            <field name="model">stock.inventory</field>
+            <field name="inherit_id" ref="stock_inventory_hierarchical.stock_inventory_hierarchical_form_view" />
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='inventory_ids']" position="attributes">
+                    <attribute name="context">{'default_parent_id': active_id, 'default_exhaustive': exhaustive}</attribute>
+                </xpath>
+            </field>
+        </record>
+
+        <!-- Show hierarchical exhaustive inventories by default -->
+        <record id="stock.action_inventory_form" model="ir.actions.act_window">
+            <field name="context">{'full':'1', 'search_default_exhaustive':1, 'search_default_main_inventories':1}</field>
+        </record>
+    </data>
+</openerp>

=== added directory 'stock_inventory_hierarchical_location/static'
=== added directory 'stock_inventory_hierarchical_location/static/src'
=== added directory 'stock_inventory_hierarchical_location/static/src/img'
=== added file 'stock_inventory_hierarchical_location/static/src/img/icon.png'
Binary files stock_inventory_hierarchical_location/static/src/img/icon.png	1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical_location/static/src/img/icon.png	2014-06-27 09:18:58 +0000 differ
=== added directory 'stock_inventory_hierarchical_location/tests'
=== added file 'stock_inventory_hierarchical_location/tests/__init__.py'
--- stock_inventory_hierarchical_location/tests/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/tests/__init__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+#
+#
+#    Authors: Laetitia Gangloff
+#    Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
+#    All Rights Reserved
+#
+#    WARNING: This program as such is intended to be used by professional
+#    programmers who take the whole responsibility of assessing all potential
+#    consequences resulting from its eventual inadequacies and bugs.
+#    End users who are looking for a ready-to-use solution with commercial
+#    guarantees and support are strongly advised to contact a Free Software
+#    Service Company.
+#
+#    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 fill_inventory_test
+
+fast_suite = [
+]
+
+checks = [
+    fill_inventory_test,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'stock_inventory_hierarchical_location/tests/fill_inventory_test.py'
--- stock_inventory_hierarchical_location/tests/fill_inventory_test.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/tests/fill_inventory_test.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+#
+#
+#    Authors: Laetitia Gangloff
+#    Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
+#    All Rights Reserved
+#
+#    WARNING: This program as such is intended to be used by professional
+#    programmers who take the whole responsibility of assessing all potential
+#    consequences resulting from its eventual inadequacies and bugs.
+#    End users who are looking for a ready-to-use solution with commercial
+#    guarantees and support are strongly advised to contact a Free Software
+#    Service Company.
+#
+#    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 openerp.tests.common as common
+
+DB = common.DB
+ADMIN_USER_ID = common.ADMIN_USER_ID
+
+
+class fill_inventory_test(common.TransactionCase):
+
+    def setUp(self):
+        super(fill_inventory_test, self).setUp()
+
+    def test_missing_location(self):
+        """
+        Test that when confirm a parent inventory, the child location are not in the confirmation result
+        """
+        parent_inventory_id = self.ref('stock_inventory_hierarchical_location.parent_inventory')
+        self.registry('stock.inventory').action_open(self.cr, self.uid, [parent_inventory_id])
+        # confirm shelf 1 inventory
+        inventory_id = self.ref('stock_inventory_hierarchical_location.child_2_id')
+        self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
+        missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
+        self.assertEqual(len(missing_location), 1, "1 missing location should be find, because the inventory is empty")
+        wizard_id = self.registry('stock.inventory.uninventoried.locations').create(self.cr, self.uid, {}, context={'active_ids': [inventory_id]})
+        self.registry('stock.inventory.uninventoried.locations').confirm_uninventoried_locations(self.cr, self.uid, wizard_id, context={'active_ids': [inventory_id]})
+        missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
+        self.assertEqual(len(missing_location), 0, "No missing location should be find, because the inventory is confirmed")
+        # confirm shelf 2 inventory
+        inventory_id = self.ref('stock_inventory_hierarchical_location.child_1_id')
+        self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
+        missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
+        self.assertEqual(len(missing_location), 1, "1 missing location should be fine, because the inventory is empty")
+        self.registry('stock.inventory.line').create(self.cr, self.uid, {'product_id': self.ref('product.product_product_7'),
+                                                                         'product_uom': self.ref('product.product_uom_unit'),
+                                                                         'company_id': self.ref('base.main_company'),
+                                                                         'inventory_id': inventory_id,
+                                                                         'product_qty': 18.0,
+                                                                         'location_id': self.ref('stock.stock_location_14')})
+        missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
+        self.assertEqual(len(missing_location), 0, "No missing location should be find, because the inventory is filled")
+        wizard_id = self.registry('stock.inventory.uninventoried.locations').create(self.cr, self.uid, {}, context={'active_ids': [inventory_id]})
+        wizard = self.registry('stock.inventory.uninventoried.locations').browse(self.cr, self.uid, wizard_id, context={'active_ids': [inventory_id]})
+        self.assertEqual(len(wizard.location_ids), 0, "The wizard should not contain any lines but contains %s." % wizard.location_ids)
+        self.registry('stock.inventory.uninventoried.locations').confirm_uninventoried_locations(self.cr, self.uid, wizard_id, context={'active_ids': [inventory_id]})
+        # confirm parent inventory
+        missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [parent_inventory_id])
+        self.assertEqual(len(missing_location), 1, "Only 1 missing location should be find, because there is some location in child inventory")
+
+    def test_fill_inventory(self):
+        """
+        Test that when fill a parent inventory, the child location are not in the result
+        """
+        parent_inventory_id = self.ref('stock_inventory_hierarchical_location.parent_inventory')
+        self.registry('stock.inventory').action_open(self.cr, self.uid, [parent_inventory_id])
+        # confirm shelf 1 inventory
+        inventory_id = self.ref('stock_inventory_hierarchical_location.child_2_id')
+        self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
+        wizard_id = self.registry('stock.fill.inventory').create(self.cr, self.uid, {'location_id': self.ref('stock.stock_location_components'),
+                                                                                     'recursive': True,
+                                                                                     'exhaustive': True,
+                                                                                     'set_stock_zero': True}, context={'active_ids': [inventory_id]})
+        self.registry('stock.fill.inventory').fill_inventory(self.cr, self.uid, [wizard_id], context={'active_ids': [inventory_id]})
+        inventory_line_ids = self.registry('stock.inventory.line').search(self.cr, self.uid, [('inventory_id', '=', inventory_id)])
+        self.assertEqual(len(inventory_line_ids), 12, "12 inventory line is fount after filling inventory")
+        # confirm shelf 2 inventory
+        inventory_id = self.ref('stock_inventory_hierarchical_location.child_1_id')
+        self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
+        wizard_id = self.registry('stock.fill.inventory').create(self.cr, self.uid, {'location_id': self.ref('stock.stock_location_14'),
+                                                                                     'recursive': True,
+                                                                                     'exhaustive': True,
+                                                                                     'set_stock_zero': True}, context={'active_ids': [inventory_id]})
+        self.registry('stock.fill.inventory').fill_inventory(self.cr, self.uid, [wizard_id], context={'active_ids': [inventory_id]})
+        inventory_line_ids = self.registry('stock.inventory.line').search(self.cr, self.uid, [('inventory_id', '=', inventory_id)])
+        self.assertEqual(len(inventory_line_ids), 4, "1 inventory line is fount after filling inventory")
+        # confirm parent inventory
+        wizard_id = self.registry('stock.fill.inventory').create(self.cr, self.uid, {'location_id': self.ref('stock.stock_location_stock'),
+                                                                                     'recursive': True,
+                                                                                     'exhaustive': True,
+                                                                                     'set_stock_zero': True}, context={'active_ids': [parent_inventory_id]})
+        try:
+            self.registry('stock.fill.inventory').fill_inventory(self.cr, self.uid, [wizard_id], context={'active_ids': [parent_inventory_id]})
+        except Exception, e:
+            self.assertEqual(e.value, 'No product in this location. Please select a location in the product form.', "The message should be ''No product in this location. Please select a location in the product form.''")
+            exception_happened = True
+            pass
+        self.assertTrue(exception_happened)
+        inventory_line_ids = self.registry('stock.inventory.line').search(self.cr, self.uid, [('inventory_id', '=', parent_inventory_id)])
+        self.assertEqual(len(inventory_line_ids), 0, "No inventory line is fount after filling inventory")
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'stock_inventory_hierarchical_location/tests/inventory_hierarchical_location_test.yml'
--- stock_inventory_hierarchical_location/tests/inventory_hierarchical_location_test.yml	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/tests/inventory_hierarchical_location_test.yml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,56 @@
+-
+  Check that the exhaustive field of parent inventory has been propagated to children.
+-
+  !python {model: stock.inventory}: |
+    exhaustive = self.read(cr, uid, [ref("child_1_id")], ['exhaustive'])[0]['exhaustive']
+    assert exhaustive, "Exhaustive field not propagated to child inventory"
+
+-
+  Check that I can't open child inventory while parent inventory is open.
+-
+  !python {model: stock.inventory}: |
+    from stock_inventory_hierarchical import HierarchicalInventoryException
+    parent_state = self.read(cr, uid, [ref("parent_inventory")], ['state'])[0]['state']
+    assert parent_state == 'draft', "Parent inventory in state '%s'. It should be 'draft'" % parent_state
+    try:
+      self.action_open(cr, uid, [ref("child_1_id")])
+    except HierarchicalInventoryException as e:
+      log("Good ! The Inventory could not be opened: %s" % e)
+    child_1_state = self.read(cr, uid, [ref("child_1_id")], ['state'])[0]['state']
+    assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
+
+-
+  I will check that the function get_missing_locations return some locations.
+-
+  !python {model: stock.inventory}: |
+    missing_loc_ids = self.get_missing_locations(cr, uid, [ref('parent_inventory')], context=context)
+    assert len(missing_loc_ids)==3, "get_missing_locations did not return any ID."
+
+-
+  I will fill the inventory and check that the function get_missing_locations return no locations.
+  Adding 17” LCD Monitor.
+-
+  !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
+    product_id: product.product_product_7
+    product_uom: product.product_uom_unit
+    company_id: base.main_company
+    inventory_id: child_1_id
+    product_qty: 18.0
+    location_id: stock.stock_location_14
+
+-
+  Adding USB Keyboard, QWERTY.
+-
+  !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
+    product_id: product.product_product_8
+    product_uom: product.product_uom_unit
+    company_id: base.main_company
+    inventory_id: child_2_id
+    product_qty: 5.0
+    location_id: stock.stock_location_components
+
+-
+  !python {model: stock.inventory}: |
+    
+    missing_loc_ids = self.get_missing_locations(cr, uid, [ref('parent_inventory')], context=context)
+    assert set(missing_loc_ids)==set([ref('stock.stock_location_stock')]), "get_missing_locations should return only %s but returned %s" % ([ref('stock.stock_location_stock')], missing_loc_ids)

=== added directory 'stock_inventory_hierarchical_location/wizard'
=== added file 'stock_inventory_hierarchical_location/wizard/__init__.py'
--- stock_inventory_hierarchical_location/wizard/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/__init__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import stock_fill_location_inventory
+from . import generate_inventory

=== added file 'stock_inventory_hierarchical_location/wizard/generate_inventory.py'
--- stock_inventory_hierarchical_location/wizard/generate_inventory.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/generate_inventory.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+#
+#
+#    Authors: Laetitia Gangloff
+#    Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
+#    All Rights Reserved
+#
+#    WARNING: This program as such is intended to be used by professional
+#    programmers who take the whole responsibility of assessing all potential
+#    consequences resulting from its eventual inadequacies and bugs.
+#    End users who are looking for a ready-to-use solution with commercial
+#    guarantees and support are strongly advised to contact a Free Software
+#    Service Company.
+#
+#    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 fields, orm
+from openerp.tools.translate import _
+
+
+class GenerateInventoryWizard(orm.TransientModel):
+    """ This wizard generate an inventory and all related sub-inventories for the specified location and level
+    Example: location = Stock / level = 1 => 1 inventory on Stock (similar to create)
+             location = Stock / level = 2 => 1 inventory on Stock, 1 sub-inventory on Shelf1, 1 sub-inventory on Shelf2
+    """
+
+    _name = "stock.generate.inventory"
+    _description = "Generate Inventory"
+
+    _columns = {
+        'prefix_inv_name': fields.char('Inventory prefix', help="Optional prefix for all created inventory"),
+        'location_id': fields.many2one('stock.location', 'Location', required=True),
+        'level': fields.integer("Level", help="number of level between inventory on location_id and sub-inventory"),
+        'only_view': fields.boolean('Only view', help="If set, only inventory on view location can be created"),
+    }
+
+    def _default_location(self, cr, uid, ids, context=None):
+        """Default stock location
+
+        @return: id of the stock location of the first warehouse of the
+        default company"""
+        location_id = False
+        company_id = self.pool['res.company']._company_default_get(
+            cr, uid, 'stock.warehouse', context=context)
+        warehouse_id = self.pool['stock.warehouse'].search(
+            cr, uid, [('company_id', '=', company_id)], limit=1)
+        if warehouse_id:
+            location_id = self.pool['stock.warehouse'].read(
+                cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
+        return location_id
+
+    _defaults = {
+        'location_id': _default_location,
+        'level': 1,
+        'only_view': True,
+    }
+
+    _sql_constraints = [
+        ('level', 'CHECK (level>0)', 'Level must be positive!'),
+    ]
+
+    def _create_subinventory(self, cr, uid, inventory_ids, prefix_inv_name, only_view, context):
+        new_inventory_ids = []
+        for inventory_id in inventory_ids:
+            location_id = self.pool['stock.inventory'].read(cr, uid, inventory_id, ['location_id'], context=context)['location_id'][0]
+            domain = [('location_id', '=', location_id)]
+            if only_view:
+                domain.append(('usage', '=', 'view'))
+            location_ids = self.pool['stock.location'].search(cr, uid, domain, context=context)
+            for location_id in location_ids:
+                location_name = self.pool['stock.location'].read(cr, uid, location_id, ['name'], context=context)['name']
+                new_inventory_ids.append(self.pool['stock.inventory'].create(cr, uid, {'name': prefix_inv_name + location_name,
+                                                                                       'exhaustive': True,
+                                                                                       'location_id': location_id,
+                                                                                       'parent_id': inventory_id}, context=context))
+        return new_inventory_ids
+
+    def generate_inventory(self, cr, uid, ids, context=None):
+        """ Generate inventory and sub-inventories for specified location and level
+
+        @param self: The object pointer.
+        @param cr: A database cursor
+        @param uid: ID of the user currently logged in
+        @param ids: the ID or list of IDs if we want more than one
+        @param context: A standard dictionary
+        @return:
+        """
+        if context is None:
+            context = {}
+
+        if ids and len(ids):
+            ids = ids[0]
+        else:
+            return {'type': 'ir.actions.act_window_close'}
+        generate_inventory = self.browse(cr, uid, ids, context=context)
+        # create first level inventory
+        prefix_inv_name = generate_inventory.prefix_inv_name or ''
+        location_id = generate_inventory.location_id.id
+        only_view = generate_inventory.only_view
+        parent_inventory_id = self.pool['stock.inventory'].create(cr, uid, {'name': prefix_inv_name + generate_inventory.location_id.name,
+                                                                            'exhaustive': True,
+                                                                            'location_id': location_id}, context=context)
+
+        inventory_ids = [parent_inventory_id]
+        for i in range(1, generate_inventory.level):
+            inventory_ids = self._create_subinventory(cr, uid, inventory_ids, prefix_inv_name, only_view, context)
+
+        mod_obj = self.pool['ir.model.data']
+        result = mod_obj.get_object_reference(cr, uid, 'stock', 'view_inventory_form')
+        view_id = result and result[1] or False
+        return {'name': _('Inventory generated'),
+                'view_mode': 'form',
+                'view_type': 'form',
+                'res_model': 'stock.inventory',
+                'type': 'ir.actions.act_window',
+                'view_id': view_id,
+                'res_id': int(parent_inventory_id),
+                }
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'stock_inventory_hierarchical_location/wizard/generate_inventory_view.xml'
--- stock_inventory_hierarchical_location/wizard/generate_inventory_view.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/generate_inventory_view.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+           <record id="view_stock_generate_inventory" model="ir.ui.view">
+            <field name="name">Generate Inventory</field>
+            <field name="model">stock.generate.inventory</field>
+            <field name="arch" type="xml">
+              <form string="Generate Inventory" version="7.0">
+                  <separator string="Generate inventory"/>
+                  <group>
+                      <field name="prefix_inv_name"/>
+                      <field name="location_id"/>
+                      <field name="only_view"/>
+                      <field name="level"/>
+                  </group>
+                  <footer>
+                      <button name="generate_inventory" string="Generate Inventory" type="object" class="oe_highlight"/>
+                      or
+                      <button string="Cancel" class="oe_link" special="cancel" />
+                  </footer>
+              </form>
+            </field>
+        </record>
+
+        <record id="action_view_stock_generate_inventory" model="ir.actions.act_window">
+            <field name="name">Generate Inventory</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">stock.generate.inventory</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">form</field>
+            <field name="view_id" ref="view_stock_generate_inventory"/>
+            <field name="target">new</field>
+        </record>
+
+    <menuitem action="action_view_stock_generate_inventory"
+              id="menu_action_stock_generate_inventory_form"
+              parent="stock.menu_stock_inventory_control"
+              sequence="20"
+              groups="stock.group_locations"/>
+
+    </data>
+</openerp>

=== added file 'stock_inventory_hierarchical_location/wizard/stock_fill_location_inventory.py'
--- stock_inventory_hierarchical_location/wizard/stock_fill_location_inventory.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/stock_fill_location_inventory.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+#
+#
+#    Authors: Laetitia Gangloff
+#    Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
+#    All Rights Reserved
+#
+#    WARNING: This program as such is intended to be used by professional
+#    programmers who take the whole responsibility of assessing all potential
+#    consequences resulting from its eventual inadequacies and bugs.
+#    End users who are looking for a ready-to-use solution with commercial
+#    guarantees and support are strongly advised to contact a Free Software
+#    Service Company.
+#
+#    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, osv
+from openerp.tools.translate import _
+
+
+class FillInventoryWizard(orm.TransientModel):
+    """If inventory as sub inventories, do not fill with sub inventories location"""
+    _inherit = 'stock.fill.inventory'
+
+    def fill_inventory(self, cr, uid, ids, context=None):
+        """ To Import stock inventory according to products available in the location and not already in a sub inventory
+
+        We split fill_inventory on many fill_inventory (one for each location)
+        @param self: The object pointer.
+        @param cr: A database cursor
+        @param uid: ID of the user currently logged in
+        @param ids: the ID or list of IDs if we want more than one
+        @param context: A standard dictionary
+        @return:
+        """
+        if context is None:
+            context = {}
+
+        if ids and len(ids):
+            ids = ids[0]
+        else:
+            return {'type': 'ir.actions.act_window_close'}
+        fill_inventory = self.browse(cr, uid, ids, context=context)
+        if fill_inventory.recursive and fill_inventory.exhaustive:
+            exclude_location_ids = []
+            for i in self.pool['stock.inventory'].browse(cr, uid, context['active_ids']):
+                for sub_inventory in i.inventory_ids:
+                    # exclude these location
+                    exclude_location_ids.append(sub_inventory.location_id.id)
+            domain = [('location_id', 'child_of', [fill_inventory.location_id.id])]
+            if exclude_location_ids:
+                domain.append('!')
+                domain.append(('location_id', 'child_of', exclude_location_ids))
+            location_ids = self.pool['stock.location'].search(cr, uid, domain,
+                                                              order="id",
+                                                              context=context)
+            all_in_exception = 0
+            for location_id in location_ids:
+                try:
+                    super(FillInventoryWizard, self).fill_inventory(cr, uid,
+                                                                    [self.copy(cr, uid, ids, {'location_id': location_id,
+                                                                                              'recursive': False, }, context=context)],
+                                                                    context=context)
+                except osv.except_osv, e:
+                    if e.value == _('No product in this location. Please select a location in the product form.'):
+                        all_in_exception = all_in_exception + 1
+                        pass
+                    else:
+                        raise e
+            if all_in_exception == len(location_ids):
+                raise orm.except_orm(_('Warning!'), _('No product in this location. Please select a location in the product form.'))
+            return {'type': 'ir.actions.act_window_close'}
+        else:
+            return super(FillInventoryWizard, self).fill_inventory(cr, uid, [ids], context=context)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added directory 'stock_inventory_location'
=== added file 'stock_inventory_location/__init__.py'
--- stock_inventory_location/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/__init__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import stock_inventory_location
+import wizard
+# Bring the main exception into the package's scope for easier reuse
+from .exceptions import ExhaustiveInventoryException

=== added file 'stock_inventory_location/__openerp__.py'
--- stock_inventory_location/__openerp__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/__openerp__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name": "Exhaustive Stock Inventories",
+    "version": "1.1",
+    "depends": ["stock"],
+    "author": u"Numérigraphe",
+    "category": "Warehouse Management",
+    "description": """
+Let users make exhaustive Inventories
+=====================================
+
+Standard Physical Inventories in OpenERP only contain a generic list of
+products by locations, which is well suited to partial Inventories and simple
+warehouses. When the a standard Inventory is confirmed, only the products in
+the inventory are checked. If a Product is present in the computed stock and
+not in the recorded Inventory, OpenERP will consider that it remains unchanged.
+
+But for exhaustive inventories in complex warehouses, it is not practical:
+ - you want to avoid Stock Moves to/from these Locations while counting goods
+ - you must make sure all the locations you want have been counted
+ - you must make sure no other location has been counted by mistake
+ - you want the computed stock to perfectly match the inventory when you
+   confirm it.
+
+This module lets choose whether an Physical Inventory is exhaustive or
+standard.
+For an exhaustive Inventory:
+ - in the state "Draft" you define the Location where goods must be counted.
+ - the new Inventory status "Open" lets you indicate that the list of Locations
+   is final and you are now counting the goods.
+   In that status, no Stock Moves can be recorded in/out of the Inventory's
+   Locations.
+ - if the Location or some of it's children have not been entered in the
+   Inventory Lines, OpenERP warns you when you confirm the Inventory.
+ - only the Inventory's Location or its children can be entered in the
+   Inventory Lines.
+ - every good that is not in the Inventory Lines is considered lost, and gets
+   moved out of the stock when you confirm the Inventory.
+""",
+    "data": [
+        "wizard/stock_confirm_uninventoried_location.xml",
+        "stock_inventory_location_view.xml",
+        "wizard/stock_fill_location_inventory_view.xml",
+    ],
+    "test": [
+        "tests/inventory_standard_test.yml",
+        "tests/inventory_exhaustive_test.yml",
+        "tests/inventory_future_test.yml",
+    ],
+    "images": [
+        "images/inventory_form.png",
+        "inventory_empty_locations.png",
+        "images/move_error.png",
+        "images/location_locked.png",
+        "images/future_inventory.png",
+    ],
+    "demo": ["stock_inventory_location_demo.xml"]
+}

=== added file 'stock_inventory_location/exceptions.py'
--- stock_inventory_location/exceptions.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/exceptions.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import orm
+
+
+class ExhaustiveInventoryException(orm.except_orm):
+    """The operation is not possible for an exhaustive inventory"""
+    pass

=== added directory 'stock_inventory_location/i18n'
=== added file 'stock_inventory_location/i18n/fr.po'
--- stock_inventory_location/i18n/fr.po	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/i18n/fr.po	2014-06-27 09:18:58 +0000
@@ -0,0 +1,241 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* stock_inventory_location
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.4\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2013-09-25 13:58+0000\n"
+"PO-Revision-Date: 2013-09-25 13:58+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:232
+#: constraint:stock.move:0
+#, python-format
+msgid "A Physical Inventory is being conducted at this location"
+msgstr "Un inventaire est en cours à cet emplacement"
+
+#. module: stock_inventory_location
+#: view:stock.inventory.uninventoried.locations:0
+msgid "Cancel"
+msgstr "Annuler"
+
+#. module: stock_inventory_location
+#: view:stock.inventory:0
+msgid "Cancel Inventory"
+msgstr "Annuler l'inventaire"
+
+#. module: stock_inventory_location
+#: help:stock.inventory,exhaustive:0
+msgid "Check the box if you are conducting an exhaustive Inventory.\n"
+"Leave the box unchecked if you are conducting a standard Inventory (partial inventory for example).\n"
+"For an exhaustive Inventory:\n"
+" - the status \"Draft\" lets you define the list of Locations where goods must be counted\n"
+" - the status \"Open\" indicates that the list of Locations is definitive and you are now counting the goods. In that status, no Stock Moves can be recorded in/out of the Inventory's Locations\n"
+" - only the Inventory's Locations can be entered in the Inventory Lines\n"
+" - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory\n"
+" - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory"
+msgstr "Cochez la case si vous effectuez un inventaire exhaustif.\n"
+"Laissez la case non-dochée si vous effectuez un inventaire standard (par exmple un inventaire partiel).\n"
+"Pour les inventaires exhaustifs :\n"
+" - le statut \"Brouillon\" permet d'indiquer la liste des emplacements dont la marchandise doit être comptée\n"
+" - le statut \"Ouvert\" indiqueque la liste des emplacements est définitive, et que le comptage est en cours. Dans ce statut, aucun mouvement de stock ne peut être enregistré depuis/vers les emplacements de l'inventaire\n"
+" - seuls les emplacements de l'inventaire peuvent être saisis dans les lignes d'inventaire\n"
+" - si certains emplacements sont absent des lignes d'inventaire, OpenERP vous en avertit lors de la confirmation de l'inventaire\n"
+" - toutes les marchandises qui ne sont pas dans les lignes d'inventaire sont considérées comme perdues, et sont supprimées lors de la confirmation de l'inventaire"
+
+#. module: stock_inventory_location
+#: view:stock.inventory:0
+msgid "Confirm Inventory"
+msgstr "Confirmer l'inventaire"
+
+#. module: stock_inventory_location
+#: model:ir.model,name:stock_inventory_location.model_stock_inventory_uninventoried_locations
+msgid "Confirm the uninventoried Locations."
+msgstr "Confirmer les emplacements non inventoriés."
+
+#. module: stock_inventory_location
+#: view:stock.inventory.uninventoried.locations:0
+msgid "Confirm empty locations"
+msgstr "Confirmez les emplacements vides"
+
+#. module: stock_inventory_location
+#: model:ir.model,name:stock_inventory_location.model_stock_location
+msgid "Emplacement"
+msgstr "Emplacement"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:54
+#, python-format
+msgid "Error"
+msgstr "Erreur"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:75
+#, python-format
+msgid "Error: Empty location"
+msgstr "Erreur : Emplacement vide"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:231
+#: code:addons/stock_inventory_location/stock_inventory_location.py:282
+#, python-format
+msgid "Error: Location locked down"
+msgstr "Erreur: emplacement en inventaire"
+
+#. module: stock_inventory_location
+#: constraint:stock.inventory:0
+msgid "Error: You can not create recursive inventories."
+msgstr "Erreur: Vous ne pouvez pas créer un inventaire récursif."
+
+#. module: stock_inventory_location
+#: constraint:stock.inventory.line:0
+msgid "Error: duplicates lines"
+msgstr "Erreur: lignes en double"
+
+#. module: stock_inventory_location
+#: view:stock.inventory:0
+#: field:stock.inventory,exhaustive:0
+msgid "Exhaustive"
+msgstr "Exhaustif"
+
+#. module: stock_inventory_location
+#: model:ir.model,name:stock_inventory_location.model_stock_inventory
+msgid "Gestion des stocks"
+msgstr "Gestion des stocks"
+
+#. module: stock_inventory_location
+#: view:stock.inventory.uninventoried.locations:0
+msgid "If you confirm the Inventory, these Locations will be considered empty and their content will be purged."
+msgstr "Si vous confirmez l'inventaire, ces emplacements seront considérés comme vides et leur contenu sera supprimé."
+
+#. module: stock_inventory_location
+#: model:ir.model,name:stock_inventory_location.model_stock_fill_inventory
+msgid "Importer un inventaire"
+msgstr "Importer un inventaire"
+
+#. module: stock_inventory_location
+#: view:stock.inventory.uninventoried.locations:0
+msgid "It could either mean that the Locations are empty, or that the Inventory is not yet complete."
+msgstr "Cela peut vouloir dire soit que l'inventaire n'est pas terminé, soit que ces emplacements sont effectivement vides."
+
+#. module: stock_inventory_location
+#: model:ir.model,name:stock_inventory_location.model_stock_inventory_line
+msgid "Ligne d'inventaire"
+msgstr "Ligne d'inventaire"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:59
+#, python-format
+msgid "Location missing for this inventory."
+msgstr "Emplacement manquant pour cet inventaire."
+
+#. module: stock_inventory_location
+#: model:ir.model,name:stock_inventory_location.model_stock_move
+msgid "Mouvement de stock"
+msgstr "Mouvement de stock"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:75
+#, python-format
+msgid "No location to import.\n"
+"You must add a location on the locations list."
+msgstr "Pas d'emplacement à importer.\n"
+"Vous devez ajouter un emplacement dans la liste des emplacements."
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:58
+#, python-format
+msgid "No locations found for the inventory."
+msgstr "Pas d'emplacement trouvé pour cet inventaire."
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:163
+#, python-format
+msgid "No product in this location."
+msgstr "Pas de produit à cet emplacement."
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:283
+#, python-format
+msgid "A Physical Inventory is being conducted at the following location(s):\n"
+"%s"
+msgstr "Les emplacements suivants sont en inventaire :\n"
+"%s"
+
+#. module: stock_inventory_location
+#: view:stock.inventory:0
+msgid "Open Inventory"
+msgstr "Ouvrir l'inventaire"
+
+#. module: stock_inventory_location
+#: constraint:stock.inventory:0
+msgid "Other Physical inventories are being conducted using the same Locations."
+msgstr "Erreur: certains emplacements sont déjà dans un autre inventaire."
+
+#. module: stock_inventory_location
+#: view:stock.inventory.uninventoried.locations:0
+msgid "Purge contents and confirm Inventory"
+msgstr "Supprimer le contenu et confirmer l'inventaire"
+
+#. module: stock_inventory_location
+#: view:stock.inventory.uninventoried.locations:0
+msgid "The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."
+msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."
+
+#. module: stock_inventory_location
+#: field:stock.inventory.uninventoried.locations,location_ids:0
+msgid "Uninventoried location"
+msgstr "Emplacements non inventoriés"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:59
+#: code:addons/stock_inventory_location/stock_inventory_location.py:163
+#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:58
+#, python-format
+msgid "Warning"
+msgstr "Attention"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:209
+#, python-format
+msgid "Warning: Wrong location"
+msgstr "Attention: emplacement incorrect"
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/stock_inventory_location.py:210
+#, python-format
+msgid "You cannot record an Inventory Line for this Location.\n"
+"You must first add this Location to the list of affected Locations on the Inventory form."
+msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"
+"Il ne fait pas partie de la liste des emplacements à inventorier"
+
+#. module: stock_inventory_location
+#: constraint:stock.move:0
+msgid "You must assign a production lot for this product"
+msgstr "Vous devez affecter un lot de fabrication à ce produit."
+
+#. module: stock_inventory_location
+#: constraint:stock.inventory.line:0
+msgid "You must not create same inventory line Product, Location, Lot on the same date"
+msgstr "Vous ne pouvez pas créer plusieurs lignes d'inventaires pour le même produit, lot, emplacement à la même date"
+
+#. module: stock_inventory_location
+#: constraint:stock.move:0
+msgid "You try to assign a lot which is not from the same product"
+msgstr "Vous essayez d'affecter un lot qui n'est pas pour ce produit."
+
+
+#. module: stock_inventory_location
+#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:54
+#, python-format
+msgid "the inventory must be in \"Open\" state."
+msgstr "l'inventaire doit etre \"Ouvert\"."
+

=== added directory 'stock_inventory_location/images'
=== added file 'stock_inventory_location/images/future_inventory.png'
Binary files stock_inventory_location/images/future_inventory.png	1970-01-01 00:00:00 +0000 and stock_inventory_location/images/future_inventory.png	2014-06-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/inventory_empty_locations.png'
Binary files stock_inventory_location/images/inventory_empty_locations.png	1970-01-01 00:00:00 +0000 and stock_inventory_location/images/inventory_empty_locations.png	2014-06-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/inventory_form.png'
Binary files stock_inventory_location/images/inventory_form.png	1970-01-01 00:00:00 +0000 and stock_inventory_location/images/inventory_form.png	2014-06-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/location_locked.png'
Binary files stock_inventory_location/images/location_locked.png	1970-01-01 00:00:00 +0000 and stock_inventory_location/images/location_locked.png	2014-06-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/move_error.png'
Binary files stock_inventory_location/images/move_error.png	1970-01-01 00:00:00 +0000 and stock_inventory_location/images/move_error.png	2014-06-27 09:18:58 +0000 differ
=== added directory 'stock_inventory_location/static'
=== added directory 'stock_inventory_location/static/src'
=== added directory 'stock_inventory_location/static/src/img'
=== added file 'stock_inventory_location/static/src/img/icon.png'
Binary files stock_inventory_location/static/src/img/icon.png	1970-01-01 00:00:00 +0000 and stock_inventory_location/static/src/img/icon.png	2014-06-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/stock_inventory_location.py'
--- stock_inventory_location/stock_inventory_location.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/stock_inventory_location.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,353 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import time
+from collections import Iterable
+
+from openerp.osv import orm, fields
+from openerp.tools.translate import _
+# The next 2 imports are only needed for a feature backported from trunk-wms
+# TODOv8! remove, feature is included upstream
+from openerp.osv import osv
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from openerp import SUPERUSER_ID
+
+from .exceptions import ExhaustiveInventoryException
+
+
+class StockInventory(orm.Model):
+    """Add locations to the inventories"""
+    _inherit = 'stock.inventory'
+
+    INVENTORY_STATE_SELECTION = [
+        ('draft', 'Draft'),
+        ('open', 'Open'),
+        ('done', 'Done'),
+        ('confirm', 'Confirmed'),
+        ('cancel', 'Cancelled')
+    ]
+
+    _columns = {
+        # TODO v8: should we use "confirm" instead of adding "open"?
+        'state': fields.selection(
+            INVENTORY_STATE_SELECTION, 'State', readonly=True, select=True),
+        # TODO v8: remove this, should not be needed anymore
+        # Make the inventory lines read-only in all states except "Open",
+        # to ensure that no unwanted Location can be inserted
+        'inventory_line_id': fields.one2many(
+            'stock.inventory.line', 'inventory_id', 'Inventory lines',
+            readonly=True, states={'open': [('readonly', False)]}),
+        # TODO v8: remove this, it's backported from v8
+        'location_id': fields.many2one(
+            'stock.location', 'Inventoried Location',
+            readonly=True, states={'draft': [('readonly', False)]}),
+        'exhaustive': fields.boolean(
+            'Exhaustive', readonly=True,
+            states={'draft': [('readonly', False)]},
+            help="Check the box if you are conducting an exhaustive "
+                 "Inventory.\n"
+                 "Leave the box unchecked if you are conducting a standard "
+                 "Inventory (partial inventory for example).\n"
+                 "For an exhaustive Inventory:\n"
+                 " - the status \"Draft\" lets you define the list of "
+                 "Locations where goods must be counted\n"
+                 " - the status \"Open\" indicates that the list of Locations "
+                 "is definitive and you are now counting the goods. In that "
+                 "status, no Stock Moves can be recorded in/out of the "
+                 "Inventory's Locations\n"
+                 " - only the Inventory's Locations can be entered in the "
+                 "Inventory Lines\n"
+                 " - if some of the Inventory's Locations have not been "
+                 "entered in the Inventory Lines, OpenERP warns you "
+                 "when you confirm the Inventory\n"
+                 " - every good that is not in the Inventory Lines is "
+                 "considered lost, and gets moved out of the stock when you "
+                 "confirm the Inventory"),
+        }
+
+    def action_open(self, cr, uid, ids, context=None):
+        """Change the state of the inventory to 'open'"""
+        return self.write(cr, uid, ids, {'state': 'open'}, context=context)
+
+    # TODO v8: remove this method? the feature looks already done upstream
+    def action_done(self, cr, uid, ids, context=None):
+        """
+        Don't allow to make an inventory with a date in the future.
+
+        This makes sure no stock moves will be introduced between the
+        moment you finish the inventory and the date of the Stock Moves.
+        Backported from trunk-wms:
+            revid:qdp-launchpad@xxxxxxxxxxx-20140317090656-o7lo22tzm8yuv3r8
+
+        @raise osv.except_osv:
+            We raise this exception on purpose instead of
+            ExhaustiveInventoryException to ensure forward-compatibility
+            with v8.
+        """
+        for inventory in self.browse(cr, uid, ids, context=None):
+            if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
+                raise osv.except_osv(
+                    _('Error!'),
+                    _('It\'s impossible to confirm an inventory in the '
+                      'future. Please change the inventory date to proceed '
+                      'further.'))
+        return super(StockInventory, self).action_done(cr, uid, ids,
+                                                       context=context)
+
+    # TODO: remove this in v8
+    def _default_location(self, cr, uid, ids, context=None):
+        """Default stock location
+
+        @return: id of the stock location of the first warehouse of the
+        default company"""
+        location_id = False
+        company_id = self.pool['res.company']._company_default_get(
+            cr, uid, 'stock.warehouse', context=context)
+        warehouse_id = self.pool['stock.warehouse'].search(
+            cr, uid, [('company_id', '=', company_id)], limit=1)
+        if warehouse_id:
+            location_id = self.pool['stock.warehouse'].read(
+                cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
+        return location_id
+
+    _defaults = {
+        'state': 'draft',
+        'exhaustive': False,
+        # TODO: remove this in v8
+        'location_id': _default_location,
+        }
+
+    def _check_location_free_from_inventories(self, cr, uid, ids):
+        """
+        Verify that no other Inventory is being conducted on the same locations
+
+        Open Inventories are matched using the exact Location IDs,
+        excluding children.
+        """
+        # We don't get a context because we're called from a _constraint
+        for inventory in self.browse(cr, uid, ids):
+            if not inventory.exhaustive:
+                # never block standard inventories
+                continue
+            if self.search(cr, uid,
+                           [('location_id', '=', inventory.location_id.id),
+                            ('id', '!=', inventory.id),
+                            ('date', '=', inventory.date),
+                            ('exhaustive', '=', True)]):
+                # Quit as soon as one offending inventory is found
+                return False
+        return True
+
+    _constraints = [
+        (_check_location_free_from_inventories,
+         'Other Physical inventories are being conducted using the same '
+         'Locations.',
+         ['location_id', 'date', 'exhaustive'])
+    ]
+
+    def _get_locations_open_inventories(self, cr, uid, context=None):
+        """IDs of location in open exhaustive inventories, with children"""
+        inv_ids = self.search(
+            cr, uid, [('state', '=', 'open'), ('exhaustive', '=', True)],
+            context=context)
+        if not inv_ids:
+            # Early exit if no match found
+            return []
+        # List the Locations - normally all exhaustive inventories have one
+        location_ids = [inventory.location_id.id
+                        for inventory in self.browse(cr, uid, inv_ids,
+                                                     context=context)]
+        # Extend to the children Locations
+        return self.pool['stock.location'].search(
+            cr, uid,
+            [('location_id', 'child_of', set(location_ids)),
+             ('usage', '=', 'internal')],
+            context=context)
+
+    def get_missing_locations(self, cr, uid, ids, context=None):
+        """Compute the list of location_ids which are missing from the lines
+
+        Here, "missing" means the location is the inventory's location or one
+        of it's children, and the inventory does not contain any line with
+        this location."""
+        inventories = self.browse(cr, uid, ids, context=context)
+        # Find the locations of the inventories
+        inv_location_ids = [i.location_id.id for i in inventories]
+        # Extend to the children locations
+        inv_location_ids = set(self.pool['stock.location'].search(
+            cr, uid, [
+                ('location_id', 'child_of', inv_location_ids),
+                ('usage', '=', 'internal')], context=context))
+        # Find the locations already recorded in inventory lines
+        line_locations_ids = set([l.location_id.id
+                                  for i in inventories
+                                    for l in i.inventory_line_id])
+        return list(inv_location_ids - line_locations_ids)
+
+    def confirm_missing_locations(self, cr, uid, ids, context=None):
+        """Open wizard to confirm empty locations on exhaustive inventories"""
+        for inventory in self.browse(cr, uid, ids, context=context):
+            if (self.get_missing_locations(cr, uid, ids, context=context)
+                    and inventory.exhaustive):
+                return {
+                    'type': 'ir.actions.act_window',
+                    'view_type': 'form',
+                    'view_mode': 'form',
+                    'res_model': 'stock.inventory.uninventoried.locations',
+                    'target': 'new',
+                    'context': dict(context,
+                                    active_ids=ids,
+                                    active_id=ids[0]),
+                    'nodestroy': True,
+                }
+        return self.action_confirm(cr, uid, ids, context=context)
+
+
+class StockInventoryLine(orm.Model):
+    """Only allow the Inventory's Locations"""
+
+    _inherit = 'stock.inventory.line'
+
+    def _default_stock_location(self, cr, uid, context=None):
+        res = super(StockInventoryLine, self)._default_stock_location(
+            cr, uid, context=context)
+        if context is None or not context.get('location_id', False):
+            return res
+        else:
+            return context['location_id']
+
+    _defaults = {
+        'location_id': _default_stock_location
+    }
+
+    def onchange_location_id(self, cr, uid, ids, inventory_location_id,
+                             exhaustive, location_id, context=None):
+        """Warn if the new is not in the location list for this inventory."""
+        if not exhaustive:
+            # Don't check if partial inventory
+            return True
+
+        # search children of location
+        if location_id not in self.pool['stock.location'].search(
+                cr, uid, [('location_id', 'child_of', inventory_location_id)],
+                context=context):
+            return {
+                'value': {'location_id': False},
+                'warning': {
+                    'title': _('Warning: Wrong location'),
+                    'message': _(
+                        "You cannot record an Inventory Line for this "
+                        "Location.\n"
+                        "You must first add this Location to the list of "
+                        "affected Locations on the Inventory form.")}
+            }
+        return True
+
+
+class StockLocation(orm.Model):
+    """Refuse changes during exhaustive Inventories"""
+    _inherit = 'stock.location'
+    _order = 'name'
+
+    def _check_inventory(self, cr, uid, ids, context=None):
+        """Error if an exhaustive Inventory is being conducted here"""
+        inv_obj = self.pool['stock.inventory']
+        location_inventory_open_ids = inv_obj._get_locations_open_inventories(
+            cr, SUPERUSER_ID, context=context)
+        if not isinstance(ids, Iterable):
+            ids = [ids]
+        for inv_id in ids:
+            if inv_id in location_inventory_open_ids:
+                raise ExhaustiveInventoryException(
+                    _('Error: Location locked down'),
+                    _('A Physical Inventory is being conducted at this '
+                      'location'))
+        return True
+
+    def write(self, cr, uid, ids, vals, context=None):
+        """Refuse write if an inventory is being conducted"""
+        self._check_inventory(cr, uid, ids, context=context)
+        if not isinstance(ids, Iterable):
+            ids_to_check = [ids]
+        else:
+            # Copy the data to avoid changing 'ids', it would trigger an infinite recursion
+            ids_to_check = list(ids)
+        # If changing the parent, no inventory must conducted there either
+        if vals.get('location_id'):
+            ids_to_check.append(vals['location_id'])
+        self._check_inventory(cr, uid, ids_to_check, context=context)
+        return super(StockLocation, self).write(cr, uid, ids, vals,
+                                                context=context)
+
+    def create(self, cr, uid, vals, context=None):
+        """Refuse create if an inventory is being conducted at the parent"""
+        self._check_inventory(cr, uid, vals.get('location_id'),
+                              context=context)
+        return super(StockLocation, self).create(cr, uid, vals,
+                                                 context=context)
+
+    def unlink(self, cr, uid, ids, context=None):
+        """Refuse unlink if an inventory is being conducted"""
+        self._check_inventory(cr, uid, ids, context=context)
+        return super(StockLocation, self).unlink(cr, uid, ids, context=context)
+
+
+class StockMove(orm.Model):
+    """Refuse Moves during exhaustive Inventories"""
+
+    _inherit = 'stock.move'
+
+    # TODOv7: adapt this to match trunk-wms
+    def _check_open_inventory_location(self, cr, uid, ids, context=None):
+        """
+        Check that the locations are not locked by an open inventory
+
+        Standard inventories are not checked.
+
+        @raise ExhaustiveInventoryException: an error is raised if locations
+            are locked, instead of returning False, in order to pass an
+            extensive error message back to users.
+        """
+        message = ""
+        inv_obj = self.pool['stock.inventory']
+        locked_location_ids = inv_obj._get_locations_open_inventories(
+            cr, SUPERUSER_ID, context=context)
+        if not locked_location_ids:
+            # Nothing to verify
+            return True
+        for move in self.browse(cr, uid, ids, context=context):
+            if (move.location_id.usage != 'inventory'
+                    and move.location_dest_id.id in locked_location_ids):
+                message += " - %s\n" % (move.location_dest_id.name)
+            if (move.location_dest_id.usage != 'inventory'
+                    and move.location_id.id in locked_location_ids):
+                message += " - %s\n" % (move.location_id.name)
+        if message:
+            raise ExhaustiveInventoryException(
+                _('Error: Location locked down'),
+                _('A Physical Inventory is being conducted at the following '
+                  'location(s):\n%s') % message)
+        return True
+
+    _constraints = [
+        (_check_open_inventory_location,
+         "A Physical Inventory is being conducted at this location",
+         ['location_id', 'location_dest_id']),
+    ]

=== added file 'stock_inventory_location/stock_inventory_location_demo.xml'
--- stock_inventory_location/stock_inventory_location_demo.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/stock_inventory_location_demo.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data noupdate="0">
+        <!-- Record a non exhaustive inventory -->
+        <record id="inventory_standard" model="stock.inventory">
+            <field name="name">Standard inventory</field>
+            <field name="state">draft</field>
+        </record>
+
+        <!-- Record an exhaustive inventory -->
+        <record id="inventory_exhaustive" model="stock.inventory">
+            <field name="name">Exhaustive inventory</field>
+            <field name="state">draft</field>
+            <field name="exhaustive">True</field>
+            <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
+        </record>
+
+        <!-- Record an inventory dated in the future -->
+        <!--  TODOv8: remove this entry, only useful for a test already in trunk-wms -->
+        <record id="inventory_future" model="stock.inventory">
+            <field name="name">Inventory in the future</field>
+            <field name="state">draft</field>
+            <field name="date">2020-03-01 00:00:00</field>
+        </record>
+    </data>
+</openerp>

=== added file 'stock_inventory_location/stock_inventory_location_view.xml'
--- stock_inventory_location/stock_inventory_location_view.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/stock_inventory_location_view.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <record model="ir.ui.view" id="stock_inventory_location_form_view">
+            <field name="name">stock.inventory.location.form</field>
+            <field name="model">stock.inventory</field>
+            <field name="inherit_id" ref="stock.view_inventory_form"/>
+            <field name="priority" eval="10"/>
+            <field name="arch" type="xml">
+                <!-- Show the state 'done' in the statusbar -->
+                <xpath expr="/form//field[@name='state']" position="attributes">
+                    <attribute name="statusbar_visible">draft,open,confirm</attribute>
+                </xpath>
+                
+                <!-- TODO v8 place "exhaustive" next to "location_id" -->
+                <!-- Add type of inventory: standard or exhaustive. -->
+                <xpath expr="/form//field[@name='name']" position="after">
+                    <field name="exhaustive"/>
+                    <field name="location_id" groups="stock.group_locations"
+                           domain="[('usage','in',('view', 'internal'))]"
+                           attrs="{'invisible':[('exhaustive','!=',True)], 'required':[('exhaustive','=',True)]}"/>
+                </xpath>
+
+                <!-- Enable Fill Inventory button when state is open -->
+                <xpath expr="//button[@string='Fill Inventory']" position="attributes">
+                    <attribute name="states">open</attribute>
+                    <attribute name="context">{'default_exhaustive': exhaustive}</attribute>
+                </xpath>
+
+                <!-- Control locations added by user on inventory line -->
+                <xpath expr="/form//field[@name='inventory_line_id']"
+                       position="attributes">
+                    <attribute name="context">{'location_id': location_id}</attribute>
+                </xpath>
+                <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']"
+                       position="attributes">
+                    <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
+                </xpath>
+                <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']"
+                       position="attributes">
+                    <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
+                </xpath>
+
+                <!-- Add button to open an inventory. Call wizard on confirm inventory -->
+                <xpath expr="/form//button[@name='action_cancel_inventory']" position="before">
+                    <button name="action_open" states="draft" string="Open Inventory" type="object" class="oe_highlight" groups="stock.group_stock_user"/>
+                </xpath>
+                <!-- Show the "cancel" button in state "open" -->
+                <xpath expr="/form//button[@name='action_cancel_inventory']" position="attributes">
+                    <attribute name="states">draft,open,confirm,done</attribute>
+                </xpath>
+                <!-- hijack the "confirm" button -->
+                <xpath expr="/form//button[@name='action_confirm']" position="attributes">
+                      <attribute name="states">open</attribute>
+                      <attribute name="name">confirm_missing_locations</attribute>
+                </xpath>
+            </field>
+        </record>
+
+        <!-- Add filter for complete or partial inventory -->
+        <record model="ir.ui.view" id="view_inventory_complete_filter">
+            <field name="name">complete.inventory.filter</field>
+            <field name="model">stock.inventory</field>
+            <field name="inherit_id" ref="stock.view_inventory_filter"/>
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='name']" position="before">
+                    <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]"
+                            help="Only select exhaustive Inventories." />
+                    <separator orientation="vertical"/>
+                </xpath>
+            </field>
+        </record>
+
+        <!-- Show exhaustive inventories by default -->
+        <record id="stock.action_inventory_form" model="ir.actions.act_window">
+            <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>
+        </record>
+
+	</data>
+</openerp>
\ No newline at end of file

=== added directory 'stock_inventory_location/tests'
=== added file 'stock_inventory_location/tests/__init__.py'
--- stock_inventory_location/tests/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/__init__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+#
+#
+#    Authors: Laetitia Gangloff
+#    Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
+#    All Rights Reserved
+#
+#    WARNING: This program as such is intended to be used by professional
+#    programmers who take the whole responsibility of assessing all potential
+#    consequences resulting from its eventual inadequacies and bugs.
+#    End users who are looking for a ready-to-use solution with commercial
+#    guarantees and support are strongly advised to contact a Free Software
+#    Service Company.
+#
+#    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 stock_inventory_location_test
+
+fast_suite = [
+]
+
+checks = [
+    stock_inventory_location_test,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'stock_inventory_location/tests/inventory_exhaustive_test.yml'
--- stock_inventory_location/tests/inventory_exhaustive_test.yml	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/inventory_exhaustive_test.yml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,128 @@
+-
+  This file will test an exhaustive inventory.
+  I will call open_action method and check if state of inventories are 'open'.
+-
+  !python {model: stock.inventory}: |
+    self.action_open(cr, uid, [ref('inventory_exhaustive')])
+    inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
+    assert inventory_state == 'open', "Inventory in state '%s'. It should be 'open'" % inventory_state
+-
+  I will check that the function get_missing_locations return some locations.
+-
+  !python {model: stock.inventory}: |
+    missing_loc_ids = self.get_missing_locations(cr, uid, [ref('inventory_exhaustive')], context=context)
+    assert len(missing_loc_ids), "get_missing_locations did not return any ID."
+-
+  I create a wizard record for stock_confirm_uninventoried_location to verify that it contains the uninventoried locations
+-
+  !python {model: stock.inventory.uninventoried.locations}: |
+    ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
+    wizard_id = self.create(cr, uid, {}, context=ctx)
+    wizard = self.browse(cr, uid, wizard_id, context=ctx)
+    assert len(wizard.location_ids) > 0 , "The wizard does not contain any lines."
+-
+  I add products to exhaustive inventory.
+  Adding 17” LCD Monitor.
+-
+  !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
+    product_id: product.product_product_7
+    product_uom: product.product_uom_unit
+    company_id: base.main_company
+    inventory_id: inventory_exhaustive
+    product_qty: 18.0
+    location_id: stock.stock_location_14
+
+-
+  Adding USB Keyboard, QWERTY.
+-
+  !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
+    product_id: product.product_product_8
+    product_uom: product.product_uom_unit
+    company_id: base.main_company
+    inventory_id: inventory_exhaustive
+    product_qty: 5.0
+    location_id: stock.stock_location_14
+
+-
+  I will call the function _get_locations_open_inventories and check the result.
+  The function will return only the location_id of the exhaustive inventory.
+-
+  !python {model: stock.inventory}: |
+    locations = self._get_locations_open_inventories(cr, uid)
+    assert len(locations) == 1, "Function return wrong results: %s" % locations
+    assert locations[0] == ref('stock.stock_location_14'), "Function '_get_locations_open_inventories' return wrong location_id. Should be '%s': '%s'" % (stock.stock_location_14, locations[0])
+-
+  I will call the function onchange_location_id.
+  The function must return True in the first case, and return a warning dictionnary in the second test.
+-
+  !python {model: stock.inventory.line}: |
+    res = self.onchange_location_id(cr, uid, [], ref('stock.stock_location_14'), True, ref('stock.stock_location_14'))
+    assert res == True, "Exhaustive: The function 'onchange_location_id' should return True and return '%s'" % res
+    res = self.onchange_location_id(cr, uid, [], ref('stock.stock_location_14'), True, ref('stock.stock_location_components'))
+    assert res.get('warning', False) != False , "Function 'onchange_location_id': Warning not raise. ('%s)" % res
+
+-
+  I will check that the function get_missing_locations does not return any locations.
+-
+  !python {model: stock.inventory}: |
+    missing_loc_ids = self.get_missing_locations(cr, uid, [ref('inventory_exhaustive')], context=context)
+    assert not missing_loc_ids, "get_missing_locations should not return IDs but returned %s" % missing_loc_ids
+-
+  I create a wizard record for stock_confirm_uninventoried_location and validate it
+-
+  !python {model: stock.inventory.uninventoried.locations}: |
+    ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
+    wizard_id = self.create(cr, uid, {}, context=ctx)
+    wizard = self.browse(cr, uid, wizard_id, context=ctx)
+    assert len(wizard.location_ids) == 0 , "The wizard should not contain any lines but contains %s." % wizard.location_ids
+    self.confirm_uninventoried_locations(cr, uid, wizard_id, context=ctx)
+-
+  Stock moves are not allowed in the locations during the inventory.
+-
+  !python {model: stock.move}: |
+    # TODOv8: remove this test, this is already part of trunk-wms
+    from stock_inventory_location import ExhaustiveInventoryException
+    try:
+      self.create(
+        cr,uid, {
+          'name': 'Bad move',
+          'location_id': ref('stock.stock_location_14'),
+          'location_dest_id': ref('stock.stock_location_3'),
+          'product_id': ref('product.product_product_8'),
+          'product_uom': ref('product.product_uom_unit'),
+          'date_expected': '2020-01-01 00:00:00'
+      })
+    except ExhaustiveInventoryException as e:
+      log("Good! The Stock Move was refused: %s" % e)
+-
+  I will confirm the exhaustive inventory
+-
+  !python {model: stock.inventory}: |
+    self.action_confirm(cr, uid, [ref('inventory_exhaustive')])
+    inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
+    assert inventory_state == 'confirm', "Exhaustive inventory is in state '%s'. It should be 'confirm'" % inventory_state
+
+-
+  I will validate the exhaustive inventory
+-
+  !python {model: stock.inventory}: |
+    self.action_done(cr, uid, [ref('inventory_exhaustive')])
+    inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
+    assert inventory_state == 'done', "Exhaustive inventory is in state '%s'. It should be 'done'" % inventory_state
+
+-
+  I will verify the quantity for each products.
+-
+  !python {model: product.product}: |
+    ctx = dict(context, location=[ref('stock.stock_location_14')])
+    prod_qty_avail = self.read(cr, uid, [ref('product.product_product_7')], ['qty_available'], context=ctx)[0]['qty_available']
+    assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0: %s" % prod_qty_avail
+
+    prod_qty_avail = self.read(cr, uid, [ref('product.product_product_8')], ['qty_available'], context=ctx)[0]['qty_available']
+    assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0: %s" % prod_qty_avail
+
+    prod_qty_avail = self.read(cr, uid, [ref('product.product_product_24')], ['qty_available'], context=ctx)[0]['qty_available']
+    assert prod_qty_avail == 0.0, "The stock of KEYA was not set to 0: %s" % prod_qty_avail
+
+    prod_qty_avail = self.read(cr, uid, [ref('product.product_product_25')], ['qty_available'], context=ctx)[0]['qty_available']
+    assert prod_qty_avail == 0.0, "The stock of MOU was not set to 0: %s" % prod_qty_avail

=== added file 'stock_inventory_location/tests/inventory_future_test.yml'
--- stock_inventory_location/tests/inventory_future_test.yml	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/inventory_future_test.yml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,13 @@
+-
+  Check that an inventory with a date in the future cannot be opened.
+-
+  !python {model: stock.inventory}: |
+    # TODO v8: maybe this is already part of the new WMS
+    from osv.osv import except_osv
+    self.action_open(cr, uid, [ref('inventory_future')])
+    try:
+      self.action_done(cr, uid, [ref('inventory_future')])
+    except except_osv as e:
+      log("Good! The Inventory could not be opened: %s" % e)
+    inventory_state = self.read(cr, uid, [ref('inventory_future')], ['state'])[0]['state']
+    assert inventory_state != 'done', "Future inventory is done."

=== added file 'stock_inventory_location/tests/inventory_standard_test.yml'
--- stock_inventory_location/tests/inventory_standard_test.yml	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/inventory_standard_test.yml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,100 @@
+-
+  This file will test a non exhaustive inventory.
+  I will call open_action method and check if state of inventories are 'open'.
+-
+  !python {model: stock.inventory}: |
+    self.action_open(cr, uid, [ref('inventory_standard')])
+    inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
+    assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state
+
+-
+  In order, I add products to inventory.
+  Adding Azerty Keyboard.
+-
+  !record {model: stock.inventory.line, id: lines_inventory_location_kbaz}:
+    product_id: product.product_product_9
+    product_uom: product.product_uom_unit
+    company_id: base.main_company
+    inventory_id: inventory_standard
+    product_qty: 18.0
+    location_id: stock.stock_location_components
+
+-
+  Adding Optical Mouse.
+-
+  !record {model: stock.inventory.line, id: lines_inventory_location_optm}:
+    product_id: product.product_product_10
+    product_uom: product.product_uom_unit
+    company_id: base.main_company
+    inventory_id: inventory_standard
+    product_qty: 12.0
+    location_id: stock.stock_location_components
+
+-
+  Adding Multimedia Speakers.
+-
+  !record {model: stock.inventory.line, id: lines_inventory_location_grca}:
+    product_id: product.product_template_31
+    product_uom: product.product_uom_unit
+    company_id: base.main_company
+    inventory_id: inventory_standard
+    product_qty: 32.0
+    location_id: stock.stock_location_components
+
+-
+  I will call the function _get_locations_open_inventories and check the result.
+  The function will return no locations because it's not an exhaustive inventory.
+-
+  !python {model: stock.inventory}: |
+    locations = self._get_locations_open_inventories(cr, uid)
+    assert len(locations) == 0, "Function return wrong results: %s" % locations
+
+-
+  I will call the function onchange_location_id.
+  The function must to return true in all test case.
+-
+  !python {model: stock.inventory.line}: |
+    res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))
+    assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
+    res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))
+    assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
+
+-
+  I will call the function _check_inventory.
+  The function must return True in all test cases.
+-
+  !python {model: stock.location}: |
+    res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))
+    assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
+    res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))
+    assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
+
+-
+  I will confirm inventory.
+-
+  !python {model: stock.inventory}: |
+    self.action_confirm(cr, uid, [ref('inventory_standard')])
+    inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
+    assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state
+
+-
+  I will validate inventory
+-
+  !python {model: stock.inventory}: |
+    self.action_done(cr, uid, [ref('inventory_standard')])
+    inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
+    assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state
+
+-
+  I will verify the quantity for each products.
+-
+  !python {model: product.product}: |
+    ctx={'location': [ref('stock.stock_location_components')]}
+    prod_qty_avail = self.read(cr, uid, [ref('product.product_product_9')], ['qty_available'], context=ctx)[0]['qty_available']
+    assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0: %s" % prod_qty_avail
+
+    prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']
+    assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0: %s" % prod_qty_avail
+
+    prod_qty_avail = self.read(cr, uid, [ref('product.product_template_31')], ['qty_available'], context=ctx)[0]['qty_available']
+    assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0: %s" % prod_qty_avail

=== added file 'stock_inventory_location/tests/stock_inventory_location_test.py'
--- stock_inventory_location/tests/stock_inventory_location_test.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/stock_inventory_location_test.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+#
+#
+#    Authors: Laetitia Gangloff
+#    Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
+#    All Rights Reserved
+#
+#    WARNING: This program as such is intended to be used by professional
+#    programmers who take the whole responsibility of assessing all potential
+#    consequences resulting from its eventual inadequacies and bugs.
+#    End users who are looking for a ready-to-use solution with commercial
+#    guarantees and support are strongly advised to contact a Free Software
+#    Service Company.
+#
+#    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 openerp.tests.common as common
+
+DB = common.DB
+ADMIN_USER_ID = common.ADMIN_USER_ID
+
+
+class stock_inventory_location_test(common.TransactionCase):
+
+    def setUp(self):
+        super(stock_inventory_location_test, self).setUp()
+
+    def test_update_parent_location(self):
+        """
+        Test the update of the parent of a location (no inventory in progress
+        """
+        self.registry('stock.location').write(self.cr, self.uid, self.ref('stock.stock_location_5'), {'location_id': self.ref('stock.stock_location_4')})
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added directory 'stock_inventory_location/wizard'
=== added file 'stock_inventory_location/wizard/__init__.py'
--- stock_inventory_location/wizard/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/__init__.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-1
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import stock_fill_location_inventory
+import stock_confirm_uninventoried_location

=== added file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.py'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,68 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, orm, osv
+
+
+class stock_inventory_uninventoried_location(orm.TransientModel):
+    _name = 'stock.inventory.uninventoried.locations'
+    _description = 'Confirm the uninventoried Locations.'
+
+    _columns = {
+        'location_ids': fields.many2many(
+            'stock.location',
+            'stock_inventory_uninventoried_location_rel',
+            'location_id', 'wizard_id',
+            'Uninventoried location', readonly=True),
+        }
+
+    def default_locations(self, cr, uid, context=None):
+        """Initialize view with the list of uninventoried locations."""
+        return self.pool['stock.inventory'].get_missing_locations(
+            cr, uid, context['active_ids'], context=context)
+
+    _defaults = {
+        'location_ids': default_locations,
+    }
+
+    def confirm_uninventoried_locations(self, cr, uid, ids, context=None):
+        """Add the missing inventory lines with qty=0 and confirm inventory"""
+        inventory_ids = context['active_ids']
+        inventory_obj = self.pool['stock.inventory']
+        wizard_obj = self.pool['stock.fill.inventory']
+        for inventory in inventory_obj.browse(cr, uid, inventory_ids,
+                                              context=context):
+            if inventory.exhaustive:
+                # silently run the import wizard with qty=0
+                try:
+                    # on parent inventory it is possible that fill inventory fail with no product
+                    wizard_id = wizard_obj.create(
+                        cr, uid, {'location_id': inventory.location_id.id,
+                                  'recursive': True,
+                                  'set_stock_zero': True,
+                                  'exhaustive': True}, context=context)
+                    wizard_obj.fill_inventory(cr, uid, [wizard_id],
+                                              context=context)
+                except osv.except_osv, e:
+                    if e.value == _('No product in this location. Please select a location in the product form.'):
+                        pass
+
+        inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)
+        return {'type': 'ir.actions.act_window_close'}

=== added file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml,
+        but the code is different.
+        This wizard compare declared locations with locations on inventory lines to present the uninventoried (empty) locations to user. -->
+        <record id="view_confirm_uninventoried_location" model="ir.ui.view">
+            <field name="name">stock.inventory.uninventoried.locations.form</field>
+            <field name="model">stock.inventory.uninventoried.locations</field>
+            <field name="arch" type="xml">
+                <form string="Confirm empty locations" version="7.0">
+                    <group colspan="4" col="1">
+                        <separator string="The following Locations are empty"/>
+                        <label string="The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."/>
+                        <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
+                        <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
+                        <field name="location_ids" nolabel="1">
+                            <tree>
+                                <field name="name"/>
+                            </tree>
+                        </field>
+                        <separator string=""/>
+                    </group>
+                  <footer>
+                      <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" class="oe_highlight"/>
+                      or
+                      <button string="Cancel" class="oe_link" special="cancel" default_focus="1"/>
+                  </footer>
+                </form>
+            </field>
+        </record>
+	</data>
+</openerp>
\ No newline at end of file

=== added file 'stock_inventory_location/wizard/stock_fill_location_inventory.py'
--- stock_inventory_location/wizard/stock_fill_location_inventory.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory.py	2014-06-27 09:18:58 +0000
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import fields, orm
+
+
+class FillInventoryWizard(orm.TransientModel):
+    """Add a field that lets the client make the location read-only"""
+    _inherit = 'stock.fill.inventory'
+
+    _columns = {
+        'exhaustive': fields.boolean('Exhaustive', readonly=True)
+    }
+
+    def default_get(self, cr, uid, fields, context=None):
+        """Get 'location_id' and 'exhaustive' from the inventory"""
+        if context is None:
+            context = {}
+        inv_id = context.get('active_id')
+
+        res = super(FillInventoryWizard, self).default_get(
+            cr, uid, fields, context=context)
+        if (context.get('active_model') == 'stock.inventory'
+                and inv_id
+                and 'location_id' in fields):
+            inventory = self.pool['stock.inventory'].browse(
+                cr, uid, context['active_id'], context=context)
+            res.update({'location_id': inventory.location_id.id,
+                        'exhaustive': inventory.exhaustive})
+        return res

=== added file 'stock_inventory_location/wizard/stock_fill_location_inventory_view.xml'
--- stock_inventory_location/wizard/stock_fill_location_inventory_view.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory_view.xml	2014-06-27 09:18:58 +0000
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="view_stock_fill_inventory_location" model="ir.ui.view">
+            <field name="name">Import Inventory</field>
+            <field name="model">stock.fill.inventory</field>
+            <field name="inherit_id" ref="stock.view_stock_fill_inventory" />
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='location_id']" position="after">
+                    <field name="exhaustive" invisible="1" />
+                </xpath>
+                <xpath expr="//field[@name='location_id']" position="attributes">
+                    <attribute name="attrs">{'readonly':[('exhaustive','=',True)]}</attribute>
+                </xpath>
+            </field>
+        </record>
+    </data>
+</openerp>


Follow ups