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