openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #07518
[Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-hierarchical into lp:stock-logistic-warehouse
Loïc Bellier - Numérigraphe has proposed merging lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-hierarchical into lp:stock-logistic-warehouse with lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location as a prerequisite.
Requested reviews:
Lionel Sausin - Numérigraphe (lionel-sausin): co-author
Laetitia Gangloff (Acsone) (laetitia-gangloff)
Stock and Logistic Core Editors (stock-logistic-core-editors)
For more details, see:
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-hierarchical/+merge/223882
Code cleanup and Bug fixes.
This proposal lets users compose inventories with sub-inventories in
a hierarchical tree.
This is useful in medium/large warehouses where
several teams must work in parallel to make inventories.
The module stock_inventory_hierarchical_location depends of stock_inventory_hierarchical and
stock_inventory_location (lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location).
Thanks to Laetitia GANGLOFF from Ascone for her contribution.
--
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-hierarchical/+merge/223882
Your team Stock and Logistic Core Editors is requested to review the proposed merge of lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-hierarchical 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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +0000
@@ -0,0 +1,102 @@
+# -*- 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 = 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]
+ if not subinv_location_ids:
+ return missing_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(set(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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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-30 09:28:51 +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 osv.except_osv(_('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:
Follow ups