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

Proposed by Lionel Sausin - Initiatives/Numérigraphe
Status: Merged
Merged at revision: 37
Proposed branch: lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-hierarchical-ls
Merge into: lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-hierarchical
Diff against target: 3062 lines (+1032/-1166)
33 files modified
stock_inventory_hierarchical/__init__.py (+7/-0)
stock_inventory_hierarchical/__openerp__.py (+13/-8)
stock_inventory_hierarchical/exceptions.py (+26/-0)
stock_inventory_hierarchical/hierarchical_inventory.py (+104/-102)
stock_inventory_hierarchical/hierarchical_inventory_demo.xml (+11/-23)
stock_inventory_hierarchical/hierarchical_inventory_view.xml (+16/-57)
stock_inventory_hierarchical/i18n/fr.po (+12/-12)
stock_inventory_hierarchical/test/hierarchical_inventory_test.yml (+54/-65)
stock_inventory_hierarchical_location/__init__.py (+3/-3)
stock_inventory_hierarchical_location/__openerp__.py (+19/-23)
stock_inventory_hierarchical_location/i18n/fr.po (+4/-4)
stock_inventory_hierarchical_location/inventory_hierarchical_location.py (+58/-104)
stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml (+16/-6)
stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml (+10/-23)
stock_inventory_hierarchical_location/test/inventory_hierarchical_location_test.yml (+14/-25)
stock_inventory_hierarchical_location/wizard/__init__.py (+2/-2)
stock_inventory_hierarchical_location/wizard/stock_confirm_uninventoried_location.py (+63/-0)
stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations.py (+0/-82)
stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml (+0/-43)
stock_inventory_location/__init__.py (+2/-0)
stock_inventory_location/__openerp__.py (+44/-29)
stock_inventory_location/exceptions.py (+26/-0)
stock_inventory_location/i18n/fr.po (+20/-40)
stock_inventory_location/stock_inventory_location.py (+249/-193)
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 (+76/-61)
stock_inventory_location/test/inventory_future_test.yml (+13/-0)
stock_inventory_location/test/inventory_standard_test.yml (+41/-50)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.py (+26/-59)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml (+12/-12)
stock_inventory_location/wizard/stock_fill_location_inventory.py (+24/-74)
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-hierarchical-ls
Reviewer Review Type Date Requested Status
Loïc Bellier - Numérigraphe Pending
Review via email: mp+222680@code.launchpad.net
To post a comment you must log in.
62. By Numérigraphe

[FIX] avoid division by zero when no inventory is found (ie. new inventory is beeing created)

63. By Numérigraphe

[MERGE] update screenshots

64. By Laetitia Gangloff (Acsone)

[IMP] stock_inventory_hierarchical : use different tree form for sub-inventories and propagate location and exhaustivity - courtesy of Laetitia Gangloff (Acsone)

65. By Numérigraphe

[MERGE] stock_inventory_location update

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'stock_inventory_hierarchical/__init__.py'
2--- stock_inventory_hierarchical/__init__.py 2014-03-12 15:08:22 +0000
3+++ stock_inventory_hierarchical/__init__.py 2014-06-11 15:04:27 +0000
4@@ -18,4 +18,11 @@
5 #
6 ##############################################################################
7
8+# This package-wide list keeps the names of the field that must be
9+# propagated from root inventories to their children.
10+# Add field names in the Model's definition.
11+PARENT_VALUES = []
12+
13 import hierarchical_inventory
14+# Bring the main exception into the package's scope for easier reuse
15+from .exceptions import HierarchicalInventoryException
16
17=== modified file 'stock_inventory_hierarchical/__openerp__.py'
18--- stock_inventory_hierarchical/__openerp__.py 2014-03-12 15:08:22 +0000
19+++ stock_inventory_hierarchical/__openerp__.py 2014-06-11 15:04:27 +0000
20@@ -20,22 +20,27 @@
21
22 {
23 "name": "Hierarchical Physical Inventory",
24- "version": "1.0",
25+ "version": "1.1",
26 "depends": ["stock"],
27 "author": "Numérigraphe",
28- "category": "stock inventory",
29+ "category": "Warehouse Management",
30 "description": """
31 Hierarchical structure for Physical Inventories and sub-Inventories
32 ===================================================================
33
34-This module adds a parent-child relationship between Physical Inventories, to help users
35-manage complex inventories.
36+This module adds a parent-child relationship between Physical Inventories, to
37+help users manage complex inventories.
38 Using several inventories, you can distribute the counting to several persons
39 and still keep a clear overview of global Inventory's status.
40
41-OpenERP will make sure the status of the Inventory and it's sub-Inventories are consistent.
42+OpenERP will make sure the status of the Inventory and it's Sub-Inventories are
43+consistent.
44 """,
45- "update_xml": ["hierarchical_inventory_view.xml"],
46- "test": ["test/hierarchical_inventory_test.yml"],
47- "demo": ["hierarchical_inventory_demo.xml"]
48+ "data": ["hierarchical_inventory_view.xml"],
49+ "test": ["test/hierarchical_inventory_test.yml"],
50+ "demo": ["hierarchical_inventory_demo.xml"],
51+ "images": [
52+ "inventory_form.png",
53+ "inventory_form_actions.png",
54+ ],
55 }
56
57=== added file 'stock_inventory_hierarchical/exceptions.py'
58--- stock_inventory_hierarchical/exceptions.py 1970-01-01 00:00:00 +0000
59+++ stock_inventory_hierarchical/exceptions.py 2014-06-11 15:04:27 +0000
60@@ -0,0 +1,26 @@
61+# -*- coding: utf-8 -*-
62+##############################################################################
63+#
64+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
65+#
66+# This program is free software: you can redistribute it and/or modify
67+# it under the terms of the GNU General Public License as published by
68+# the Free Software Foundation, either version 3 of the License, or
69+# (at your option) any later version.
70+#
71+# This program is distributed in the hope that it will be useful,
72+# but WITHOUT ANY WARRANTY; without even the implied warranty of
73+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
74+# GNU General Public License for more details.
75+#
76+# You should have received a copy of the GNU General Public License
77+# along with this program. If not, see <http://www.gnu.org/licenses/>.
78+#
79+##############################################################################
80+
81+from openerp.osv import orm
82+
83+
84+class HierarchicalInventoryException(orm.except_orm):
85+ """The operation is not possible for a hierarchical inventory"""
86+ pass
87
88=== modified file 'stock_inventory_hierarchical/hierarchical_inventory.py'
89--- stock_inventory_hierarchical/hierarchical_inventory.py 2014-03-12 15:08:22 +0000
90+++ stock_inventory_hierarchical/hierarchical_inventory.py 2014-06-11 15:04:27 +0000
91@@ -18,11 +18,17 @@
92 #
93 ##############################################################################
94
95-from openerp.osv import osv, fields
96+from openerp.osv import orm, fields
97 from openerp.tools.translate import _
98
99-
100-class stock_inventory_hierarchical(osv.osv):
101+from .exceptions import HierarchicalInventoryException
102+
103+# Add the date to the list of fields we must propagate to children inventories
104+from . import PARENT_VALUES
105+PARENT_VALUES.append('date')
106+
107+
108+class HierarchicalInventory(orm.Model):
109 _inherit = 'stock.inventory'
110
111 _parent_store = True
112@@ -30,7 +36,19 @@
113 _order = 'parent_left'
114
115 def name_get(self, cr, uid, ids, context=None):
116- """Show the parent inventory's name in the name of the children"""
117+ """Show the parent inventory's name in the name of the children
118+
119+ :param dict context: the ``inventory_display`` key can be
120+ used to select the short version of the
121+ inventory name (without the direct parent),
122+ when set to ``'short'``. The default is
123+ the long version."""
124+ if context is None:
125+ context = {}
126+ if context.get('inventory_display') == 'short':
127+ # Short name context: just do the usual stuff
128+ return super(HierarchicalInventory, self).name_get(
129+ cr, uid, ids, context=context)
130 if isinstance(ids, (list, tuple)) and not len(ids):
131 return []
132 if isinstance(ids, (long, int)):
133@@ -44,130 +62,105 @@
134 res.append((record['id'], name))
135 return res
136
137- def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
138- """Allow search on value returned by name_get ("parent/ child")"""
139+ def name_search(self, cr, uid, name='', args=None, operator='ilike',
140+ context=None, limit=100):
141+ """Enable search on value returned by name_get ("parent / child")"""
142 if not args:
143 args = []
144 if not context:
145 context = {}
146 if name:
147- # Be sure name_search is symetric to name_get
148+ # Make sure name_search is symmetric to name_get
149 name = name.split(' / ')[-1]
150- ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context)
151+ ids = self.search(cr, uid, [('name', operator, name)] + args,
152+ limit=limit, context=context)
153 else:
154 ids = self.search(cr, uid, args, limit=limit, context=context)
155 return self.name_get(cr, uid, ids, context=context)
156
157- def _name_get_fnc(self, cr, uid, ids, field_name, arg, context=None):
158- """Function field containing the long name"""
159+ def _complete_name(self, cr, uid, ids, field_name, arg, context=None):
160+ """Function-field wrapper to get the complete name from name_get"""
161 res = self.name_get(cr, uid, ids, context=context)
162 return dict(res)
163
164- def _confirmed_rate(self, cr, uid, ids, field_name, arg, context=None):
165- """Number of (sub)inventories confirmed/total"""
166+ def _progress_rate(self, cr, uid, ids, field_name, arg, context=None):
167+ """Rate of (sub)inventories done/total"""
168 rates = {}
169- for id in ids:
170- nb = self.search(cr, uid, [('parent_id', 'child_of', id)], context=context, count=True)
171- nb_confirmed = self.search(cr, uid, [('parent_id', 'child_of', id),
172- ('state', 'in', ('confirm', 'done'))], context=context, count=True)
173- rates[id] = 100 * nb_confirmed / nb
174+ for current_id in ids:
175+ nb = self.search(
176+ cr, uid, [('parent_id', 'child_of', current_id)],
177+ context=context, count=True)
178+ if not nb:
179+ # No inventory, consider it's 0% done
180+ rates[current_id] = 0
181+ continue
182+ nb_done = self.search(
183+ cr, uid, [('parent_id', 'child_of', current_id),
184+ ('state', '=', 'done')],
185+ context=context, count=True)
186+ rates[current_id] = 100 * nb_done / nb
187 return rates
188
189 _columns = {
190- # XXX remove "method=True" in v7 ?
191- 'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Complete reference'),
192- 'parent_id': fields.many2one('stock.inventory', 'Parent', ondelete='cascade', readonly=True, states={'draft': [('readonly', False)]}),
193- 'inventory_ids': fields.one2many('stock.inventory', 'parent_id', 'List of Sub-inventories', readonly=True, states={'draft': [('readonly', False)]}),
194+ # name_get() only changes the default name of the record, not the
195+ # content of the field "name" so we add another field for that
196+ 'complete_name': fields.function(
197+ _complete_name, type="char",
198+ string='Complete reference'),
199+ 'parent_id': fields.many2one(
200+ 'stock.inventory', 'Parent', ondelete='cascade', readonly=True,
201+ states={'draft': [('readonly', False)]}),
202+ 'inventory_ids': fields.one2many(
203+ 'stock.inventory', 'parent_id', 'List of Sub-inventories',
204+ readonly=True, states={'draft': [('readonly', False)]}),
205 'parent_left': fields.integer('Parent Left', select=1),
206 'parent_right': fields.integer('Parent Right', select=1),
207- 'confirmed_rate': fields.function(_confirmed_rate, method=True, string='Confirmed', type='float'),
208+ 'progress_rate': fields.function(
209+ _progress_rate, string='Progress', type='float'),
210 }
211
212- # XXX: drop this in v7
213- def _check_recursion(self, cr, uid, ids, context=None, parent=None):
214- """Backport of osv.osv._check_recursion from v7.0, to allow writing parents and children in the same write()"""
215- if not parent:
216- parent = self._parent_name
217-
218- # must ignore 'active' flag, ir.rules, etc. => direct SQL query
219- query = 'SELECT "%s" FROM "%s" WHERE id = %%s' % (parent, self._table)
220- for id in ids:
221- current_id = id
222- while current_id is not None:
223- cr.execute(query, (current_id,))
224- result = cr.fetchone()
225- current_id = result[0] if result else None
226- if current_id == id:
227- return False
228- return True
229-
230- # XXX: use this in v7
231- # _constraints = [(osv.osv._check_recursion, 'Error! You can not create recursive inventories.', ['parent_id']), ]
232 _constraints = [
233- (_check_recursion,
234- _('Error! You can not create recursive inventories.'), ['parent_id']),
235+ (orm.Model._check_recursion,
236+ 'Error: You can not create recursive inventories.',
237+ ['parent_id']),
238 ]
239
240- # This is the list of fields that must be forced from Inventories to Sub-Inventories
241- # TODO: propose this as a new feature of the ORM's API using (using a field named _parent_values for example)
242- PARENT_VALUES = ['date']
243-
244-# XXX: Ideally we would have liked to have a button to open Sub-inventories,
245-# but unfortunately the v6.0 GTK client crashes, and the 6.0 web client opens a windows without action buttons.
246-# Maybe we may try that again with the new web client one day...
247-# def open_sub_inventory(self, cr, uid, id, context=None):
248-# """Method to open Sub-inventory from one2many list on new tab, with specific view."""
249-# # Find out the form view id
250-# if not isinstance(id, list):
251-# id = [id]
252-# id = id[0]
253-# res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'view_inventory_form')
254-# view_id = res and res[1] or False
255-# inv = self.browse(cr, uid, id, context=context)
256-# return {
257-# 'type': 'ir.actions.act_window',
258-# 'name': _("Sub-inventory : %s") % inv.name,
259-# 'view_type': 'form',
260-# 'view_mode': 'form',
261-# 'view_id': [view_id],
262-# 'res_model': 'stock.inventory',
263-# 'res_id': id,
264-# }
265-# return True
266-
267- # TODO: propose this as a new feature of the ORM's API using (using a field named _parent_values for example)
268- def create(self, cr, user, vals, context=None):
269+ def create(self, cr, uid, vals, context=None):
270 """Copy selected values from parent to child"""
271 if vals and vals.get('parent_id'):
272- for f in self.PARENT_VALUES:
273- parent_field = self.read(cr, user, [vals['parent_id']], [f], context=context)
274- vals = vals.copy()
275- vals[f] = parent_field[0][f]
276- return super(stock_inventory_hierarchical, self).create(cr, user, vals, context=context)
277+ existing_fields = self.fields_get_keys(cr, uid, context=context)
278+ parent_values = self.read(cr, uid, [vals['parent_id']],
279+ PARENT_VALUES, context=context)
280+ vals = vals.copy()
281+ vals.update({field: parent_values[0][field]
282+ for field in PARENT_VALUES
283+ if field in existing_fields})
284+ return super(HierarchicalInventory, self).create(
285+ cr, uid, vals, context=context)
286
287- # TODO: propose this as a new feature of the ORM's API using (using a field named _parent_values for example)
288 def write(self, cr, uid, ids, vals, context=None):
289 """Copy selected values from parent to children"""
290 if context is None:
291 context = {}
292
293- values = super(stock_inventory_hierarchical, self).write(cr, uid, ids, vals, context=context)
294- if not vals or context.get('norecurs'):
295+ values = super(HierarchicalInventory, self).write(
296+ cr, uid, ids, vals, context=context)
297+ if not vals or context.get('norecurse', False):
298 return values
299
300- record = {}
301- for f in self.PARENT_VALUES:
302- if f in vals:
303- record[f] = vals[f]
304- if not record:
305+ # filter the fields we want to propagate
306+ children_values = {
307+ field: vals[field] for field in PARENT_VALUES if field in vals
308+ }
309+ if not children_values:
310 return values
311
312 if not isinstance(ids, list):
313 ids = [ids]
314- children_ids = self.search(cr, uid, [('parent_id', 'child_of', ids)])
315- ctx = context.copy()
316- ctx['norecurs'] = True # needed to write children once.
317- return self.write(cr, uid, children_ids, record, context=ctx)
318+ # The context disables recursion - children are already included
319+ return self.write(
320+ cr, uid, self.search(cr, uid, [('parent_id', 'child_of', ids)]),
321+ children_values, context=dict(context, norecurse=True))
322
323 def action_cancel_inventory(self, cr, uid, ids, context=None):
324 """Cancel inventory only if all the parents are canceled"""
325@@ -176,25 +169,34 @@
326 while inventory.parent_id:
327 inventory = inventory.parent_id
328 if inventory.state != 'cancel':
329- raise osv.except_osv(_('Warning !'),
330- _('One of the parent Inventories is not canceled.'))
331- return super(stock_inventory_hierarchical, self).action_cancel_inventory(cr, uid, ids, context=context)
332+ raise HierarchicalInventoryException(
333+ _('Warning'),
334+ _('One of the parent Inventories is not canceled.'))
335+ return super(HierarchicalInventory,
336+ self).action_cancel_inventory(cr, uid, ids,
337+ context=context)
338
339 def action_confirm(self, cr, uid, ids, context=None):
340 """Confirm inventory only if all the children are confirmed"""
341- children_count = self.search(cr, uid, [('parent_id', 'child_of', ids),
342- ('state', 'not in', ['confirm', 'done'])], context=context, count=True)
343+ children_count = self.search(
344+ cr, uid, [('parent_id', 'child_of', ids),
345+ ('state', 'not in', ['confirm', 'done'])],
346+ context=context, count=True)
347 if children_count > 1:
348- raise osv.except_osv(_('Warning !'),
349- _('Some Sub-inventories are not confirmed.'))
350- return super(stock_inventory_hierarchical, self).action_confirm(cr, uid, ids, context=context)
351+ raise HierarchicalInventoryException(
352+ _('Warning'),
353+ _('Some Sub-inventories are not confirmed.'))
354+ return super(HierarchicalInventory, self).action_confirm(
355+ cr, uid, ids, context=context)
356
357 def action_done(self, cr, uid, ids, context=None):
358 """Perform validation only if all the children states are 'done'."""
359 children_count = self.search(cr, uid, [('parent_id', 'child_of', ids),
360 ('state', '!=', 'done')],
361- context=context, count=True)
362+ context=context, count=True)
363 if children_count > 1:
364- raise osv.except_osv(_('Warning !'),
365- _('Some Sub-inventories are not done.'))
366- return super(stock_inventory_hierarchical, self).action_done(cr, uid, ids, context=context)
367+ raise HierarchicalInventoryException(
368+ _('Warning'),
369+ _('Some Sub-inventories are not validated.'))
370+ return super(HierarchicalInventory, self).action_done(
371+ cr, uid, ids, context=context)
372
373=== modified file 'stock_inventory_hierarchical/hierarchical_inventory_demo.xml'
374--- stock_inventory_hierarchical/hierarchical_inventory_demo.xml 2014-03-12 15:08:22 +0000
375+++ stock_inventory_hierarchical/hierarchical_inventory_demo.xml 2014-06-11 15:04:27 +0000
376@@ -1,30 +1,18 @@
377 <?xml version="1.0" encoding="utf-8"?>
378 <openerp>
379 <data noupdate="0">
380-
381- <!-- Record a production lot we can use in the tests. -->
382- <record id="lot_test0" model="stock.production.lot">
383- <field name="product_id" ref="product.product_product_10" />
384- </record>
385-
386- <!-- Record inventories we can use in the tests. -->
387- <!-- We need them in the demo data because test data is rolled back
388- whenever an exception is raised. -->
389-
390- <!-- Record an inventory to make sure the next one will have stock moves posted. -->
391+ <!-- Example Inventory with Sub-Inventories. -->
392 <record id="stock_inventory_parent0" model="stock.inventory">
393- <field name="name">Parent test inventory</field>
394- <field name="state">draft</field>
395+ <field name="name">Main Inventory</field>
396 <field name="date">2020-01-01 00:00:00</field>
397- </record>
398- <record id="stock_inventory_line" model="stock.inventory.line">
399- <field name="inventory_id" ref="stock_inventory_parent0" />
400- <field name="product_id" ref="product.product_product_10" />
401- <field name="prod_lot_id" ref="lot_test0"/>
402- <field name="product_uom" ref="product.product_uom_unit" />
403- <field name="product_qty">27.0</field>
404- <field name="location_id" ref="stock.stock_location_components" />
405- </record>
406-
407+ </record>
408+ <record id="child_1_id" model="stock.inventory">
409+ <field name="name">Sub-Inventory 1</field>
410+ <field name="parent_id" ref="stock_inventory_parent0" />
411+ </record>
412+ <record id="child_2_id" model="stock.inventory">
413+ <field name="name">Sub-Inventory 2</field>
414+ <field name="parent_id" ref="stock_inventory_parent0" />
415+ </record>
416 </data>
417 </openerp>
418
419=== modified file 'stock_inventory_hierarchical/hierarchical_inventory_view.xml'
420--- stock_inventory_hierarchical/hierarchical_inventory_view.xml 2014-03-12 15:08:22 +0000
421+++ stock_inventory_hierarchical/hierarchical_inventory_view.xml 2014-06-11 15:04:27 +0000
422@@ -5,16 +5,14 @@
423 <record model="ir.ui.view" id="stock_inventory_hierarchical_tree_view">
424 <field name="name">hierarchical.inventory.tree</field>
425 <field name="model">stock.inventory</field>
426- <field name="type">tree</field>
427 <field name="inherit_id" ref="stock.view_inventory_tree" />
428 <field name="field_parent">inventory_ids</field>
429 <field name="arch" type="xml">
430 <xpath expr="//field[@name='name']" position="replace">
431- <field name="complete_name"/>
432+ <field name="complete_name" string="Reference"/>
433 </xpath>
434 <xpath expr="//field[@name='state']" position="after">
435- <field name="confirmed_rate" widget="progressbar" />
436- <field name="inventory_ids" string="Number of Sub-inventories" />
437+ <field name="progress_rate" widget="progressbar" />
438 </xpath>
439 </field>
440 </record>
441@@ -23,7 +21,6 @@
442 <record model="ir.ui.view" id="view_inventory_subinventories_filter">
443 <field name="name">hierarchical.inventory.filter</field>
444 <field name="model">stock.inventory</field>
445- <field name="type">search</field>
446 <field name="inherit_id" ref="stock.view_inventory_filter" />
447 <field name="arch" type="xml">
448 <xpath expr="//field[@name='name']" position="before">
449@@ -43,9 +40,11 @@
450 <record model="ir.ui.view" id="stock_inventory_hierarchical_form_view">
451 <field name="name">hierarchical.inventory.form</field>
452 <field name="model">stock.inventory</field>
453- <field name="type">form</field>
454 <field name="inherit_id" ref="stock.view_inventory_form" />
455 <field name="arch" type="xml">
456+ <xpath expr="/form//field[@name='name']" position="after">
457+ <field name="parent_id"/>
458+ </xpath>
459 <xpath expr="/form//field[@name='date']" position="attributes">
460 <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
461 </xpath>
462@@ -53,62 +52,22 @@
463 expr="//page[@string='General Information']"
464 position="after">
465 <page string="Sub-inventories">
466- <field name="parent_id" invisible="1" />
467- <field name="inventory_ids" nolabel="1"
468- context="{'form_view_ref' : 'stock_inventory_hierarchical.stock_inventory_hierarchical_subinventory_form_view', 'tree_view_ref' : 'stock_inventory_hierarchical.stock_inventory_hierarchical_subinventory_tree_view'}" />
469+ <field name="inventory_ids" nolabel="1" context="{'default_parent_id': active_id}">
470+ <tree>
471+ <field name="name" />
472+ <field name="state" />
473+ <field name="progress_rate" widget="progressbar" />
474+ </tree>
475+ </field>
476 </page>
477 </xpath>
478 </field>
479 </record>
480
481- <record model="ir.ui.view"
482- id="stock_inventory_hierarchical_subinventory_tree_view">
483- <field name="name">hierarchical.inventory.subinventory.tree</field>
484- <field name="model">stock.inventory</field>
485- <field name="type">tree</field>
486- <field name="priority" eval="99" />
487- <field name="arch" type="xml">
488- <tree string="Sub-inventories" version="7.0">
489- <field name="name" />
490- <field name="state" />
491- <field name="confirmed_rate" widget="progressbar" />
492- <field name="inventory_ids" string="Number of Sub-inventories"/>
493- <!-- XXX: Ideally we would have liked to have a button to open Sub-inventories,
494- but unfortunately the v6.0 GTK client crashes, and the 6.0 web client opens a windows without action buttons.
495- Maybe we may try that again with the new web client one day...
496- <button string="View this inventory" type="object" name="open_sub_inventory"/>
497- -->
498- </tree>
499- </field>
500- </record>
501-
502- <record model="ir.ui.view"
503- id="stock_inventory_hierarchical_subinventory_form_view">
504- <field name="name">hierarchical.inventory.subinventory.form</field>
505- <field name="model">stock.inventory</field>
506- <field name="type">form</field>
507- <field name="priority" eval="99" />
508- <field name="arch" type="xml">
509- <form string="Sub-inventories" version="7.0">
510- <group colspan="4" col="2">
511- <field name="name" default_focus="1"/>
512- </group>
513- <field name="inventory_ids" nolabel="1" colspan="4"
514- context="{'form_view_ref' : 'stock_inventory_hierarchical.stock_inventory_hierarchical_subinventory_form_view', 'tree_view_ref' : 'stock_inventory_hierarchical.stock_inventory_hierarchical_subinventory_tree_view'}" />
515- <group colspan="4" col="3">
516- <field name="state" />
517- <!-- XXX: Ideally we would have liked to have a button to open Sub-inventories,
518- but unfortunately the v6.0 GTK client crashes, and the 6.0 web client opens a windows without action buttons.
519- Maybe we may try that again with the new web client one day...
520- <button string="View this inventory" type="object" name="open_sub_inventory"/>
521- -->
522- </group>
523- </form>
524- </field>
525- </record>
526-
527- <act_window id="action_view_sub_inventory"
528- name="Sub-inventories"
529+ <!-- Open the children of the current Inventory in a distinct list
530+ to let users work in a normal window instead of a popup -->
531+ <act_window id="action_view_sub_inventory"
532+ name="View Sub-inventories"
533 res_model="stock.inventory"
534 src_model="stock.inventory"
535 view_mode="tree,form"
536
537=== modified file 'stock_inventory_hierarchical/i18n/fr.po'
538--- stock_inventory_hierarchical/i18n/fr.po 2014-03-12 15:08:22 +0000
539+++ stock_inventory_hierarchical/i18n/fr.po 2014-06-11 15:04:27 +0000
540@@ -21,16 +21,16 @@
541 msgstr "Réference complète"
542
543 #. module: stock_inventory_hierarchical
544-#: field:stock.inventory,confirmed_rate:0
545-msgid "Confirmed"
546-msgstr "Confirmé"
547+#: field:stock.inventory,progress_rate:0
548+msgid "Done"
549+msgstr "Terminé"
550
551 #. module: stock_inventory_hierarchical
552 #: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:108
553 #: constraint:stock.inventory:0
554 #, python-format
555-msgid "Error! You can not create recursive inventories."
556-msgstr "Erreur! Vous ne pouvez pas créer d'inventaire récursifs."
557+msgid "Error: You can not create recursive inventories."
558+msgstr "Erreur : Vous ne pouvez pas créer d'inventaire récursifs."
559
560 #. module: stock_inventory_hierarchical
561 #: model:ir.model,name:stock_inventory_hierarchical.model_stock_inventory
562@@ -61,7 +61,7 @@
563 #. module: stock_inventory_hierarchical
564 #: constraint:stock.inventory:0
565 msgid "Other Physical inventories are being conducted using the same Locations."
566-msgstr "Erreur: certains emplacements sont déjà dans un autre inventaire."
567+msgstr "Certains emplacements sont déjà dans un autre inventaire."
568
569 #. module: stock_inventory_hierarchical
570 #: field:stock.inventory,parent_id:0
571@@ -87,19 +87,19 @@
572 #. module: stock_inventory_hierarchical
573 #: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
574 #, python-format
575-msgid "Some Sub-inventories are not done."
576+msgid "Some Sub-inventories are not validated."
577 msgstr "Certains sous-inventaires ne sont pas terminés."
578
579 #. module: stock_inventory_hierarchical
580 #: model:ir.actions.act_window,name:stock_inventory_hierarchical.action_view_sub_inventory
581 #: view:stock.inventory:0
582-msgid "Sub-inventories"
583-msgstr "Sous-inventaires"
584+msgid "View Sub-inventories"
585+msgstr "Voir les sous-inventaires"
586
587 #. module: stock_inventory_hierarchical
588 #: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:130
589 #, python-format
590-msgid "Sub-inventory : %s"
591+msgid "Sub-inventory: %s"
592 msgstr "Sous-inventaire : %s"
593
594 #. module: stock_inventory_hierarchical
595@@ -107,6 +107,6 @@
596 #: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:188
597 #: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
598 #, python-format
599-msgid "Warning !"
600-msgstr "Attention !"
601+msgid "Warning"
602+msgstr "Attention"
603
604
605=== added directory 'stock_inventory_hierarchical/images'
606=== added file 'stock_inventory_hierarchical/images/inventory_form.png'
607Binary files stock_inventory_hierarchical/images/inventory_form.png 1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical/images/inventory_form.png 2014-06-11 15:04:27 +0000 differ
608=== added file 'stock_inventory_hierarchical/images/inventory_form_actions.png'
609Binary files stock_inventory_hierarchical/images/inventory_form_actions.png 1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical/images/inventory_form_actions.png 2014-06-11 15:04:27 +0000 differ
610=== added directory 'stock_inventory_hierarchical/static'
611=== added directory 'stock_inventory_hierarchical/static/src'
612=== added directory 'stock_inventory_hierarchical/static/src/img'
613=== added file 'stock_inventory_hierarchical/static/src/img/icon.png'
614Binary files stock_inventory_hierarchical/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical/static/src/img/icon.png 2014-06-11 15:04:27 +0000 differ
615=== modified file 'stock_inventory_hierarchical/test/hierarchical_inventory_test.yml'
616--- stock_inventory_hierarchical/test/hierarchical_inventory_test.yml 2014-03-12 15:08:22 +0000
617+++ stock_inventory_hierarchical/test/hierarchical_inventory_test.yml 2014-06-11 15:04:27 +0000
618@@ -1,48 +1,39 @@
619 -
620 In this file, i check rules about hierarchical inventories.
621- children date must be the same of parent date for each state,
622- the state of parent and children can change only if conditions are true.
623- First, i'll create two children inventory of stock_inventory_parent inventory.
624--
625- !python {model: stock.inventory}: |
626- from osv import orm
627- self.child_1_id = self.create(cr, uid, {'parent_id': ref('stock_inventory_parent0'),
628- 'inventory_ids': [],
629- 'name': 'Children 1 test inventory'})
630- self.child_2_id = self.create(cr, uid, {'parent_id': ref('stock_inventory_parent0'),
631- 'inventory_ids': [],
632- 'name': 'Children 2 test inventory'})
633--
634- Check if date of children are the same than parent date.
635- I'll read the date for 2 children and compare them with parent inventory date.
636--
637- !python {model: stock.inventory}: |
638- from osv import orm
639- parent_date = self.read(cr, uid, [ref('stock_inventory_parent0')], ['date'])[0]['date']
640- child_1_date = self.read(cr, uid, [self.child_1_id], ['date'])[0]['date']
641- assert child_1_date == parent_date, "Date are not equals : %s - %s" % (parent_date, child_1_date)
642-
643- child_2_date = self.read(cr, uid, [self.child_2_id], ['date'])[0]['date']
644- assert child_2_date == parent_date, "Date are not equals : %s - %s" % (parent_date, child_2_date)
645+ Children date must be the same of parent date for each state,
646+ the state of parent and children can change only if conditions are correct.
647+-
648+ Check if date of children are the same as the parent's.
649+-
650+ !python {model: stock.inventory}: |
651+ parent_date = self.read(
652+ cr, uid, [ref('stock_inventory_parent0')], ['date'])[0]['date']
653+ child_1_date = self.read(
654+ cr, uid, [ref('child_1_id')], ['date'])[0]['date']
655+ assert child_1_date == parent_date, "Date are different: %s - %s" % (parent_date, child_1_date)
656+
657+ child_2_date = self.read(
658+ cr, uid, [ref('child_2_id')], ['date'])[0]['date']
659+ assert child_2_date == parent_date, "Date are different: %s - %s" % (parent_date, child_2_date)
660
661 -
662 Check if children cannot be canceled if the parent was not canceled.
663 I'll try to cancel both children inventory while parent inventory having "draft" state.
664- After, i'll verify the state of each inventory.
665+ After, i'll verify the state of each inventory.
666 -
667- !python {model: stock.inventory}: |
668- from osv import orm, osv
669- try:
670- self.action_cancel_inventary(cr, uid, [self.child_1_id])
671- except osv.except_osv as e:
672- log("Good ! The Inventory could not be canceled : %s" % e)
673- try:
674- self.action_cancel_inventary(cr, uid, [self.child_2_id])
675- except osv.except_osv as e:
676- log("Good ! The Inventory could not be canceled : %s" % e)
677- child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']
678+ !python {model: stock.inventory}: |
679+ from stock_inventory_hierarchical import HierarchicalInventoryException
680+ try:
681+ self.action_cancel_inventory(cr, uid, [ref('child_1_id')])
682+ except HierarchicalInventoryException as e:
683+ log("Good ! The Inventory could not be canceled: %s" % e)
684+ try:
685+ self.action_cancel_inventory(cr, uid, [ref('child_2_id')])
686+ except HierarchicalInventoryException as e:
687+ log("Good ! The Inventory could not be canceled: %s" % e)
688+ child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
689 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
690- child_2_state = self.read(cr, uid, [self.child_2_id], ['state'])[0]['state']
691+ child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
692 assert child_2_state == 'draft', "Child inventory 2 have '%s' state. It should be 'draft'" % child_2_state
693
694 -
695@@ -50,58 +41,56 @@
696 To check this, i'll try to confirm parent inventory when children inventory having "draft" state,
697 and i'll check if state is still 'draft'.
698 -
699- !python {model: stock.inventory}: |
700- from osv import orm, osv
701+ !python {model: stock.inventory}: |
702+ from stock_inventory_hierarchical import HierarchicalInventoryException
703 try:
704- self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
705- except osv.except_osv as e:
706- log("Good, the inventory could not be confirm : %s", e)
707+ self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
708+ except HierarchicalInventoryException as e:
709+ log("Good, the inventory could not be confirmed: %s", e)
710 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
711 assert parent_state == 'draft', "Parent inventory have '%s' state. It should be 'draft'" % parent_state
712
713 -
714 In order, i'll confirm the children inventories, and the parent inventory after.
715 -
716- !python {model: stock.inventory}: |
717- from osv import orm, osv
718- self.action_confirm(cr, uid, [self.child_1_id])
719- child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']
720+ !python {model: stock.inventory}: |
721+ self.action_confirm(cr, uid, [ref('child_1_id')])
722+ child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
723 assert child_1_state == 'confirm', "Child inventory 1 have '%s' state. It should be 'confirm'" % child_1_state
724-
725- self.action_confirm(cr, uid, [self.child_2_id])
726- child_2_state = self.read(cr, uid, [self.child_2_id], ['state'])[0]['state']
727+
728+ self.action_confirm(cr, uid, [ref('child_2_id')])
729+ child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
730 assert child_2_state == 'confirm', "Child inventory 2 have '%s' state. It should be 'confirm'" % child_2_state
731-
732+
733 self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
734 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
735 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
736-
737+
738 -
739 Check if children inventory have done state before validate parent inventory.
740- I'll try to validate parent inventory before children.
741+ I'll try to validate parent inventory before children.
742 -
743- !python {model: stock.inventory}: |
744- from osv import orm, osv
745+ !python {model: stock.inventory}: |
746+ from stock_inventory_hierarchical import HierarchicalInventoryException
747 try:
748 self.action_done(cr, uid, [ref('stock_inventory_parent0')])
749- except osv.except_osv as e:
750- log("Good, the inventory could not be validate : %s", e)
751+ except HierarchicalInventoryException as e:
752+ log("Good, the inventory could not be validated: %s", e)
753 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
754 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
755
756 -
757 Now, i'll validate all children inventory before validate the parent.
758 -
759- !python {model: stock.inventory}: |
760- from osv import orm, osv
761- self.action_done(cr, uid, [self.child_1_id])
762- child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']
763+ !python {model: stock.inventory}: |
764+ self.action_done(cr, uid, [ref('child_1_id')])
765+ child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
766 assert child_1_state == 'done', "Child inventory 1 have '%s' state. It should be 'done'" % child_1_state
767-
768- self.action_done(cr, uid, [self.child_2_id])
769- child_2_state = self.read(cr, uid, [self.child_2_id], ['state'])[0]['state']
770+
771+ self.action_done(cr, uid, [ref('child_2_id')])
772+ child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
773 assert child_2_state == 'done', "Child inventory 2 have '%s' state. It should be 'done'" % child_2_state
774-
775+
776 self.action_done(cr, uid, [ref('stock_inventory_parent0')])
777 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
778- assert parent_state == 'done', "Parent inventory have '%s' state. It should be 'done'" % parent_state
779+ assert parent_state == 'done', "Parent inventory have '%s' state. It should be 'done'" % parent_state
780
781=== modified file 'stock_inventory_hierarchical_location/__init__.py'
782--- stock_inventory_hierarchical_location/__init__.py 2014-03-12 15:08:22 +0000
783+++ stock_inventory_hierarchical_location/__init__.py 2014-06-11 15:04:27 +0000
784@@ -1,4 +1,4 @@
785-# -*- encoding: utf-8 -*-
786+# -*- coding: utf-8 -*-
787 ##############################################################################
788 #
789 # This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
790@@ -18,5 +18,5 @@
791 #
792 ##############################################################################
793
794-import inventory_hierarchical_location
795-import wizard
796+from . import inventory_hierarchical_location
797+from . import wizard
798
799=== modified file 'stock_inventory_hierarchical_location/__openerp__.py'
800--- stock_inventory_hierarchical_location/__openerp__.py 2014-03-12 15:08:22 +0000
801+++ stock_inventory_hierarchical_location/__openerp__.py 2014-06-11 15:04:27 +0000
802@@ -1,4 +1,4 @@
803-# -*- encoding: utf-8 -*-
804+# -*- coding: utf-8 -*-
805 ##############################################################################
806 #
807 # This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
808@@ -19,34 +19,30 @@
809 ##############################################################################
810
811 {
812- "name": "Exhaustive and hierachical Stock Inventories",
813- "version": "1.0",
814+ "name": "Exhaustive and hierarchical Stock Inventories",
815+ "version": "1.1",
816 "depends": ["stock_inventory_hierarchical", "stock_inventory_location"],
817+ "auto_install": True,
818 "author": u"Numérigraphe",
819- "category": "stock inventory",
820+ "category": "Hidden",
821 "description": """
822-This module makes exhaustive Inventories aware of their Sub-Inventories.
823-========================================================================
824-
825-It should be installed if both modules "stock_inventory_location"
826-and "stock_inventory_hierarchical" are installed.
827+Make exhaustive Inventories aware of their Sub-Inventories.
828+===========================================================
829
830 This module allows an inventory to contain a general Location,
831 and it's sub-inventories to contain some of it's sub-Locations.
832-When you open an inventory, OpenERP will warn you if some of the sub-Locations
833-of an inventory are absent from the sub-Inventories.
834-It will also prevent you from setting the Inventories and sub-Inventories
835+It will prevent you from setting the Inventories and sub-Inventories
836 in inconsistent status.
837+
838+This module will be installed automatically if the modules
839+"stock_inventory_location" and "stock_inventory_hierarchical" are both
840+installed.
841+You must keep this module installed to ensure proper functioning.
842+
843 """,
844- "init_xml": [],
845- "update_xml": [
846- "wizard/stock_inventory_missing_locations_view.xml",
847- "inventory_hierarchical_location_view.xml",
848- ],
849-
850- "test": ["test/inventory_hierarchical_location_test.yml"],
851- "demo": ["inventory_hierarchical_location_demo.xml"],
852-
853- # Will work with v6.1 and later
854- "auto_install": True,
855+ "data": [
856+ "inventory_hierarchical_location_view.xml",
857+ ],
858+ "test": ["test/inventory_hierarchical_location_test.yml"],
859+ "demo": ["inventory_hierarchical_location_demo.xml"],
860 }
861
862=== modified file 'stock_inventory_hierarchical_location/i18n/fr.po'
863--- stock_inventory_hierarchical_location/i18n/fr.po 2014-03-12 15:08:22 +0000
864+++ stock_inventory_hierarchical_location/i18n/fr.po 2014-06-11 15:04:27 +0000
865@@ -64,8 +64,8 @@
866 #. module: stock_inventory_hierarchical_location
867 #: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
868 #, python-format
869-msgid "One of the parent inventories are not open."
870-msgstr "Un inventaire parent n'est pas ouvert."
871+msgid "One of the parent inventories is not open."
872+msgstr "Un des inventaire parent n'est pas ouvert."
873
874 #. module: stock_inventory_hierarchical_location
875 #: view:stock.inventory:0
876@@ -97,9 +97,9 @@
877 #: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:58
878 #, python-format
879 msgid "This location is not declared on the parent inventory\n"
880-"You cannot add it !"
881+"It cannot be added."
882 msgstr "Cet emplacement n'est pas déclaré dans l'inventaire parent\n"
883-"Vous ne pouvez pas l'ajouter !"
884+"Vous ne pouvez pas l'ajouter."
885
886 #. module: stock_inventory_hierarchical_location
887 #: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
888
889=== modified file 'stock_inventory_hierarchical_location/inventory_hierarchical_location.py'
890--- stock_inventory_hierarchical_location/inventory_hierarchical_location.py 2014-03-12 15:08:22 +0000
891+++ stock_inventory_hierarchical_location/inventory_hierarchical_location.py 2014-06-11 15:04:27 +0000
892@@ -1,4 +1,4 @@
893-# -*- encoding: utf-8 -*-
894+# -*- coding: utf-8 -*-
895 ##############################################################################
896 #
897 # This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
898@@ -18,120 +18,74 @@
899 #
900 ##############################################################################
901
902-from openerp.osv import osv
903+from openerp.osv import orm
904 from openerp.tools.translate import _
905
906-
907-class StockInventory(osv.osv):
908+from stock_inventory_hierarchical import HierarchicalInventoryException
909+
910+# Add the date to the list of fields we must propagate to children inventories
911+from stock_inventory_hierarchical import PARENT_VALUES
912+PARENT_VALUES.append('exhaustive')
913+
914+
915+class HierarchicalExhInventory(orm.Model):
916+ """Add hierarchical structure features to exhaustive Inventories"""
917 _inherit = 'stock.inventory'
918
919- def __init__(self, pool, cr):
920- """Propagate the "exhaustive" field from inventories to sub-inventories"""
921- s = super(StockInventory, self)
922- s.PARENT_VALUES.append('exhaustive')
923- return s.__init__(pool, cr)
924-
925 def action_open(self, cr, uid, ids, context=None):
926 """Open only if all the parents are Open."""
927- #XXX the dosctring used to say this but it's not implemented, normal?
928- # --> "Before opening, if locations are missing, ask the user
929- # to validate the opening without these locations."
930 for inventory in self.browse(cr, uid, ids, context=context):
931 while inventory.parent_id:
932 inventory = inventory.parent_id
933 if inventory.state != 'open':
934- raise osv.except_osv(_('Warning !'),
935- _('One of the parent inventories are not open.'))
936- return super(StockInventory, self).action_open(cr, uid, ids, context=context)
937-
938- def check_location(self, cr, uid, ids, location_ids, name, context=None):
939+ raise HierarchicalInventoryException(
940+ _('Warning'),
941+ _('One of the parent inventories is not open.'))
942+ return super(HierarchicalExhInventory, self).action_open(
943+ cr, uid, ids, context=context)
944+
945+ # TODO v8: probably only keep the state "done"
946+ def confirm_missing_locations(self, cr, uid, ids, context=None):
947+ """Do something only if children state are confirm or done."""
948+ children_count = self.search(
949+ cr, uid, [('parent_id', 'child_of', ids),
950+ ('id', 'not in', ids),
951+ ('state', 'not in', ['confirm', 'done'])],
952+ context=context, count=True)
953+ if children_count > 0:
954+ raise HierarchicalInventoryException(
955+ _('Warning'),
956+ _('Some Sub-inventories are not confirmed.'))
957+ return super(HierarchicalExhInventory,
958+ self).confirm_missing_locations(
959+ cr, uid, ids, context=context)
960+
961+ def onchange_location_id(self, cr, uid, ids, location_id, context=None):
962 """Check if location is a child of parent inventory location"""
963- res_location_ids = location_ids[0][2]
964- nbr_location_ids = len(res_location_ids)
965- for inventory in self.browse(cr, uid, ids, context=None):
966- if inventory.parent_id.id:
967- parent_locations = self.read(cr, uid, [inventory.parent_id.id], ['location_ids'])
968- parent_children_locations = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', parent_locations[0]['location_ids'])])
969- for location_id in res_location_ids:
970- if location_id not in parent_children_locations:
971- res_location_ids.remove(location_id)
972- res = {}
973- if nbr_location_ids != len(res_location_ids):
974- res['warning'] = {'title': _('Warning: Wrong location'),
975- 'message': _("This location is not declared on the parent inventory\n"
976- "You cannot add it !")}
977- res['value'] = {'location_ids': res_location_ids, }
978- return res
979+ loc_obj = self.pool['stock.location']
980+ for inventory in self.browse(cr, uid, ids, context=context):
981+ if inventory.parent_id:
982+ allowed_location_ids = loc_obj.search(
983+ cr, uid, [('location_id', 'child_of',
984+ inventory.parent_id.location_id.id)],
985+ context=context)
986+ if location_id not in allowed_location_ids:
987+ return {
988+ 'location_id': False,
989+ 'warning': {
990+ 'title': _('Warning: Wrong location'),
991+ 'message': _("This location is not declared on "
992+ "the parent inventory\n"
993+ "It cannot be added.")}
994+ }
995+ return {}
996
997- def _fill_location_lines(self, cr, uid, inventory_id, location_ids, set_stock_zero, context=None):
998+ def _fill_location_lines(self, cr, uid, inventory_id, location_ids,
999+ set_stock_zero, context=None):
1000 """Add ids of children inventory into list """
1001- children_inventory_ids = self.search(cr, uid, [('parent_id', 'child_of', inventory_id)])
1002+ children_inventory_ids = self.search(
1003+ cr, uid, [('parent_id', 'child_of', inventory_id)])
1004 context['children_inventory_ids'] = children_inventory_ids
1005- return super(StockInventory, self)._fill_location_lines(cr, uid, inventory_id, location_ids, set_stock_zero, context=context)
1006-
1007- def open_missing_location_wizard(self, cr, uid, ids, context=None):
1008- """Open wizard if inventory have children.
1009- Before, verify if all children of exhaustive inventory have at least one location."""
1010- children_ids = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
1011- for inventory in self.browse(cr, uid, children_ids, context=context):
1012- if inventory.exhaustive:
1013- if not inventory.location_ids:
1014- raise osv.except_osv(_('Warning !'),
1015- _('Location missing for inventory "%s".') % inventory.name)
1016- children_count = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', ids)], count=True)
1017- if children_count == 1:
1018- return self.action_open(cr, uid, ids, context)
1019- else:
1020- context['active_ids'] = ids
1021- context['active_id'] = ids[0]
1022- return {
1023- 'type': 'ir.actions.act_window',
1024- 'view_type': 'form',
1025- 'view_mode': 'form',
1026- 'res_model': 'stock.inventory.missing.location',
1027- 'target': 'new',
1028- 'context': context,
1029- 'nodestroy': True,
1030- }
1031-
1032-
1033-# XXX: move to /wizard
1034-class StockInventoryUninventoriedLocation(osv.osv_memory):
1035- _inherit = 'stock.inventory.uninventoried.locations'
1036-
1037- def inventories(self, cr, uid, inventory_parent_id):
1038- """Iterator of children inventories.
1039- return inventory_id;
1040- """
1041- children_ids = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', inventory_parent_id)])
1042- for inventory_id in children_ids:
1043- yield inventory_id
1044-
1045- def get_locations(self, cr, uid, inventory_id, context=None):
1046- """Get all locations through inventory tree."""
1047- list_inventories_locations_ids = []
1048- for i_id in self.inventories(cr, uid, inventory_id):
1049- location_ids = super(StockInventoryUninventoriedLocation, self).get_locations(cr, uid, i_id, context=context)
1050- list_inventories_locations_ids = list(set(list_inventories_locations_ids + location_ids))
1051- return list_inventories_locations_ids
1052-
1053- def get_locations_inventoried(self, cr, uid, inventory_id, location_ids, context=None):
1054- """Get all locations on inventory lines through inventory tree."""
1055- list_all_inventoried_location_ids = []
1056- for i_id in self.inventories(cr, uid, inventory_id):
1057- list_loc_ids = super(StockInventoryUninventoriedLocation, self).get_locations_inventoried(cr, uid, i_id, location_ids, context=context)
1058- list_all_inventoried_location_ids = list(set(list_all_inventoried_location_ids + list_loc_ids))
1059- return list_all_inventoried_location_ids
1060-
1061- def default_locations(self, cr, uid, context=None):
1062- """Do something only if children state are confirm or done."""
1063- children_count = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', context['active_id']),
1064- ('state', 'not in', ['confirm', 'done'])], context=context, count=True)
1065- if children_count > 1:
1066- raise osv.except_osv(_('Warning !'), _('Some Sub-inventories are not confirmed.'))
1067- return super(StockInventoryUninventoriedLocation, self).default_locations(cr, uid, context=context)
1068-
1069- _defaults = {
1070- 'location_ids': default_locations,
1071- }
1072-
1073+ return super(HierarchicalExhInventory, self)._fill_location_lines(
1074+ cr, uid, inventory_id, location_ids, set_stock_zero,
1075+ context=context)
1076
1077=== modified file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml'
1078--- stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml 2014-03-12 15:08:22 +0000
1079+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml 2014-06-11 15:04:27 +0000
1080@@ -3,16 +3,26 @@
1081 <data noupdate="0">
1082
1083 <!-- Record inventories we can use in the tests. -->
1084- <!-- We need them in the demo data because test data is rolled back
1085+ <!-- We need them in the demo data because test data is rolled back
1086 whenever an exception is raised. -->
1087-
1088- <!-- Record an exhaustive inventory -->
1089+
1090+ <!-- Record a hierarchical exhaustive inventory -->
1091 <record id="parent_inventory_location" model="stock.inventory">
1092- <field name="name">Hierarchical location exhaustive inventory</field>
1093+ <field name="name">Hierarchical exhaustive inventory</field>
1094 <field name="state">draft</field>
1095 <field name="date">2020-04-15 00:00:00</field>
1096- <field name="exhaustive">True</field>
1097- <field name="location_ids" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
1098+ <field name="exhaustive">True</field>
1099+ <field name="location_ids" model="stock.location" search="[('name', 'like', 'Stock')]" />
1100+ </record>
1101+ <record id="child_1_id" model="stock.inventory">
1102+ <field name="name">Team A</field>
1103+ <field name="parent_id" ref="parent_inventory_location"/>
1104+ <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 1')]" />
1105+ </record>
1106+ <record id="child_2_id" model="stock.inventory">
1107+ <field name="name">Team B</field>
1108+ <field name="parent_id" ref="parent_inventory_location"/>
1109+ <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
1110 </record>
1111 </data>
1112 </openerp>
1113
1114=== modified file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml'
1115--- stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml 2014-03-12 15:08:22 +0000
1116+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml 2014-06-11 15:04:27 +0000
1117@@ -5,34 +5,21 @@
1118 <record model="ir.ui.view" id="stock_inventory_hierarchical_location_form_view">
1119 <field name="name">hierarchical.inventory.location.form</field>
1120 <field name="model">stock.inventory</field>
1121- <field name="type">form</field>
1122 <field name="inherit_id" ref="stock.view_inventory_form" />
1123 <field name="arch" type="xml">
1124-
1125- <xpath expr="//form[@string='Physical Inventory']//field[@name='exhaustive']" position="attributes">
1126- <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
1127- </xpath>
1128-
1129- <xpath expr="/form[@string='Physical Inventory']//page[@string='General Information']/field[@name='location_ids']" position="replace">
1130- <field colspan="1" nolabel="1" name="location_ids" domain="[('usage','in',('view', 'internal'))]"
1131- attrs="{'invisible':[('exhaustive','!=',True)]}"
1132- on_change="check_location(location_ids, name)">
1133- <tree string="Locations" editable="bottom">
1134- <field name="name"/>
1135- </tree>
1136- </field>
1137- </xpath>
1138-
1139- <!-- replace action_open button to open wizard missing locations -->
1140- <xpath expr="/form//button[@name='action_open']" position="replace">
1141- <button name="open_missing_location_wizard"
1142- string="Open Inventory" type="object" states="draft" icon="gtk-apply"/>
1143- </xpath>
1144-
1145+ <xpath expr="/form//field[@name='exhaustive']" position="attributes">
1146+ <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
1147+ </xpath>
1148+ <xpath expr="/form//field[@name='location_id']" position="attributes">
1149+ <attribute name="on_change">onchange_location_id(location_id)</attribute>
1150+ </xpath>
1151+ <xpath expr="/form//field[@name='inventory_ids']" position="attributes">
1152+ <attribute name="context">{'default_parent_id': active_id, 'default_exhaustive': exhaustive}</attribute>
1153+ </xpath>
1154 </field>
1155 </record>
1156
1157- <!-- Show exhaustive inventories by default -->
1158+ <!-- Show hierarchical exhaustive inventories by default -->
1159 <record id="stock.action_inventory_form" model="ir.actions.act_window">
1160 <field name="context">{'full':'1', 'search_default_exhaustive':1, 'search_default_main_inventories':1}</field>
1161 </record>
1162
1163=== added directory 'stock_inventory_hierarchical_location/static'
1164=== added directory 'stock_inventory_hierarchical_location/static/src'
1165=== added directory 'stock_inventory_hierarchical_location/static/src/img'
1166=== added file 'stock_inventory_hierarchical_location/static/src/img/icon.png'
1167Binary files stock_inventory_hierarchical_location/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and stock_inventory_hierarchical_location/static/src/img/icon.png 2014-06-11 15:04:27 +0000 differ
1168=== modified file 'stock_inventory_hierarchical_location/test/inventory_hierarchical_location_test.yml'
1169--- stock_inventory_hierarchical_location/test/inventory_hierarchical_location_test.yml 2014-03-12 15:08:22 +0000
1170+++ stock_inventory_hierarchical_location/test/inventory_hierarchical_location_test.yml 2014-06-11 15:04:27 +0000
1171@@ -1,31 +1,20 @@
1172 -
1173- I will create children inventory.
1174+ Check that the exhaustive field of parent inventory has been propagated to children.
1175 -
1176- !python {model: stock.inventory}: |
1177- self.child_1_id = self.create(cr, uid, {'parent_id': ref('parent_inventory_location'),
1178- 'inventory_ids': [],
1179- 'name': 'Children 1 test inventory'})
1180+ !python {model: stock.inventory}: |
1181+ exhaustive = self.read(cr, uid, [ref("child_1_id")], ['exhaustive'])[0]['exhaustive']
1182+ assert exhaustive, "Exhaustive field not propagated to child inventory"
1183
1184 -
1185- I will check if exhaustive information of parent inventory has been added to children.
1186--
1187- !python {model: stock.inventory}: |
1188- exhaustive = self.read(cr, uid, [self.child_1_id], ['exhaustive'])[0]['exhaustive']
1189- assert exhaustive == True, "Exhaustive information not added to children inventory"
1190-
1191--
1192- I will check if i can't open children inventory while parent inventory is open.
1193--
1194- !python {model: stock.inventory}: |
1195- from osv import orm, osv
1196+ Check that I can't open child inventory while parent inventory is open.
1197+-
1198+ !python {model: stock.inventory}: |
1199+ from stock_inventory_hierarchical import HierarchicalInventoryException
1200+ parent_state = self.read(cr, uid, [ref("parent_inventory_location")], ['state'])[0]['state']
1201+ assert parent_state == 'draft', "Parent inventory in state '%s'. It should be 'draft'" % parent_state
1202 try:
1203- self.action_open(cr, uid, [self.child_1_id])
1204- except osv.except_osv as e:
1205- log("Good ! The Inventory could not be opened : %s" % e)
1206- child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']
1207+ self.action_open(cr, uid, [ref("child_1_id")])
1208+ except HierarchicalInventoryException as e:
1209+ log("Good ! The Inventory could not be opened: %s" % e)
1210+ child_1_state = self.read(cr, uid, [ref("child_1_id")], ['state'])[0]['state']
1211 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
1212-
1213-
1214-
1215-
1216-
1217\ No newline at end of file
1218
1219=== modified file 'stock_inventory_hierarchical_location/wizard/__init__.py'
1220--- stock_inventory_hierarchical_location/wizard/__init__.py 2014-03-12 15:08:22 +0000
1221+++ stock_inventory_hierarchical_location/wizard/__init__.py 2014-06-11 15:04:27 +0000
1222@@ -1,4 +1,4 @@
1223-# -*- encoding: utf-8 -*-
1224+# -*- coding: utf-8 -*-
1225 ##############################################################################
1226 #
1227 # This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
1228@@ -18,4 +18,4 @@
1229 #
1230 ##############################################################################
1231
1232-import stock_inventory_missing_locations
1233+import stock_confirm_uninventoried_location
1234
1235=== added file 'stock_inventory_hierarchical_location/wizard/stock_confirm_uninventoried_location.py'
1236--- stock_inventory_hierarchical_location/wizard/stock_confirm_uninventoried_location.py 1970-01-01 00:00:00 +0000
1237+++ stock_inventory_hierarchical_location/wizard/stock_confirm_uninventoried_location.py 2014-06-11 15:04:27 +0000
1238@@ -0,0 +1,63 @@
1239+# -*- coding: utf-8 -*-
1240+##############################################################################
1241+#
1242+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
1243+#
1244+# This program is free software: you can redistribute it and/or modify
1245+# it under the terms of the GNU General Public License as published by
1246+# the Free Software Foundation, either version 3 of the License, or
1247+# (at your option) any later version.
1248+#
1249+# This program is distributed in the hope that it will be useful,
1250+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1251+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1252+# GNU General Public License for more details.
1253+#
1254+# You should have received a copy of the GNU General Public License
1255+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1256+#
1257+##############################################################################
1258+
1259+from openerp.osv import orm
1260+from openerp.tools.translate import _
1261+
1262+from stock_inventory_hierarchical import HierarchicalInventoryException
1263+
1264+
1265+class StockInventoryUninventoriedLocation(orm.TransientModel):
1266+ """Consider the lines of the sub-inventories when checking for locations"""
1267+ _inherit = 'stock.inventory.uninventoried.locations'
1268+
1269+ def inventories(self, cr, uid, inventory_parent_id):
1270+ """Iterator of children inventories.
1271+
1272+ @return: inventory_id
1273+ """
1274+ children_ids = self.pool['stock.inventory'].search(
1275+ cr, uid, [('parent_id', 'child_of', inventory_parent_id)])
1276+ for inventory_id in children_ids:
1277+ yield inventory_id
1278+
1279+ def get_locations(self, cr, uid, inventory_id, context=None):
1280+ """Get all locations through inventory tree."""
1281+ list_inventories_locations_ids = []
1282+ for i_id in self.inventories(cr, uid, inventory_id):
1283+ location_ids = super(
1284+ StockInventoryUninventoriedLocation, self).get_locations(
1285+ cr, uid, i_id, context=context)
1286+ list_inventories_locations_ids = list(set(
1287+ list_inventories_locations_ids + location_ids))
1288+ return list_inventories_locations_ids
1289+
1290+ def get_locations_inventoried(self, cr, uid, inventory_id, location_ids,
1291+ context=None):
1292+ """Get all locations on inventory lines throughout inventory tree."""
1293+ list_all_inventoried_location_ids = []
1294+ for i_id in self.inventories(cr, uid, inventory_id):
1295+ list_loc_ids = super(
1296+ StockInventoryUninventoriedLocation,
1297+ self).get_locations_inventoried(
1298+ cr, uid, i_id, location_ids, context=context)
1299+ list_all_inventoried_location_ids = list(set(
1300+ list_all_inventoried_location_ids + list_loc_ids))
1301+ return list_all_inventoried_location_ids
1302
1303=== removed file 'stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations.py'
1304--- stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations.py 2014-03-12 15:08:22 +0000
1305+++ stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations.py 1970-01-01 00:00:00 +0000
1306@@ -1,82 +0,0 @@
1307-# -*- encoding: utf-8 -*-
1308-##############################################################################
1309-#
1310-# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
1311-#
1312-# This program is free software: you can redistribute it and/or modify
1313-# it under the terms of the GNU General Public License as published by
1314-# the Free Software Foundation, either version 3 of the License, or
1315-# (at your option) any later version.
1316-#
1317-# This program is distributed in the hope that it will be useful,
1318-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1319-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1320-# GNU General Public License for more details.
1321-#
1322-# You should have received a copy of the GNU General Public License
1323-# along with this program. If not, see <http://www.gnu.org/licenses/>.
1324-#
1325-##############################################################################
1326-
1327-from openerp.osv import osv, fields
1328-from openerp.tools.translate import _
1329-
1330-
1331-class inventory_missing_location(osv.osv_memory):
1332-
1333- _name = 'stock.inventory.missing.location'
1334- _description = 'Search on inventory tree for missing declared locations.'
1335-
1336- _columns = {
1337- 'location_ids': fields.many2many('stock.location',
1338- 'stock_inventory_missing_location_rel',
1339- 'location_id',
1340- 'wizard_id',
1341- 'Missing location', readonly=True),
1342- }
1343-
1344- def confirm_missing_locations(self, cr, uid, ids, context=None):
1345- """ Call action open method from stock.inventory """
1346- ids = context['active_ids']
1347- self.pool.get('stock.inventory').action_open(cr, uid, ids, context=context)
1348- return {'type': 'ir.actions.act_window_close'}
1349-
1350- def inventories(self, cr, uid, inventory_parent_id):
1351- """ Iterator of children inventories.
1352- """
1353- children_ids = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', inventory_parent_id)])
1354- for inventory_id in children_ids:
1355- if inventory_id == inventory_parent_id:
1356- continue # pass the parent inventory
1357- yield inventory_id
1358-
1359- def get_locations_from_children(self, cr, uid, inventory_id, context=None):
1360- """ Get all locations through inventory tree. """
1361- list_inventories_locations_ids = []
1362- for i_id in self.inventories(cr, uid, inventory_id):
1363- location_ids = self.pool.get('stock.inventory').read(cr, uid, [i_id], ['location_ids'], context=context)[0]
1364- location_ids = self.pool.get('stock.location').search(cr, uid, [
1365- ('location_id', 'child_of', location_ids['location_ids']),
1366- ('usage', '=', 'internal')], context=context)
1367- list_inventories_locations_ids = list(set(list_inventories_locations_ids + location_ids))
1368- return list_inventories_locations_ids
1369-
1370- def default_missing_locations(self, cr, uid, context=None):
1371- """ Initialize view with the list of missing locations on inventory tree.
1372- """
1373- if context is None:
1374- context = {}
1375-
1376- # get children locations for parent/current inventory
1377- parent_location_ids = self.pool.get('stock.inventory').read(cr, uid, [context['active_id']], ['location_ids'], context=context)[0]
1378- parent_location_ids = self.pool.get('stock.location').search(cr, uid, [
1379- ('location_id', 'child_of', parent_location_ids['location_ids']),
1380- ('usage', '=', 'internal')], context=context)
1381- # get locations for each sub-inventory
1382- location_ids = self.get_locations_from_children(cr, uid, context['active_id'])
1383- list_missing_ids = [_id for _id in parent_location_ids if _id not in location_ids]
1384- return list_missing_ids
1385-
1386- _defaults = {
1387- 'location_ids': default_missing_locations,
1388- }
1389
1390=== removed file 'stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml'
1391--- stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml 2014-03-12 15:08:22 +0000
1392+++ stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml 1970-01-01 00:00:00 +0000
1393@@ -1,43 +0,0 @@
1394-<?xml version="1.0" encoding="utf-8"?>
1395-<openerp>
1396- <data>
1397-
1398- <record id="action_view_stock_inventory_missing_location" model="ir.actions.act_window">
1399- <field name="name">Confirm missing location</field>
1400- <field name="type">ir.actions.act_window</field>
1401- <field name="res_model">stock.inventory.missing.location</field>
1402- <field name="view_type">form</field>
1403- <field name="view_mode">form</field>
1404- <field name="target">new</field>
1405- </record>
1406-
1407- <!-- The view definition is similar with stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml,
1408- but the code is different.
1409- This wizard compare the locations of inventory with locations declared on parent inventory to
1410- to present to user the missing locations. -->
1411- <record id="view_confirm_missing_location" model="ir.ui.view">
1412- <field name="name">Confirm missing location</field>
1413- <field name="model">stock.inventory.missing.location</field>
1414- <field name="type">form</field>
1415- <field name="arch" type="xml">
1416- <form string="Confirm missing locations">
1417- <group colspan="4" col="1">
1418- <field name="location_ids" nolabel="1">
1419- <tree>
1420- <field name="name"/>
1421- </tree>
1422- </field>
1423- </group>
1424- <group colspan="4" col="2">
1425- <label string="This is the list of missing locations."/>
1426- <label string="Do you want to continue ?"/>
1427- <separator string="" colspan="4" />
1428- <button special="cancel" string="_Cancel" icon='gtk-cancel'/>
1429- <button name="confirm_missing_locations" string="_Confirm missing locations" type="object" icon="gtk-ok"/>
1430- </group>
1431- </form>
1432- </field>
1433- </record>
1434-
1435- </data>
1436-</openerp>
1437\ No newline at end of file
1438
1439=== modified file 'stock_inventory_location/__init__.py'
1440--- stock_inventory_location/__init__.py 2014-03-12 14:55:39 +0000
1441+++ stock_inventory_location/__init__.py 2014-06-11 15:04:27 +0000
1442@@ -20,3 +20,5 @@
1443
1444 import stock_inventory_location
1445 import wizard
1446+# Bring the main exception into the package's scope for easier reuse
1447+from .exceptions import ExhaustiveInventoryException
1448
1449=== modified file 'stock_inventory_location/__openerp__.py'
1450--- stock_inventory_location/__openerp__.py 2014-03-12 14:55:39 +0000
1451+++ stock_inventory_location/__openerp__.py 2014-06-11 15:04:27 +0000
1452@@ -18,45 +18,60 @@
1453 #
1454 ##############################################################################
1455
1456-
1457 {
1458 "name": "Exhaustive Stock Inventories",
1459- "version": "1.0",
1460+ "version": "1.1",
1461 "depends": ["stock"],
1462 "author": u"Numérigraphe",
1463- "category": "Inventory",
1464+ "category": "Warehouse Management",
1465 "description": """
1466-Let users choose between standard and exhaustive Inventories
1467-============================================================
1468+Let users make exhaustive Inventories
1469+=====================================
1470
1471-Standard Physical Inventories in OpenERP only contain a generic list of products by locations,
1472-which is well suited to partial Inventories and simple warehouses.
1473-When the a standard Inventory is confirmed, only the products in the inventory are checked.
1474-If a Product is present in the computed stock and not in the recorded Inventory, OpenERP will
1475-consider that it remains unchanged.
1476+Standard Physical Inventories in OpenERP only contain a generic list of
1477+products by locations, which is well suited to partial Inventories and simple
1478+warehouses. When the a standard Inventory is confirmed, only the products in
1479+the inventory are checked. If a Product is present in the computed stock and
1480+not in the recorded Inventory, OpenERP will consider that it remains unchanged.
1481
1482 But for exhaustive inventories in complex warehouses, it is not practical:
1483- - you want to avoid Stock Moves in/out of these Locations while you count the goods
1484+ - you want to avoid Stock Moves to/from these Locations while counting goods
1485 - you must make sure all the locations you want have been counted
1486 - you must make sure no other location has been counted by mistake
1487- - you want the computed stock to perfectly match the inventory when you confirm it.
1488+ - you want the computed stock to perfectly match the inventory when you
1489+ confirm it.
1490
1491-This module lets choose whether an Physical Inventory is exhaustive or standard.
1492-For an exhaustive Inventory, in the state "Draft" you define the list of Locations where goods must be counted.
1493- - a new Inventory status ("Open") lets you indicate that the list of Locations is definitive and you are now counting the goods.
1494- In that status, no Stock Moves can be recorded in/out of the Inventory's Locations.
1495- - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory.
1496- - only the Inventory's Locations can be entered in the Inventory Lines.
1497- - 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
1498+This module lets choose whether an Physical Inventory is exhaustive or
1499+standard.
1500+For an exhaustive Inventory:
1501+ - in the state "Draft" you define the Location where goods must be counted.
1502+ - the new Inventory status "Open" lets you indicate that the list of Locations
1503+ is final and you are now counting the goods.
1504+ In that status, no Stock Moves can be recorded in/out of the Inventory's
1505+ Locations.
1506+ - if the Location or some of it's children have not been entered in the
1507+ Inventory Lines, OpenERP warns you when you confirm the Inventory.
1508+ - only the Inventory's Location or its children can be entered in the
1509+ Inventory Lines.
1510+ - every good that is not in the Inventory Lines is considered lost, and gets
1511+ moved out of the stock when you confirm the Inventory.
1512 """,
1513- "update_xml": [
1514- "wizard/stock_confirm_uninventoried_location.xml",
1515- "stock_inventory_location_view.xml",
1516- "wizard/stock_fill_location_inventory_view.xml",
1517- ],
1518- "test": ["test/location_inventory_test.yml",
1519- "test/location_exhaustive_inventory_test.yml",
1520- ],
1521- "demo": ["stock_inventory_location_demo.xml"]
1522-
1523+ "data": [
1524+ "wizard/stock_confirm_uninventoried_location.xml",
1525+ "stock_inventory_location_view.xml",
1526+ "wizard/stock_fill_location_inventory_view.xml",
1527+ ],
1528+ "test": [
1529+ "test/inventory_standard_test.yml",
1530+ "test/inventory_exhaustive_test.yml",
1531+ "test/inventory_future_test.yml",
1532+ ],
1533+ "images": [
1534+ "images/inventory_form.png",
1535+ "inventory_empty_locations.png",
1536+ "images/move_error.png",
1537+ "images/location_locked.png",
1538+ "images/future_inventory.png",
1539+ ],
1540+ "demo": ["stock_inventory_location_demo.xml"]
1541 }
1542
1543=== added file 'stock_inventory_location/exceptions.py'
1544--- stock_inventory_location/exceptions.py 1970-01-01 00:00:00 +0000
1545+++ stock_inventory_location/exceptions.py 2014-06-11 15:04:27 +0000
1546@@ -0,0 +1,26 @@
1547+# -*- coding: utf-8 -*-
1548+##############################################################################
1549+#
1550+# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
1551+#
1552+# This program is free software: you can redistribute it and/or modify
1553+# it under the terms of the GNU General Public License as published by
1554+# the Free Software Foundation, either version 3 of the License, or
1555+# (at your option) any later version.
1556+#
1557+# This program is distributed in the hope that it will be useful,
1558+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1559+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1560+# GNU General Public License for more details.
1561+#
1562+# You should have received a copy of the GNU General Public License
1563+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1564+#
1565+##############################################################################
1566+
1567+from openerp.osv import orm
1568+
1569+
1570+class ExhaustiveInventoryException(orm.except_orm):
1571+ """The operation is not possible for an exhaustive inventory"""
1572+ pass
1573
1574=== modified file 'stock_inventory_location/i18n/fr.po'
1575--- stock_inventory_location/i18n/fr.po 2014-03-12 14:55:39 +0000
1576+++ stock_inventory_location/i18n/fr.po 2014-06-11 15:04:27 +0000
1577@@ -20,7 +20,7 @@
1578 #: constraint:stock.move:0
1579 #, python-format
1580 msgid "A Physical Inventory is being conducted at this location"
1581-msgstr "Un inventaire est déjà en cours à cet emplacement"
1582+msgstr "Un inventaire est en cours à cet emplacement"
1583
1584 #. module: stock_inventory_location
1585 #: view:stock.inventory.uninventoried.locations:0
1586@@ -63,8 +63,8 @@
1587
1588 #. module: stock_inventory_location
1589 #: view:stock.inventory.uninventoried.locations:0
1590-msgid "Confirm uninventoried locations"
1591-msgstr "Confirmez les emplacements non-inventoriés"
1592+msgid "Confirm empty locations"
1593+msgstr "Confirmez les emplacements vides"
1594
1595 #. module: stock_inventory_location
1596 #: model:ir.model,name:stock_inventory_location.model_stock_location
1597@@ -74,26 +74,26 @@
1598 #. module: stock_inventory_location
1599 #: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:54
1600 #, python-format
1601-msgid "Error !"
1602-msgstr "Erreur !"
1603+msgid "Error"
1604+msgstr "Erreur"
1605
1606 #. module: stock_inventory_location
1607 #: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:75
1608 #, python-format
1609-msgid "Error : Empty location !"
1610-msgstr "Erreur : Emplacement vide !"
1611+msgid "Error: Empty location"
1612+msgstr "Erreur : Emplacement vide"
1613
1614 #. module: stock_inventory_location
1615 #: code:addons/stock_inventory_location/stock_inventory_location.py:231
1616 #: code:addons/stock_inventory_location/stock_inventory_location.py:282
1617 #, python-format
1618-msgid "Error! Location on inventory"
1619-msgstr "Erreur! emplacement en inventaire"
1620+msgid "Error: Location locked down"
1621+msgstr "Erreur: emplacement en inventaire"
1622
1623 #. module: stock_inventory_location
1624 #: constraint:stock.inventory:0
1625-msgid "Error! You can not create recursive inventories."
1626-msgstr "Erreur! Vous ne pouvez pas créer un inventaire récursif."
1627+msgid "Error: You can not create recursive inventories."
1628+msgstr "Erreur: Vous ne pouvez pas créer un inventaire récursif."
1629
1630 #. module: stock_inventory_location
1631 #: constraint:stock.inventory.line:0
1632@@ -138,12 +138,6 @@
1633 msgstr "Emplacement manquant pour cet inventaire."
1634
1635 #. module: stock_inventory_location
1636-#: view:stock.inventory:0
1637-#: field:stock.inventory,location_ids:0
1638-msgid "Locations"
1639-msgstr "Emplacements"
1640-
1641-#. module: stock_inventory_location
1642 #: model:ir.model,name:stock_inventory_location.model_stock_move
1643 msgid "Mouvement de stock"
1644 msgstr "Mouvement de stock"
1645@@ -171,15 +165,10 @@
1646 #. module: stock_inventory_location
1647 #: code:addons/stock_inventory_location/stock_inventory_location.py:283
1648 #, python-format
1649-msgid "One or more locations are inventoried :\n"
1650-"%s"
1651-msgstr "Un ou plusieurs emplacements sont en inventaire :\n"
1652-"%s"
1653-
1654-#. module: stock_inventory_location
1655-#: constraint:stock.move:0
1656-msgid "One or more lots are awaiting quality control and cannot be moved."
1657-msgstr "Un ou plusieurs lots sont en attente de contrôle qualité, et ne peuvent pas être déplacés."
1658+msgid "A Physical Inventory is being conducted at the following location(s):\n"
1659+"%s"
1660+msgstr "Les emplacements suivants sont en inventaire :\n"
1661+"%s"
1662
1663 #. module: stock_inventory_location
1664 #: view:stock.inventory:0
1665@@ -198,19 +187,10 @@
1666
1667 #. module: stock_inventory_location
1668 #: view:stock.inventory.uninventoried.locations:0
1669-msgid "The following Stock Locations are part of the current Physical Inventory, but not Inventory Line has been recorded for them."
1670+msgid "The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."
1671 msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."
1672
1673 #. module: stock_inventory_location
1674-#: help:stock.inventory,location_ids:0
1675-msgid "This is the list of the Stock Locations that you want to count the goods in.\n"
1676-"Only these Locations can be entered in the Inventory Lines.\n"
1677-"If some of them have not been entered in the Inventory Lines, OpenERP will warn you when you confirm the Inventory."
1678-msgstr "Ceci est la liste des emplacements dont vous voulez compter la marchandise.\n"
1679-"Seuls ces emplacements peuvent être enregistrés dans les lignes d'inventaire.\n"
1680-"Si certains ne sont enregistrés sur aucune ligne d'inventaire, OpenERP vous avertit lors de la confirmation de l'inventaire."
1681-
1682-#. module: stock_inventory_location
1683 #: field:stock.inventory.uninventoried.locations,location_ids:0
1684 msgid "Uninventoried location"
1685 msgstr "Emplacements non inventoriés"
1686@@ -220,8 +200,8 @@
1687 #: code:addons/stock_inventory_location/stock_inventory_location.py:163
1688 #: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:58
1689 #, python-format
1690-msgid "Warning !"
1691-msgstr "Attention !"
1692+msgid "Warning"
1693+msgstr "Attention"
1694
1695 #. module: stock_inventory_location
1696 #: code:addons/stock_inventory_location/stock_inventory_location.py:209
1697@@ -232,8 +212,8 @@
1698 #. module: stock_inventory_location
1699 #: code:addons/stock_inventory_location/stock_inventory_location.py:210
1700 #, python-format
1701-msgid "You cannot add this location to inventory line.\n"
1702-"You must add this location on the locations list"
1703+msgid "You cannot record an Inventory Line for this Location.\n"
1704+"You must first add this Location to the list of affected Locations on the Inventory form."
1705 msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"
1706 "Il ne fait pas partie de la liste des emplacements à inventorier"
1707
1708
1709=== added directory 'stock_inventory_location/images'
1710=== added file 'stock_inventory_location/images/future_inventory.png'
1711Binary 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:04:27 +0000 differ
1712=== added file 'stock_inventory_location/images/inventory_empty_locations.png'
1713Binary 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:04:27 +0000 differ
1714=== added file 'stock_inventory_location/images/inventory_form.png'
1715Binary 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:04:27 +0000 differ
1716=== added file 'stock_inventory_location/images/location_locked.png'
1717Binary 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:04:27 +0000 differ
1718=== added file 'stock_inventory_location/images/move_error.png'
1719Binary 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:04:27 +0000 differ
1720=== added directory 'stock_inventory_location/static'
1721=== added directory 'stock_inventory_location/static/src'
1722=== added directory 'stock_inventory_location/static/src/img'
1723=== added file 'stock_inventory_location/static/src/img/icon.png'
1724Binary 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:04:27 +0000 differ
1725=== modified file 'stock_inventory_location/stock_inventory_location.py'
1726--- stock_inventory_location/stock_inventory_location.py 2014-03-12 14:55:39 +0000
1727+++ stock_inventory_location/stock_inventory_location.py 2014-06-11 15:04:27 +0000
1728@@ -18,225 +18,267 @@
1729 #
1730 ##############################################################################
1731
1732+import time
1733 from collections import Iterable
1734
1735-from openerp.osv import osv, orm, fields
1736+from openerp.osv import orm, fields
1737 from openerp.tools.translate import _
1738-
1739-
1740-class StockInventory(osv.osv):
1741+# The next 2 imports are only needed for a feature backported from trunk-wms
1742+# TODOv8! remove, feature is included upstream
1743+from openerp.osv import osv
1744+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
1745+
1746+from .exceptions import ExhaustiveInventoryException
1747+
1748+
1749+class StockInventory(orm.Model):
1750 """Add locations to the inventories"""
1751 _inherit = 'stock.inventory'
1752+
1753+ INVENTORY_STATE_SELECTION = [
1754+ ('draft', 'Draft'),
1755+ ('open', 'Open'),
1756+ ('done', 'Done'),
1757+ ('confirm', 'Confirmed'),
1758+ ('cancel', 'Cancelled')
1759+ ]
1760+
1761 _columns = {
1762- # XXX refactor if ever lp:~numerigraphe/openobject-addons/7.0-inventory-states is accepted upstream
1763- 'state': fields.selection((('draft', 'Draft'),
1764- ('open', 'Open'),
1765- ('done', 'Done'),
1766- ('confirm', 'Confirmed'),
1767- ('cancel', 'Cancelled')), 'State', readonly=True, select=True),
1768- # Make the inventory lines read-only in all states except "Open", to ensure that no unwanted Location can be inserted
1769- 'inventory_line_id': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventory lines', readonly=True, states={'open': [('readonly', False)]}),
1770- 'location_ids': fields.many2many('stock.location', 'stock_inventory_location_rel',
1771- 'location_id', 'inventory_id',
1772- 'Locations',
1773- readonly=True, states={'draft': [('readonly', False)]},
1774- help="""This is the list of the Stock Locations that you want to count the goods in.
1775-Only these Locations can be entered in the Inventory Lines.
1776-If some of them have not been entered in the Inventory Lines, OpenERP will warn you when you confirm the Inventory."""),
1777- 'exhaustive': fields.boolean('Exhaustive', readonly=True, states={'draft': [('readonly', False)]},
1778- help="""Check the box if you are conducting an exhaustive Inventory.
1779-Leave the box unchecked if you are conducting a standard Inventory (partial inventory for example).
1780-For an exhaustive Inventory:
1781- - the status "Draft" lets you define the list of Locations where goods must be counted
1782- - 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
1783- - only the Inventory's Locations can be entered in the Inventory Lines
1784- - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory
1785- - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory"""),
1786+ # TODO v8: should we use "confirm" instead of adding "open"?
1787+ 'state': fields.selection(
1788+ INVENTORY_STATE_SELECTION, 'State', readonly=True, select=True),
1789+ # TODO v8: remove this, should not be needed anymore
1790+ # Make the inventory lines read-only in all states except "Open",
1791+ # to ensure that no unwanted Location can be inserted
1792+ 'inventory_line_id': fields.one2many(
1793+ 'stock.inventory.line', 'inventory_id', 'Inventory lines',
1794+ readonly=True, states={'open': [('readonly', False)]}),
1795+ # TODO v8: remove this, it's backported from v8
1796+ 'location_id': fields.many2one(
1797+ 'stock.location', 'Inventoried Location',
1798+ readonly=True, states={'draft': [('readonly', False)]}),
1799+ 'exhaustive': fields.boolean(
1800+ 'Exhaustive', readonly=True,
1801+ states={'draft': [('readonly', False)]},
1802+ help="Check the box if you are conducting an exhaustive "
1803+ "Inventory.\n"
1804+ "Leave the box unchecked if you are conducting a standard "
1805+ "Inventory (partial inventory for example).\n"
1806+ "For an exhaustive Inventory:\n"
1807+ " - the status \"Draft\" lets you define the list of "
1808+ "Locations where goods must be counted\n"
1809+ " - the status \"Open\" indicates that the list of Locations "
1810+ "is definitive and you are now counting the goods. In that "
1811+ "status, no Stock Moves can be recorded in/out of the "
1812+ "Inventory's Locations\n"
1813+ " - only the Inventory's Locations can be entered in the "
1814+ "Inventory Lines\n"
1815+ " - if some of the Inventory's Locations have not been "
1816+ "entered in the Inventory Lines, OpenERP warns you "
1817+ "when you confirm the Inventory\n"
1818+ " - every good that is not in the Inventory Lines is "
1819+ "considered lost, and gets moved out of the stock when you "
1820+ "confirm the Inventory"),
1821 }
1822
1823 def action_open(self, cr, uid, ids, context=None):
1824- """Open the inventory: open all locations, import and print inventory sheet become possible"""
1825- # verify if exhaustive inventory have locations before open inventory
1826- for inventory in self.browse(cr, uid, ids, context=None):
1827- if inventory.exhaustive:
1828- if not inventory.location_ids:
1829- raise osv.except_osv(_('Warning !'), _('Location missing for this inventory.'))
1830+ """Change the state of the inventory to 'open'"""
1831 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
1832
1833- # XXX refactor if ever lp:~numerigraphe/openobject-addons/7.0-inventory-states is accepted upstream
1834+ # TODO v8: remove this method? the feature looks already done upstream
1835+ def action_done(self, cr, uid, ids, context=None):
1836+ """
1837+ Don't allow to make an inventory with a date in the future.
1838+
1839+ This makes sure no stock moves will be introduced between the
1840+ moment you finish the inventory and the date of the Stock Moves.
1841+ Backported from trunk-wms:
1842+ revid:qdp-launchpad@openerp.com-20140317090656-o7lo22tzm8yuv3r8
1843+
1844+ @raise osv.except_osv:
1845+ We raise this exception on purpose instead of
1846+ ExhaustiveInventoryException to ensure forward-compatibility
1847+ with v8.
1848+ """
1849+ for inventory in self.browse(cr, uid, ids, context=None):
1850+ if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
1851+ raise osv.except_osv(
1852+ _('Error!'),
1853+ _('It\'s impossible to confirm an inventory in the '
1854+ 'future. Please change the inventory date to proceed '
1855+ 'further.'))
1856+ return super(StockInventory, self).action_done(cr, uid, ids,
1857+ context=context)
1858+
1859+ # TODO: remove this in v8
1860+ def _default_location(self, cr, uid, ids, context=None):
1861+ """Default stock location
1862+
1863+ @return: id of the stock location of the first warehouse of the
1864+ default company"""
1865+ location_id = False
1866+ company_id = self.pool['res.company']._company_default_get(
1867+ cr, uid, 'stock.warehouse', context=context)
1868+ warehouse_id = self.pool['stock.warehouse'].search(
1869+ cr, uid, [('company_id', '=', company_id)], limit=1)
1870+ if warehouse_id:
1871+ location_id = self.pool['stock.warehouse'].read(
1872+ cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
1873+ return location_id
1874+
1875 _defaults = {
1876- 'state': lambda *a: 'draft',
1877- 'exhaustive': lambda *a: False,
1878+ 'state': 'draft',
1879+ 'exhaustive': False,
1880+ # TODO: remove this in v8
1881+ 'location_id': _default_location,
1882 }
1883
1884 def _check_location_free_from_inventories(self, cr, uid, ids):
1885- """Verify that no other Inventory is being conducted on the location (exact id, not children)."""
1886- for inventory in self.browse(cr, uid, ids, context=None):
1887+ """
1888+ Verify that no other Inventory is being conducted on the same locations
1889+
1890+ Open Inventories are matched using the exact Location IDs,
1891+ excluding children.
1892+ """
1893+ # We don't get a context because we're called from a _constraint
1894+ for inventory in self.browse(cr, uid, ids):
1895 if not inventory.exhaustive:
1896- continue # always accepted on partial inventories
1897- location_ids = [location.id for location in inventory.location_ids]
1898- inv_ids = self.search(cr, uid, [('location_ids', 'in', location_ids),
1899- ('id', '!=', inventory.id),
1900- ('date', '=', inventory.date),
1901- ('exhaustive', '=', True), ])
1902- if inv_ids:
1903+ # never block standard inventories
1904+ continue
1905+ if self.search(cr, uid,
1906+ [('location_id', '=', inventory.location_id.id),
1907+ ('id', '!=', inventory.id),
1908+ ('date', '=', inventory.date),
1909+ ('exhaustive', '=', True)]):
1910+ # Quit as soon as one offending inventory is found
1911 return False
1912 return True
1913
1914- _constraints = [(_check_location_free_from_inventories,
1915- 'Other Physical inventories are being conducted using the same Locations.',
1916- ['location_ids', 'date', 'exhaustive'])]
1917+ _constraints = [
1918+ (_check_location_free_from_inventories,
1919+ 'Other Physical inventories are being conducted using the same '
1920+ 'Locations.',
1921+ ['location_id', 'date', 'exhaustive'])
1922+ ]
1923
1924 def _get_locations_open_inventories(self, cr, uid, context=None):
1925- """Search for locations on open inventories (exhaustive inventory only), and their children """
1926- open_inventories_ids = self.search(cr, uid, [('state', '=', 'open'), ], context=context)
1927- location_ids = set()
1928- for open_inventory in self.browse(cr, uid, open_inventories_ids, context=context):
1929- location_ids.update([location.id for location in open_inventory.location_ids])
1930+ """IDs of location in open exhaustive inventories, with children"""
1931+ inv_ids = self.search(
1932+ cr, uid, [('state', '=', 'open'), ('exhaustive', '=', True)],
1933+ context=context)
1934+ if not inv_ids:
1935+ # Early exit if no match found
1936+ return []
1937+ # List the Locations - normally all exhaustive inventories have one
1938+ location_ids = [inventory.location_id.id
1939+ for inventory in self.browse(cr, uid, inv_ids,
1940+ context=context)]
1941 # Extend to the children Locations
1942- if location_ids: # XXX probably works even otherwise
1943- location_ids = self.pool.get('stock.location').search(cr, uid,
1944- [('location_id', 'child_of', location_ids), ('usage', '=', 'internal')],
1945- context=context)
1946- return location_ids
1947-
1948- def confirm_uninventoried_location_wizard(self, cr, uid, ids, context=None):
1949- """ Open wizard if inventory is exhautive """
1950+ return self.pool['stock.location'].search(
1951+ cr, uid,
1952+ [('location_id', 'child_of', set(location_ids)),
1953+ ('usage', '=', 'internal')],
1954+ context=context)
1955+
1956+ def get_missing_locations(self, cr, uid, ids, context=None):
1957+ """Compute the list of location_ids which are missing from the lines
1958+
1959+ Here, "missing" means the location is the inventory's location or one
1960+ of it's children, and the inventory does not contain any line with
1961+ this location."""
1962+ inventories = self.browse(cr, uid, ids, context=context)
1963+ # Find the locations of the inventories
1964+ inv_location_ids = [i.location_id.id for i in inventories]
1965+ # Extend to the children locations
1966+ inv_location_ids = set(self.pool['stock.location'].search(
1967+ cr, uid, [
1968+ ('location_id', 'child_of', inv_location_ids),
1969+ ('usage', '=', 'internal')], context=context))
1970+ # Find the locations already recorded in inventory lines
1971+ line_locations_ids = set([l.location_id.id
1972+ for l in i.inventory_line_id
1973+ for i in inventories])
1974+ return list(inv_location_ids - line_locations_ids)
1975+
1976+ def confirm_missing_locations(self, cr, uid, ids, context=None):
1977+ """Open wizard to confirm empty locations on exhaustive inventories"""
1978 for inventory in self.browse(cr, uid, ids, context=context):
1979- if not inventory.exhaustive:
1980- return self.action_confirm(cr, uid, ids, context=context)
1981- else:
1982- context['active_ids'] = ids
1983- context['active_id'] = ids[0]
1984+ if (self.get_missing_locations(cr, uid, ids, context=context)
1985+ and inventory.exhaustive):
1986 return {
1987 'type': 'ir.actions.act_window',
1988 'view_type': 'form',
1989 'view_mode': 'form',
1990 'res_model': 'stock.inventory.uninventoried.locations',
1991 'target': 'new',
1992- 'context': context,
1993+ 'context': dict(context,
1994+ active_ids=ids,
1995+ active_id=ids[0]),
1996 'nodestroy': True,
1997- }
1998-
1999- # XXX : get from stock.py v6.0 patch. Waiting for integration on standard by openerp ...
2000- def _fill_location_lines(self, cr, uid, inventory_id, location_ids, set_stock_zero, context=None):
2001- """ To Import stock inventory according to products available in the selected locations.
2002- @param self: The object pointer.
2003- @param cr: A database cursor
2004- @param uid: ID of the user currently logged in
2005- @param location_ids: the location ID or list of location IDs if we want more than one
2006- @param inventory_id: the inventory ID
2007- @param set_stock_zero: indicate if all the lines will be imported with zero quantity
2008- @param context: A standard dictionary
2009- @return:
2010- """
2011- inventory_line_obj = self.pool.get('stock.inventory.line')
2012- move_obj = self.pool.get('stock.move')
2013- uom_obj = self.pool.get('product.uom')
2014- res = []
2015- flag = False
2016- for location in location_ids:
2017- datas = {}
2018- move_ids = move_obj.search(cr, uid, ['|', ('location_dest_id', '=', location),
2019- ('location_id', '=', location),
2020- ('state', '=', 'done')], context=context)
2021- for move in move_obj.browse(cr, uid, move_ids, context=context):
2022- if move.location_dest_id.id == move.location_id.id:
2023- continue
2024- lot_id = move.prodlot_id.id
2025- prod_id = move.product_id.id
2026- if move.location_dest_id.id == location:
2027- qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
2028- else:
2029- qty = -uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
2030- if datas.get((prod_id, lot_id)):
2031- qty = qty + datas[(prod_id, lot_id)]['product_qty']
2032-
2033- datas[(prod_id, lot_id)] = {'product_id': prod_id,
2034- 'location_id': location,
2035- # Floating point sum could introduce some tiny rounding errors.
2036- # The uom are the same on input and output to use api for rounding.
2037- 'product_qty': uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_id.uom_id),
2038- 'product_uom': move.product_id.uom_id.id,
2039- 'prod_lot_id': lot_id,
2040- 'default_code': move.product_id.default_code,
2041- 'prodlot_name': move.prodlot_id.name}
2042- if datas:
2043- flag = True
2044- res.append(datas)
2045- if not flag:
2046- raise osv.except_osv(_('Warning !'), _('No product in this location.'))
2047-
2048- stock_moves = []
2049- for stock_move in res:
2050- prod_lots = sorted(stock_move, key=lambda k: (stock_move[k]['default_code'], stock_move[k]['prodlot_name']))
2051- for prod_lot in prod_lots:
2052- stock_move_details = stock_move.get(prod_lot)
2053-
2054- if stock_move_details['product_qty'] == 0:
2055- continue # ignore product if stock equal 0
2056-
2057- stock_move_details.update({'inventory_id': inventory_id})
2058-
2059- if set_stock_zero:
2060- stock_move_details.update({'product_qty': 0})
2061-
2062- domain = [(field, '=', stock_move_details[field])
2063- for field in ['location_id',
2064- 'product_id',
2065- 'prod_lot_id']
2066- ]
2067- # children_inventory_ids is present if stock_inventory_hierarchical_location module has been installed
2068- inventory_ids = context.get('children_inventory_ids', False)
2069- if inventory_ids:
2070- domain.append(('inventory_id', 'child_of', inventory_ids))
2071- else:
2072- domain.append(('inventory_id', '=', stock_move_details['inventory_id']))
2073-
2074- line_ids = inventory_line_obj.search(cr, uid, domain, context=context)
2075- if not line_ids:
2076- stock_moves.append(stock_move_details)
2077- return stock_moves
2078-
2079-
2080-class StockInventoryLine(osv.osv):
2081+ }
2082+ return self.action_confirm(cr, uid, ids, context=context)
2083+
2084+
2085+class StockInventoryLine(orm.Model):
2086 """Only allow the Inventory's Locations"""
2087
2088 _inherit = 'stock.inventory.line'
2089
2090- def onchange_location_id(self, cr, uid, ids, location_ids, exhaustive, location_id, context=None):
2091- """ Raise exception if Location is not internal, or location_id not in locations list for this inventory """
2092- location_ids = location_ids[0][2]
2093- if not exhaustive or not location_ids:
2094- return True # don't check if partial inventory
2095+ def _default_stock_location(self, cr, uid, context=None):
2096+ res = super(StockInventoryLine, self)._default_stock_location(
2097+ cr, uid, context=context)
2098+ if context is None or not context.get('location_id', False):
2099+ return res
2100+ else:
2101+ return context['location_id']
2102+
2103+ _defaults = {
2104+ 'location_id': _default_stock_location
2105+ }
2106+
2107+ def onchange_location_id(self, cr, uid, ids, inventory_location_id,
2108+ exhaustive, location_id, context=None):
2109+ """Warn if the new is not in the location list for this inventory."""
2110+ if not exhaustive:
2111+ # Don't check if partial inventory
2112+ return True
2113
2114 # search children of location
2115- location_ids = self.pool.get('stock.location').search(cr, uid,
2116- [('location_id', 'child_of', location_ids)], context=context)
2117- if location_id not in location_ids:
2118- return {'value': {'location_id': False},
2119- 'warning': {'title': _('Warning: Wrong location'),
2120- 'message': _("You cannot add this location to inventory line.\n"
2121- "You must add this location on the locations list")}
2122- }
2123+ if location_id not in self.pool['stock.location'].search(
2124+ cr, uid, [('location_id', 'child_of', inventory_location_id)],
2125+ context=context):
2126+ return {
2127+ 'value': {'location_id': False},
2128+ 'warning': {
2129+ 'title': _('Warning: Wrong location'),
2130+ 'message': _(
2131+ "You cannot record an Inventory Line for this "
2132+ "Location.\n"
2133+ "You must first add this Location to the list of "
2134+ "affected Locations on the Inventory form.")}
2135+ }
2136 return True
2137
2138
2139-class StockLocation(osv.osv):
2140+class StockLocation(orm.Model):
2141 """Refuse changes during exhaustive Inventories"""
2142 _inherit = 'stock.location'
2143 _order = 'name'
2144
2145+ # TODOv7: why not put this in an ORM "_constraint" instead?
2146 def _check_inventory(self, cr, uid, ids, context=None):
2147- """Raise an error if an exhaustive Inventory is being conducted on this Location"""
2148- inventory_obj = self.pool.get('stock.inventory')
2149- location_inventory_open_ids = inventory_obj._get_locations_open_inventories(cr, uid, context=context)
2150+ """Error if an exhaustive Inventory is being conducted here"""
2151+ inv_obj = self.pool['stock.inventory']
2152+ location_inventory_open_ids = inv_obj._get_locations_open_inventories(
2153+ cr, uid, context=context)
2154 if not isinstance(ids, Iterable):
2155 ids = [ids]
2156- for id in ids:
2157- if id in location_inventory_open_ids:
2158- raise osv.except_osv(_('Error! Location on inventory'),
2159- _('A Physical Inventory is being conducted at this location'))
2160+ for inv_id in ids:
2161+ if inv_id in location_inventory_open_ids:
2162+ raise ExhaustiveInventoryException(
2163+ _('Error: Location locked down'),
2164+ _('A Physical Inventory is being conducted at this '
2165+ 'location'))
2166 return True
2167
2168 def write(self, cr, uid, ids, vals, context=None):
2169@@ -245,16 +287,19 @@
2170 if not isinstance(ids, Iterable):
2171 ids = [ids]
2172 ids_to_check = ids
2173- # If we are changing the parent, there must be no inventory must conducted there either
2174- if vals.get('location_id'):
2175+ # If changing the parent, no inventory must conducted there either
2176+ if vals.get('location_id'):
2177 ids_to_check.append(vals['location_id'])
2178 self._check_inventory(cr, uid, ids_to_check, context=context)
2179- return super(StockLocation, self).write(cr, uid, ids, vals, context=context)
2180+ return super(StockLocation, self).write(cr, uid, ids, vals,
2181+ context=context)
2182
2183 def create(self, cr, uid, vals, context=None):
2184 """Refuse create if an inventory is being conducted at the parent"""
2185- self._check_inventory(cr, uid, vals.get('location_id'), context=context)
2186- return super(StockLocation, self).create(cr, uid, vals, context=context)
2187+ self._check_inventory(cr, uid, vals.get('location_id'),
2188+ context=context)
2189+ return super(StockLocation, self).create(cr, uid, vals,
2190+ context=context)
2191
2192 def unlink(self, cr, uid, ids, context=None):
2193 """Refuse unlink if an inventory is being conducted"""
2194@@ -262,34 +307,45 @@
2195 return super(StockLocation, self).unlink(cr, uid, ids, context=context)
2196
2197
2198-class StockMove(osv.osv):
2199+class StockMove(orm.Model):
2200 """Refuse Moves during exhaustive Inventories"""
2201
2202 _inherit = 'stock.move'
2203
2204+ # TODOv7: adapt this to match trunk-wms
2205 def _check_open_inventory_location(self, cr, uid, ids, context=None):
2206- """ Check if location is not in opened inventory
2207- Don't check on partial inventory (checkbox "Exhaustive" not checked).
2208+ """
2209+ Check that the locations are not locked by an open inventory
2210+
2211+ Standard inventories are not checked.
2212+
2213+ @raise ExhaustiveInventoryException: an error is raised if locations
2214+ are locked, instead of returning False, in order to pass an
2215+ extensive error message back to users.
2216 """
2217 message = ""
2218- inventory_obj = self.pool.get('stock.inventory')
2219- location_inventory_open_ids = inventory_obj._get_locations_open_inventories(cr, uid, context=context)
2220- if not location_inventory_open_ids:
2221- return True # Nothing to verify
2222+ inv_obj = self.pool['stock.inventory']
2223+ locked_location_ids = inv_obj._get_locations_open_inventories(
2224+ cr, uid, context=context)
2225+ if not locked_location_ids:
2226+ # Nothing to verify
2227+ return True
2228 for move in self.browse(cr, uid, ids, context=context):
2229 if (move.location_id.usage != 'inventory'
2230- and move.location_dest_id.id in location_inventory_open_ids):
2231+ and move.location_dest_id.id in locked_location_ids):
2232 message += " - %s\n" % (move.location_dest_id.name)
2233-
2234 if (move.location_dest_id.usage != 'inventory'
2235- and move.location_id.id in location_inventory_open_ids):
2236+ and move.location_id.id in locked_location_ids):
2237 message += " - %s\n" % (move.location_id.name)
2238 if message:
2239- raise osv.except_osv(_('Error! Location on inventory'),
2240- _('One or more locations are inventoried :\n%s') % message)
2241+ raise ExhaustiveInventoryException(
2242+ _('Error: Location locked down'),
2243+ _('A Physical Inventory is being conducted at the following '
2244+ 'location(s):\n%s') % message)
2245 return True
2246
2247 _constraints = [
2248- (_check_open_inventory_location,
2249- "A Physical Inventory is being conducted at this location", ['location_id', 'location_dest_id']),
2250- ]
2251+ (_check_open_inventory_location,
2252+ "A Physical Inventory is being conducted at this location",
2253+ ['location_id', 'location_dest_id']),
2254+ ]
2255
2256=== modified file 'stock_inventory_location/stock_inventory_location_demo.xml'
2257--- stock_inventory_location/stock_inventory_location_demo.xml 2014-03-12 14:55:39 +0000
2258+++ stock_inventory_location/stock_inventory_location_demo.xml 2014-06-11 15:04:27 +0000
2259@@ -1,25 +1,26 @@
2260 <?xml version="1.0" encoding="utf-8"?>
2261 <openerp>
2262 <data noupdate="0">
2263+ <!-- Record a non exhaustive inventory -->
2264+ <record id="inventory_standard" model="stock.inventory">
2265+ <field name="name">Standard inventory</field>
2266+ <field name="state">draft</field>
2267+ </record>
2268
2269- <!-- Record inventories we can use in the tests. -->
2270- <!-- We need them in the demo data because test data is rolled back
2271- whenever an exception is raised. -->
2272-
2273- <!-- Record an non exhaustive inventory -->
2274- <record id="stock_inventory_location_0" model="stock.inventory">
2275- <field name="name">Location test inventory</field>
2276- <field name="state">draft</field>
2277- <field name="date">2020-03-01 00:00:00</field>
2278- </record>
2279-
2280 <!-- Record an exhaustive inventory -->
2281- <record id="stock_inventory_location_1" model="stock.inventory">
2282- <field name="name">Location test exhaustive inventory</field>
2283- <field name="state">draft</field>
2284- <field name="date">2020-03-15 00:00:00</field>
2285- <field name="exhaustive">True</field>
2286- <field name="location_ids" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
2287+ <record id="inventory_exhaustive" model="stock.inventory">
2288+ <field name="name">Exhaustive inventory</field>
2289+ <field name="state">draft</field>
2290+ <field name="exhaustive">True</field>
2291+ <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
2292+ </record>
2293+
2294+ <!-- Record an inventory dated in the future -->
2295+ <!-- TODOv8: remove this entry, only useful for a test already in trunk-wms -->
2296+ <record id="inventory_future" model="stock.inventory">
2297+ <field name="name">Inventory in the future</field>
2298+ <field name="state">draft</field>
2299+ <field name="date">2020-03-01 00:00:00</field>
2300 </record>
2301 </data>
2302 </openerp>
2303
2304=== modified file 'stock_inventory_location/stock_inventory_location_view.xml'
2305--- stock_inventory_location/stock_inventory_location_view.xml 2014-03-12 14:55:39 +0000
2306+++ stock_inventory_location/stock_inventory_location_view.xml 2014-06-11 15:04:27 +0000
2307@@ -1,59 +1,59 @@
2308 <?xml version="1.0" encoding="utf-8"?>
2309 <openerp>
2310 <data>
2311-
2312+
2313 <record model="ir.ui.view" id="stock_inventory_location_form_view">
2314 <field name="name">stock.inventory.location.form</field>
2315 <field name="model">stock.inventory</field>
2316 <field name="inherit_id" ref="stock.view_inventory_form"/>
2317 <field name="priority" eval="10"/>
2318 <field name="arch" type="xml">
2319- <!-- Add type of inventory : partial or complete. -->
2320- <xpath expr="/form//field[@name='date']" position="after">
2321+ <!-- Show the state 'done' in the statusbar -->
2322+ <xpath expr="/form//field[@name='state']" position="attributes">
2323+ <attribute name="statusbar_visible">draft,open,confirm</attribute>
2324+ </xpath>
2325+
2326+ <!-- TODO v8 place "exhaustive" next to "location_id" -->
2327+ <!-- Add type of inventory: standard or exhaustive. -->
2328+ <xpath expr="/form//field[@name='name']" position="after">
2329 <field name="exhaustive"/>
2330- </xpath>
2331-
2332- <!-- Add the list of Locations on exhaustive inventories -->
2333- <xpath expr="/form//field[@name='inventory_line_id']" position="before">
2334- <!-- Add the locations list for inventory -->
2335- <field colspan="1" nolabel="1" name="location_ids" domain="[('usage','in',('view', 'internal'))]" attrs="{'invisible':[('exhaustive','!=',True)]}">
2336- <tree string="Locations" editable="bottom">
2337- <field name="name"/>
2338- </tree>
2339- </field>
2340- </xpath>
2341-
2342- <!-- Add Fill Inventory button when state is open -->
2343- <xpath expr="//button[@string='Fill Inventory']" position="attributes">
2344- <attribute name="states">draft,open,confirm</attribute>
2345- </xpath>
2346-
2347- <!-- Reduce the colspan of the lines to make room for the Locations-->
2348- <xpath expr="/form//field[@name='inventory_line_id']" position="attributes">
2349- <attribute name="colspan">3</attribute>
2350- </xpath>
2351-
2352- <!-- Control locations added by user on inventory line -->
2353- <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']" position="attributes">
2354- <attribute name="on_change">onchange_location_id(parent.location_ids, parent.exhaustive, location_id)</attribute>
2355- </xpath>
2356- <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']" position="attributes">
2357- <attribute name="on_change">onchange_location_id(parent.location_ids, parent.exhaustive, location_id)</attribute>
2358- </xpath>
2359-
2360- <!-- #XXX change the attributes instead and add the button -->
2361- <!-- #XXX enlarge the group's colspan? -->
2362+ <field name="location_id" groups="stock.group_locations"
2363+ domain="[('usage','in',('view', 'internal'))]"
2364+ attrs="{'invisible':[('exhaustive','!=',True)], 'required':[('exhaustive','=',True)]}"/>
2365+ </xpath>
2366+
2367+ <!-- Enable Fill Inventory button when state is open -->
2368+ <xpath expr="//button[@string='Fill Inventory']" position="attributes">
2369+ <attribute name="states">open</attribute>
2370+ <attribute name="context">{'default_exhaustive': exhaustive}</attribute>
2371+ </xpath>
2372+
2373+ <!-- Control locations added by user on inventory line -->
2374+ <xpath expr="/form//field[@name='inventory_line_id']"
2375+ position="attributes">
2376+ <attribute name="context">{'location_id': location_id}</attribute>
2377+ </xpath>
2378+ <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']"
2379+ position="attributes">
2380+ <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
2381+ </xpath>
2382+ <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']"
2383+ position="attributes">
2384+ <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
2385+ </xpath>
2386+
2387 <!-- Add button to open an inventory. Call wizard on confirm inventory -->
2388- <xpath expr="/form//button[@name='action_cancel_inventory']" position="replace">
2389- <button name="action_cancel_inventory" states="draft,open,confirm,done" string="Cancel Inventory" type="object" icon="gtk-cancel"/>
2390- <button name="action_open" states="draft" string="Open Inventory" type="object" icon="gtk-go-forward"/>
2391- </xpath>
2392-
2393- <!-- replace action_confirm button -->
2394- <!-- #XXX change the attributes instead -->
2395- <xpath expr="/form//button[@name='action_confirm']" position="replace">
2396- <button name="confirm_uninventoried_location_wizard"
2397- string="Confirm Inventory" type="object" states="open" icon="gtk-apply"/>
2398+ <xpath expr="/form//button[@name='action_cancel_inventory']" position="before">
2399+ <button name="action_open" states="draft" string="Open Inventory" type="object" class="oe_highlight" groups="stock.group_stock_user"/>
2400+ </xpath>
2401+ <!-- Show the "cancel" button in state "open" -->
2402+ <xpath expr="/form//button[@name='action_cancel_inventory']" position="attributes">
2403+ <attribute name="states">draft,open,confirm,done</attribute>
2404+ </xpath>
2405+ <!-- hijack the "confirm" button -->
2406+ <xpath expr="/form//button[@name='action_confirm']" position="attributes">
2407+ <attribute name="states">open</attribute>
2408+ <attribute name="name">confirm_missing_locations</attribute>
2409 </xpath>
2410 </field>
2411 </record>
2412@@ -62,16 +62,16 @@
2413 <record model="ir.ui.view" id="view_inventory_complete_filter">
2414 <field name="name">complete.inventory.filter</field>
2415 <field name="model">stock.inventory</field>
2416- <field name="type">search</field>
2417 <field name="inherit_id" ref="stock.view_inventory_filter"/>
2418 <field name="arch" type="xml">
2419 <xpath expr="//field[@name='name']" position="before">
2420- <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]" help="Only select inventories that have no parents." />
2421+ <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]"
2422+ help="Only select exhaustive Inventories." />
2423 <separator orientation="vertical"/>
2424 </xpath>
2425 </field>
2426 </record>
2427-
2428+
2429 <!-- Show exhaustive inventories by default -->
2430 <record id="stock.action_inventory_form" model="ir.actions.act_window">
2431 <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>
2432
2433=== renamed file 'stock_inventory_location/test/location_exhaustive_inventory_test.yml' => 'stock_inventory_location/test/inventory_exhaustive_test.yml'
2434--- stock_inventory_location/test/location_exhaustive_inventory_test.yml 2014-03-12 14:55:39 +0000
2435+++ stock_inventory_location/test/inventory_exhaustive_test.yml 2014-06-11 15:04:27 +0000
2436@@ -3,99 +3,114 @@
2437 I will call open_action method and check if state of inventories are 'open'.
2438 -
2439 !python {model: stock.inventory}: |
2440- from osv import orm, osv
2441- self.action_open(cr, uid, [ref('stock_inventory_location_1')])
2442- inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']
2443- assert inventory_state == 'open', "Parent inventory have '%s' state. It should be 'open'" % inventory_state
2444-
2445--
2446- In order, i add products to exhaustive inventory.
2447- Don't add mou(product.product_product_25) and keya (product.product_product_24) products.
2448- Adding pc1.
2449--
2450+ self.action_open(cr, uid, [ref('inventory_exhaustive')])
2451+ inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
2452+ assert inventory_state == 'open', "Inventory in state '%s'. It should be 'open'" % inventory_state
2453+-
2454+ I create a wizard record for stock_confirm_uninventoried_location to verify that it contains the uninventoried locations
2455+-
2456+ !python {model: stock.inventory.uninventoried.locations}: |
2457+ ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
2458+ wizard_id = self.create(cr, uid, {}, context=ctx)
2459+ wizard = self.browse(cr, uid, wizard_id, context=ctx)
2460+ assert len(wizard.location_ids) > 0 , "The wizard does not contain any lines."
2461+-
2462+ I add products to exhaustive inventory.
2463+ Adding 17” LCD Monitor.
2464+-
2465 !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
2466- product_id: product.product_product_10
2467+ product_id: product.product_product_7
2468 product_uom: product.product_uom_unit
2469 company_id: base.main_company
2470- inventory_id: stock_inventory_location_1
2471+ inventory_id: inventory_exhaustive
2472 product_qty: 18.0
2473 location_id: stock.stock_location_14
2474
2475 -
2476- Adding pc3.
2477--
2478+ Adding USB Keyboard, QWERTY.
2479+-
2480 !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
2481- product_id: product.product_product_pc3
2482+ product_id: product.product_product_8
2483 product_uom: product.product_uom_unit
2484 company_id: base.main_company
2485- inventory_id: stock_inventory_location_1
2486+ inventory_id: inventory_exhaustive
2487 product_qty: 5.0
2488- location_id: stock.stock_location_14
2489+ location_id: stock.stock_location_14
2490
2491 -
2492 I will call the function _get_locations_open_inventories and check the result.
2493- The function will return only the location_ids of the exhaustive inventory.
2494+ The function will return only the location_id of the exhaustive inventory.
2495 -
2496 !python {model: stock.inventory}: |
2497- from osv import orm, osv
2498- locations = self._get_locations_open_inventories(cr, uid)
2499- assert len(locations) == 1, "Function return wrong results : %s" % locations
2500- 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])
2501-
2502--
2503+ locations = self._get_locations_open_inventories(cr, uid)
2504+ assert len(locations) == 1, "Function return wrong results: %s" % locations
2505+ 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])
2506+-
2507 I will call the function onchange_location_id.
2508- The function must to return true in the first case, and return a warning dictionnary in the second test.
2509+ The function must return True in the first case, and return a warning dictionnary in the second test.
2510 -
2511 !python {model: stock.inventory.line}: |
2512- from osv import orm, osv
2513 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_14'))
2514- assert res == True, "Exhaustive : The function 'onchange_location_id' should return True and return '%s'" % res
2515+ assert res == True, "Exhaustive: The function 'onchange_location_id' should return True and return '%s'" % res
2516 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_components'))
2517- assert res.get('warning', False) != False , "Function 'onchange_location_id' : Warning not raise. ('%s)" % res
2518+ assert res.get('warning', False) != False , "Function 'onchange_location_id': Warning not raise. ('%s)" % res
2519
2520 -
2521- I will call _fill_location_lines to simulate confirmation for stock_confirm_uninventoried_location,
2522- and create stock_moves
2523--
2524- !python {model: stock.inventory}: |
2525- from osv import orm, osv
2526- ctx={'location': [ref('stock.stock_location_14')]}
2527- lines = self._fill_location_lines(cr, uid, ref('stock_inventory_location_1'), [ref('stock.stock_location_14')], True, context=ctx)
2528- for line in lines:
2529- self.pool.get('stock.inventory.line').create(cr, uid, line, context=context)
2530-
2531--
2532- I will confirm exhaustive inventory
2533--
2534- !python {model: stock.inventory}: |
2535- from osv import orm, osv
2536- self.action_confirm(cr, uid, [ref('stock_inventory_location_1')])
2537- inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']
2538+ I create a wizard record for stock_confirm_uninventoried_location and validate it
2539+-
2540+ !python {model: stock.inventory.uninventoried.locations}: |
2541+ ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
2542+ wizard_id = self.create(cr, uid, {}, context=ctx)
2543+ wizard = self.browse(cr, uid, wizard_id, context=ctx)
2544+ assert len(wizard.location_ids) == 0 , "The wizard should not contain any lines but contains %s." % wizard.location_ids
2545+ self.confirm_uninventoried_locations(cr, uid, wizard_id, context=ctx)
2546+-
2547+ Stock moves are not allowed in the locations during the inventory.
2548+-
2549+ !python {model: stock.move}: |
2550+ # TODOv8: remove this test, this is already part of trunk-wms
2551+ from stock_inventory_location import ExhaustiveInventoryException
2552+ try:
2553+ self.create(
2554+ cr,uid, {
2555+ 'name': 'Bad move',
2556+ 'location_id': ref('stock.stock_location_14'),
2557+ 'location_dest_id': ref('stock.stock_location_3'),
2558+ 'product_id': ref('product.product_product_8'),
2559+ 'product_uom': ref('product.product_uom_unit'),
2560+ 'date_expected': '2020-01-01 00:00:00'
2561+ })
2562+ except ExhaustiveInventoryException as e:
2563+ log("Good! The Stock Move was refused: %s" % e)
2564+-
2565+ I will confirm the exhaustive inventory
2566+-
2567+ !python {model: stock.inventory}: |
2568+ self.action_confirm(cr, uid, [ref('inventory_exhaustive')])
2569+ inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
2570 assert inventory_state == 'confirm', "Exhaustive inventory have '%s' state. It should be 'confirm'" % inventory_state
2571-
2572+
2573 -
2574- I will validate exhaustive inventory
2575+ I will validate the exhaustive inventory
2576 -
2577 !python {model: stock.inventory}: |
2578- from osv import orm, osv
2579- self.action_done(cr, uid, [ref('stock_inventory_location_1')])
2580- inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']
2581+ self.action_done(cr, uid, [ref('inventory_exhaustive')])
2582+ inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
2583 assert inventory_state == 'done', "Exhaustive inventory have '%s' state. It should be 'done'" % inventory_state
2584-
2585+
2586 -
2587 I will verify the quantity for each products.
2588--
2589+-
2590 !python {model: product.product}: |
2591- from osv import orm, osv
2592- ctx={'location': [ref('stock.stock_location_14')], 'to_date': '2020-12-31 23:59:59'}
2593- prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']
2594- assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0 : %s" % prod_qty_avail
2595-
2596- prod_qty_avail = self.read(cr, uid, [ref('product.product_product_pc3')], ['qty_available'], context=ctx)[0]['qty_available']
2597- assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0 : %s" % prod_qty_avail
2598+ ctx = dict(context, location=[ref('stock.stock_location_14')])
2599+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_7')], ['qty_available'], context=ctx)[0]['qty_available']
2600+ assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0: %s" % prod_qty_avail
2601+
2602+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_8')], ['qty_available'], context=ctx)[0]['qty_available']
2603+ assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0: %s" % prod_qty_avail
2604
2605 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_24')], ['qty_available'], context=ctx)[0]['qty_available']
2606- assert prod_qty_avail == 0.0, "The stock of KEYA was not set to 0 : %s" % prod_qty_avail
2607+ assert prod_qty_avail == 0.0, "The stock of KEYA was not set to 0: %s" % prod_qty_avail
2608
2609 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_25')], ['qty_available'], context=ctx)[0]['qty_available']
2610- assert prod_qty_avail == 0.0, "The stock of MOU was not set to 0 : %s" % prod_qty_avail
2611+ assert prod_qty_avail == 0.0, "The stock of MOU was not set to 0: %s" % prod_qty_avail
2612
2613=== added file 'stock_inventory_location/test/inventory_future_test.yml'
2614--- stock_inventory_location/test/inventory_future_test.yml 1970-01-01 00:00:00 +0000
2615+++ stock_inventory_location/test/inventory_future_test.yml 2014-06-11 15:04:27 +0000
2616@@ -0,0 +1,13 @@
2617+-
2618+ Check that an inventory with a date in the future cannot be opened.
2619+-
2620+ !python {model: stock.inventory}: |
2621+ # TODO v8: maybe this is already part of the new WMS
2622+ from osv.osv import except_osv
2623+ self.action_open(cr, uid, [ref('inventory_future')])
2624+ try:
2625+ self.action_done(cr, uid, [ref('inventory_future')])
2626+ except except_osv as e:
2627+ log("Good! The Inventory could not be opened: %s" % e)
2628+ inventory_state = self.read(cr, uid, [ref('inventory_future')], ['state'])[0]['state']
2629+ assert inventory_state != 'done', "Future inventory is done."
2630
2631=== renamed file 'stock_inventory_location/test/location_inventory_test.yml' => 'stock_inventory_location/test/inventory_standard_test.yml'
2632--- stock_inventory_location/test/location_inventory_test.yml 2014-03-12 14:55:39 +0000
2633+++ stock_inventory_location/test/inventory_standard_test.yml 2014-06-11 15:04:27 +0000
2634@@ -3,61 +3,57 @@
2635 I will call open_action method and check if state of inventories are 'open'.
2636 -
2637 !python {model: stock.inventory}: |
2638- from osv import orm, osv
2639- self.action_open(cr, uid, [ref('stock_inventory_location_0')])
2640- inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']
2641- assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state
2642-
2643+ self.action_open(cr, uid, [ref('inventory_standard')])
2644+ inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
2645+ assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state
2646+
2647 -
2648 In order, I add products to inventory.
2649- Adding cpu1.
2650+ Adding Azerty Keyboard.
2651 -
2652- !record {model: stock.inventory.line, id: lines_inventory_location_cpu1}:
2653- product_id: product.product_product_cpu1
2654+ !record {model: stock.inventory.line, id: lines_inventory_location_kbaz}:
2655+ product_id: product.product_product_9
2656 product_uom: product.product_uom_unit
2657 company_id: base.main_company
2658- inventory_id: stock_inventory_location_0
2659+ inventory_id: inventory_standard
2660 product_qty: 18.0
2661 location_id: stock.stock_location_components
2662
2663 -
2664- Adding cpu3.
2665+ Adding Optical Mouse.
2666 -
2667- !record {model: stock.inventory.line, id: lines_inventory_location_cpu3}:
2668- product_id: product.product_product_cpu3
2669+ !record {model: stock.inventory.line, id: lines_inventory_location_optm}:
2670+ product_id: product.product_product_10
2671 product_uom: product.product_uom_unit
2672 company_id: base.main_company
2673- inventory_id: stock_inventory_location_0
2674+ inventory_id: inventory_standard
2675 product_qty: 12.0
2676 location_id: stock.stock_location_components
2677
2678 -
2679- Adding fan.
2680+ Adding Multimedia Speakers.
2681 -
2682- !record {model: stock.inventory.line, id: lines_inventory_location_fan}:
2683- product_id: product.product_product_fan
2684+ !record {model: stock.inventory.line, id: lines_inventory_location_grca}:
2685+ product_id: product.product_template_31
2686 product_uom: product.product_uom_unit
2687 company_id: base.main_company
2688- inventory_id: stock_inventory_location_0
2689+ inventory_id: inventory_standard
2690 product_qty: 32.0
2691 location_id: stock.stock_location_components
2692
2693 -
2694 I will call the function _get_locations_open_inventories and check the result.
2695- The function will return no locations.
2696+ The function will return no locations because it's not an exhaustive inventory.
2697 -
2698 !python {model: stock.inventory}: |
2699- from osv import orm, osv
2700- locations = self._get_locations_open_inventories(cr, uid)
2701- assert len(locations) == 0, "Function return wrong results : %s" % locations
2702-
2703+ locations = self._get_locations_open_inventories(cr, uid)
2704+ assert len(locations) == 0, "Function return wrong results: %s" % locations
2705+
2706 -
2707 I will call the function onchange_location_id.
2708- The function must to return true in all test case.
2709+ The function must to return true in all test case.
2710 -
2711 !python {model: stock.inventory.line}: |
2712- from osv import orm, osv
2713- pass
2714 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))
2715 assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
2716 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))
2717@@ -68,42 +64,37 @@
2718 The function must return True in all test cases.
2719 -
2720 !python {model: stock.location}: |
2721- from osv import orm, osv
2722 res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))
2723 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
2724 res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))
2725 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
2726-
2727--
2728- I will confirm inventory.
2729--
2730+
2731+-
2732+ I will confirm inventory.
2733+-
2734 !python {model: stock.inventory}: |
2735- from osv import orm, osv
2736- self.action_confirm(cr, uid, [ref('stock_inventory_location_0')])
2737- inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']
2738+ self.action_confirm(cr, uid, [ref('inventory_standard')])
2739+ inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
2740 assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state
2741
2742 -
2743 I will validate inventory
2744 -
2745 !python {model: stock.inventory}: |
2746- from osv import orm, osv
2747- self.action_done(cr, uid, [ref('stock_inventory_location_0')])
2748- inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']
2749- assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state
2750-
2751+ self.action_done(cr, uid, [ref('inventory_standard')])
2752+ inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
2753+ assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state
2754+
2755 -
2756 I will verify the quantity for each products.
2757--
2758+-
2759 !python {model: product.product}: |
2760- from osv import orm, osv
2761- ctx={'location': [ref('stock.stock_location_components')], 'to_date': '2020-12-31 23:59:59'}
2762- prod_qty_avail = self.read(cr, uid, [ref('product.product_product_cpu1')], ['qty_available'], context=ctx)[0]['qty_available']
2763- assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0 : %s" % prod_qty_avail
2764-
2765- prod_qty_avail = self.read(cr, uid, [ref('product.product_product_cpu3')], ['qty_available'], context=ctx)[0]['qty_available']
2766- assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0 : %s" % prod_qty_avail
2767-
2768- prod_qty_avail = self.read(cr, uid, [ref('product.product_product_fan')], ['qty_available'], context=ctx)[0]['qty_available']
2769- assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0 : %s" % prod_qty_avail
2770-
2771\ No newline at end of file
2772+ ctx={'location': [ref('stock.stock_location_components')]}
2773+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_9')], ['qty_available'], context=ctx)[0]['qty_available']
2774+ assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0: %s" % prod_qty_avail
2775+
2776+ prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']
2777+ assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0: %s" % prod_qty_avail
2778+
2779+ prod_qty_avail = self.read(cr, uid, [ref('product.product_template_31')], ['qty_available'], context=ctx)[0]['qty_available']
2780+ assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0: %s" % prod_qty_avail
2781
2782=== modified file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.py'
2783--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-03-12 14:55:39 +0000
2784+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-06-11 15:04:27 +0000
2785@@ -18,78 +18,45 @@
2786 #
2787 ##############################################################################
2788
2789-from openerp.osv import fields, osv
2790-from openerp.tools.translate import _
2791-
2792-
2793-class stock_inventory_uninventoried_location(osv.osv_memory):
2794+from openerp.osv import fields, orm
2795+
2796+
2797+class stock_inventory_uninventoried_location(orm.TransientModel):
2798 _name = 'stock.inventory.uninventoried.locations'
2799 _description = 'Confirm the uninventoried Locations.'
2800
2801 _columns = {
2802- 'location_ids': fields.many2many('stock.location',
2803- 'stock_inventory_uninventoried_location_rel',
2804- 'location_id',
2805- 'wizard_id',
2806- 'Uninventoried location', readonly=True),
2807- }
2808-
2809- def get_locations(self, cr, uid, inventory_id, context=None):
2810- """ Get all locations from inventory. """
2811- location_ids = self.pool.get('stock.inventory').read(cr, uid, [inventory_id], ['location_ids'], context=context)[0]
2812- return self.pool.get('stock.location').search(cr, uid, [
2813- ('location_id', 'child_of', location_ids['location_ids']),
2814- ('usage', '=', 'internal')], context=context)
2815-
2816- def get_locations_inventoried(self, cr, uid, inventory_id, location_ids, context=None):
2817- """ Get all locations on inventory lines. """
2818- inventory_line_obj = self.pool.get('stock.inventory.line')
2819- inventory_line_ids = inventory_line_obj.search(cr, uid, [('location_id', 'in', location_ids),
2820- ('inventory_id', '=', inventory_id)], context=context)
2821- inventory_line_locations_ids = inventory_line_obj.read(cr, uid, inventory_line_ids, ['location_id'], context=context)
2822- return list(set([_id['location_id'][0] for _id in inventory_line_locations_ids]))
2823+ 'location_ids': fields.many2many(
2824+ 'stock.location',
2825+ 'stock_inventory_uninventoried_location_rel',
2826+ 'location_id', 'wizard_id',
2827+ 'Uninventoried location', readonly=True),
2828+ }
2829
2830 def default_locations(self, cr, uid, context=None):
2831- """ Initialize view with the list of uninventoried locations.
2832- Search for children of the location if exists.
2833- """
2834- if context is None:
2835- context = {}
2836- location_ids = self.get_locations(cr, uid, context['active_id'])
2837- inventory_line_locations_ids = self.get_locations_inventoried(cr, uid, context['active_id'], location_ids)
2838- return [_id for _id in location_ids if _id not in inventory_line_locations_ids]
2839+ """Initialize view with the list of uninventoried locations."""
2840+ return self.pool['stock.inventory'].get_missing_locations(
2841+ cr, uid, context['active_ids'], context=context)
2842
2843 _defaults = {
2844 'location_ids': default_locations,
2845- }
2846+ }
2847
2848 def confirm_uninventoried_locations(self, cr, uid, ids, context=None):
2849- """ Call action confirm method from stock.inventory """
2850+ """Add the missing inventory lines with qty=0 and confirm inventory"""
2851 inventory_ids = context['active_ids']
2852- # call the wizard to add lines for uninventoried locations with zero quantity
2853- inventory_obj = self.pool.get('stock.inventory')
2854- if not isinstance(inventory_ids, list):
2855- inventory_ids = [inventory_ids]
2856-
2857- for inventory in inventory_obj.browse(cr, uid, inventory_ids, context=context):
2858+ inventory_obj = self.pool['stock.inventory']
2859+ wizard_obj = self.pool['stock.fill.inventory']
2860+ for inventory in inventory_obj.browse(cr, uid, inventory_ids,
2861+ context=context):
2862 if inventory.exhaustive:
2863- location_ids = self.get_locations(cr, uid, inventory.id, context=context)
2864- # get stock inventory lines
2865- lines = []
2866- try:
2867- # create inventory lines with zero qty
2868- lines = inventory_obj._fill_location_lines(cr, uid,
2869- inventory.id,
2870- location_ids,
2871- True,
2872- context=context)
2873- except osv.except_osv as e:
2874- pass
2875-
2876- for line in lines:
2877- self.pool.get('stock.inventory.line').create(cr, uid, line, context=context)
2878+ # silently run the import wizard with qty=0
2879+ wizard_id = wizard_obj.create(
2880+ cr, uid, {'location_id': inventory.location_id.id,
2881+ 'recursive': True,
2882+ 'set_stock_zero': True}, context=context)
2883+ wizard_obj.fill_inventory(cr, uid, [wizard_id],
2884+ context=context)
2885
2886 inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)
2887 return {'type': 'ir.actions.act_window_close'}
2888-
2889-
2890
2891=== modified file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml'
2892--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-03-12 14:55:39 +0000
2893+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-06-11 15:04:27 +0000
2894@@ -1,33 +1,33 @@
2895 <?xml version="1.0" encoding="utf-8"?>
2896 <openerp>
2897 <data>
2898- <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml,
2899- but the code is different.
2900+ <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml,
2901+ but the code is different.
2902 This wizard compare declared locations with locations on inventory lines to present the uninventoried (empty) locations to user. -->
2903 <record id="view_confirm_uninventoried_location" model="ir.ui.view">
2904 <field name="name">stock.inventory.uninventoried.locations.form</field>
2905 <field name="model">stock.inventory.uninventoried.locations</field>
2906- <field name="type">form</field>
2907 <field name="arch" type="xml">
2908- <form string="Confirm uninventoried locations">
2909+ <form string="Confirm empty locations" version="7.0">
2910 <group colspan="4" col="1">
2911- <label string="The following Stock Locations are part of the current Physical Inventory, but not Inventory Line has been recorded for them."/>
2912+ <separator string="The following Locations are empty"/>
2913+ <label string="The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."/>
2914+ <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
2915+ <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
2916 <field name="location_ids" nolabel="1">
2917 <tree>
2918 <field name="name"/>
2919 </tree>
2920 </field>
2921- <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
2922- <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
2923 <separator string=""/>
2924 </group>
2925- <group colspan="4" col="2">
2926- <button special="cancel" string="Cancel" icon="gtk-cancel" default_focus="1" />
2927- <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" icon="gtk-ok"/>
2928- </group>
2929+ <footer>
2930+ <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" class="oe_highlight"/>
2931+ or
2932+ <button string="Cancel" class="oe_link" special="cancel" default_focus="1"/>
2933+ </footer>
2934 </form>
2935 </field>
2936 </record>
2937-
2938 </data>
2939 </openerp>
2940\ No newline at end of file
2941
2942=== modified file 'stock_inventory_location/wizard/stock_fill_location_inventory.py'
2943--- stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-03-12 14:55:39 +0000
2944+++ stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-06-11 15:04:27 +0000
2945@@ -18,80 +18,30 @@
2946 #
2947 ##############################################################################
2948
2949-from openerp.osv import fields, osv
2950-from openerp.tools.translate import _
2951-from collections import OrderedDict
2952-
2953-
2954-class stock_fill_location_inventory(osv.osv_memory):
2955+from openerp.osv import fields, orm
2956+
2957+
2958+class FillInventoryWizard(orm.TransientModel):
2959+ """Add a field that lets the client make the location read-only"""
2960 _inherit = 'stock.fill.inventory'
2961
2962 _columns = {
2963- 'location_id': fields.many2one('stock.location', 'Location'),
2964- #'exhaustive': fields.boolean('stock.inventory', 'Type'),
2965- 'exhaustive': fields.boolean('stock.inventory'),
2966- }
2967-
2968- def get_inventory_type(self, cr, uid, context=None):
2969- if context.get('active_id', False):
2970- inventory_obj = self.pool.get('stock.inventory')
2971- exhaustive = inventory_obj.read(cr, uid, [context.get('active_id')], ['exhaustive'], context=context)[0]['exhaustive']
2972- return exhaustive
2973- return False
2974-
2975- _defaults = {
2976- 'exhaustive': get_inventory_type,
2977- }
2978-
2979- def view_init(self, cr, uid, fields_list, context=None):
2980- """ inherit from original to add multiple selection of location
2981- and exclude from location list the locations already choosen by another inventory """
2982- if context is None:
2983- context = {}
2984-
2985- inventory_obj = self.pool.get('stock.inventory')
2986- inventory_state = inventory_obj.read(cr, uid, [context.get('active_id')], ['state'], context=context)[0]
2987- if inventory_state['state'] != 'open':
2988- raise osv.except_osv(_('Error !'),
2989- _('the inventory must be in "Open" state.'))
2990-
2991- nb_inventory = inventory_obj.search(cr, uid, [('id', '=', context.get('active_id'))], count=True, context=context)
2992- if nb_inventory == 0:
2993- raise osv.except_osv(_('Warning !'),
2994- _('No locations found for the inventory.'))
2995-
2996- return super(stock_fill_location_inventory, self).view_init(cr, uid, fields_list, context=context)
2997-
2998- def fill_inventory(self, cr, uid, ids, context=None):
2999- """ Fill the inventory only with open locations on the inventory.
3000- """
3001- if context is None:
3002- context = {}
3003-
3004- fill_inventory = self.browse(cr, uid, ids[0], context=context)
3005- if not fill_inventory.exhaustive:
3006- return super(stock_fill_location_inventory, self).fill_inventory(cr, uid, ids, context=context) # call standard wizard
3007-
3008- location_ids = self.pool.get('stock.inventory').read(cr, uid, [context.get('active_id')], ['location_ids'])[0]
3009-
3010- if not location_ids['location_ids']:
3011- raise osv.except_osv(_('Error : Empty location !'), _('No location to import.\nYou must add a location on the locations list.'))
3012-
3013- if fill_inventory.recursive:
3014- location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids['location_ids']),
3015- ('usage', '=', 'internal')], context=context)
3016- else:
3017- location_ids = location_ids['location_ids']
3018-
3019- location_ids = list(OrderedDict.fromkeys(location_ids))
3020-
3021- lines = self.pool.get('stock.inventory')._fill_location_lines(cr, uid,
3022- context['active_ids'][0],
3023- location_ids,
3024- fill_inventory.set_stock_zero,
3025- context=context)
3026-
3027- inventory_lines_obj = self.pool.get('stock.inventory.line')
3028- for line in lines:
3029- inventory_lines_obj.create(cr, uid, line, context=context)
3030- return {'type': 'ir.actions.act_window_close'}
3031+ 'exhaustive': fields.boolean('Exhaustive', readonly=True)
3032+ }
3033+
3034+ def default_get(self, cr, uid, fields, context=None):
3035+ """Get 'location_id' and 'exhaustive' from the inventory"""
3036+ if context is None:
3037+ context = {}
3038+ inv_id = context.get('active_id')
3039+
3040+ res = super(FillInventoryWizard, self).default_get(
3041+ cr, uid, fields, context=context)
3042+ if (context.get('active_model') == 'stock.inventory'
3043+ and inv_id
3044+ and 'location_id' in fields):
3045+ inventory = self.pool['stock.inventory'].browse(
3046+ cr, uid, context['active_id'], context=context)
3047+ res.update({'location_id': inventory.location_id.id,
3048+ 'exhaustive': inventory.exhaustive})
3049+ return res
3050
3051=== modified file 'stock_inventory_location/wizard/stock_fill_location_inventory_view.xml'
3052--- stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-03-12 14:55:39 +0000
3053+++ stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-06-11 15:04:27 +0000
3054@@ -10,7 +10,7 @@
3055 <field name="exhaustive" invisible="1" />
3056 </xpath>
3057 <xpath expr="//field[@name='location_id']" position="attributes">
3058- <attribute name="attrs">{'invisible':[('exhaustive','=',True)]}</attribute>
3059+ <attribute name="attrs">{'readonly':[('exhaustive','=',True)]}</attribute>
3060 </xpath>
3061 </field>
3062 </record>

Subscribers

People subscribed via source and target branches