openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #07300
[Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
Loïc Bellier - Numérigraphe has proposed merging lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse.
Requested reviews:
Lionel Sausin - Numérigraphe (lionel-sausin)
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c): code review, test
For more details, see:
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location/+merge/223880
New version of the branch which only one location is permit by inventory to be inline with the v8.
This proposal feature obsoletes Tiny's "stock_alternative_inventory", and
implements exhaustive inventory in a much more practical way.
It lets you explicitly state which locations are being inventoried, and makes
sure the stock levels exactly reflect the physical inventory for those
locations.
It also checks that all locations been entered in the inventory lines,
and no other locations.
Finally it will block Stock Moves during the inventory, to keep
warehouse workers from changing the stock while the inventory is not
finished.
Thanks to Laetitia GANGLOFF of Acsone.eu for her help.
--
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location/+merge/223880
Your team Stock and Logistic Core Editors is subscribed to branch lp:stock-logistic-warehouse.
=== 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-20 09:34:02 +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-20 09:34:02 +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": [
+ "test/inventory_standard_test.yml",
+ "test/inventory_exhaustive_test.yml",
+ "test/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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +0000
@@ -0,0 +1,351 @@
+# -*- 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 .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'
+
+ # TODOv7: why not put this in an ORM "_constraint" instead?
+ 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, uid, 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 = [ids]
+ ids_to_check = 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, uid, 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-20 09:34:02 +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-20 09:34:02 +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/test'
=== added file 'stock_inventory_location/test/inventory_exhaustive_test.yml'
--- stock_inventory_location/test/inventory_exhaustive_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/test/inventory_exhaustive_test.yml 2014-06-20 09:34:02 +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/test/inventory_future_test.yml'
--- stock_inventory_location/test/inventory_future_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/test/inventory_future_test.yml 2014-06-20 09:34:02 +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/test/inventory_standard_test.yml'
--- stock_inventory_location/test/inventory_standard_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/test/inventory_standard_test.yml 2014-06-20 09:34:02 +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 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-20 09:34:02 +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-20 09:34:02 +0000
@@ -0,0 +1,62 @@
+# -*- 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
+
+
+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
+ wizard_id = wizard_obj.create(
+ cr, uid, {'location_id': inventory.location_id.id,
+ 'recursive': True,
+ 'set_stock_zero': True}, context=context)
+ wizard_obj.fill_inventory(cr, uid, [wizard_id],
+ context=context)
+
+ 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-20 09:34:02 +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-20 09:34:02 +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-20 09:34:02 +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
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-10-20
-
[Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-10-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Alexandre Fayolle - camptocamp, 2014-07-03
-
[Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Laetitia Gangloff (Acsone), 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Laetitia Gangloff (Acsone), 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Laetitia Gangloff (Acsone), 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Laetitia Gangloff (Acsone), 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Laetitia Gangloff (Acsone), 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Lionel Sausin - Numérigraphe, 2014-06-20
-
Re: [Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse
From: Laetitia Gangloff (Acsone), 2014-06-20