Merge lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location into lp:stock-logistic-warehouse

Proposed by Loïc Bellier - Numérigraphe
Status: Rejected
Rejected by: Pedro Manuel Baeza
Proposed branch: lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location
Merge into: lp:stock-logistic-warehouse
Diff against target: 1703 lines (+1592/-0)
18 files modified
stock_inventory_location/__init__.py (+24/-0)
stock_inventory_location/__openerp__.py (+77/-0)
stock_inventory_location/exceptions.py (+26/-0)
stock_inventory_location/i18n/fr.po (+250/-0)
stock_inventory_location/i18n/stock_inventory_location.pot (+239/-0)
stock_inventory_location/stock_inventory_location.py (+353/-0)
stock_inventory_location/stock_inventory_location_demo.xml (+26/-0)
stock_inventory_location/stock_inventory_location_view.xml (+81/-0)
stock_inventory_location/tests/__init__.py (+39/-0)
stock_inventory_location/tests/inventory_exhaustive_test.yml (+128/-0)
stock_inventory_location/tests/inventory_future_test.yml (+13/-0)
stock_inventory_location/tests/inventory_standard_test.yml (+100/-0)
stock_inventory_location/tests/stock_inventory_location_test.py (+47/-0)
stock_inventory_location/wizard/__init__.py (+22/-0)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.py (+69/-0)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml (+33/-0)
stock_inventory_location/wizard/stock_fill_location_inventory.py (+47/-0)
stock_inventory_location/wizard/stock_fill_location_inventory_view.xml (+18/-0)
To merge this branch: bzr merge lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location
Reviewer Review Type Date Requested Status
Alexandre Fayolle - camptocamp Needs Resubmitting
Lionel Sausin - Initiatives/Numérigraphe (community) i'm a contributor Abstain
Laetitia Gangloff (Acsone) (community) code, test, functional test Approve
Stock and Logistic Core Editors Pending
Review via email: mp+223880@code.launchpad.net

This proposal supersedes a proposal from 2014-03-12.

Description of the change

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.

To post a comment you must log in.
Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : Posted in a previous version of this proposal

Hello,

Thanks for this contribution, the functionality is great and I'd love to see it in OCA. And thanks for including tests.

A few minor changes are needed to bring the module to the coding standards

* use orm.Model / orm.TransientModel as base classes instead of osv.osv and osv.osv_memory
* model column default values: in OpenERP 7.0, not need to use a lambda for a constant value, just use the constant

Additionally:

* in __openerp__.py, I think you should use 'Warehouse Management' for the category (i.e. the same one as the official 'stock' addon
* no space before '!' in english (I'd remove the exclamation marks in exception messages altogether, that the UI job to display an icon)
* in confirm_uninventoried_location_wizard: you should add a 'if context is None: context = {}' at the beginning.
* in confirm_uninventoried_locations: you are ignoring an osv_except exception. Could you add a comment in the code explaining why this is safe? If this is about the "No product in this location" warning raised in the reimplementation of _fill_location_lines, then I think a check that we are ignoring the correct exception should be added. You could for instance subclass except_osv and raise + ignore the subclass. Otherwise I think the code may hide things we do not want to hide (or even fail because of an except_osv

review: Needs Fixing (code review, test)
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote : Posted in a previous version of this proposal

Recent developments in trunk-wms implement some features of this proposal but the implementation is different.
I suggest we refactor this proposal and the following patches, to make it easy to port the module to v8 later on.
- Add locations as many2one instead of one2many
  http://bazaar.launchpad.net/~openerp-dev/openobject-addons/trunk-wms/revision/9596
- forbid inventories in the future
  http://bazaar.launchpad.net/~openerp-dev/openobject-addons/trunk-wms/revision/9598

review: Needs Resubmitting
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote : Posted in a previous version of this proposal

Loïc and I are still working on this proposal. Good progress was made: we re-aligned the features with v8 in mind, and improved the code style and the views.
Acsone joined in and we're still making tests, and i hope Loïc can make a new proposal in the coming days.

Revision history for this message
Laetitia Gangloff (Acsone) (laetitia-gangloff) wrote :

Hello,

I see some little mistake that I correct in :
http://bazaar.launchpad.net/~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-fill2-lga/revision/40
http://bazaar.launchpad.net/~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-fill2-lga/revision/41

It seems I forgot to report them on this branch :(.

The most important of them is the second one.

The change is the following:

l.699 ids_to_check = ids
should be : ids_to_check = list(ids)

review: Needs Fixing
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

laetitia-gangloff you're right, Loïc had to split the proposal in 2 branches to make the review easier but I forgot to push back the fixes he an you made to the modules in this branch.
I'll fix it now.

Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

laetitia-gangloff,
Can you please explain this change in your branch? I don't see the point because ids is already sure to be Iterable. Does it really have to be a list?

@@ -285,11 +285,11 @@
     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 = list(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,

Revision history for this message
Laetitia Gangloff (Acsone) (laetitia-gangloff) wrote :

It is to avoid a "RuntimeError: maximum recursion depth exceeded" when change location_id.

ids was just assigned in ids_to_check so, when ids_to_check change, ids change too and the write is not done with right parameter, an we get the above error.

With list(ids) we create a new list, so ids and ids_to_check are not same.

41. By Laetitia Gangloff (Acsone)

[FIX] read inventories as superuser to allow the control on locations even when the user has no permission on inventories. Courtesy of ACSONE.

Revision history for this message
Laetitia Gangloff (Acsone) (laetitia-gangloff) wrote :
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

laetitia-gangloff, right you are again.
I propose to write it explicitly this way, is it OK for you?
        if not isinstance(ids, Iterable):
            ids_to_check = [ids]
        else:
            # Copy the date to avoid changing 'ids', it would trigger an infinite recursion
            ids_to_check = list(ids)

Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

I meant "copy the datA", not "copy the date" of course...

42. By Laetitia Gangloff (Acsone)

[FIX] don't mutate the data received by the method, it causes an infinite recursion. Fix provided by ACSONE.

Revision history for this message
Laetitia Gangloff (Acsone) (laetitia-gangloff) wrote :

Your proposition is OK for me.

Maybe, can you take test of this problem ?

43. By Laetitia Gangloff (Acsone)

[FIX] missing test files from the previous fixes by ASCONE

Revision history for this message
Laetitia Gangloff (Acsone) (laetitia-gangloff) wrote :

l. 509/l. 516 : raise osv.except_osv

Is it not better to use orm.except_orm ?

Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

laetitia-gangloff: that part of the code was backported from trunk-wms at the time, and I wanted to ease the migration to v8.
But eventually Odoo's Quentin Di Paoli changed his mind about that feature and removed it from odoo/master. But he still uses osv.except_osv all over the WMS code.
So I have no idea what's best, opinions welcome.

Revision history for this message
Laetitia Gangloff (Acsone) (laetitia-gangloff) wrote :

Thank you for the explanation.
I have no idea what's best.

I retested this module and it seems now OK.

review: Approve (code, test, functional test)
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) :
review: Abstain (i'm a contributor)
44. By Laetitia Gangloff (Acsone)

[FIX] exhaustive was not passed to the wizard when emptying the locations that were not in the inventory

45. By Numérigraphe

[REF] remove TODO

46. By Laetitia Gangloff (Acsone)

[FIX] missing import

47. By Numérigraphe

[IMP] French translation

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

The source code management for this project has been moved to https://github.com/OCA/stock-logistics-warehouse

Could you resubmit this MP on the new site?

review: Needs Resubmitting
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

I've finally resubmitted this as part of the pull request at https://github.com/OCA/stock-logistics-warehouse/pull/17.
Please reject the merge proposal here to make it clear it won't be processed on Launchpad.

Unmerged revisions

47. By Numérigraphe

[IMP] French translation

46. By Laetitia Gangloff (Acsone)

[FIX] missing import

45. By Numérigraphe

[REF] remove TODO

44. By Laetitia Gangloff (Acsone)

[FIX] exhaustive was not passed to the wizard when emptying the locations that were not in the inventory

43. By Laetitia Gangloff (Acsone)

[FIX] missing test files from the previous fixes by ASCONE

42. By Laetitia Gangloff (Acsone)

[FIX] don't mutate the data received by the method, it causes an infinite recursion. Fix provided by ACSONE.

41. By Laetitia Gangloff (Acsone)

[FIX] read inventories as superuser to allow the control on locations even when the user has no permission on inventories. Courtesy of ACSONE.

40. By Numérigraphe

[FIX] wrong nesting in list comprehension

39. By Numérigraphe

[FIX] test and add a test for the refactored function

38. By Numérigraphe

[MERGE] only allow one location per inventory to make it easier to migrate to v8 when the time comes. code & views cleaned up.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'stock_inventory_location'
2=== added file 'stock_inventory_location/__init__.py'
3--- stock_inventory_location/__init__.py 1970-01-01 00:00:00 +0000
4+++ stock_inventory_location/__init__.py 2014-07-01 12:00:29 +0000
5@@ -0,0 +1,24 @@
6+# -*- coding: utf-8 -*-
7+##############################################################################
8+#
9+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
10+#
11+# This program is free software: you can redistribute it and/or modify
12+# it under the terms of the GNU General Public License as published by
13+# the Free Software Foundation, either version 3 of the License, or
14+# (at your option) any later version.
15+#
16+# This program is distributed in the hope that it will be useful,
17+# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+# GNU General Public License for more details.
20+#
21+# You should have received a copy of the GNU General Public License
22+# along with this program. If not, see <http://www.gnu.org/licenses/>.
23+#
24+##############################################################################
25+
26+import stock_inventory_location
27+import wizard
28+# Bring the main exception into the package's scope for easier reuse
29+from .exceptions import ExhaustiveInventoryException
30
31=== added file 'stock_inventory_location/__openerp__.py'
32--- stock_inventory_location/__openerp__.py 1970-01-01 00:00:00 +0000
33+++ stock_inventory_location/__openerp__.py 2014-07-01 12:00:29 +0000
34@@ -0,0 +1,77 @@
35+# -*- coding: utf-8 -*-
36+##############################################################################
37+#
38+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
39+#
40+# This program is free software: you can redistribute it and/or modify
41+# it under the terms of the GNU General Public License as published by
42+# the Free Software Foundation, either version 3 of the License, or
43+# (at your option) any later version.
44+#
45+# This program is distributed in the hope that it will be useful,
46+# but WITHOUT ANY WARRANTY; without even the implied warranty of
47+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48+# GNU General Public License for more details.
49+#
50+# You should have received a copy of the GNU General Public License
51+# along with this program. If not, see <http://www.gnu.org/licenses/>.
52+#
53+##############################################################################
54+
55+{
56+ "name": "Exhaustive Stock Inventories",
57+ "version": "1.1",
58+ "depends": ["stock"],
59+ "author": u"Numérigraphe",
60+ "category": "Warehouse Management",
61+ "description": """
62+Let users make exhaustive Inventories
63+=====================================
64+
65+Standard Physical Inventories in OpenERP only contain a generic list of
66+products by locations, which is well suited to partial Inventories and simple
67+warehouses. When the a standard Inventory is confirmed, only the products in
68+the inventory are checked. If a Product is present in the computed stock and
69+not in the recorded Inventory, OpenERP will consider that it remains unchanged.
70+
71+But for exhaustive inventories in complex warehouses, it is not practical:
72+ - you want to avoid Stock Moves to/from these Locations while counting goods
73+ - you must make sure all the locations you want have been counted
74+ - you must make sure no other location has been counted by mistake
75+ - you want the computed stock to perfectly match the inventory when you
76+ confirm it.
77+
78+This module lets choose whether an Physical Inventory is exhaustive or
79+standard.
80+For an exhaustive Inventory:
81+ - in the state "Draft" you define the Location where goods must be counted.
82+ - the new Inventory status "Open" lets you indicate that the list of Locations
83+ is final and you are now counting the goods.
84+ In that status, no Stock Moves can be recorded in/out of the Inventory's
85+ Locations.
86+ - if the Location or some of it's children have not been entered in the
87+ Inventory Lines, OpenERP warns you when you confirm the Inventory.
88+ - only the Inventory's Location or its children can be entered in the
89+ Inventory Lines.
90+ - every good that is not in the Inventory Lines is considered lost, and gets
91+ moved out of the stock when you confirm the Inventory.
92+""",
93+ "data": [
94+ "wizard/stock_confirm_uninventoried_location.xml",
95+ "stock_inventory_location_view.xml",
96+ "wizard/stock_fill_location_inventory_view.xml",
97+ ],
98+ "test": [
99+ "tests/inventory_standard_test.yml",
100+ "tests/inventory_exhaustive_test.yml",
101+ "tests/inventory_future_test.yml",
102+ ],
103+ "images": [
104+ "images/inventory_form.png",
105+ "inventory_empty_locations.png",
106+ "images/move_error.png",
107+ "images/location_locked.png",
108+ "images/future_inventory.png",
109+ ],
110+ "demo": ["stock_inventory_location_demo.xml"]
111+}
112
113=== added file 'stock_inventory_location/exceptions.py'
114--- stock_inventory_location/exceptions.py 1970-01-01 00:00:00 +0000
115+++ stock_inventory_location/exceptions.py 2014-07-01 12:00:29 +0000
116@@ -0,0 +1,26 @@
117+# -*- coding: utf-8 -*-
118+##############################################################################
119+#
120+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
121+#
122+# This program is free software: you can redistribute it and/or modify
123+# it under the terms of the GNU General Public License as published by
124+# the Free Software Foundation, either version 3 of the License, or
125+# (at your option) any later version.
126+#
127+# This program is distributed in the hope that it will be useful,
128+# but WITHOUT ANY WARRANTY; without even the implied warranty of
129+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
130+# GNU General Public License for more details.
131+#
132+# You should have received a copy of the GNU General Public License
133+# along with this program. If not, see <http://www.gnu.org/licenses/>.
134+#
135+##############################################################################
136+
137+from openerp.osv import orm
138+
139+
140+class ExhaustiveInventoryException(orm.except_orm):
141+ """The operation is not possible for an exhaustive inventory"""
142+ pass
143
144=== added directory 'stock_inventory_location/i18n'
145=== added file 'stock_inventory_location/i18n/fr.po'
146--- stock_inventory_location/i18n/fr.po 1970-01-01 00:00:00 +0000
147+++ stock_inventory_location/i18n/fr.po 2014-07-01 12:00:29 +0000
148@@ -0,0 +1,250 @@
149+# Translation of OpenERP Server.
150+# This file contains the translation of the following modules:
151+# * stock_inventory_location
152+#
153+msgid ""
154+msgstr ""
155+"Project-Id-Version: OpenERP Server 7.0\n"
156+"Report-Msgid-Bugs-To: \n"
157+"POT-Creation-Date: 2014-07-01 11:55+0000\n"
158+"PO-Revision-Date: 2014-07-01 11:55+0000\n"
159+"Last-Translator: <>\n"
160+"Language-Team: \n"
161+"MIME-Version: 1.0\n"
162+"Content-Type: text/plain; charset=UTF-8\n"
163+"Content-Transfer-Encoding: \n"
164+"Plural-Forms: \n"
165+
166+#. module: stock_inventory_location
167+#: code:addons/stock_inventory_location/stock_inventory_location.py:345
168+#: code:addons/stock_inventory_location/stock_inventory_location.py:346
169+#, python-format
170+msgid "A Physical Inventory is being conducted at the following location(s):\n"
171+"%s"
172+msgstr "Les emplacements suivants sont en inventaire :\n"
173+"%s"
174+
175+#. module: stock_inventory_location
176+#: code:addons/stock_inventory_location/stock_inventory_location.py:280
177+#: code:addons/stock_inventory_location/stock_inventory_location.py:281
178+#: constraint:stock.move:0
179+#, python-format
180+msgid "A Physical Inventory is being conducted at this location"
181+msgstr "Un inventaire est en cours à cet emplacement"
182+
183+#. module: stock_inventory_location
184+#: view:stock.inventory.uninventoried.locations:0
185+msgid "Cancel"
186+msgstr "Annuler"
187+
188+#. module: stock_inventory_location
189+#: help:stock.inventory,exhaustive:0
190+msgid "Check the box if you are conducting an exhaustive Inventory.\n"
191+"Leave the box unchecked if you are conducting a standard Inventory (partial inventory for example).\n"
192+"For an exhaustive Inventory:\n"
193+" - the status \"Draft\" lets you define the list of Locations where goods must be counted\n"
194+" - 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"
195+" - only the Inventory's Locations can be entered in the Inventory Lines\n"
196+" - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory\n"
197+" - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory"
198+msgstr "Cochez la case si vous effectuez un inventaire exhaustif.\n"
199+"Laissez la case non-dochée si vous effectuez un inventaire standard (par exmple un inventaire partiel).\n"
200+"Pour les inventaires exhaustifs :\n"
201+" - le statut \"Brouillon\" permet d'indiquer la liste des emplacements dont la marchandise doit être comptée\n"
202+" - 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"
203+" - seuls les emplacements de l'inventaire peuvent être saisis dans les lignes d'inventaire\n"
204+" - si certains emplacements sont absent des lignes d'inventaire, OpenERP vous en avertit lors de la confirmation de l'inventaire\n"
205+" - 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"
206+
207+#. module: stock_inventory_location
208+#: view:stock.inventory.uninventoried.locations:0
209+msgid "Confirm empty locations"
210+msgstr "Confirmez les emplacements vides"
211+
212+#. module: stock_inventory_location
213+#: code:_description:0
214+#: model:ir.model,name:stock_inventory_location.model_stock_inventory_uninventoried_locations
215+#, python-format
216+msgid "Confirm the uninventoried Locations."
217+msgstr "Confirmer les emplacements non inventoriés."
218+
219+#. module: stock_inventory_location
220+#: code:addons/stock_inventory_location/stock_inventory_location.py:107
221+#, python-format
222+msgid "Error!"
223+msgstr "Erreur!"
224+
225+#. module: stock_inventory_location
226+#: code:addons/stock_inventory_location/stock_inventory_location.py:279
227+#: code:addons/stock_inventory_location/stock_inventory_location.py:280
228+#: code:addons/stock_inventory_location/stock_inventory_location.py:344
229+#: code:addons/stock_inventory_location/stock_inventory_location.py:345
230+#, python-format
231+msgid "Error: Location locked down"
232+msgstr "Erreur: emplacement en inventaire"
233+
234+#. module: stock_inventory_location
235+#: field:stock.fill.inventory,exhaustive:0
236+#: view:stock.inventory:0
237+#: field:stock.inventory,exhaustive:0
238+msgid "Exhaustive"
239+msgstr "Exhaustif"
240+
241+#. module: stock_inventory_location
242+#: view:stock.inventory.uninventoried.locations:0
243+msgid "If you confirm the Inventory, these Locations will be considered empty and their content will be purged."
244+msgstr "Si vous confirmez l'inventaire, ces emplacements seront considérés comme vides et leur contenu sera supprimé."
245+
246+#. module: stock_inventory_location
247+#: code:_description:0
248+#: model:ir.model,name:stock_inventory_location.model_stock_fill_inventory
249+#, python-format
250+msgid "Import Inventory"
251+msgstr "Importer un inventaire"
252+
253+#. module: stock_inventory_location
254+#: field:stock.inventory,location_id:0
255+msgid "Inventoried Location"
256+msgstr "Emplacement inventorié"
257+
258+#. module: stock_inventory_location
259+#: code:_description:0
260+#: model:ir.model,name:stock_inventory_location.model_stock_inventory
261+#, python-format
262+msgid "Inventory"
263+msgstr "Inventaire"
264+
265+#. module: stock_inventory_location
266+#: code:_description:0
267+#: model:ir.model,name:stock_inventory_location.model_stock_inventory_line
268+#, python-format
269+msgid "Inventory Line"
270+msgstr "Ligne d'inventaire"
271+
272+#. module: stock_inventory_location
273+#: view:stock.inventory.uninventoried.locations:0
274+msgid "It could either mean that the Locations are empty, or that the Inventory is not yet complete."
275+msgstr "Cela peut vouloir dire soit que l'inventaire n'est pas terminé, soit que ces emplacements sont effectivement vides."
276+
277+#. module: stock_inventory_location
278+#: code:addons/stock_inventory_location/stock_inventory_location.py:108
279+#, python-format
280+msgid "It's impossible to confirm an inventory in the future. Please change the inventory date to proceed further."
281+msgstr "Il n'est pas possible de confirmer un inventaire à une date future. Veuillez changer la date de l'inventaire pour continuer."
282+
283+#. module: stock_inventory_location
284+#: code:_description:0
285+#: model:ir.model,name:stock_inventory_location.model_stock_location
286+#, python-format
287+msgid "Location"
288+msgstr "Emplacement"
289+
290+#. module: stock_inventory_location
291+#: code:addons/stock_inventory_location/wizard/stock_confirm_uninventoried_location.py:65
292+#, python-format
293+msgid "No product in this location. Please select a location in the product form."
294+msgstr ""
295+"Aucun article dans cet emplacement. Veuillez choisir un emplacement dans le "
296+"formulaire produit."
297+
298+#. module: stock_inventory_location
299+#: view:stock.inventory:0
300+msgid "Only select exhaustive Inventories."
301+msgstr "Ne sélectionne que les inventaires exhaustifs."
302+
303+#. module: stock_inventory_location
304+#: view:stock.inventory:0
305+msgid "Open Inventory"
306+msgstr "Ouvrir l'inventaire"
307+
308+#. module: stock_inventory_location
309+#: constraint:stock.inventory:0
310+msgid "Other Physical inventories are being conducted using the same Locations."
311+msgstr "Certains emplacements sont déjà dans un autre inventaire."
312+
313+#. module: stock_inventory_location
314+#: view:stock.inventory.uninventoried.locations:0
315+msgid "Purge contents and confirm Inventory"
316+msgstr "Supprimer le contenu et confirmer l'inventaire"
317+
318+#. module: stock_inventory_location
319+#: code:_description:0
320+#: model:ir.model,name:stock_inventory_location.model_stock_move
321+#, python-format
322+msgid "Stock Move"
323+msgstr "Mouvement de stock"
324+
325+#. module: stock_inventory_location
326+#: view:stock.inventory.uninventoried.locations:0
327+msgid "The following Locations are empty"
328+msgstr "Les emplacements suivants sont vides"
329+
330+#. module: stock_inventory_location
331+#: view:stock.inventory.uninventoried.locations:0
332+msgid "The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."
333+msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."
334+
335+#. module: stock_inventory_location
336+#: field:stock.inventory.uninventoried.locations,location_ids:0
337+msgid "Uninventoried location"
338+msgstr "Emplacements non inventoriés"
339+
340+#. module: stock_inventory_location
341+#: code:addons/stock_inventory_location/stock_inventory_location.py:254
342+#, python-format
343+msgid "Warning: Wrong location"
344+msgstr "Attention: emplacement incorrect"
345+
346+#. module: stock_inventory_location
347+#: code:addons/stock_inventory_location/stock_inventory_location.py:255
348+#, python-format
349+msgid "You cannot record an Inventory Line for this Location.\n"
350+"You must first add this Location to the list of affected Locations on the Inventory form."
351+msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"
352+"Il ne fait pas partie de la liste des emplacements à inventorier"
353+
354+#. module: stock_inventory_location
355+#: view:stock.inventory:0
356+msgid "confirm_missing_locations"
357+msgstr "confirm_missing_locations"
358+
359+#. module: stock_inventory_location
360+#: view:stock.inventory:0
361+msgid "draft,open,confirm"
362+msgstr "draft,open,confirm"
363+
364+#. module: stock_inventory_location
365+#: view:stock.inventory:0
366+msgid "draft,open,confirm,done"
367+msgstr "draft,open,confirm,done"
368+
369+#. module: stock_inventory_location
370+#: view:stock.inventory:0
371+msgid "onchange_location_id(parent.location_id, parent.exhaustive, location_id)"
372+msgstr "onchange_location_id(parent.location_id, parent.exhaustive, location_id)"
373+
374+#. module: stock_inventory_location
375+#: view:stock.inventory:0
376+msgid "open"
377+msgstr "open"
378+
379+#. module: stock_inventory_location
380+#: view:stock.inventory.uninventoried.locations:0
381+msgid "or"
382+msgstr "ou"
383+
384+#. module: stock_inventory_location
385+#: view:stock.inventory:0
386+msgid "{'default_exhaustive': exhaustive}"
387+msgstr "{'default_exhaustive': exhaustive}"
388+
389+#. module: stock_inventory_location
390+#: view:stock.inventory:0
391+msgid "{'location_id': location_id}"
392+msgstr "{'location_id': location_id}"
393+
394+#. module: stock_inventory_location
395+#: view:stock.fill.inventory:0
396+msgid "{'readonly':[('exhaustive','=',True)]}"
397+msgstr "{'readonly':[('exhaustive','=',True)]}"
398+
399
400=== added file 'stock_inventory_location/i18n/stock_inventory_location.pot'
401--- stock_inventory_location/i18n/stock_inventory_location.pot 1970-01-01 00:00:00 +0000
402+++ stock_inventory_location/i18n/stock_inventory_location.pot 2014-07-01 12:00:29 +0000
403@@ -0,0 +1,239 @@
404+# Translation of OpenERP Server.
405+# This file contains the translation of the following modules:
406+# * stock_inventory_location
407+#
408+msgid ""
409+msgstr ""
410+"Project-Id-Version: OpenERP Server 7.0\n"
411+"Report-Msgid-Bugs-To: \n"
412+"POT-Creation-Date: 2014-07-01 11:54+0000\n"
413+"PO-Revision-Date: 2014-07-01 11:54+0000\n"
414+"Last-Translator: <>\n"
415+"Language-Team: \n"
416+"MIME-Version: 1.0\n"
417+"Content-Type: text/plain; charset=UTF-8\n"
418+"Content-Transfer-Encoding: \n"
419+"Plural-Forms: \n"
420+
421+#. module: stock_inventory_location
422+#: code:addons/stock_inventory_location/stock_inventory_location.py:345
423+#: code:addons/stock_inventory_location/stock_inventory_location.py:346
424+#, python-format
425+msgid "A Physical Inventory is being conducted at the following location(s):\n"
426+"%s"
427+msgstr ""
428+
429+#. module: stock_inventory_location
430+#: code:addons/stock_inventory_location/stock_inventory_location.py:280
431+#: code:addons/stock_inventory_location/stock_inventory_location.py:281
432+#: constraint:stock.move:0
433+#, python-format
434+msgid "A Physical Inventory is being conducted at this location"
435+msgstr ""
436+
437+#. module: stock_inventory_location
438+#: view:stock.inventory.uninventoried.locations:0
439+msgid "Cancel"
440+msgstr ""
441+
442+#. module: stock_inventory_location
443+#: help:stock.inventory,exhaustive:0
444+msgid "Check the box if you are conducting an exhaustive Inventory.\n"
445+"Leave the box unchecked if you are conducting a standard Inventory (partial inventory for example).\n"
446+"For an exhaustive Inventory:\n"
447+" - the status \"Draft\" lets you define the list of Locations where goods must be counted\n"
448+" - 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"
449+" - only the Inventory's Locations can be entered in the Inventory Lines\n"
450+" - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory\n"
451+" - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory"
452+msgstr ""
453+
454+#. module: stock_inventory_location
455+#: view:stock.inventory.uninventoried.locations:0
456+msgid "Confirm empty locations"
457+msgstr ""
458+
459+#. module: stock_inventory_location
460+#: code:_description:0
461+#: model:ir.model,name:stock_inventory_location.model_stock_inventory_uninventoried_locations
462+#, python-format
463+msgid "Confirm the uninventoried Locations."
464+msgstr ""
465+
466+#. module: stock_inventory_location
467+#: code:addons/stock_inventory_location/stock_inventory_location.py:107
468+#, python-format
469+msgid "Error!"
470+msgstr ""
471+
472+#. module: stock_inventory_location
473+#: code:addons/stock_inventory_location/stock_inventory_location.py:279
474+#: code:addons/stock_inventory_location/stock_inventory_location.py:280
475+#: code:addons/stock_inventory_location/stock_inventory_location.py:344
476+#: code:addons/stock_inventory_location/stock_inventory_location.py:345
477+#, python-format
478+msgid "Error: Location locked down"
479+msgstr ""
480+
481+#. module: stock_inventory_location
482+#: field:stock.fill.inventory,exhaustive:0
483+#: view:stock.inventory:0
484+#: field:stock.inventory,exhaustive:0
485+msgid "Exhaustive"
486+msgstr ""
487+
488+#. module: stock_inventory_location
489+#: view:stock.inventory.uninventoried.locations:0
490+msgid "If you confirm the Inventory, these Locations will be considered empty and their content will be purged."
491+msgstr ""
492+
493+#. module: stock_inventory_location
494+#: code:_description:0
495+#: model:ir.model,name:stock_inventory_location.model_stock_fill_inventory
496+#, python-format
497+msgid "Import Inventory"
498+msgstr ""
499+
500+#. module: stock_inventory_location
501+#: field:stock.inventory,location_id:0
502+msgid "Inventoried Location"
503+msgstr ""
504+
505+#. module: stock_inventory_location
506+#: code:_description:0
507+#: model:ir.model,name:stock_inventory_location.model_stock_inventory
508+#, python-format
509+msgid "Inventory"
510+msgstr ""
511+
512+#. module: stock_inventory_location
513+#: code:_description:0
514+#: model:ir.model,name:stock_inventory_location.model_stock_inventory_line
515+#, python-format
516+msgid "Inventory Line"
517+msgstr ""
518+
519+#. module: stock_inventory_location
520+#: view:stock.inventory.uninventoried.locations:0
521+msgid "It could either mean that the Locations are empty, or that the Inventory is not yet complete."
522+msgstr ""
523+
524+#. module: stock_inventory_location
525+#: code:addons/stock_inventory_location/stock_inventory_location.py:108
526+#, python-format
527+msgid "It's impossible to confirm an inventory in the future. Please change the inventory date to proceed further."
528+msgstr ""
529+
530+#. module: stock_inventory_location
531+#: code:_description:0
532+#: model:ir.model,name:stock_inventory_location.model_stock_location
533+#, python-format
534+msgid "Location"
535+msgstr ""
536+
537+#. module: stock_inventory_location
538+#: code:addons/stock_inventory_location/wizard/stock_confirm_uninventoried_location.py:65
539+#, python-format
540+msgid "No product in this location. Please select a location in the product form."
541+msgstr ""
542+
543+#. module: stock_inventory_location
544+#: view:stock.inventory:0
545+msgid "Only select exhaustive Inventories."
546+msgstr ""
547+
548+#. module: stock_inventory_location
549+#: view:stock.inventory:0
550+msgid "Open Inventory"
551+msgstr ""
552+
553+#. module: stock_inventory_location
554+#: constraint:stock.inventory:0
555+msgid "Other Physical inventories are being conducted using the same Locations."
556+msgstr ""
557+
558+#. module: stock_inventory_location
559+#: view:stock.inventory.uninventoried.locations:0
560+msgid "Purge contents and confirm Inventory"
561+msgstr ""
562+
563+#. module: stock_inventory_location
564+#: code:_description:0
565+#: model:ir.model,name:stock_inventory_location.model_stock_move
566+#, python-format
567+msgid "Stock Move"
568+msgstr ""
569+
570+#. module: stock_inventory_location
571+#: view:stock.inventory.uninventoried.locations:0
572+msgid "The following Locations are empty"
573+msgstr ""
574+
575+#. module: stock_inventory_location
576+#: view:stock.inventory.uninventoried.locations:0
577+msgid "The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."
578+msgstr ""
579+
580+#. module: stock_inventory_location
581+#: field:stock.inventory.uninventoried.locations,location_ids:0
582+msgid "Uninventoried location"
583+msgstr ""
584+
585+#. module: stock_inventory_location
586+#: code:addons/stock_inventory_location/stock_inventory_location.py:254
587+#, python-format
588+msgid "Warning: Wrong location"
589+msgstr ""
590+
591+#. module: stock_inventory_location
592+#: code:addons/stock_inventory_location/stock_inventory_location.py:255
593+#, python-format
594+msgid "You cannot record an Inventory Line for this Location.\n"
595+"You must first add this Location to the list of affected Locations on the Inventory form."
596+msgstr ""
597+
598+#. module: stock_inventory_location
599+#: view:stock.inventory:0
600+msgid "confirm_missing_locations"
601+msgstr ""
602+
603+#. module: stock_inventory_location
604+#: view:stock.inventory:0
605+msgid "draft,open,confirm"
606+msgstr ""
607+
608+#. module: stock_inventory_location
609+#: view:stock.inventory:0
610+msgid "draft,open,confirm,done"
611+msgstr ""
612+
613+#. module: stock_inventory_location
614+#: view:stock.inventory:0
615+msgid "onchange_location_id(parent.location_id, parent.exhaustive, location_id)"
616+msgstr ""
617+
618+#. module: stock_inventory_location
619+#: view:stock.inventory:0
620+msgid "open"
621+msgstr ""
622+
623+#. module: stock_inventory_location
624+#: view:stock.inventory.uninventoried.locations:0
625+msgid "or"
626+msgstr ""
627+
628+#. module: stock_inventory_location
629+#: view:stock.inventory:0
630+msgid "{'default_exhaustive': exhaustive}"
631+msgstr ""
632+
633+#. module: stock_inventory_location
634+#: view:stock.inventory:0
635+msgid "{'location_id': location_id}"
636+msgstr ""
637+
638+#. module: stock_inventory_location
639+#: view:stock.fill.inventory:0
640+msgid "{'readonly':[('exhaustive','=',True)]}"
641+msgstr ""
642+
643
644=== added directory 'stock_inventory_location/images'
645=== added file 'stock_inventory_location/images/future_inventory.png'
646Binary files stock_inventory_location/images/future_inventory.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/future_inventory.png 2014-07-01 12:00:29 +0000 differ
647=== added file 'stock_inventory_location/images/inventory_empty_locations.png'
648Binary 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-07-01 12:00:29 +0000 differ
649=== added file 'stock_inventory_location/images/inventory_form.png'
650Binary files stock_inventory_location/images/inventory_form.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/inventory_form.png 2014-07-01 12:00:29 +0000 differ
651=== added file 'stock_inventory_location/images/location_locked.png'
652Binary files stock_inventory_location/images/location_locked.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/location_locked.png 2014-07-01 12:00:29 +0000 differ
653=== added file 'stock_inventory_location/images/move_error.png'
654Binary files stock_inventory_location/images/move_error.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/move_error.png 2014-07-01 12:00:29 +0000 differ
655=== added directory 'stock_inventory_location/static'
656=== added directory 'stock_inventory_location/static/src'
657=== added directory 'stock_inventory_location/static/src/img'
658=== added file 'stock_inventory_location/static/src/img/icon.png'
659Binary 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-07-01 12:00:29 +0000 differ
660=== added file 'stock_inventory_location/stock_inventory_location.py'
661--- stock_inventory_location/stock_inventory_location.py 1970-01-01 00:00:00 +0000
662+++ stock_inventory_location/stock_inventory_location.py 2014-07-01 12:00:29 +0000
663@@ -0,0 +1,353 @@
664+# -*- coding: utf-8 -*-
665+##############################################################################
666+#
667+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
668+#
669+# This program is free software: you can redistribute it and/or modify
670+# it under the terms of the GNU General Public License as published by
671+# the Free Software Foundation, either version 3 of the License, or
672+# (at your option) any later version.
673+#
674+# This program is distributed in the hope that it will be useful,
675+# but WITHOUT ANY WARRANTY; without even the implied warranty of
676+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
677+# GNU General Public License for more details.
678+#
679+# You should have received a copy of the GNU General Public License
680+# along with this program. If not, see <http://www.gnu.org/licenses/>.
681+#
682+##############################################################################
683+
684+import time
685+from collections import Iterable
686+
687+from openerp.osv import orm, fields
688+from openerp.tools.translate import _
689+# The next 2 imports are only needed for a feature backported from trunk-wms
690+# TODOv8! remove, feature is included upstream
691+from openerp.osv import osv
692+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
693+from openerp import SUPERUSER_ID
694+
695+from .exceptions import ExhaustiveInventoryException
696+
697+
698+class StockInventory(orm.Model):
699+ """Add locations to the inventories"""
700+ _inherit = 'stock.inventory'
701+
702+ INVENTORY_STATE_SELECTION = [
703+ ('draft', 'Draft'),
704+ ('open', 'Open'),
705+ ('done', 'Done'),
706+ ('confirm', 'Confirmed'),
707+ ('cancel', 'Cancelled')
708+ ]
709+
710+ _columns = {
711+ # TODO v8: should we use "confirm" instead of adding "open"?
712+ 'state': fields.selection(
713+ INVENTORY_STATE_SELECTION, 'State', readonly=True, select=True),
714+ # TODO v8: remove this, should not be needed anymore
715+ # Make the inventory lines read-only in all states except "Open",
716+ # to ensure that no unwanted Location can be inserted
717+ 'inventory_line_id': fields.one2many(
718+ 'stock.inventory.line', 'inventory_id', 'Inventory lines',
719+ readonly=True, states={'open': [('readonly', False)]}),
720+ # TODO v8: remove this, it's backported from v8
721+ 'location_id': fields.many2one(
722+ 'stock.location', 'Inventoried Location',
723+ readonly=True, states={'draft': [('readonly', False)]}),
724+ 'exhaustive': fields.boolean(
725+ 'Exhaustive', readonly=True,
726+ states={'draft': [('readonly', False)]},
727+ help="Check the box if you are conducting an exhaustive "
728+ "Inventory.\n"
729+ "Leave the box unchecked if you are conducting a standard "
730+ "Inventory (partial inventory for example).\n"
731+ "For an exhaustive Inventory:\n"
732+ " - the status \"Draft\" lets you define the list of "
733+ "Locations where goods must be counted\n"
734+ " - the status \"Open\" indicates that the list of Locations "
735+ "is definitive and you are now counting the goods. In that "
736+ "status, no Stock Moves can be recorded in/out of the "
737+ "Inventory's Locations\n"
738+ " - only the Inventory's Locations can be entered in the "
739+ "Inventory Lines\n"
740+ " - if some of the Inventory's Locations have not been "
741+ "entered in the Inventory Lines, OpenERP warns you "
742+ "when you confirm the Inventory\n"
743+ " - every good that is not in the Inventory Lines is "
744+ "considered lost, and gets moved out of the stock when you "
745+ "confirm the Inventory"),
746+ }
747+
748+ def action_open(self, cr, uid, ids, context=None):
749+ """Change the state of the inventory to 'open'"""
750+ return self.write(cr, uid, ids, {'state': 'open'}, context=context)
751+
752+ # TODO v8: remove this method? the feature looks already done upstream
753+ def action_done(self, cr, uid, ids, context=None):
754+ """
755+ Don't allow to make an inventory with a date in the future.
756+
757+ This makes sure no stock moves will be introduced between the
758+ moment you finish the inventory and the date of the Stock Moves.
759+ Backported from trunk-wms:
760+ revid:qdp-launchpad@openerp.com-20140317090656-o7lo22tzm8yuv3r8
761+
762+ @raise osv.except_osv:
763+ We raise this exception on purpose instead of
764+ ExhaustiveInventoryException to ensure forward-compatibility
765+ with v8.
766+ """
767+ for inventory in self.browse(cr, uid, ids, context=None):
768+ if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
769+ raise osv.except_osv(
770+ _('Error!'),
771+ _('It\'s impossible to confirm an inventory in the '
772+ 'future. Please change the inventory date to proceed '
773+ 'further.'))
774+ return super(StockInventory, self).action_done(cr, uid, ids,
775+ context=context)
776+
777+ # TODO: remove this in v8
778+ def _default_location(self, cr, uid, ids, context=None):
779+ """Default stock location
780+
781+ @return: id of the stock location of the first warehouse of the
782+ default company"""
783+ location_id = False
784+ company_id = self.pool['res.company']._company_default_get(
785+ cr, uid, 'stock.warehouse', context=context)
786+ warehouse_id = self.pool['stock.warehouse'].search(
787+ cr, uid, [('company_id', '=', company_id)], limit=1)
788+ if warehouse_id:
789+ location_id = self.pool['stock.warehouse'].read(
790+ cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
791+ return location_id
792+
793+ _defaults = {
794+ 'state': 'draft',
795+ 'exhaustive': False,
796+ # TODO: remove this in v8
797+ 'location_id': _default_location,
798+ }
799+
800+ def _check_location_free_from_inventories(self, cr, uid, ids):
801+ """
802+ Verify that no other Inventory is being conducted on the same locations
803+
804+ Open Inventories are matched using the exact Location IDs,
805+ excluding children.
806+ """
807+ # We don't get a context because we're called from a _constraint
808+ for inventory in self.browse(cr, uid, ids):
809+ if not inventory.exhaustive:
810+ # never block standard inventories
811+ continue
812+ if self.search(cr, uid,
813+ [('location_id', '=', inventory.location_id.id),
814+ ('id', '!=', inventory.id),
815+ ('date', '=', inventory.date),
816+ ('exhaustive', '=', True)]):
817+ # Quit as soon as one offending inventory is found
818+ return False
819+ return True
820+
821+ _constraints = [
822+ (_check_location_free_from_inventories,
823+ 'Other Physical inventories are being conducted using the same '
824+ 'Locations.',
825+ ['location_id', 'date', 'exhaustive'])
826+ ]
827+
828+ def _get_locations_open_inventories(self, cr, uid, context=None):
829+ """IDs of location in open exhaustive inventories, with children"""
830+ inv_ids = self.search(
831+ cr, uid, [('state', '=', 'open'), ('exhaustive', '=', True)],
832+ context=context)
833+ if not inv_ids:
834+ # Early exit if no match found
835+ return []
836+ # List the Locations - normally all exhaustive inventories have one
837+ location_ids = [inventory.location_id.id
838+ for inventory in self.browse(cr, uid, inv_ids,
839+ context=context)]
840+ # Extend to the children Locations
841+ return self.pool['stock.location'].search(
842+ cr, uid,
843+ [('location_id', 'child_of', set(location_ids)),
844+ ('usage', '=', 'internal')],
845+ context=context)
846+
847+ def get_missing_locations(self, cr, uid, ids, context=None):
848+ """Compute the list of location_ids which are missing from the lines
849+
850+ Here, "missing" means the location is the inventory's location or one
851+ of it's children, and the inventory does not contain any line with
852+ this location."""
853+ inventories = self.browse(cr, uid, ids, context=context)
854+ # Find the locations of the inventories
855+ inv_location_ids = [i.location_id.id for i in inventories]
856+ # Extend to the children locations
857+ inv_location_ids = set(self.pool['stock.location'].search(
858+ cr, uid, [
859+ ('location_id', 'child_of', inv_location_ids),
860+ ('usage', '=', 'internal')], context=context))
861+ # Find the locations already recorded in inventory lines
862+ line_locations_ids = set([l.location_id.id
863+ for i in inventories
864+ for l in i.inventory_line_id])
865+ return list(inv_location_ids - line_locations_ids)
866+
867+ def confirm_missing_locations(self, cr, uid, ids, context=None):
868+ """Open wizard to confirm empty locations on exhaustive inventories"""
869+ for inventory in self.browse(cr, uid, ids, context=context):
870+ if (self.get_missing_locations(cr, uid, ids, context=context)
871+ and inventory.exhaustive):
872+ return {
873+ 'type': 'ir.actions.act_window',
874+ 'view_type': 'form',
875+ 'view_mode': 'form',
876+ 'res_model': 'stock.inventory.uninventoried.locations',
877+ 'target': 'new',
878+ 'context': dict(context,
879+ active_ids=ids,
880+ active_id=ids[0]),
881+ 'nodestroy': True,
882+ }
883+ return self.action_confirm(cr, uid, ids, context=context)
884+
885+
886+class StockInventoryLine(orm.Model):
887+ """Only allow the Inventory's Locations"""
888+
889+ _inherit = 'stock.inventory.line'
890+
891+ def _default_stock_location(self, cr, uid, context=None):
892+ res = super(StockInventoryLine, self)._default_stock_location(
893+ cr, uid, context=context)
894+ if context is None or not context.get('location_id', False):
895+ return res
896+ else:
897+ return context['location_id']
898+
899+ _defaults = {
900+ 'location_id': _default_stock_location
901+ }
902+
903+ def onchange_location_id(self, cr, uid, ids, inventory_location_id,
904+ exhaustive, location_id, context=None):
905+ """Warn if the new is not in the location list for this inventory."""
906+ if not exhaustive:
907+ # Don't check if partial inventory
908+ return True
909+
910+ # search children of location
911+ if location_id not in self.pool['stock.location'].search(
912+ cr, uid, [('location_id', 'child_of', inventory_location_id)],
913+ context=context):
914+ return {
915+ 'value': {'location_id': False},
916+ 'warning': {
917+ 'title': _('Warning: Wrong location'),
918+ 'message': _(
919+ "You cannot record an Inventory Line for this "
920+ "Location.\n"
921+ "You must first add this Location to the list of "
922+ "affected Locations on the Inventory form.")}
923+ }
924+ return True
925+
926+
927+class StockLocation(orm.Model):
928+ """Refuse changes during exhaustive Inventories"""
929+ _inherit = 'stock.location'
930+ _order = 'name'
931+
932+ def _check_inventory(self, cr, uid, ids, context=None):
933+ """Error if an exhaustive Inventory is being conducted here"""
934+ inv_obj = self.pool['stock.inventory']
935+ location_inventory_open_ids = inv_obj._get_locations_open_inventories(
936+ cr, SUPERUSER_ID, context=context)
937+ if not isinstance(ids, Iterable):
938+ ids = [ids]
939+ for inv_id in ids:
940+ if inv_id in location_inventory_open_ids:
941+ raise ExhaustiveInventoryException(
942+ _('Error: Location locked down'),
943+ _('A Physical Inventory is being conducted at this '
944+ 'location'))
945+ return True
946+
947+ def write(self, cr, uid, ids, vals, context=None):
948+ """Refuse write if an inventory is being conducted"""
949+ self._check_inventory(cr, uid, ids, context=context)
950+ if not isinstance(ids, Iterable):
951+ ids_to_check = [ids]
952+ else:
953+ # Copy the data to avoid changing 'ids', it would trigger an infinite recursion
954+ ids_to_check = list(ids)
955+ # If changing the parent, no inventory must conducted there either
956+ if vals.get('location_id'):
957+ ids_to_check.append(vals['location_id'])
958+ self._check_inventory(cr, uid, ids_to_check, context=context)
959+ return super(StockLocation, self).write(cr, uid, ids, vals,
960+ context=context)
961+
962+ def create(self, cr, uid, vals, context=None):
963+ """Refuse create if an inventory is being conducted at the parent"""
964+ self._check_inventory(cr, uid, vals.get('location_id'),
965+ context=context)
966+ return super(StockLocation, self).create(cr, uid, vals,
967+ context=context)
968+
969+ def unlink(self, cr, uid, ids, context=None):
970+ """Refuse unlink if an inventory is being conducted"""
971+ self._check_inventory(cr, uid, ids, context=context)
972+ return super(StockLocation, self).unlink(cr, uid, ids, context=context)
973+
974+
975+class StockMove(orm.Model):
976+ """Refuse Moves during exhaustive Inventories"""
977+
978+ _inherit = 'stock.move'
979+
980+ # TODOv7: adapt this to match trunk-wms
981+ def _check_open_inventory_location(self, cr, uid, ids, context=None):
982+ """
983+ Check that the locations are not locked by an open inventory
984+
985+ Standard inventories are not checked.
986+
987+ @raise ExhaustiveInventoryException: an error is raised if locations
988+ are locked, instead of returning False, in order to pass an
989+ extensive error message back to users.
990+ """
991+ message = ""
992+ inv_obj = self.pool['stock.inventory']
993+ locked_location_ids = inv_obj._get_locations_open_inventories(
994+ cr, SUPERUSER_ID, context=context)
995+ if not locked_location_ids:
996+ # Nothing to verify
997+ return True
998+ for move in self.browse(cr, uid, ids, context=context):
999+ if (move.location_id.usage != 'inventory'
1000+ and move.location_dest_id.id in locked_location_ids):
1001+ message += " - %s\n" % (move.location_dest_id.name)
1002+ if (move.location_dest_id.usage != 'inventory'
1003+ and move.location_id.id in locked_location_ids):
1004+ message += " - %s\n" % (move.location_id.name)
1005+ if message:
1006+ raise ExhaustiveInventoryException(
1007+ _('Error: Location locked down'),
1008+ _('A Physical Inventory is being conducted at the following '
1009+ 'location(s):\n%s') % message)
1010+ return True
1011+
1012+ _constraints = [
1013+ (_check_open_inventory_location,
1014+ "A Physical Inventory is being conducted at this location",
1015+ ['location_id', 'location_dest_id']),
1016+ ]
1017
1018=== added file 'stock_inventory_location/stock_inventory_location_demo.xml'
1019--- stock_inventory_location/stock_inventory_location_demo.xml 1970-01-01 00:00:00 +0000
1020+++ stock_inventory_location/stock_inventory_location_demo.xml 2014-07-01 12:00:29 +0000
1021@@ -0,0 +1,26 @@
1022+<?xml version="1.0" encoding="utf-8"?>
1023+<openerp>
1024+ <data noupdate="0">
1025+ <!-- Record a non exhaustive inventory -->
1026+ <record id="inventory_standard" model="stock.inventory">
1027+ <field name="name">Standard inventory</field>
1028+ <field name="state">draft</field>
1029+ </record>
1030+
1031+ <!-- Record an exhaustive inventory -->
1032+ <record id="inventory_exhaustive" model="stock.inventory">
1033+ <field name="name">Exhaustive inventory</field>
1034+ <field name="state">draft</field>
1035+ <field name="exhaustive">True</field>
1036+ <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
1037+ </record>
1038+
1039+ <!-- Record an inventory dated in the future -->
1040+ <!-- TODOv8: remove this entry, only useful for a test already in trunk-wms -->
1041+ <record id="inventory_future" model="stock.inventory">
1042+ <field name="name">Inventory in the future</field>
1043+ <field name="state">draft</field>
1044+ <field name="date">2020-03-01 00:00:00</field>
1045+ </record>
1046+ </data>
1047+</openerp>
1048
1049=== added file 'stock_inventory_location/stock_inventory_location_view.xml'
1050--- stock_inventory_location/stock_inventory_location_view.xml 1970-01-01 00:00:00 +0000
1051+++ stock_inventory_location/stock_inventory_location_view.xml 2014-07-01 12:00:29 +0000
1052@@ -0,0 +1,81 @@
1053+<?xml version="1.0" encoding="utf-8"?>
1054+<openerp>
1055+ <data>
1056+
1057+ <record model="ir.ui.view" id="stock_inventory_location_form_view">
1058+ <field name="name">stock.inventory.location.form</field>
1059+ <field name="model">stock.inventory</field>
1060+ <field name="inherit_id" ref="stock.view_inventory_form"/>
1061+ <field name="priority" eval="10"/>
1062+ <field name="arch" type="xml">
1063+ <!-- Show the state 'done' in the statusbar -->
1064+ <xpath expr="/form//field[@name='state']" position="attributes">
1065+ <attribute name="statusbar_visible">draft,open,confirm</attribute>
1066+ </xpath>
1067+
1068+ <!-- TODO v8 place "exhaustive" next to "location_id" -->
1069+ <!-- Add type of inventory: standard or exhaustive. -->
1070+ <xpath expr="/form//field[@name='name']" position="after">
1071+ <field name="exhaustive"/>
1072+ <field name="location_id" groups="stock.group_locations"
1073+ domain="[('usage','in',('view', 'internal'))]"
1074+ attrs="{'invisible':[('exhaustive','!=',True)], 'required':[('exhaustive','=',True)]}"/>
1075+ </xpath>
1076+
1077+ <!-- Enable Fill Inventory button when state is open -->
1078+ <xpath expr="//button[@string='Fill Inventory']" position="attributes">
1079+ <attribute name="states">open</attribute>
1080+ <attribute name="context">{'default_exhaustive': exhaustive}</attribute>
1081+ </xpath>
1082+
1083+ <!-- Control locations added by user on inventory line -->
1084+ <xpath expr="/form//field[@name='inventory_line_id']"
1085+ position="attributes">
1086+ <attribute name="context">{'location_id': location_id}</attribute>
1087+ </xpath>
1088+ <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']"
1089+ position="attributes">
1090+ <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
1091+ </xpath>
1092+ <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']"
1093+ position="attributes">
1094+ <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
1095+ </xpath>
1096+
1097+ <!-- Add button to open an inventory. Call wizard on confirm inventory -->
1098+ <xpath expr="/form//button[@name='action_cancel_inventory']" position="before">
1099+ <button name="action_open" states="draft" string="Open Inventory" type="object" class="oe_highlight" groups="stock.group_stock_user"/>
1100+ </xpath>
1101+ <!-- Show the "cancel" button in state "open" -->
1102+ <xpath expr="/form//button[@name='action_cancel_inventory']" position="attributes">
1103+ <attribute name="states">draft,open,confirm,done</attribute>
1104+ </xpath>
1105+ <!-- hijack the "confirm" button -->
1106+ <xpath expr="/form//button[@name='action_confirm']" position="attributes">
1107+ <attribute name="states">open</attribute>
1108+ <attribute name="name">confirm_missing_locations</attribute>
1109+ </xpath>
1110+ </field>
1111+ </record>
1112+
1113+ <!-- Add filter for complete or partial inventory -->
1114+ <record model="ir.ui.view" id="view_inventory_complete_filter">
1115+ <field name="name">complete.inventory.filter</field>
1116+ <field name="model">stock.inventory</field>
1117+ <field name="inherit_id" ref="stock.view_inventory_filter"/>
1118+ <field name="arch" type="xml">
1119+ <xpath expr="//field[@name='name']" position="before">
1120+ <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]"
1121+ help="Only select exhaustive Inventories." />
1122+ <separator orientation="vertical"/>
1123+ </xpath>
1124+ </field>
1125+ </record>
1126+
1127+ <!-- Show exhaustive inventories by default -->
1128+ <record id="stock.action_inventory_form" model="ir.actions.act_window">
1129+ <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>
1130+ </record>
1131+
1132+ </data>
1133+</openerp>
1134\ No newline at end of file
1135
1136=== added directory 'stock_inventory_location/tests'
1137=== added file 'stock_inventory_location/tests/__init__.py'
1138--- stock_inventory_location/tests/__init__.py 1970-01-01 00:00:00 +0000
1139+++ stock_inventory_location/tests/__init__.py 2014-07-01 12:00:29 +0000
1140@@ -0,0 +1,39 @@
1141+# -*- coding: utf-8 -*-
1142+#
1143+#
1144+# Authors: Laetitia Gangloff
1145+# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
1146+# All Rights Reserved
1147+#
1148+# WARNING: This program as such is intended to be used by professional
1149+# programmers who take the whole responsibility of assessing all potential
1150+# consequences resulting from its eventual inadequacies and bugs.
1151+# End users who are looking for a ready-to-use solution with commercial
1152+# guarantees and support are strongly advised to contact a Free Software
1153+# Service Company.
1154+#
1155+# This program is free software: you can redistribute it and/or modify
1156+# it under the terms of the GNU Affero General Public License as
1157+# published by the Free Software Foundation, either version 3 of the
1158+# License, or (at your option) any later version.
1159+#
1160+# This program is distributed in the hope that it will be useful,
1161+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1162+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1163+# GNU Affero General Public License for more details.
1164+#
1165+# You should have received a copy of the GNU Affero General Public License
1166+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1167+#
1168+#
1169+
1170+import stock_inventory_location_test
1171+
1172+fast_suite = [
1173+]
1174+
1175+checks = [
1176+ stock_inventory_location_test,
1177+]
1178+
1179+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1180
1181=== added file 'stock_inventory_location/tests/inventory_exhaustive_test.yml'
1182--- stock_inventory_location/tests/inventory_exhaustive_test.yml 1970-01-01 00:00:00 +0000
1183+++ stock_inventory_location/tests/inventory_exhaustive_test.yml 2014-07-01 12:00:29 +0000
1184@@ -0,0 +1,128 @@
1185+-
1186+ This file will test an exhaustive inventory.
1187+ I will call open_action method and check if state of inventories are 'open'.
1188+-
1189+ !python {model: stock.inventory}: |
1190+ self.action_open(cr, uid, [ref('inventory_exhaustive')])
1191+ inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
1192+ assert inventory_state == 'open', "Inventory in state '%s'. It should be 'open'" % inventory_state
1193+-
1194+ I will check that the function get_missing_locations return some locations.
1195+-
1196+ !python {model: stock.inventory}: |
1197+ missing_loc_ids = self.get_missing_locations(cr, uid, [ref('inventory_exhaustive')], context=context)
1198+ assert len(missing_loc_ids), "get_missing_locations did not return any ID."
1199+-
1200+ I create a wizard record for stock_confirm_uninventoried_location to verify that it contains the uninventoried locations
1201+-
1202+ !python {model: stock.inventory.uninventoried.locations}: |
1203+ ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
1204+ wizard_id = self.create(cr, uid, {}, context=ctx)
1205+ wizard = self.browse(cr, uid, wizard_id, context=ctx)
1206+ assert len(wizard.location_ids) > 0 , "The wizard does not contain any lines."
1207+-
1208+ I add products to exhaustive inventory.
1209+ Adding 17” LCD Monitor.
1210+-
1211+ !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
1212+ product_id: product.product_product_7
1213+ product_uom: product.product_uom_unit
1214+ company_id: base.main_company
1215+ inventory_id: inventory_exhaustive
1216+ product_qty: 18.0
1217+ location_id: stock.stock_location_14
1218+
1219+-
1220+ Adding USB Keyboard, QWERTY.
1221+-
1222+ !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
1223+ product_id: product.product_product_8
1224+ product_uom: product.product_uom_unit
1225+ company_id: base.main_company
1226+ inventory_id: inventory_exhaustive
1227+ product_qty: 5.0
1228+ location_id: stock.stock_location_14
1229+
1230+-
1231+ I will call the function _get_locations_open_inventories and check the result.
1232+ The function will return only the location_id of the exhaustive inventory.
1233+-
1234+ !python {model: stock.inventory}: |
1235+ locations = self._get_locations_open_inventories(cr, uid)
1236+ assert len(locations) == 1, "Function return wrong results: %s" % locations
1237+ 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])
1238+-
1239+ I will call the function onchange_location_id.
1240+ The function must return True in the first case, and return a warning dictionnary in the second test.
1241+-
1242+ !python {model: stock.inventory.line}: |
1243+ res = self.onchange_location_id(cr, uid, [], ref('stock.stock_location_14'), True, ref('stock.stock_location_14'))
1244+ assert res == True, "Exhaustive: The function 'onchange_location_id' should return True and return '%s'" % res
1245+ res = self.onchange_location_id(cr, uid, [], ref('stock.stock_location_14'), True, ref('stock.stock_location_components'))
1246+ assert res.get('warning', False) != False , "Function 'onchange_location_id': Warning not raise. ('%s)" % res
1247+
1248+-
1249+ I will check that the function get_missing_locations does not return any locations.
1250+-
1251+ !python {model: stock.inventory}: |
1252+ missing_loc_ids = self.get_missing_locations(cr, uid, [ref('inventory_exhaustive')], context=context)
1253+ assert not missing_loc_ids, "get_missing_locations should not return IDs but returned %s" % missing_loc_ids
1254+-
1255+ I create a wizard record for stock_confirm_uninventoried_location and validate it
1256+-
1257+ !python {model: stock.inventory.uninventoried.locations}: |
1258+ ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
1259+ wizard_id = self.create(cr, uid, {}, context=ctx)
1260+ wizard = self.browse(cr, uid, wizard_id, context=ctx)
1261+ assert len(wizard.location_ids) == 0 , "The wizard should not contain any lines but contains %s." % wizard.location_ids
1262+ self.confirm_uninventoried_locations(cr, uid, wizard_id, context=ctx)
1263+-
1264+ Stock moves are not allowed in the locations during the inventory.
1265+-
1266+ !python {model: stock.move}: |
1267+ # TODOv8: remove this test, this is already part of trunk-wms
1268+ from stock_inventory_location import ExhaustiveInventoryException
1269+ try:
1270+ self.create(
1271+ cr,uid, {
1272+ 'name': 'Bad move',
1273+ 'location_id': ref('stock.stock_location_14'),
1274+ 'location_dest_id': ref('stock.stock_location_3'),
1275+ 'product_id': ref('product.product_product_8'),
1276+ 'product_uom': ref('product.product_uom_unit'),
1277+ 'date_expected': '2020-01-01 00:00:00'
1278+ })
1279+ except ExhaustiveInventoryException as e:
1280+ log("Good! The Stock Move was refused: %s" % e)
1281+-
1282+ I will confirm the exhaustive inventory
1283+-
1284+ !python {model: stock.inventory}: |
1285+ self.action_confirm(cr, uid, [ref('inventory_exhaustive')])
1286+ inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
1287+ assert inventory_state == 'confirm', "Exhaustive inventory is in state '%s'. It should be 'confirm'" % inventory_state
1288+
1289+-
1290+ I will validate the exhaustive inventory
1291+-
1292+ !python {model: stock.inventory}: |
1293+ self.action_done(cr, uid, [ref('inventory_exhaustive')])
1294+ inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
1295+ assert inventory_state == 'done', "Exhaustive inventory is in state '%s'. It should be 'done'" % inventory_state
1296+
1297+-
1298+ I will verify the quantity for each products.
1299+-
1300+ !python {model: product.product}: |
1301+ ctx = dict(context, location=[ref('stock.stock_location_14')])
1302+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_7')], ['qty_available'], context=ctx)[0]['qty_available']
1303+ assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0: %s" % prod_qty_avail
1304+
1305+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_8')], ['qty_available'], context=ctx)[0]['qty_available']
1306+ assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0: %s" % prod_qty_avail
1307+
1308+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_24')], ['qty_available'], context=ctx)[0]['qty_available']
1309+ assert prod_qty_avail == 0.0, "The stock of KEYA was not set to 0: %s" % prod_qty_avail
1310+
1311+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_25')], ['qty_available'], context=ctx)[0]['qty_available']
1312+ assert prod_qty_avail == 0.0, "The stock of MOU was not set to 0: %s" % prod_qty_avail
1313
1314=== added file 'stock_inventory_location/tests/inventory_future_test.yml'
1315--- stock_inventory_location/tests/inventory_future_test.yml 1970-01-01 00:00:00 +0000
1316+++ stock_inventory_location/tests/inventory_future_test.yml 2014-07-01 12:00:29 +0000
1317@@ -0,0 +1,13 @@
1318+-
1319+ Check that an inventory with a date in the future cannot be opened.
1320+-
1321+ !python {model: stock.inventory}: |
1322+ # TODO v8: maybe this is already part of the new WMS
1323+ from osv.osv import except_osv
1324+ self.action_open(cr, uid, [ref('inventory_future')])
1325+ try:
1326+ self.action_done(cr, uid, [ref('inventory_future')])
1327+ except except_osv as e:
1328+ log("Good! The Inventory could not be opened: %s" % e)
1329+ inventory_state = self.read(cr, uid, [ref('inventory_future')], ['state'])[0]['state']
1330+ assert inventory_state != 'done', "Future inventory is done."
1331
1332=== added file 'stock_inventory_location/tests/inventory_standard_test.yml'
1333--- stock_inventory_location/tests/inventory_standard_test.yml 1970-01-01 00:00:00 +0000
1334+++ stock_inventory_location/tests/inventory_standard_test.yml 2014-07-01 12:00:29 +0000
1335@@ -0,0 +1,100 @@
1336+-
1337+ This file will test a non exhaustive inventory.
1338+ I will call open_action method and check if state of inventories are 'open'.
1339+-
1340+ !python {model: stock.inventory}: |
1341+ self.action_open(cr, uid, [ref('inventory_standard')])
1342+ inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
1343+ assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state
1344+
1345+-
1346+ In order, I add products to inventory.
1347+ Adding Azerty Keyboard.
1348+-
1349+ !record {model: stock.inventory.line, id: lines_inventory_location_kbaz}:
1350+ product_id: product.product_product_9
1351+ product_uom: product.product_uom_unit
1352+ company_id: base.main_company
1353+ inventory_id: inventory_standard
1354+ product_qty: 18.0
1355+ location_id: stock.stock_location_components
1356+
1357+-
1358+ Adding Optical Mouse.
1359+-
1360+ !record {model: stock.inventory.line, id: lines_inventory_location_optm}:
1361+ product_id: product.product_product_10
1362+ product_uom: product.product_uom_unit
1363+ company_id: base.main_company
1364+ inventory_id: inventory_standard
1365+ product_qty: 12.0
1366+ location_id: stock.stock_location_components
1367+
1368+-
1369+ Adding Multimedia Speakers.
1370+-
1371+ !record {model: stock.inventory.line, id: lines_inventory_location_grca}:
1372+ product_id: product.product_template_31
1373+ product_uom: product.product_uom_unit
1374+ company_id: base.main_company
1375+ inventory_id: inventory_standard
1376+ product_qty: 32.0
1377+ location_id: stock.stock_location_components
1378+
1379+-
1380+ I will call the function _get_locations_open_inventories and check the result.
1381+ The function will return no locations because it's not an exhaustive inventory.
1382+-
1383+ !python {model: stock.inventory}: |
1384+ locations = self._get_locations_open_inventories(cr, uid)
1385+ assert len(locations) == 0, "Function return wrong results: %s" % locations
1386+
1387+-
1388+ I will call the function onchange_location_id.
1389+ The function must to return true in all test case.
1390+-
1391+ !python {model: stock.inventory.line}: |
1392+ res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))
1393+ assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
1394+ res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))
1395+ assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
1396+
1397+-
1398+ I will call the function _check_inventory.
1399+ The function must return True in all test cases.
1400+-
1401+ !python {model: stock.location}: |
1402+ res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))
1403+ assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
1404+ res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))
1405+ assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
1406+
1407+-
1408+ I will confirm inventory.
1409+-
1410+ !python {model: stock.inventory}: |
1411+ self.action_confirm(cr, uid, [ref('inventory_standard')])
1412+ inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
1413+ assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state
1414+
1415+-
1416+ I will validate inventory
1417+-
1418+ !python {model: stock.inventory}: |
1419+ self.action_done(cr, uid, [ref('inventory_standard')])
1420+ inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
1421+ assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state
1422+
1423+-
1424+ I will verify the quantity for each products.
1425+-
1426+ !python {model: product.product}: |
1427+ ctx={'location': [ref('stock.stock_location_components')]}
1428+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_9')], ['qty_available'], context=ctx)[0]['qty_available']
1429+ assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0: %s" % prod_qty_avail
1430+
1431+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']
1432+ assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0: %s" % prod_qty_avail
1433+
1434+ prod_qty_avail = self.read(cr, uid, [ref('product.product_template_31')], ['qty_available'], context=ctx)[0]['qty_available']
1435+ assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0: %s" % prod_qty_avail
1436
1437=== added file 'stock_inventory_location/tests/stock_inventory_location_test.py'
1438--- stock_inventory_location/tests/stock_inventory_location_test.py 1970-01-01 00:00:00 +0000
1439+++ stock_inventory_location/tests/stock_inventory_location_test.py 2014-07-01 12:00:29 +0000
1440@@ -0,0 +1,47 @@
1441+# -*- coding: utf-8 -*-
1442+#
1443+#
1444+# Authors: Laetitia Gangloff
1445+# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
1446+# All Rights Reserved
1447+#
1448+# WARNING: This program as such is intended to be used by professional
1449+# programmers who take the whole responsibility of assessing all potential
1450+# consequences resulting from its eventual inadequacies and bugs.
1451+# End users who are looking for a ready-to-use solution with commercial
1452+# guarantees and support are strongly advised to contact a Free Software
1453+# Service Company.
1454+#
1455+# This program is free software: you can redistribute it and/or modify
1456+# it under the terms of the GNU Affero General Public License as
1457+# published by the Free Software Foundation, either version 3 of the
1458+# License, or (at your option) any later version.
1459+#
1460+# This program is distributed in the hope that it will be useful,
1461+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1462+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1463+# GNU Affero General Public License for more details.
1464+#
1465+# You should have received a copy of the GNU Affero General Public License
1466+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1467+#
1468+#
1469+
1470+import openerp.tests.common as common
1471+
1472+DB = common.DB
1473+ADMIN_USER_ID = common.ADMIN_USER_ID
1474+
1475+
1476+class stock_inventory_location_test(common.TransactionCase):
1477+
1478+ def setUp(self):
1479+ super(stock_inventory_location_test, self).setUp()
1480+
1481+ def test_update_parent_location(self):
1482+ """
1483+ Test the update of the parent of a location (no inventory in progress
1484+ """
1485+ self.registry('stock.location').write(self.cr, self.uid, self.ref('stock.stock_location_5'), {'location_id': self.ref('stock.stock_location_4')})
1486+
1487+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1488
1489=== added directory 'stock_inventory_location/wizard'
1490=== added file 'stock_inventory_location/wizard/__init__.py'
1491--- stock_inventory_location/wizard/__init__.py 1970-01-01 00:00:00 +0000
1492+++ stock_inventory_location/wizard/__init__.py 2014-07-01 12:00:29 +0000
1493@@ -0,0 +1,22 @@
1494+# -*- coding: utf-8 -*-1
1495+##############################################################################
1496+#
1497+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
1498+#
1499+# This program is free software: you can redistribute it and/or modify
1500+# it under the terms of the GNU General Public License as published by
1501+# the Free Software Foundation, either version 3 of the License, or
1502+# (at your option) any later version.
1503+#
1504+# This program is distributed in the hope that it will be useful,
1505+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1506+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1507+# GNU General Public License for more details.
1508+#
1509+# You should have received a copy of the GNU General Public License
1510+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1511+#
1512+##############################################################################
1513+
1514+import stock_fill_location_inventory
1515+import stock_confirm_uninventoried_location
1516
1517=== added file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.py'
1518--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 1970-01-01 00:00:00 +0000
1519+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-07-01 12:00:29 +0000
1520@@ -0,0 +1,69 @@
1521+# -*- encoding: utf-8 -*-
1522+##############################################################################
1523+#
1524+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
1525+#
1526+# This program is free software: you can redistribute it and/or modify
1527+# it under the terms of the GNU General Public License as published by
1528+# the Free Software Foundation, either version 3 of the License, or
1529+# (at your option) any later version.
1530+#
1531+# This program is distributed in the hope that it will be useful,
1532+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1533+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1534+# GNU General Public License for more details.
1535+#
1536+# You should have received a copy of the GNU General Public License
1537+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1538+#
1539+##############################################################################
1540+
1541+from openerp.tools.translate import _
1542+from openerp.osv import fields, orm, osv
1543+
1544+
1545+class stock_inventory_uninventoried_location(orm.TransientModel):
1546+ _name = 'stock.inventory.uninventoried.locations'
1547+ _description = 'Confirm the uninventoried Locations.'
1548+
1549+ _columns = {
1550+ 'location_ids': fields.many2many(
1551+ 'stock.location',
1552+ 'stock_inventory_uninventoried_location_rel',
1553+ 'location_id', 'wizard_id',
1554+ 'Uninventoried location', readonly=True),
1555+ }
1556+
1557+ def default_locations(self, cr, uid, context=None):
1558+ """Initialize view with the list of uninventoried locations."""
1559+ return self.pool['stock.inventory'].get_missing_locations(
1560+ cr, uid, context['active_ids'], context=context)
1561+
1562+ _defaults = {
1563+ 'location_ids': default_locations,
1564+ }
1565+
1566+ def confirm_uninventoried_locations(self, cr, uid, ids, context=None):
1567+ """Add the missing inventory lines with qty=0 and confirm inventory"""
1568+ inventory_ids = context['active_ids']
1569+ inventory_obj = self.pool['stock.inventory']
1570+ wizard_obj = self.pool['stock.fill.inventory']
1571+ for inventory in inventory_obj.browse(cr, uid, inventory_ids,
1572+ context=context):
1573+ if inventory.exhaustive:
1574+ # silently run the import wizard with qty=0
1575+ try:
1576+ # on parent inventory it is possible that fill inventory fail with no product
1577+ wizard_id = wizard_obj.create(
1578+ cr, uid, {'location_id': inventory.location_id.id,
1579+ 'recursive': True,
1580+ 'set_stock_zero': True,
1581+ 'exhaustive': True}, context=context)
1582+ wizard_obj.fill_inventory(cr, uid, [wizard_id],
1583+ context=context)
1584+ except osv.except_osv, e:
1585+ if e.value == _('No product in this location. Please select a location in the product form.'):
1586+ pass
1587+
1588+ inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)
1589+ return {'type': 'ir.actions.act_window_close'}
1590
1591=== added file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml'
1592--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 1970-01-01 00:00:00 +0000
1593+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-07-01 12:00:29 +0000
1594@@ -0,0 +1,33 @@
1595+<?xml version="1.0" encoding="utf-8"?>
1596+<openerp>
1597+ <data>
1598+ <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml,
1599+ but the code is different.
1600+ This wizard compare declared locations with locations on inventory lines to present the uninventoried (empty) locations to user. -->
1601+ <record id="view_confirm_uninventoried_location" model="ir.ui.view">
1602+ <field name="name">stock.inventory.uninventoried.locations.form</field>
1603+ <field name="model">stock.inventory.uninventoried.locations</field>
1604+ <field name="arch" type="xml">
1605+ <form string="Confirm empty locations" version="7.0">
1606+ <group colspan="4" col="1">
1607+ <separator string="The following Locations are empty"/>
1608+ <label string="The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."/>
1609+ <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
1610+ <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
1611+ <field name="location_ids" nolabel="1">
1612+ <tree>
1613+ <field name="name"/>
1614+ </tree>
1615+ </field>
1616+ <separator string=""/>
1617+ </group>
1618+ <footer>
1619+ <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" class="oe_highlight"/>
1620+ or
1621+ <button string="Cancel" class="oe_link" special="cancel" default_focus="1"/>
1622+ </footer>
1623+ </form>
1624+ </field>
1625+ </record>
1626+ </data>
1627+</openerp>
1628\ No newline at end of file
1629
1630=== added file 'stock_inventory_location/wizard/stock_fill_location_inventory.py'
1631--- stock_inventory_location/wizard/stock_fill_location_inventory.py 1970-01-01 00:00:00 +0000
1632+++ stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-07-01 12:00:29 +0000
1633@@ -0,0 +1,47 @@
1634+# -*- coding: utf-8 -*-
1635+##############################################################################
1636+#
1637+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
1638+#
1639+# This program is free software: you can redistribute it and/or modify
1640+# it under the terms of the GNU General Public License as published by
1641+# the Free Software Foundation, either version 3 of the License, or
1642+# (at your option) any later version.
1643+#
1644+# This program is distributed in the hope that it will be useful,
1645+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1646+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1647+# GNU General Public License for more details.
1648+#
1649+# You should have received a copy of the GNU General Public License
1650+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1651+#
1652+##############################################################################
1653+
1654+from openerp.osv import fields, orm
1655+
1656+
1657+class FillInventoryWizard(orm.TransientModel):
1658+ """Add a field that lets the client make the location read-only"""
1659+ _inherit = 'stock.fill.inventory'
1660+
1661+ _columns = {
1662+ 'exhaustive': fields.boolean('Exhaustive', readonly=True)
1663+ }
1664+
1665+ def default_get(self, cr, uid, fields, context=None):
1666+ """Get 'location_id' and 'exhaustive' from the inventory"""
1667+ if context is None:
1668+ context = {}
1669+ inv_id = context.get('active_id')
1670+
1671+ res = super(FillInventoryWizard, self).default_get(
1672+ cr, uid, fields, context=context)
1673+ if (context.get('active_model') == 'stock.inventory'
1674+ and inv_id
1675+ and 'location_id' in fields):
1676+ inventory = self.pool['stock.inventory'].browse(
1677+ cr, uid, context['active_id'], context=context)
1678+ res.update({'location_id': inventory.location_id.id,
1679+ 'exhaustive': inventory.exhaustive})
1680+ return res
1681
1682=== added file 'stock_inventory_location/wizard/stock_fill_location_inventory_view.xml'
1683--- stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 1970-01-01 00:00:00 +0000
1684+++ stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-07-01 12:00:29 +0000
1685@@ -0,0 +1,18 @@
1686+<?xml version="1.0" encoding="utf-8"?>
1687+<openerp>
1688+ <data>
1689+ <record id="view_stock_fill_inventory_location" model="ir.ui.view">
1690+ <field name="name">Import Inventory</field>
1691+ <field name="model">stock.fill.inventory</field>
1692+ <field name="inherit_id" ref="stock.view_stock_fill_inventory" />
1693+ <field name="arch" type="xml">
1694+ <xpath expr="//field[@name='location_id']" position="after">
1695+ <field name="exhaustive" invisible="1" />
1696+ </xpath>
1697+ <xpath expr="//field[@name='location_id']" position="attributes">
1698+ <attribute name="attrs">{'readonly':[('exhaustive','=',True)]}</attribute>
1699+ </xpath>
1700+ </field>
1701+ </record>
1702+ </data>
1703+</openerp>