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
=== modified file 'stock_inventory_hierarchical/__init__.py'
--- stock_inventory_hierarchical/__init__.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical/__init__.py 2014-06-11 15:04:27 +0000
@@ -18,4 +18,11 @@
18#18#
19##############################################################################19##############################################################################
2020
21# This package-wide list keeps the names of the field that must be
22# propagated from root inventories to their children.
23# Add field names in the Model's definition.
24PARENT_VALUES = []
25
21import hierarchical_inventory26import hierarchical_inventory
27# Bring the main exception into the package's scope for easier reuse
28from .exceptions import HierarchicalInventoryException
2229
=== modified file 'stock_inventory_hierarchical/__openerp__.py'
--- stock_inventory_hierarchical/__openerp__.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical/__openerp__.py 2014-06-11 15:04:27 +0000
@@ -20,22 +20,27 @@
2020
21{21{
22 "name": "Hierarchical Physical Inventory",22 "name": "Hierarchical Physical Inventory",
23 "version": "1.0",23 "version": "1.1",
24 "depends": ["stock"],24 "depends": ["stock"],
25 "author": "Numérigraphe",25 "author": "Numérigraphe",
26 "category": "stock inventory",26 "category": "Warehouse Management",
27 "description": """27 "description": """
28Hierarchical structure for Physical Inventories and sub-Inventories28Hierarchical structure for Physical Inventories and sub-Inventories
29===================================================================29===================================================================
3030
31This module adds a parent-child relationship between Physical Inventories, to help users31This module adds a parent-child relationship between Physical Inventories, to
32manage complex inventories.32help users manage complex inventories.
33Using several inventories, you can distribute the counting to several persons33Using several inventories, you can distribute the counting to several persons
34and still keep a clear overview of global Inventory's status.34and still keep a clear overview of global Inventory's status.
3535
36OpenERP will make sure the status of the Inventory and it's sub-Inventories are consistent.36OpenERP will make sure the status of the Inventory and it's Sub-Inventories are
37consistent.
37""",38""",
38 "update_xml": ["hierarchical_inventory_view.xml"],39 "data": ["hierarchical_inventory_view.xml"],
39 "test": ["test/hierarchical_inventory_test.yml"],40 "test": ["test/hierarchical_inventory_test.yml"],
40 "demo": ["hierarchical_inventory_demo.xml"]41 "demo": ["hierarchical_inventory_demo.xml"],
42 "images": [
43 "inventory_form.png",
44 "inventory_form_actions.png",
45 ],
41}46}
4247
=== added file 'stock_inventory_hierarchical/exceptions.py'
--- stock_inventory_hierarchical/exceptions.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/exceptions.py 2014-06-11 15:04:27 +0000
@@ -0,0 +1,26 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import orm
22
23
24class HierarchicalInventoryException(orm.except_orm):
25 """The operation is not possible for a hierarchical inventory"""
26 pass
027
=== modified file 'stock_inventory_hierarchical/hierarchical_inventory.py'
--- stock_inventory_hierarchical/hierarchical_inventory.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory.py 2014-06-11 15:04:27 +0000
@@ -18,11 +18,17 @@
18#18#
19##############################################################################19##############################################################################
2020
21from openerp.osv import osv, fields21from openerp.osv import orm, fields
22from openerp.tools.translate import _22from openerp.tools.translate import _
2323
2424from .exceptions import HierarchicalInventoryException
25class stock_inventory_hierarchical(osv.osv):25
26# Add the date to the list of fields we must propagate to children inventories
27from . import PARENT_VALUES
28PARENT_VALUES.append('date')
29
30
31class HierarchicalInventory(orm.Model):
26 _inherit = 'stock.inventory'32 _inherit = 'stock.inventory'
2733
28 _parent_store = True34 _parent_store = True
@@ -30,7 +36,19 @@
30 _order = 'parent_left'36 _order = 'parent_left'
3137
32 def name_get(self, cr, uid, ids, context=None):38 def name_get(self, cr, uid, ids, context=None):
33 """Show the parent inventory's name in the name of the children"""39 """Show the parent inventory's name in the name of the children
40
41 :param dict context: the ``inventory_display`` key can be
42 used to select the short version of the
43 inventory name (without the direct parent),
44 when set to ``'short'``. The default is
45 the long version."""
46 if context is None:
47 context = {}
48 if context.get('inventory_display') == 'short':
49 # Short name context: just do the usual stuff
50 return super(HierarchicalInventory, self).name_get(
51 cr, uid, ids, context=context)
34 if isinstance(ids, (list, tuple)) and not len(ids):52 if isinstance(ids, (list, tuple)) and not len(ids):
35 return []53 return []
36 if isinstance(ids, (long, int)):54 if isinstance(ids, (long, int)):
@@ -44,130 +62,105 @@
44 res.append((record['id'], name))62 res.append((record['id'], name))
45 return res63 return res
4664
47 def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):65 def name_search(self, cr, uid, name='', args=None, operator='ilike',
48 """Allow search on value returned by name_get ("parent/ child")"""66 context=None, limit=100):
67 """Enable search on value returned by name_get ("parent / child")"""
49 if not args:68 if not args:
50 args = []69 args = []
51 if not context:70 if not context:
52 context = {}71 context = {}
53 if name:72 if name:
54 # Be sure name_search is symetric to name_get73 # Make sure name_search is symmetric to name_get
55 name = name.split(' / ')[-1]74 name = name.split(' / ')[-1]
56 ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context)75 ids = self.search(cr, uid, [('name', operator, name)] + args,
76 limit=limit, context=context)
57 else:77 else:
58 ids = self.search(cr, uid, args, limit=limit, context=context)78 ids = self.search(cr, uid, args, limit=limit, context=context)
59 return self.name_get(cr, uid, ids, context=context)79 return self.name_get(cr, uid, ids, context=context)
6080
61 def _name_get_fnc(self, cr, uid, ids, field_name, arg, context=None):81 def _complete_name(self, cr, uid, ids, field_name, arg, context=None):
62 """Function field containing the long name"""82 """Function-field wrapper to get the complete name from name_get"""
63 res = self.name_get(cr, uid, ids, context=context)83 res = self.name_get(cr, uid, ids, context=context)
64 return dict(res)84 return dict(res)
6585
66 def _confirmed_rate(self, cr, uid, ids, field_name, arg, context=None):86 def _progress_rate(self, cr, uid, ids, field_name, arg, context=None):
67 """Number of (sub)inventories confirmed/total"""87 """Rate of (sub)inventories done/total"""
68 rates = {}88 rates = {}
69 for id in ids:89 for current_id in ids:
70 nb = self.search(cr, uid, [('parent_id', 'child_of', id)], context=context, count=True)90 nb = self.search(
71 nb_confirmed = self.search(cr, uid, [('parent_id', 'child_of', id),91 cr, uid, [('parent_id', 'child_of', current_id)],
72 ('state', 'in', ('confirm', 'done'))], context=context, count=True)92 context=context, count=True)
73 rates[id] = 100 * nb_confirmed / nb93 if not nb:
94 # No inventory, consider it's 0% done
95 rates[current_id] = 0
96 continue
97 nb_done = self.search(
98 cr, uid, [('parent_id', 'child_of', current_id),
99 ('state', '=', 'done')],
100 context=context, count=True)
101 rates[current_id] = 100 * nb_done / nb
74 return rates102 return rates
75103
76 _columns = {104 _columns = {
77 # XXX remove "method=True" in v7 ?105 # name_get() only changes the default name of the record, not the
78 'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Complete reference'),106 # content of the field "name" so we add another field for that
79 'parent_id': fields.many2one('stock.inventory', 'Parent', ondelete='cascade', readonly=True, states={'draft': [('readonly', False)]}),107 'complete_name': fields.function(
80 'inventory_ids': fields.one2many('stock.inventory', 'parent_id', 'List of Sub-inventories', readonly=True, states={'draft': [('readonly', False)]}),108 _complete_name, type="char",
109 string='Complete reference'),
110 'parent_id': fields.many2one(
111 'stock.inventory', 'Parent', ondelete='cascade', readonly=True,
112 states={'draft': [('readonly', False)]}),
113 'inventory_ids': fields.one2many(
114 'stock.inventory', 'parent_id', 'List of Sub-inventories',
115 readonly=True, states={'draft': [('readonly', False)]}),
81 'parent_left': fields.integer('Parent Left', select=1),116 'parent_left': fields.integer('Parent Left', select=1),
82 'parent_right': fields.integer('Parent Right', select=1),117 'parent_right': fields.integer('Parent Right', select=1),
83 'confirmed_rate': fields.function(_confirmed_rate, method=True, string='Confirmed', type='float'),118 'progress_rate': fields.function(
119 _progress_rate, string='Progress', type='float'),
84 }120 }
85121
86 # XXX: drop this in v7
87 def _check_recursion(self, cr, uid, ids, context=None, parent=None):
88 """Backport of osv.osv._check_recursion from v7.0, to allow writing parents and children in the same write()"""
89 if not parent:
90 parent = self._parent_name
91
92 # must ignore 'active' flag, ir.rules, etc. => direct SQL query
93 query = 'SELECT "%s" FROM "%s" WHERE id = %%s' % (parent, self._table)
94 for id in ids:
95 current_id = id
96 while current_id is not None:
97 cr.execute(query, (current_id,))
98 result = cr.fetchone()
99 current_id = result[0] if result else None
100 if current_id == id:
101 return False
102 return True
103
104 # XXX: use this in v7
105 # _constraints = [(osv.osv._check_recursion, 'Error! You can not create recursive inventories.', ['parent_id']), ]
106 _constraints = [122 _constraints = [
107 (_check_recursion,123 (orm.Model._check_recursion,
108 _('Error! You can not create recursive inventories.'), ['parent_id']),124 'Error: You can not create recursive inventories.',
125 ['parent_id']),
109 ]126 ]
110127
111 # This is the list of fields that must be forced from Inventories to Sub-Inventories 128 def create(self, cr, uid, vals, context=None):
112 # TODO: propose this as a new feature of the ORM's API using (using a field named _parent_values for example)
113 PARENT_VALUES = ['date']
114
115# XXX: Ideally we would have liked to have a button to open Sub-inventories,
116# but unfortunately the v6.0 GTK client crashes, and the 6.0 web client opens a windows without action buttons.
117# Maybe we may try that again with the new web client one day...
118# def open_sub_inventory(self, cr, uid, id, context=None):
119# """Method to open Sub-inventory from one2many list on new tab, with specific view."""
120# # Find out the form view id
121# if not isinstance(id, list):
122# id = [id]
123# id = id[0]
124# res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'view_inventory_form')
125# view_id = res and res[1] or False
126# inv = self.browse(cr, uid, id, context=context)
127# return {
128# 'type': 'ir.actions.act_window',
129# 'name': _("Sub-inventory : %s") % inv.name,
130# 'view_type': 'form',
131# 'view_mode': 'form',
132# 'view_id': [view_id],
133# 'res_model': 'stock.inventory',
134# 'res_id': id,
135# }
136# return True
137
138 # TODO: propose this as a new feature of the ORM's API using (using a field named _parent_values for example)
139 def create(self, cr, user, vals, context=None):
140 """Copy selected values from parent to child"""129 """Copy selected values from parent to child"""
141 if vals and vals.get('parent_id'):130 if vals and vals.get('parent_id'):
142 for f in self.PARENT_VALUES:131 existing_fields = self.fields_get_keys(cr, uid, context=context)
143 parent_field = self.read(cr, user, [vals['parent_id']], [f], context=context)132 parent_values = self.read(cr, uid, [vals['parent_id']],
144 vals = vals.copy()133 PARENT_VALUES, context=context)
145 vals[f] = parent_field[0][f]134 vals = vals.copy()
146 return super(stock_inventory_hierarchical, self).create(cr, user, vals, context=context)135 vals.update({field: parent_values[0][field]
136 for field in PARENT_VALUES
137 if field in existing_fields})
138 return super(HierarchicalInventory, self).create(
139 cr, uid, vals, context=context)
147140
148 # TODO: propose this as a new feature of the ORM's API using (using a field named _parent_values for example)
149 def write(self, cr, uid, ids, vals, context=None):141 def write(self, cr, uid, ids, vals, context=None):
150 """Copy selected values from parent to children"""142 """Copy selected values from parent to children"""
151 if context is None:143 if context is None:
152 context = {}144 context = {}
153145
154 values = super(stock_inventory_hierarchical, self).write(cr, uid, ids, vals, context=context)146 values = super(HierarchicalInventory, self).write(
155 if not vals or context.get('norecurs'):147 cr, uid, ids, vals, context=context)
148 if not vals or context.get('norecurse', False):
156 return values149 return values
157150
158 record = {}151 # filter the fields we want to propagate
159 for f in self.PARENT_VALUES:152 children_values = {
160 if f in vals:153 field: vals[field] for field in PARENT_VALUES if field in vals
161 record[f] = vals[f]154 }
162 if not record:155 if not children_values:
163 return values156 return values
164157
165 if not isinstance(ids, list):158 if not isinstance(ids, list):
166 ids = [ids]159 ids = [ids]
167 children_ids = self.search(cr, uid, [('parent_id', 'child_of', ids)])160 # The context disables recursion - children are already included
168 ctx = context.copy()161 return self.write(
169 ctx['norecurs'] = True # needed to write children once.162 cr, uid, self.search(cr, uid, [('parent_id', 'child_of', ids)]),
170 return self.write(cr, uid, children_ids, record, context=ctx)163 children_values, context=dict(context, norecurse=True))
171164
172 def action_cancel_inventory(self, cr, uid, ids, context=None):165 def action_cancel_inventory(self, cr, uid, ids, context=None):
173 """Cancel inventory only if all the parents are canceled"""166 """Cancel inventory only if all the parents are canceled"""
@@ -176,25 +169,34 @@
176 while inventory.parent_id:169 while inventory.parent_id:
177 inventory = inventory.parent_id170 inventory = inventory.parent_id
178 if inventory.state != 'cancel':171 if inventory.state != 'cancel':
179 raise osv.except_osv(_('Warning !'), 172 raise HierarchicalInventoryException(
180 _('One of the parent Inventories is not canceled.'))173 _('Warning'),
181 return super(stock_inventory_hierarchical, self).action_cancel_inventory(cr, uid, ids, context=context)174 _('One of the parent Inventories is not canceled.'))
175 return super(HierarchicalInventory,
176 self).action_cancel_inventory(cr, uid, ids,
177 context=context)
182178
183 def action_confirm(self, cr, uid, ids, context=None):179 def action_confirm(self, cr, uid, ids, context=None):
184 """Confirm inventory only if all the children are confirmed"""180 """Confirm inventory only if all the children are confirmed"""
185 children_count = self.search(cr, uid, [('parent_id', 'child_of', ids),181 children_count = self.search(
186 ('state', 'not in', ['confirm', 'done'])], context=context, count=True)182 cr, uid, [('parent_id', 'child_of', ids),
183 ('state', 'not in', ['confirm', 'done'])],
184 context=context, count=True)
187 if children_count > 1:185 if children_count > 1:
188 raise osv.except_osv(_('Warning !'),186 raise HierarchicalInventoryException(
189 _('Some Sub-inventories are not confirmed.'))187 _('Warning'),
190 return super(stock_inventory_hierarchical, self).action_confirm(cr, uid, ids, context=context)188 _('Some Sub-inventories are not confirmed.'))
189 return super(HierarchicalInventory, self).action_confirm(
190 cr, uid, ids, context=context)
191191
192 def action_done(self, cr, uid, ids, context=None):192 def action_done(self, cr, uid, ids, context=None):
193 """Perform validation only if all the children states are 'done'."""193 """Perform validation only if all the children states are 'done'."""
194 children_count = self.search(cr, uid, [('parent_id', 'child_of', ids),194 children_count = self.search(cr, uid, [('parent_id', 'child_of', ids),
195 ('state', '!=', 'done')],195 ('state', '!=', 'done')],
196 context=context, count=True)196 context=context, count=True)
197 if children_count > 1:197 if children_count > 1:
198 raise osv.except_osv(_('Warning !'),198 raise HierarchicalInventoryException(
199 _('Some Sub-inventories are not done.'))199 _('Warning'),
200 return super(stock_inventory_hierarchical, self).action_done(cr, uid, ids, context=context)200 _('Some Sub-inventories are not validated.'))
201 return super(HierarchicalInventory, self).action_done(
202 cr, uid, ids, context=context)
201203
=== modified file 'stock_inventory_hierarchical/hierarchical_inventory_demo.xml'
--- stock_inventory_hierarchical/hierarchical_inventory_demo.xml 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory_demo.xml 2014-06-11 15:04:27 +0000
@@ -1,30 +1,18 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<openerp>2<openerp>
3 <data noupdate="0">3 <data noupdate="0">
44 <!-- Example Inventory with Sub-Inventories. -->
5 <!-- Record a production lot we can use in the tests. -->
6 <record id="lot_test0" model="stock.production.lot">
7 <field name="product_id" ref="product.product_product_10" />
8 </record>
9
10 <!-- Record inventories we can use in the tests. -->
11 <!-- We need them in the demo data because test data is rolled back
12 whenever an exception is raised. -->
13
14 <!-- Record an inventory to make sure the next one will have stock moves posted. -->
15 <record id="stock_inventory_parent0" model="stock.inventory">5 <record id="stock_inventory_parent0" model="stock.inventory">
16 <field name="name">Parent test inventory</field>6 <field name="name">Main Inventory</field>
17 <field name="state">draft</field>
18 <field name="date">2020-01-01 00:00:00</field>7 <field name="date">2020-01-01 00:00:00</field>
19 </record> 8 </record>
20 <record id="stock_inventory_line" model="stock.inventory.line">9 <record id="child_1_id" model="stock.inventory">
21 <field name="inventory_id" ref="stock_inventory_parent0" />10 <field name="name">Sub-Inventory 1</field>
22 <field name="product_id" ref="product.product_product_10" />11 <field name="parent_id" ref="stock_inventory_parent0" />
23 <field name="prod_lot_id" ref="lot_test0"/>12 </record>
24 <field name="product_uom" ref="product.product_uom_unit" />13 <record id="child_2_id" model="stock.inventory">
25 <field name="product_qty">27.0</field>14 <field name="name">Sub-Inventory 2</field>
26 <field name="location_id" ref="stock.stock_location_components" />15 <field name="parent_id" ref="stock_inventory_parent0" />
27 </record> 16 </record>
28
29 </data>17 </data>
30</openerp>18</openerp>
3119
=== modified file 'stock_inventory_hierarchical/hierarchical_inventory_view.xml'
--- stock_inventory_hierarchical/hierarchical_inventory_view.xml 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory_view.xml 2014-06-11 15:04:27 +0000
@@ -5,16 +5,14 @@
5 <record model="ir.ui.view" id="stock_inventory_hierarchical_tree_view">5 <record model="ir.ui.view" id="stock_inventory_hierarchical_tree_view">
6 <field name="name">hierarchical.inventory.tree</field>6 <field name="name">hierarchical.inventory.tree</field>
7 <field name="model">stock.inventory</field>7 <field name="model">stock.inventory</field>
8 <field name="type">tree</field>
9 <field name="inherit_id" ref="stock.view_inventory_tree" />8 <field name="inherit_id" ref="stock.view_inventory_tree" />
10 <field name="field_parent">inventory_ids</field>9 <field name="field_parent">inventory_ids</field>
11 <field name="arch" type="xml">10 <field name="arch" type="xml">
12 <xpath expr="//field[@name='name']" position="replace">11 <xpath expr="//field[@name='name']" position="replace">
13 <field name="complete_name"/>12 <field name="complete_name" string="Reference"/>
14 </xpath>13 </xpath>
15 <xpath expr="//field[@name='state']" position="after">14 <xpath expr="//field[@name='state']" position="after">
16 <field name="confirmed_rate" widget="progressbar" />15 <field name="progress_rate" widget="progressbar" />
17 <field name="inventory_ids" string="Number of Sub-inventories" />
18 </xpath>16 </xpath>
19 </field>17 </field>
20 </record>18 </record>
@@ -23,7 +21,6 @@
23 <record model="ir.ui.view" id="view_inventory_subinventories_filter">21 <record model="ir.ui.view" id="view_inventory_subinventories_filter">
24 <field name="name">hierarchical.inventory.filter</field>22 <field name="name">hierarchical.inventory.filter</field>
25 <field name="model">stock.inventory</field>23 <field name="model">stock.inventory</field>
26 <field name="type">search</field>
27 <field name="inherit_id" ref="stock.view_inventory_filter" />24 <field name="inherit_id" ref="stock.view_inventory_filter" />
28 <field name="arch" type="xml">25 <field name="arch" type="xml">
29 <xpath expr="//field[@name='name']" position="before">26 <xpath expr="//field[@name='name']" position="before">
@@ -43,9 +40,11 @@
43 <record model="ir.ui.view" id="stock_inventory_hierarchical_form_view">40 <record model="ir.ui.view" id="stock_inventory_hierarchical_form_view">
44 <field name="name">hierarchical.inventory.form</field>41 <field name="name">hierarchical.inventory.form</field>
45 <field name="model">stock.inventory</field>42 <field name="model">stock.inventory</field>
46 <field name="type">form</field>
47 <field name="inherit_id" ref="stock.view_inventory_form" />43 <field name="inherit_id" ref="stock.view_inventory_form" />
48 <field name="arch" type="xml">44 <field name="arch" type="xml">
45 <xpath expr="/form//field[@name='name']" position="after">
46 <field name="parent_id"/>
47 </xpath>
49 <xpath expr="/form//field[@name='date']" position="attributes">48 <xpath expr="/form//field[@name='date']" position="attributes">
50 <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>49 <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
51 </xpath>50 </xpath>
@@ -53,62 +52,22 @@
53 expr="//page[@string='General Information']"52 expr="//page[@string='General Information']"
54 position="after">53 position="after">
55 <page string="Sub-inventories">54 <page string="Sub-inventories">
56 <field name="parent_id" invisible="1" />55 <field name="inventory_ids" nolabel="1" context="{'default_parent_id': active_id}">
57 <field name="inventory_ids" nolabel="1" 56 <tree>
58 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'}" />57 <field name="name" />
58 <field name="state" />
59 <field name="progress_rate" widget="progressbar" />
60 </tree>
61 </field>
59 </page>62 </page>
60 </xpath>63 </xpath>
61 </field>64 </field>
62 </record>65 </record>
6366
64 <record model="ir.ui.view"67 <!-- Open the children of the current Inventory in a distinct list
65 id="stock_inventory_hierarchical_subinventory_tree_view">68 to let users work in a normal window instead of a popup -->
66 <field name="name">hierarchical.inventory.subinventory.tree</field>69 <act_window id="action_view_sub_inventory"
67 <field name="model">stock.inventory</field>70 name="View Sub-inventories"
68 <field name="type">tree</field>
69 <field name="priority" eval="99" />
70 <field name="arch" type="xml">
71 <tree string="Sub-inventories" version="7.0">
72 <field name="name" />
73 <field name="state" />
74 <field name="confirmed_rate" widget="progressbar" />
75 <field name="inventory_ids" string="Number of Sub-inventories"/>
76 <!-- XXX: Ideally we would have liked to have a button to open Sub-inventories,
77 but unfortunately the v6.0 GTK client crashes, and the 6.0 web client opens a windows without action buttons.
78 Maybe we may try that again with the new web client one day...
79 <button string="View this inventory" type="object" name="open_sub_inventory"/>
80 -->
81 </tree>
82 </field>
83 </record>
84
85 <record model="ir.ui.view"
86 id="stock_inventory_hierarchical_subinventory_form_view">
87 <field name="name">hierarchical.inventory.subinventory.form</field>
88 <field name="model">stock.inventory</field>
89 <field name="type">form</field>
90 <field name="priority" eval="99" />
91 <field name="arch" type="xml">
92 <form string="Sub-inventories" version="7.0">
93 <group colspan="4" col="2">
94 <field name="name" default_focus="1"/>
95 </group>
96 <field name="inventory_ids" nolabel="1" colspan="4"
97 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'}" />
98 <group colspan="4" col="3">
99 <field name="state" />
100 <!-- XXX: Ideally we would have liked to have a button to open Sub-inventories,
101 but unfortunately the v6.0 GTK client crashes, and the 6.0 web client opens a windows without action buttons.
102 Maybe we may try that again with the new web client one day...
103 <button string="View this inventory" type="object" name="open_sub_inventory"/>
104 -->
105 </group>
106 </form>
107 </field>
108 </record>
109
110 <act_window id="action_view_sub_inventory"
111 name="Sub-inventories"
112 res_model="stock.inventory"71 res_model="stock.inventory"
113 src_model="stock.inventory"72 src_model="stock.inventory"
114 view_mode="tree,form"73 view_mode="tree,form"
11574
=== modified file 'stock_inventory_hierarchical/i18n/fr.po'
--- stock_inventory_hierarchical/i18n/fr.po 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical/i18n/fr.po 2014-06-11 15:04:27 +0000
@@ -21,16 +21,16 @@
21msgstr "Réference complète"21msgstr "Réference complète"
2222
23#. module: stock_inventory_hierarchical23#. module: stock_inventory_hierarchical
24#: field:stock.inventory,confirmed_rate:024#: field:stock.inventory,progress_rate:0
25msgid "Confirmed"25msgid "Done"
26msgstr "Confirmé"26msgstr "Terminé"
2727
28#. module: stock_inventory_hierarchical28#. module: stock_inventory_hierarchical
29#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:10829#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:108
30#: constraint:stock.inventory:030#: constraint:stock.inventory:0
31#, python-format31#, python-format
32msgid "Error! You can not create recursive inventories."32msgid "Error: You can not create recursive inventories."
33msgstr "Erreur! Vous ne pouvez pas créer d'inventaire récursifs."33msgstr "Erreur : Vous ne pouvez pas créer d'inventaire récursifs."
3434
35#. module: stock_inventory_hierarchical35#. module: stock_inventory_hierarchical
36#: model:ir.model,name:stock_inventory_hierarchical.model_stock_inventory36#: model:ir.model,name:stock_inventory_hierarchical.model_stock_inventory
@@ -61,7 +61,7 @@
61#. module: stock_inventory_hierarchical61#. module: stock_inventory_hierarchical
62#: constraint:stock.inventory:062#: constraint:stock.inventory:0
63msgid "Other Physical inventories are being conducted using the same Locations."63msgid "Other Physical inventories are being conducted using the same Locations."
64msgstr "Erreur: certains emplacements sont déjà dans un autre inventaire."64msgstr "Certains emplacements sont déjà dans un autre inventaire."
6565
66#. module: stock_inventory_hierarchical66#. module: stock_inventory_hierarchical
67#: field:stock.inventory,parent_id:067#: field:stock.inventory,parent_id:0
@@ -87,19 +87,19 @@
87#. module: stock_inventory_hierarchical87#. module: stock_inventory_hierarchical
88#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:19688#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
89#, python-format89#, python-format
90msgid "Some Sub-inventories are not done."90msgid "Some Sub-inventories are not validated."
91msgstr "Certains sous-inventaires ne sont pas terminés."91msgstr "Certains sous-inventaires ne sont pas terminés."
9292
93#. module: stock_inventory_hierarchical93#. module: stock_inventory_hierarchical
94#: model:ir.actions.act_window,name:stock_inventory_hierarchical.action_view_sub_inventory94#: model:ir.actions.act_window,name:stock_inventory_hierarchical.action_view_sub_inventory
95#: view:stock.inventory:095#: view:stock.inventory:0
96msgid "Sub-inventories"96msgid "View Sub-inventories"
97msgstr "Sous-inventaires"97msgstr "Voir les sous-inventaires"
9898
99#. module: stock_inventory_hierarchical99#. module: stock_inventory_hierarchical
100#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:130100#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:130
101#, python-format101#, python-format
102msgid "Sub-inventory : %s"102msgid "Sub-inventory: %s"
103msgstr "Sous-inventaire : %s"103msgstr "Sous-inventaire : %s"
104104
105#. module: stock_inventory_hierarchical105#. module: stock_inventory_hierarchical
@@ -107,6 +107,6 @@
107#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:188107#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:188
108#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196108#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
109#, python-format109#, python-format
110msgid "Warning !"110msgid "Warning"
111msgstr "Attention !"111msgstr "Attention"
112112
113113
=== added directory 'stock_inventory_hierarchical/images'
=== added file 'stock_inventory_hierarchical/images/inventory_form.png'
114Binary 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 differ114Binary 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
=== added file 'stock_inventory_hierarchical/images/inventory_form_actions.png'
115Binary 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 differ115Binary 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
=== added directory 'stock_inventory_hierarchical/static'
=== added directory 'stock_inventory_hierarchical/static/src'
=== added directory 'stock_inventory_hierarchical/static/src/img'
=== added file 'stock_inventory_hierarchical/static/src/img/icon.png'
116Binary 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 differ116Binary 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
=== modified file 'stock_inventory_hierarchical/test/hierarchical_inventory_test.yml'
--- stock_inventory_hierarchical/test/hierarchical_inventory_test.yml 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical/test/hierarchical_inventory_test.yml 2014-06-11 15:04:27 +0000
@@ -1,48 +1,39 @@
1-1-
2 In this file, i check rules about hierarchical inventories.2 In this file, i check rules about hierarchical inventories.
3 children date must be the same of parent date for each state,3 Children date must be the same of parent date for each state,
4 the state of parent and children can change only if conditions are true.4 the state of parent and children can change only if conditions are correct.
5 First, i'll create two children inventory of stock_inventory_parent inventory.5-
6-6 Check if date of children are the same as the parent's.
7 !python {model: stock.inventory}: |7-
8 from osv import orm 8 !python {model: stock.inventory}: |
9 self.child_1_id = self.create(cr, uid, {'parent_id': ref('stock_inventory_parent0'), 9 parent_date = self.read(
10 'inventory_ids': [], 10 cr, uid, [ref('stock_inventory_parent0')], ['date'])[0]['date']
11 'name': 'Children 1 test inventory'})11 child_1_date = self.read(
12 self.child_2_id = self.create(cr, uid, {'parent_id': ref('stock_inventory_parent0'), 12 cr, uid, [ref('child_1_id')], ['date'])[0]['date']
13 'inventory_ids': [], 13 assert child_1_date == parent_date, "Date are different: %s - %s" % (parent_date, child_1_date)
14 'name': 'Children 2 test inventory'})14
15-15 child_2_date = self.read(
16 Check if date of children are the same than parent date.16 cr, uid, [ref('child_2_id')], ['date'])[0]['date']
17 I'll read the date for 2 children and compare them with parent inventory date. 17 assert child_2_date == parent_date, "Date are different: %s - %s" % (parent_date, child_2_date)
18-
19 !python {model: stock.inventory}: |
20 from osv import orm
21 parent_date = self.read(cr, uid, [ref('stock_inventory_parent0')], ['date'])[0]['date']
22 child_1_date = self.read(cr, uid, [self.child_1_id], ['date'])[0]['date']
23 assert child_1_date == parent_date, "Date are not equals : %s - %s" % (parent_date, child_1_date)
24
25 child_2_date = self.read(cr, uid, [self.child_2_id], ['date'])[0]['date']
26 assert child_2_date == parent_date, "Date are not equals : %s - %s" % (parent_date, child_2_date)
2718
28-19-
29 Check if children cannot be canceled if the parent was not canceled.20 Check if children cannot be canceled if the parent was not canceled.
30 I'll try to cancel both children inventory while parent inventory having "draft" state.21 I'll try to cancel both children inventory while parent inventory having "draft" state.
31 After, i'll verify the state of each inventory. 22 After, i'll verify the state of each inventory.
32-23-
33 !python {model: stock.inventory}: | 24 !python {model: stock.inventory}: |
34 from osv import orm, osv 25 from stock_inventory_hierarchical import HierarchicalInventoryException
35 try:26 try:
36 self.action_cancel_inventary(cr, uid, [self.child_1_id]) 27 self.action_cancel_inventory(cr, uid, [ref('child_1_id')])
37 except osv.except_osv as e:28 except HierarchicalInventoryException as e:
38 log("Good ! The Inventory could not be canceled : %s" % e)29 log("Good ! The Inventory could not be canceled: %s" % e)
39 try:30 try:
40 self.action_cancel_inventary(cr, uid, [self.child_2_id]) 31 self.action_cancel_inventory(cr, uid, [ref('child_2_id')])
41 except osv.except_osv as e:32 except HierarchicalInventoryException as e:
42 log("Good ! The Inventory could not be canceled : %s" % e) 33 log("Good ! The Inventory could not be canceled: %s" % e)
43 child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']34 child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
44 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state35 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
45 child_2_state = self.read(cr, uid, [self.child_2_id], ['state'])[0]['state']36 child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
46 assert child_2_state == 'draft', "Child inventory 2 have '%s' state. It should be 'draft'" % child_2_state37 assert child_2_state == 'draft', "Child inventory 2 have '%s' state. It should be 'draft'" % child_2_state
4738
48-39-
@@ -50,58 +41,56 @@
50 To check this, i'll try to confirm parent inventory when children inventory having "draft" state,41 To check this, i'll try to confirm parent inventory when children inventory having "draft" state,
51 and i'll check if state is still 'draft'.42 and i'll check if state is still 'draft'.
52-43-
53 !python {model: stock.inventory}: | 44 !python {model: stock.inventory}: |
54 from osv import orm, osv 45 from stock_inventory_hierarchical import HierarchicalInventoryException
55 try:46 try:
56 self.action_confirm(cr, uid, [ref('stock_inventory_parent0')]) 47 self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
57 except osv.except_osv as e:48 except HierarchicalInventoryException as e:
58 log("Good, the inventory could not be confirm : %s", e)49 log("Good, the inventory could not be confirmed: %s", e)
59 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']50 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
60 assert parent_state == 'draft', "Parent inventory have '%s' state. It should be 'draft'" % parent_state51 assert parent_state == 'draft', "Parent inventory have '%s' state. It should be 'draft'" % parent_state
6152
62-53-
63 In order, i'll confirm the children inventories, and the parent inventory after.54 In order, i'll confirm the children inventories, and the parent inventory after.
64-55-
65 !python {model: stock.inventory}: | 56 !python {model: stock.inventory}: |
66 from osv import orm, osv57 self.action_confirm(cr, uid, [ref('child_1_id')])
67 self.action_confirm(cr, uid, [self.child_1_id])58 child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
68 child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']
69 assert child_1_state == 'confirm', "Child inventory 1 have '%s' state. It should be 'confirm'" % child_1_state59 assert child_1_state == 'confirm', "Child inventory 1 have '%s' state. It should be 'confirm'" % child_1_state
70 60
71 self.action_confirm(cr, uid, [self.child_2_id])61 self.action_confirm(cr, uid, [ref('child_2_id')])
72 child_2_state = self.read(cr, uid, [self.child_2_id], ['state'])[0]['state']62 child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
73 assert child_2_state == 'confirm', "Child inventory 2 have '%s' state. It should be 'confirm'" % child_2_state63 assert child_2_state == 'confirm', "Child inventory 2 have '%s' state. It should be 'confirm'" % child_2_state
74 64
75 self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])65 self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
76 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']66 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
77 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state67 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
78 68
79-69-
80 Check if children inventory have done state before validate parent inventory.70 Check if children inventory have done state before validate parent inventory.
81 I'll try to validate parent inventory before children. 71 I'll try to validate parent inventory before children.
82-72-
83 !python {model: stock.inventory}: | 73 !python {model: stock.inventory}: |
84 from osv import orm, osv74 from stock_inventory_hierarchical import HierarchicalInventoryException
85 try:75 try:
86 self.action_done(cr, uid, [ref('stock_inventory_parent0')])76 self.action_done(cr, uid, [ref('stock_inventory_parent0')])
87 except osv.except_osv as e:77 except HierarchicalInventoryException as e:
88 log("Good, the inventory could not be validate : %s", e)78 log("Good, the inventory could not be validated: %s", e)
89 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']79 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
90 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state80 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
9181
92-82-
93 Now, i'll validate all children inventory before validate the parent.83 Now, i'll validate all children inventory before validate the parent.
94-84-
95 !python {model: stock.inventory}: | 85 !python {model: stock.inventory}: |
96 from osv import orm, osv86 self.action_done(cr, uid, [ref('child_1_id')])
97 self.action_done(cr, uid, [self.child_1_id])87 child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
98 child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']
99 assert child_1_state == 'done', "Child inventory 1 have '%s' state. It should be 'done'" % child_1_state88 assert child_1_state == 'done', "Child inventory 1 have '%s' state. It should be 'done'" % child_1_state
100 89
101 self.action_done(cr, uid, [self.child_2_id])90 self.action_done(cr, uid, [ref('child_2_id')])
102 child_2_state = self.read(cr, uid, [self.child_2_id], ['state'])[0]['state']91 child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
103 assert child_2_state == 'done', "Child inventory 2 have '%s' state. It should be 'done'" % child_2_state92 assert child_2_state == 'done', "Child inventory 2 have '%s' state. It should be 'done'" % child_2_state
104 93
105 self.action_done(cr, uid, [ref('stock_inventory_parent0')])94 self.action_done(cr, uid, [ref('stock_inventory_parent0')])
106 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']95 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
107 assert parent_state == 'done', "Parent inventory have '%s' state. It should be 'done'" % parent_state 96 assert parent_state == 'done', "Parent inventory have '%s' state. It should be 'done'" % parent_state
10897
=== modified file 'stock_inventory_hierarchical_location/__init__.py'
--- stock_inventory_hierarchical_location/__init__.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/__init__.py 2014-06-11 15:04:27 +0000
@@ -1,4 +1,4 @@
1# -*- encoding: utf-8 -*-1# -*- coding: utf-8 -*-
2##############################################################################2##############################################################################
3#3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
@@ -18,5 +18,5 @@
18#18#
19##############################################################################19##############################################################################
2020
21import inventory_hierarchical_location21from . import inventory_hierarchical_location
22import wizard22from . import wizard
2323
=== modified file 'stock_inventory_hierarchical_location/__openerp__.py'
--- stock_inventory_hierarchical_location/__openerp__.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/__openerp__.py 2014-06-11 15:04:27 +0000
@@ -1,4 +1,4 @@
1# -*- encoding: utf-8 -*-1# -*- coding: utf-8 -*-
2##############################################################################2##############################################################################
3#3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
@@ -19,34 +19,30 @@
19##############################################################################19##############################################################################
2020
21{21{
22 "name": "Exhaustive and hierachical Stock Inventories",22 "name": "Exhaustive and hierarchical Stock Inventories",
23 "version": "1.0",23 "version": "1.1",
24 "depends": ["stock_inventory_hierarchical", "stock_inventory_location"],24 "depends": ["stock_inventory_hierarchical", "stock_inventory_location"],
25 "auto_install": True,
25 "author": u"Numérigraphe",26 "author": u"Numérigraphe",
26 "category": "stock inventory",27 "category": "Hidden",
27 "description": """28 "description": """
28This module makes exhaustive Inventories aware of their Sub-Inventories.29Make exhaustive Inventories aware of their Sub-Inventories.
29========================================================================30===========================================================
30
31It should be installed if both modules "stock_inventory_location"
32and "stock_inventory_hierarchical" are installed.
3331
34This module allows an inventory to contain a general Location,32This module allows an inventory to contain a general Location,
35and it's sub-inventories to contain some of it's sub-Locations.33and it's sub-inventories to contain some of it's sub-Locations.
36When you open an inventory, OpenERP will warn you if some of the sub-Locations34It will prevent you from setting the Inventories and sub-Inventories
37of an inventory are absent from the sub-Inventories.
38It will also prevent you from setting the Inventories and sub-Inventories
39in inconsistent status.35in inconsistent status.
36
37This module will be installed automatically if the modules
38"stock_inventory_location" and "stock_inventory_hierarchical" are both
39installed.
40You must keep this module installed to ensure proper functioning.
41
40 """,42 """,
41 "init_xml": [],43 "data": [
42 "update_xml": [44 "inventory_hierarchical_location_view.xml",
43 "wizard/stock_inventory_missing_locations_view.xml",45 ],
44 "inventory_hierarchical_location_view.xml",46 "test": ["test/inventory_hierarchical_location_test.yml"],
45 ],47 "demo": ["inventory_hierarchical_location_demo.xml"],
46
47 "test": ["test/inventory_hierarchical_location_test.yml"],
48 "demo": ["inventory_hierarchical_location_demo.xml"],
49
50 # Will work with v6.1 and later
51 "auto_install": True,
52}48}
5349
=== modified file 'stock_inventory_hierarchical_location/i18n/fr.po'
--- stock_inventory_hierarchical_location/i18n/fr.po 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/i18n/fr.po 2014-06-11 15:04:27 +0000
@@ -64,8 +64,8 @@
64#. module: stock_inventory_hierarchical_location64#. module: stock_inventory_hierarchical_location
65#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:4165#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
66#, python-format66#, python-format
67msgid "One of the parent inventories are not open."67msgid "One of the parent inventories is not open."
68msgstr "Un inventaire parent n'est pas ouvert."68msgstr "Un des inventaire parent n'est pas ouvert."
6969
70#. module: stock_inventory_hierarchical_location70#. module: stock_inventory_hierarchical_location
71#: view:stock.inventory:071#: view:stock.inventory:0
@@ -97,9 +97,9 @@
97#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:5897#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:58
98#, python-format98#, python-format
99msgid "This location is not declared on the parent inventory\n"99msgid "This location is not declared on the parent inventory\n"
100"You cannot add it !"100"It cannot be added."
101msgstr "Cet emplacement n'est pas déclaré dans l'inventaire parent\n"101msgstr "Cet emplacement n'est pas déclaré dans l'inventaire parent\n"
102"Vous ne pouvez pas l'ajouter !"102"Vous ne pouvez pas l'ajouter."
103103
104#. module: stock_inventory_hierarchical_location104#. module: stock_inventory_hierarchical_location
105#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41105#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
106106
=== modified file 'stock_inventory_hierarchical_location/inventory_hierarchical_location.py'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location.py 2014-06-11 15:04:27 +0000
@@ -1,4 +1,4 @@
1# -*- encoding: utf-8 -*-1# -*- coding: utf-8 -*-
2##############################################################################2##############################################################################
3#3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
@@ -18,120 +18,74 @@
18#18#
19##############################################################################19##############################################################################
2020
21from openerp.osv import osv21from openerp.osv import orm
22from openerp.tools.translate import _22from openerp.tools.translate import _
2323
2424from stock_inventory_hierarchical import HierarchicalInventoryException
25class StockInventory(osv.osv):25
26# Add the date to the list of fields we must propagate to children inventories
27from stock_inventory_hierarchical import PARENT_VALUES
28PARENT_VALUES.append('exhaustive')
29
30
31class HierarchicalExhInventory(orm.Model):
32 """Add hierarchical structure features to exhaustive Inventories"""
26 _inherit = 'stock.inventory'33 _inherit = 'stock.inventory'
2734
28 def __init__(self, pool, cr):
29 """Propagate the "exhaustive" field from inventories to sub-inventories"""
30 s = super(StockInventory, self)
31 s.PARENT_VALUES.append('exhaustive')
32 return s.__init__(pool, cr)
33
34 def action_open(self, cr, uid, ids, context=None):35 def action_open(self, cr, uid, ids, context=None):
35 """Open only if all the parents are Open."""36 """Open only if all the parents are Open."""
36 #XXX the dosctring used to say this but it's not implemented, normal?
37 # --> "Before opening, if locations are missing, ask the user
38 # to validate the opening without these locations."
39 for inventory in self.browse(cr, uid, ids, context=context):37 for inventory in self.browse(cr, uid, ids, context=context):
40 while inventory.parent_id:38 while inventory.parent_id:
41 inventory = inventory.parent_id39 inventory = inventory.parent_id
42 if inventory.state != 'open':40 if inventory.state != 'open':
43 raise osv.except_osv(_('Warning !'),41 raise HierarchicalInventoryException(
44 _('One of the parent inventories are not open.'))42 _('Warning'),
45 return super(StockInventory, self).action_open(cr, uid, ids, context=context)43 _('One of the parent inventories is not open.'))
4644 return super(HierarchicalExhInventory, self).action_open(
47 def check_location(self, cr, uid, ids, location_ids, name, context=None):45 cr, uid, ids, context=context)
46
47 # TODO v8: probably only keep the state "done"
48 def confirm_missing_locations(self, cr, uid, ids, context=None):
49 """Do something only if children state are confirm or done."""
50 children_count = self.search(
51 cr, uid, [('parent_id', 'child_of', ids),
52 ('id', 'not in', ids),
53 ('state', 'not in', ['confirm', 'done'])],
54 context=context, count=True)
55 if children_count > 0:
56 raise HierarchicalInventoryException(
57 _('Warning'),
58 _('Some Sub-inventories are not confirmed.'))
59 return super(HierarchicalExhInventory,
60 self).confirm_missing_locations(
61 cr, uid, ids, context=context)
62
63 def onchange_location_id(self, cr, uid, ids, location_id, context=None):
48 """Check if location is a child of parent inventory location"""64 """Check if location is a child of parent inventory location"""
49 res_location_ids = location_ids[0][2]65 loc_obj = self.pool['stock.location']
50 nbr_location_ids = len(res_location_ids)66 for inventory in self.browse(cr, uid, ids, context=context):
51 for inventory in self.browse(cr, uid, ids, context=None):67 if inventory.parent_id:
52 if inventory.parent_id.id:68 allowed_location_ids = loc_obj.search(
53 parent_locations = self.read(cr, uid, [inventory.parent_id.id], ['location_ids'])69 cr, uid, [('location_id', 'child_of',
54 parent_children_locations = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', parent_locations[0]['location_ids'])])70 inventory.parent_id.location_id.id)],
55 for location_id in res_location_ids:71 context=context)
56 if location_id not in parent_children_locations:72 if location_id not in allowed_location_ids:
57 res_location_ids.remove(location_id)73 return {
58 res = {}74 'location_id': False,
59 if nbr_location_ids != len(res_location_ids):75 'warning': {
60 res['warning'] = {'title': _('Warning: Wrong location'),76 'title': _('Warning: Wrong location'),
61 'message': _("This location is not declared on the parent inventory\n"77 'message': _("This location is not declared on "
62 "You cannot add it !")}78 "the parent inventory\n"
63 res['value'] = {'location_ids': res_location_ids, }79 "It cannot be added.")}
64 return res80 }
81 return {}
6582
66 def _fill_location_lines(self, cr, uid, inventory_id, location_ids, set_stock_zero, context=None):83 def _fill_location_lines(self, cr, uid, inventory_id, location_ids,
84 set_stock_zero, context=None):
67 """Add ids of children inventory into list """85 """Add ids of children inventory into list """
68 children_inventory_ids = self.search(cr, uid, [('parent_id', 'child_of', inventory_id)])86 children_inventory_ids = self.search(
87 cr, uid, [('parent_id', 'child_of', inventory_id)])
69 context['children_inventory_ids'] = children_inventory_ids88 context['children_inventory_ids'] = children_inventory_ids
70 return super(StockInventory, self)._fill_location_lines(cr, uid, inventory_id, location_ids, set_stock_zero, context=context)89 return super(HierarchicalExhInventory, self)._fill_location_lines(
7190 cr, uid, inventory_id, location_ids, set_stock_zero,
72 def open_missing_location_wizard(self, cr, uid, ids, context=None):91 context=context)
73 """Open wizard if inventory have children.
74 Before, verify if all children of exhaustive inventory have at least one location."""
75 children_ids = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
76 for inventory in self.browse(cr, uid, children_ids, context=context):
77 if inventory.exhaustive:
78 if not inventory.location_ids:
79 raise osv.except_osv(_('Warning !'),
80 _('Location missing for inventory "%s".') % inventory.name)
81 children_count = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', ids)], count=True)
82 if children_count == 1:
83 return self.action_open(cr, uid, ids, context)
84 else:
85 context['active_ids'] = ids
86 context['active_id'] = ids[0]
87 return {
88 'type': 'ir.actions.act_window',
89 'view_type': 'form',
90 'view_mode': 'form',
91 'res_model': 'stock.inventory.missing.location',
92 'target': 'new',
93 'context': context,
94 'nodestroy': True,
95 }
96
97
98# XXX: move to /wizard
99class StockInventoryUninventoriedLocation(osv.osv_memory):
100 _inherit = 'stock.inventory.uninventoried.locations'
101
102 def inventories(self, cr, uid, inventory_parent_id):
103 """Iterator of children inventories.
104 return inventory_id;
105 """
106 children_ids = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', inventory_parent_id)])
107 for inventory_id in children_ids:
108 yield inventory_id
109
110 def get_locations(self, cr, uid, inventory_id, context=None):
111 """Get all locations through inventory tree."""
112 list_inventories_locations_ids = []
113 for i_id in self.inventories(cr, uid, inventory_id):
114 location_ids = super(StockInventoryUninventoriedLocation, self).get_locations(cr, uid, i_id, context=context)
115 list_inventories_locations_ids = list(set(list_inventories_locations_ids + location_ids))
116 return list_inventories_locations_ids
117
118 def get_locations_inventoried(self, cr, uid, inventory_id, location_ids, context=None):
119 """Get all locations on inventory lines through inventory tree."""
120 list_all_inventoried_location_ids = []
121 for i_id in self.inventories(cr, uid, inventory_id):
122 list_loc_ids = super(StockInventoryUninventoriedLocation, self).get_locations_inventoried(cr, uid, i_id, location_ids, context=context)
123 list_all_inventoried_location_ids = list(set(list_all_inventoried_location_ids + list_loc_ids))
124 return list_all_inventoried_location_ids
125
126 def default_locations(self, cr, uid, context=None):
127 """Do something only if children state are confirm or done."""
128 children_count = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', context['active_id']),
129 ('state', 'not in', ['confirm', 'done'])], context=context, count=True)
130 if children_count > 1:
131 raise osv.except_osv(_('Warning !'), _('Some Sub-inventories are not confirmed.'))
132 return super(StockInventoryUninventoriedLocation, self).default_locations(cr, uid, context=context)
133
134 _defaults = {
135 'location_ids': default_locations,
136 }
137
13892
=== modified file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml 2014-06-11 15:04:27 +0000
@@ -3,16 +3,26 @@
3 <data noupdate="0">3 <data noupdate="0">
44
5 <!-- Record inventories we can use in the tests. -->5 <!-- Record inventories we can use in the tests. -->
6 <!-- We need them in the demo data because test data is rolled back 6 <!-- We need them in the demo data because test data is rolled back
7 whenever an exception is raised. -->7 whenever an exception is raised. -->
8 8
9 <!-- Record an exhaustive inventory -->9 <!-- Record a hierarchical exhaustive inventory -->
10 <record id="parent_inventory_location" model="stock.inventory">10 <record id="parent_inventory_location" model="stock.inventory">
11 <field name="name">Hierarchical location exhaustive inventory</field>11 <field name="name">Hierarchical exhaustive inventory</field>
12 <field name="state">draft</field>12 <field name="state">draft</field>
13 <field name="date">2020-04-15 00:00:00</field>13 <field name="date">2020-04-15 00:00:00</field>
14 <field name="exhaustive">True</field> 14 <field name="exhaustive">True</field>
15 <field name="location_ids" model="stock.location" search="[('name', '=', 'Shelf 2')]" />15 <field name="location_ids" model="stock.location" search="[('name', 'like', 'Stock')]" />
16 </record>
17 <record id="child_1_id" model="stock.inventory">
18 <field name="name">Team A</field>
19 <field name="parent_id" ref="parent_inventory_location"/>
20 <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 1')]" />
21 </record>
22 <record id="child_2_id" model="stock.inventory">
23 <field name="name">Team B</field>
24 <field name="parent_id" ref="parent_inventory_location"/>
25 <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
16 </record>26 </record>
17 </data>27 </data>
18</openerp>28</openerp>
1929
=== modified file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml 2014-06-11 15:04:27 +0000
@@ -5,34 +5,21 @@
5 <record model="ir.ui.view" id="stock_inventory_hierarchical_location_form_view">5 <record model="ir.ui.view" id="stock_inventory_hierarchical_location_form_view">
6 <field name="name">hierarchical.inventory.location.form</field>6 <field name="name">hierarchical.inventory.location.form</field>
7 <field name="model">stock.inventory</field>7 <field name="model">stock.inventory</field>
8 <field name="type">form</field>
9 <field name="inherit_id" ref="stock.view_inventory_form" />8 <field name="inherit_id" ref="stock.view_inventory_form" />
10 <field name="arch" type="xml">9 <field name="arch" type="xml">
1110 <xpath expr="/form//field[@name='exhaustive']" position="attributes">
12 <xpath expr="//form[@string='Physical Inventory']//field[@name='exhaustive']" position="attributes">11 <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
13 <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute> 12 </xpath>
14 </xpath>13 <xpath expr="/form//field[@name='location_id']" position="attributes">
15 14 <attribute name="on_change">onchange_location_id(location_id)</attribute>
16 <xpath expr="/form[@string='Physical Inventory']//page[@string='General Information']/field[@name='location_ids']" position="replace"> 15 </xpath>
17 <field colspan="1" nolabel="1" name="location_ids" domain="[('usage','in',('view', 'internal'))]" 16 <xpath expr="/form//field[@name='inventory_ids']" position="attributes">
18 attrs="{'invisible':[('exhaustive','!=',True)]}"17 <attribute name="context">{'default_parent_id': active_id, 'default_exhaustive': exhaustive}</attribute>
19 on_change="check_location(location_ids, name)">18 </xpath>
20 <tree string="Locations" editable="bottom">
21 <field name="name"/>
22 </tree>
23 </field>
24 </xpath>
25
26 <!-- replace action_open button to open wizard missing locations -->
27 <xpath expr="/form//button[@name='action_open']" position="replace">
28 <button name="open_missing_location_wizard"
29 string="Open Inventory" type="object" states="draft" icon="gtk-apply"/>
30 </xpath>
31
32 </field>19 </field>
33 </record>20 </record>
3421
35 <!-- Show exhaustive inventories by default -->22 <!-- Show hierarchical exhaustive inventories by default -->
36 <record id="stock.action_inventory_form" model="ir.actions.act_window">23 <record id="stock.action_inventory_form" model="ir.actions.act_window">
37 <field name="context">{'full':'1', 'search_default_exhaustive':1, 'search_default_main_inventories':1}</field>24 <field name="context">{'full':'1', 'search_default_exhaustive':1, 'search_default_main_inventories':1}</field>
38 </record>25 </record>
3926
=== added directory 'stock_inventory_hierarchical_location/static'
=== added directory 'stock_inventory_hierarchical_location/static/src'
=== added directory 'stock_inventory_hierarchical_location/static/src/img'
=== added file 'stock_inventory_hierarchical_location/static/src/img/icon.png'
40Binary 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 differ27Binary 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
=== modified file 'stock_inventory_hierarchical_location/test/inventory_hierarchical_location_test.yml'
--- stock_inventory_hierarchical_location/test/inventory_hierarchical_location_test.yml 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/test/inventory_hierarchical_location_test.yml 2014-06-11 15:04:27 +0000
@@ -1,31 +1,20 @@
1-1-
2 I will create children inventory.2 Check that the exhaustive field of parent inventory has been propagated to children.
3-3-
4 !python {model: stock.inventory}: | 4 !python {model: stock.inventory}: |
5 self.child_1_id = self.create(cr, uid, {'parent_id': ref('parent_inventory_location'), 5 exhaustive = self.read(cr, uid, [ref("child_1_id")], ['exhaustive'])[0]['exhaustive']
6 'inventory_ids': [], 6 assert exhaustive, "Exhaustive field not propagated to child inventory"
7 'name': 'Children 1 test inventory'})
87
9-8-
10 I will check if exhaustive information of parent inventory has been added to children.9 Check that I can't open child inventory while parent inventory is open.
11-10-
12 !python {model: stock.inventory}: |11 !python {model: stock.inventory}: |
13 exhaustive = self.read(cr, uid, [self.child_1_id], ['exhaustive'])[0]['exhaustive']12 from stock_inventory_hierarchical import HierarchicalInventoryException
14 assert exhaustive == True, "Exhaustive information not added to children inventory"13 parent_state = self.read(cr, uid, [ref("parent_inventory_location")], ['state'])[0]['state']
15 14 assert parent_state == 'draft', "Parent inventory in state '%s'. It should be 'draft'" % parent_state
16-
17 I will check if i can't open children inventory while parent inventory is open.
18-
19 !python {model: stock.inventory}: |
20 from osv import orm, osv
21 try:15 try:
22 self.action_open(cr, uid, [self.child_1_id])16 self.action_open(cr, uid, [ref("child_1_id")])
23 except osv.except_osv as e:17 except HierarchicalInventoryException as e:
24 log("Good ! The Inventory could not be opened : %s" % e) 18 log("Good ! The Inventory could not be opened: %s" % e)
25 child_1_state = self.read(cr, uid, [self.child_1_id], ['state'])[0]['state']19 child_1_state = self.read(cr, uid, [ref("child_1_id")], ['state'])[0]['state']
26 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state20 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
27
28
29
30
31
32\ No newline at end of file21\ No newline at end of file
3322
=== modified file 'stock_inventory_hierarchical_location/wizard/__init__.py'
--- stock_inventory_hierarchical_location/wizard/__init__.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/wizard/__init__.py 2014-06-11 15:04:27 +0000
@@ -1,4 +1,4 @@
1# -*- encoding: utf-8 -*-1# -*- coding: utf-8 -*-
2##############################################################################2##############################################################################
3#3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
@@ -18,4 +18,4 @@
18#18#
19##############################################################################19##############################################################################
2020
21import stock_inventory_missing_locations21import stock_confirm_uninventoried_location
2222
=== added file 'stock_inventory_hierarchical_location/wizard/stock_confirm_uninventoried_location.py'
--- stock_inventory_hierarchical_location/wizard/stock_confirm_uninventoried_location.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/stock_confirm_uninventoried_location.py 2014-06-11 15:04:27 +0000
@@ -0,0 +1,63 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import orm
22from openerp.tools.translate import _
23
24from stock_inventory_hierarchical import HierarchicalInventoryException
25
26
27class StockInventoryUninventoriedLocation(orm.TransientModel):
28 """Consider the lines of the sub-inventories when checking for locations"""
29 _inherit = 'stock.inventory.uninventoried.locations'
30
31 def inventories(self, cr, uid, inventory_parent_id):
32 """Iterator of children inventories.
33
34 @return: inventory_id
35 """
36 children_ids = self.pool['stock.inventory'].search(
37 cr, uid, [('parent_id', 'child_of', inventory_parent_id)])
38 for inventory_id in children_ids:
39 yield inventory_id
40
41 def get_locations(self, cr, uid, inventory_id, context=None):
42 """Get all locations through inventory tree."""
43 list_inventories_locations_ids = []
44 for i_id in self.inventories(cr, uid, inventory_id):
45 location_ids = super(
46 StockInventoryUninventoriedLocation, self).get_locations(
47 cr, uid, i_id, context=context)
48 list_inventories_locations_ids = list(set(
49 list_inventories_locations_ids + location_ids))
50 return list_inventories_locations_ids
51
52 def get_locations_inventoried(self, cr, uid, inventory_id, location_ids,
53 context=None):
54 """Get all locations on inventory lines throughout inventory tree."""
55 list_all_inventoried_location_ids = []
56 for i_id in self.inventories(cr, uid, inventory_id):
57 list_loc_ids = super(
58 StockInventoryUninventoriedLocation,
59 self).get_locations_inventoried(
60 cr, uid, i_id, location_ids, context=context)
61 list_all_inventoried_location_ids = list(set(
62 list_all_inventoried_location_ids + list_loc_ids))
63 return list_all_inventoried_location_ids
064
=== removed file 'stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations.py'
--- stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations.py 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations.py 1970-01-01 00:00:00 +0000
@@ -1,82 +0,0 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import osv, fields
22from openerp.tools.translate import _
23
24
25class inventory_missing_location(osv.osv_memory):
26
27 _name = 'stock.inventory.missing.location'
28 _description = 'Search on inventory tree for missing declared locations.'
29
30 _columns = {
31 'location_ids': fields.many2many('stock.location',
32 'stock_inventory_missing_location_rel',
33 'location_id',
34 'wizard_id',
35 'Missing location', readonly=True),
36 }
37
38 def confirm_missing_locations(self, cr, uid, ids, context=None):
39 """ Call action open method from stock.inventory """
40 ids = context['active_ids']
41 self.pool.get('stock.inventory').action_open(cr, uid, ids, context=context)
42 return {'type': 'ir.actions.act_window_close'}
43
44 def inventories(self, cr, uid, inventory_parent_id):
45 """ Iterator of children inventories.
46 """
47 children_ids = self.pool.get('stock.inventory').search(cr, uid, [('parent_id', 'child_of', inventory_parent_id)])
48 for inventory_id in children_ids:
49 if inventory_id == inventory_parent_id:
50 continue # pass the parent inventory
51 yield inventory_id
52
53 def get_locations_from_children(self, cr, uid, inventory_id, context=None):
54 """ Get all locations through inventory tree. """
55 list_inventories_locations_ids = []
56 for i_id in self.inventories(cr, uid, inventory_id):
57 location_ids = self.pool.get('stock.inventory').read(cr, uid, [i_id], ['location_ids'], context=context)[0]
58 location_ids = self.pool.get('stock.location').search(cr, uid, [
59 ('location_id', 'child_of', location_ids['location_ids']),
60 ('usage', '=', 'internal')], context=context)
61 list_inventories_locations_ids = list(set(list_inventories_locations_ids + location_ids))
62 return list_inventories_locations_ids
63
64 def default_missing_locations(self, cr, uid, context=None):
65 """ Initialize view with the list of missing locations on inventory tree.
66 """
67 if context is None:
68 context = {}
69
70 # get children locations for parent/current inventory
71 parent_location_ids = self.pool.get('stock.inventory').read(cr, uid, [context['active_id']], ['location_ids'], context=context)[0]
72 parent_location_ids = self.pool.get('stock.location').search(cr, uid, [
73 ('location_id', 'child_of', parent_location_ids['location_ids']),
74 ('usage', '=', 'internal')], context=context)
75 # get locations for each sub-inventory
76 location_ids = self.get_locations_from_children(cr, uid, context['active_id'])
77 list_missing_ids = [_id for _id in parent_location_ids if _id not in location_ids]
78 return list_missing_ids
79
80 _defaults = {
81 'location_ids': default_missing_locations,
82 }
830
=== removed file 'stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml'
--- stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml 2014-03-12 15:08:22 +0000
+++ stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml 1970-01-01 00:00:00 +0000
@@ -1,43 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="action_view_stock_inventory_missing_location" model="ir.actions.act_window">
6 <field name="name">Confirm missing location</field>
7 <field name="type">ir.actions.act_window</field>
8 <field name="res_model">stock.inventory.missing.location</field>
9 <field name="view_type">form</field>
10 <field name="view_mode">form</field>
11 <field name="target">new</field>
12 </record>
13
14 <!-- The view definition is similar with stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml,
15 but the code is different.
16 This wizard compare the locations of inventory with locations declared on parent inventory to
17 to present to user the missing locations. -->
18 <record id="view_confirm_missing_location" model="ir.ui.view">
19 <field name="name">Confirm missing location</field>
20 <field name="model">stock.inventory.missing.location</field>
21 <field name="type">form</field>
22 <field name="arch" type="xml">
23 <form string="Confirm missing locations">
24 <group colspan="4" col="1">
25 <field name="location_ids" nolabel="1">
26 <tree>
27 <field name="name"/>
28 </tree>
29 </field>
30 </group>
31 <group colspan="4" col="2">
32 <label string="This is the list of missing locations."/>
33 <label string="Do you want to continue ?"/>
34 <separator string="" colspan="4" />
35 <button special="cancel" string="_Cancel" icon='gtk-cancel'/>
36 <button name="confirm_missing_locations" string="_Confirm missing locations" type="object" icon="gtk-ok"/>
37 </group>
38 </form>
39 </field>
40 </record>
41
42 </data>
43</openerp>
44\ No newline at end of file0\ No newline at end of file
451
=== modified file 'stock_inventory_location/__init__.py'
--- stock_inventory_location/__init__.py 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/__init__.py 2014-06-11 15:04:27 +0000
@@ -20,3 +20,5 @@
2020
21import stock_inventory_location21import stock_inventory_location
22import wizard22import wizard
23# Bring the main exception into the package's scope for easier reuse
24from .exceptions import ExhaustiveInventoryException
2325
=== modified file 'stock_inventory_location/__openerp__.py'
--- stock_inventory_location/__openerp__.py 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/__openerp__.py 2014-06-11 15:04:27 +0000
@@ -18,45 +18,60 @@
18#18#
19##############################################################################19##############################################################################
2020
21
22{21{
23 "name": "Exhaustive Stock Inventories",22 "name": "Exhaustive Stock Inventories",
24 "version": "1.0",23 "version": "1.1",
25 "depends": ["stock"],24 "depends": ["stock"],
26 "author": u"Numérigraphe",25 "author": u"Numérigraphe",
27 "category": "Inventory",26 "category": "Warehouse Management",
28 "description": """27 "description": """
29Let users choose between standard and exhaustive Inventories28Let users make exhaustive Inventories
30============================================================29=====================================
3130
32Standard Physical Inventories in OpenERP only contain a generic list of products by locations,31Standard Physical Inventories in OpenERP only contain a generic list of
33which is well suited to partial Inventories and simple warehouses.32products by locations, which is well suited to partial Inventories and simple
34When the a standard Inventory is confirmed, only the products in the inventory are checked.33warehouses. When the a standard Inventory is confirmed, only the products in
35If a Product is present in the computed stock and not in the recorded Inventory, OpenERP will34the inventory are checked. If a Product is present in the computed stock and
36consider that it remains unchanged.35not in the recorded Inventory, OpenERP will consider that it remains unchanged.
3736
38But for exhaustive inventories in complex warehouses, it is not practical:37But for exhaustive inventories in complex warehouses, it is not practical:
39 - you want to avoid Stock Moves in/out of these Locations while you count the goods38 - you want to avoid Stock Moves to/from these Locations while counting goods
40 - you must make sure all the locations you want have been counted39 - you must make sure all the locations you want have been counted
41 - you must make sure no other location has been counted by mistake40 - you must make sure no other location has been counted by mistake
42 - you want the computed stock to perfectly match the inventory when you confirm it.41 - you want the computed stock to perfectly match the inventory when you
42 confirm it.
4343
44This module lets choose whether an Physical Inventory is exhaustive or standard.44This module lets choose whether an Physical Inventory is exhaustive or
45For an exhaustive Inventory, in the state "Draft" you define the list of Locations where goods must be counted.45standard.
46 - a new Inventory status ("Open") lets you indicate that the list of Locations is definitive and you are now counting the goods.46For an exhaustive Inventory:
47 In that status, no Stock Moves can be recorded in/out of the Inventory's Locations.47 - in the state "Draft" you define the Location where goods must be counted.
48 - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory.48 - the new Inventory status "Open" lets you indicate that the list of Locations
49 - only the Inventory's Locations can be entered in the Inventory Lines.49 is final and you are now counting the goods.
50 - 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 openerp50 In that status, no Stock Moves can be recorded in/out of the Inventory's
51 Locations.
52 - if the Location or some of it's children have not been entered in the
53 Inventory Lines, OpenERP warns you when you confirm the Inventory.
54 - only the Inventory's Location or its children can be entered in the
55 Inventory Lines.
56 - every good that is not in the Inventory Lines is considered lost, and gets
57 moved out of the stock when you confirm the Inventory.
51""",58""",
52 "update_xml": [59 "data": [
53 "wizard/stock_confirm_uninventoried_location.xml",60 "wizard/stock_confirm_uninventoried_location.xml",
54 "stock_inventory_location_view.xml",61 "stock_inventory_location_view.xml",
55 "wizard/stock_fill_location_inventory_view.xml",62 "wizard/stock_fill_location_inventory_view.xml",
56 ],63 ],
57 "test": ["test/location_inventory_test.yml",64 "test": [
58 "test/location_exhaustive_inventory_test.yml",65 "test/inventory_standard_test.yml",
59 ],66 "test/inventory_exhaustive_test.yml",
60 "demo": ["stock_inventory_location_demo.xml"]67 "test/inventory_future_test.yml",
6168 ],
69 "images": [
70 "images/inventory_form.png",
71 "inventory_empty_locations.png",
72 "images/move_error.png",
73 "images/location_locked.png",
74 "images/future_inventory.png",
75 ],
76 "demo": ["stock_inventory_location_demo.xml"]
62}77}
6378
=== added file 'stock_inventory_location/exceptions.py'
--- stock_inventory_location/exceptions.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/exceptions.py 2014-06-11 15:04:27 +0000
@@ -0,0 +1,26 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import orm
22
23
24class ExhaustiveInventoryException(orm.except_orm):
25 """The operation is not possible for an exhaustive inventory"""
26 pass
027
=== modified file 'stock_inventory_location/i18n/fr.po'
--- stock_inventory_location/i18n/fr.po 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/i18n/fr.po 2014-06-11 15:04:27 +0000
@@ -20,7 +20,7 @@
20#: constraint:stock.move:020#: constraint:stock.move:0
21#, python-format21#, python-format
22msgid "A Physical Inventory is being conducted at this location"22msgid "A Physical Inventory is being conducted at this location"
23msgstr "Un inventaire est déjà en cours à cet emplacement"23msgstr "Un inventaire est en cours à cet emplacement"
2424
25#. module: stock_inventory_location25#. module: stock_inventory_location
26#: view:stock.inventory.uninventoried.locations:026#: view:stock.inventory.uninventoried.locations:0
@@ -63,8 +63,8 @@
6363
64#. module: stock_inventory_location64#. module: stock_inventory_location
65#: view:stock.inventory.uninventoried.locations:065#: view:stock.inventory.uninventoried.locations:0
66msgid "Confirm uninventoried locations"66msgid "Confirm empty locations"
67msgstr "Confirmez les emplacements non-inventoriés"67msgstr "Confirmez les emplacements vides"
6868
69#. module: stock_inventory_location69#. module: stock_inventory_location
70#: model:ir.model,name:stock_inventory_location.model_stock_location70#: model:ir.model,name:stock_inventory_location.model_stock_location
@@ -74,26 +74,26 @@
74#. module: stock_inventory_location74#. module: stock_inventory_location
75#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:5475#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:54
76#, python-format76#, python-format
77msgid "Error !"77msgid "Error"
78msgstr "Erreur !"78msgstr "Erreur"
7979
80#. module: stock_inventory_location80#. module: stock_inventory_location
81#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:7581#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:75
82#, python-format82#, python-format
83msgid "Error : Empty location !"83msgid "Error: Empty location"
84msgstr "Erreur : Emplacement vide !"84msgstr "Erreur : Emplacement vide"
8585
86#. module: stock_inventory_location86#. module: stock_inventory_location
87#: code:addons/stock_inventory_location/stock_inventory_location.py:23187#: code:addons/stock_inventory_location/stock_inventory_location.py:231
88#: code:addons/stock_inventory_location/stock_inventory_location.py:28288#: code:addons/stock_inventory_location/stock_inventory_location.py:282
89#, python-format89#, python-format
90msgid "Error! Location on inventory"90msgid "Error: Location locked down"
91msgstr "Erreur! emplacement en inventaire"91msgstr "Erreur: emplacement en inventaire"
9292
93#. module: stock_inventory_location93#. module: stock_inventory_location
94#: constraint:stock.inventory:094#: constraint:stock.inventory:0
95msgid "Error! You can not create recursive inventories."95msgid "Error: You can not create recursive inventories."
96msgstr "Erreur! Vous ne pouvez pas créer un inventaire récursif."96msgstr "Erreur: Vous ne pouvez pas créer un inventaire récursif."
9797
98#. module: stock_inventory_location98#. module: stock_inventory_location
99#: constraint:stock.inventory.line:099#: constraint:stock.inventory.line:0
@@ -138,12 +138,6 @@
138msgstr "Emplacement manquant pour cet inventaire."138msgstr "Emplacement manquant pour cet inventaire."
139139
140#. module: stock_inventory_location140#. module: stock_inventory_location
141#: view:stock.inventory:0
142#: field:stock.inventory,location_ids:0
143msgid "Locations"
144msgstr "Emplacements"
145
146#. module: stock_inventory_location
147#: model:ir.model,name:stock_inventory_location.model_stock_move141#: model:ir.model,name:stock_inventory_location.model_stock_move
148msgid "Mouvement de stock"142msgid "Mouvement de stock"
149msgstr "Mouvement de stock"143msgstr "Mouvement de stock"
@@ -171,15 +165,10 @@
171#. module: stock_inventory_location165#. module: stock_inventory_location
172#: code:addons/stock_inventory_location/stock_inventory_location.py:283166#: code:addons/stock_inventory_location/stock_inventory_location.py:283
173#, python-format167#, python-format
174msgid "One or more locations are inventoried :\n"168msgid "A Physical Inventory is being conducted at the following location(s):\n"
175"%s"169"%s"
176msgstr "Un ou plusieurs emplacements sont en inventaire :\n"170msgstr "Les emplacements suivants sont en inventaire :\n"
177"%s"171"%s"
178
179#. module: stock_inventory_location
180#: constraint:stock.move:0
181msgid "One or more lots are awaiting quality control and cannot be moved."
182msgstr "Un ou plusieurs lots sont en attente de contrôle qualité, et ne peuvent pas être déplacés."
183172
184#. module: stock_inventory_location173#. module: stock_inventory_location
185#: view:stock.inventory:0174#: view:stock.inventory:0
@@ -198,19 +187,10 @@
198187
199#. module: stock_inventory_location188#. module: stock_inventory_location
200#: view:stock.inventory.uninventoried.locations:0189#: view:stock.inventory.uninventoried.locations:0
201msgid "The following Stock Locations are part of the current Physical Inventory, but not Inventory Line has been recorded for them."190msgid "The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."
202msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."191msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."
203192
204#. module: stock_inventory_location193#. module: stock_inventory_location
205#: help:stock.inventory,location_ids:0
206msgid "This is the list of the Stock Locations that you want to count the goods in.\n"
207"Only these Locations can be entered in the Inventory Lines.\n"
208"If some of them have not been entered in the Inventory Lines, OpenERP will warn you when you confirm the Inventory."
209msgstr "Ceci est la liste des emplacements dont vous voulez compter la marchandise.\n"
210"Seuls ces emplacements peuvent être enregistrés dans les lignes d'inventaire.\n"
211"Si certains ne sont enregistrés sur aucune ligne d'inventaire, OpenERP vous avertit lors de la confirmation de l'inventaire."
212
213#. module: stock_inventory_location
214#: field:stock.inventory.uninventoried.locations,location_ids:0194#: field:stock.inventory.uninventoried.locations,location_ids:0
215msgid "Uninventoried location"195msgid "Uninventoried location"
216msgstr "Emplacements non inventoriés"196msgstr "Emplacements non inventoriés"
@@ -220,8 +200,8 @@
220#: code:addons/stock_inventory_location/stock_inventory_location.py:163200#: code:addons/stock_inventory_location/stock_inventory_location.py:163
221#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:58201#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:58
222#, python-format202#, python-format
223msgid "Warning !"203msgid "Warning"
224msgstr "Attention !"204msgstr "Attention"
225205
226#. module: stock_inventory_location206#. module: stock_inventory_location
227#: code:addons/stock_inventory_location/stock_inventory_location.py:209207#: code:addons/stock_inventory_location/stock_inventory_location.py:209
@@ -232,8 +212,8 @@
232#. module: stock_inventory_location212#. module: stock_inventory_location
233#: code:addons/stock_inventory_location/stock_inventory_location.py:210213#: code:addons/stock_inventory_location/stock_inventory_location.py:210
234#, python-format214#, python-format
235msgid "You cannot add this location to inventory line.\n"215msgid "You cannot record an Inventory Line for this Location.\n"
236"You must add this location on the locations list"216"You must first add this Location to the list of affected Locations on the Inventory form."
237msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"217msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"
238"Il ne fait pas partie de la liste des emplacements à inventorier"218"Il ne fait pas partie de la liste des emplacements à inventorier"
239219
240220
=== added directory 'stock_inventory_location/images'
=== added file 'stock_inventory_location/images/future_inventory.png'
241Binary 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 differ221Binary 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
=== added file 'stock_inventory_location/images/inventory_empty_locations.png'
242Binary 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 differ222Binary 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
=== added file 'stock_inventory_location/images/inventory_form.png'
243Binary 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 differ223Binary 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
=== added file 'stock_inventory_location/images/location_locked.png'
244Binary 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 differ224Binary 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
=== added file 'stock_inventory_location/images/move_error.png'
245Binary 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 differ225Binary 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
=== added directory 'stock_inventory_location/static'
=== added directory 'stock_inventory_location/static/src'
=== added directory 'stock_inventory_location/static/src/img'
=== added file 'stock_inventory_location/static/src/img/icon.png'
246Binary 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 differ226Binary 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
=== modified file 'stock_inventory_location/stock_inventory_location.py'
--- stock_inventory_location/stock_inventory_location.py 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/stock_inventory_location.py 2014-06-11 15:04:27 +0000
@@ -18,225 +18,267 @@
18#18#
19##############################################################################19##############################################################################
2020
21import time
21from collections import Iterable22from collections import Iterable
2223
23from openerp.osv import osv, orm, fields24from openerp.osv import orm, fields
24from openerp.tools.translate import _25from openerp.tools.translate import _
2526# The next 2 imports are only needed for a feature backported from trunk-wms
2627# TODOv8! remove, feature is included upstream
27class StockInventory(osv.osv):28from openerp.osv import osv
29from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
30
31from .exceptions import ExhaustiveInventoryException
32
33
34class StockInventory(orm.Model):
28 """Add locations to the inventories"""35 """Add locations to the inventories"""
29 _inherit = 'stock.inventory'36 _inherit = 'stock.inventory'
37
38 INVENTORY_STATE_SELECTION = [
39 ('draft', 'Draft'),
40 ('open', 'Open'),
41 ('done', 'Done'),
42 ('confirm', 'Confirmed'),
43 ('cancel', 'Cancelled')
44 ]
45
30 _columns = {46 _columns = {
31 # XXX refactor if ever lp:~numerigraphe/openobject-addons/7.0-inventory-states is accepted upstream47 # TODO v8: should we use "confirm" instead of adding "open"?
32 'state': fields.selection((('draft', 'Draft'),48 'state': fields.selection(
33 ('open', 'Open'),49 INVENTORY_STATE_SELECTION, 'State', readonly=True, select=True),
34 ('done', 'Done'),50 # TODO v8: remove this, should not be needed anymore
35 ('confirm', 'Confirmed'),51 # Make the inventory lines read-only in all states except "Open",
36 ('cancel', 'Cancelled')), 'State', readonly=True, select=True),52 # to ensure that no unwanted Location can be inserted
37 # Make the inventory lines read-only in all states except "Open", to ensure that no unwanted Location can be inserted53 'inventory_line_id': fields.one2many(
38 'inventory_line_id': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventory lines', readonly=True, states={'open': [('readonly', False)]}),54 'stock.inventory.line', 'inventory_id', 'Inventory lines',
39 'location_ids': fields.many2many('stock.location', 'stock_inventory_location_rel',55 readonly=True, states={'open': [('readonly', False)]}),
40 'location_id', 'inventory_id',56 # TODO v8: remove this, it's backported from v8
41 'Locations',57 'location_id': fields.many2one(
42 readonly=True, states={'draft': [('readonly', False)]},58 'stock.location', 'Inventoried Location',
43 help="""This is the list of the Stock Locations that you want to count the goods in.59 readonly=True, states={'draft': [('readonly', False)]}),
44Only these Locations can be entered in the Inventory Lines.60 'exhaustive': fields.boolean(
45If some of them have not been entered in the Inventory Lines, OpenERP will warn you when you confirm the Inventory."""),61 'Exhaustive', readonly=True,
46 'exhaustive': fields.boolean('Exhaustive', readonly=True, states={'draft': [('readonly', False)]},62 states={'draft': [('readonly', False)]},
47 help="""Check the box if you are conducting an exhaustive Inventory.63 help="Check the box if you are conducting an exhaustive "
48Leave the box unchecked if you are conducting a standard Inventory (partial inventory for example).64 "Inventory.\n"
49For an exhaustive Inventory:65 "Leave the box unchecked if you are conducting a standard "
50 - the status "Draft" lets you define the list of Locations where goods must be counted66 "Inventory (partial inventory for example).\n"
51 - 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 Locations67 "For an exhaustive Inventory:\n"
52 - only the Inventory's Locations can be entered in the Inventory Lines68 " - the status \"Draft\" lets you define the list of "
53 - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory69 "Locations where goods must be counted\n"
54 - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory"""),70 " - the status \"Open\" indicates that the list of Locations "
71 "is definitive and you are now counting the goods. In that "
72 "status, no Stock Moves can be recorded in/out of the "
73 "Inventory's Locations\n"
74 " - only the Inventory's Locations can be entered in the "
75 "Inventory Lines\n"
76 " - if some of the Inventory's Locations have not been "
77 "entered in the Inventory Lines, OpenERP warns you "
78 "when you confirm the Inventory\n"
79 " - every good that is not in the Inventory Lines is "
80 "considered lost, and gets moved out of the stock when you "
81 "confirm the Inventory"),
55 }82 }
5683
57 def action_open(self, cr, uid, ids, context=None):84 def action_open(self, cr, uid, ids, context=None):
58 """Open the inventory: open all locations, import and print inventory sheet become possible"""85 """Change the state of the inventory to 'open'"""
59 # verify if exhaustive inventory have locations before open inventory
60 for inventory in self.browse(cr, uid, ids, context=None):
61 if inventory.exhaustive:
62 if not inventory.location_ids:
63 raise osv.except_osv(_('Warning !'), _('Location missing for this inventory.'))
64 return self.write(cr, uid, ids, {'state': 'open'}, context=context)86 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
6587
66 # XXX refactor if ever lp:~numerigraphe/openobject-addons/7.0-inventory-states is accepted upstream88 # TODO v8: remove this method? the feature looks already done upstream
89 def action_done(self, cr, uid, ids, context=None):
90 """
91 Don't allow to make an inventory with a date in the future.
92
93 This makes sure no stock moves will be introduced between the
94 moment you finish the inventory and the date of the Stock Moves.
95 Backported from trunk-wms:
96 revid:qdp-launchpad@openerp.com-20140317090656-o7lo22tzm8yuv3r8
97
98 @raise osv.except_osv:
99 We raise this exception on purpose instead of
100 ExhaustiveInventoryException to ensure forward-compatibility
101 with v8.
102 """
103 for inventory in self.browse(cr, uid, ids, context=None):
104 if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
105 raise osv.except_osv(
106 _('Error!'),
107 _('It\'s impossible to confirm an inventory in the '
108 'future. Please change the inventory date to proceed '
109 'further.'))
110 return super(StockInventory, self).action_done(cr, uid, ids,
111 context=context)
112
113 # TODO: remove this in v8
114 def _default_location(self, cr, uid, ids, context=None):
115 """Default stock location
116
117 @return: id of the stock location of the first warehouse of the
118 default company"""
119 location_id = False
120 company_id = self.pool['res.company']._company_default_get(
121 cr, uid, 'stock.warehouse', context=context)
122 warehouse_id = self.pool['stock.warehouse'].search(
123 cr, uid, [('company_id', '=', company_id)], limit=1)
124 if warehouse_id:
125 location_id = self.pool['stock.warehouse'].read(
126 cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
127 return location_id
128
67 _defaults = {129 _defaults = {
68 'state': lambda *a: 'draft',130 'state': 'draft',
69 'exhaustive': lambda *a: False,131 'exhaustive': False,
132 # TODO: remove this in v8
133 'location_id': _default_location,
70 }134 }
71135
72 def _check_location_free_from_inventories(self, cr, uid, ids):136 def _check_location_free_from_inventories(self, cr, uid, ids):
73 """Verify that no other Inventory is being conducted on the location (exact id, not children)."""137 """
74 for inventory in self.browse(cr, uid, ids, context=None):138 Verify that no other Inventory is being conducted on the same locations
139
140 Open Inventories are matched using the exact Location IDs,
141 excluding children.
142 """
143 # We don't get a context because we're called from a _constraint
144 for inventory in self.browse(cr, uid, ids):
75 if not inventory.exhaustive:145 if not inventory.exhaustive:
76 continue # always accepted on partial inventories146 # never block standard inventories
77 location_ids = [location.id for location in inventory.location_ids]147 continue
78 inv_ids = self.search(cr, uid, [('location_ids', 'in', location_ids),148 if self.search(cr, uid,
79 ('id', '!=', inventory.id),149 [('location_id', '=', inventory.location_id.id),
80 ('date', '=', inventory.date),150 ('id', '!=', inventory.id),
81 ('exhaustive', '=', True), ])151 ('date', '=', inventory.date),
82 if inv_ids:152 ('exhaustive', '=', True)]):
153 # Quit as soon as one offending inventory is found
83 return False154 return False
84 return True155 return True
85156
86 _constraints = [(_check_location_free_from_inventories,157 _constraints = [
87 'Other Physical inventories are being conducted using the same Locations.',158 (_check_location_free_from_inventories,
88 ['location_ids', 'date', 'exhaustive'])]159 'Other Physical inventories are being conducted using the same '
160 'Locations.',
161 ['location_id', 'date', 'exhaustive'])
162 ]
89163
90 def _get_locations_open_inventories(self, cr, uid, context=None):164 def _get_locations_open_inventories(self, cr, uid, context=None):
91 """Search for locations on open inventories (exhaustive inventory only), and their children """165 """IDs of location in open exhaustive inventories, with children"""
92 open_inventories_ids = self.search(cr, uid, [('state', '=', 'open'), ], context=context)166 inv_ids = self.search(
93 location_ids = set()167 cr, uid, [('state', '=', 'open'), ('exhaustive', '=', True)],
94 for open_inventory in self.browse(cr, uid, open_inventories_ids, context=context):168 context=context)
95 location_ids.update([location.id for location in open_inventory.location_ids])169 if not inv_ids:
170 # Early exit if no match found
171 return []
172 # List the Locations - normally all exhaustive inventories have one
173 location_ids = [inventory.location_id.id
174 for inventory in self.browse(cr, uid, inv_ids,
175 context=context)]
96 # Extend to the children Locations176 # Extend to the children Locations
97 if location_ids: # XXX probably works even otherwise177 return self.pool['stock.location'].search(
98 location_ids = self.pool.get('stock.location').search(cr, uid,178 cr, uid,
99 [('location_id', 'child_of', location_ids), ('usage', '=', 'internal')],179 [('location_id', 'child_of', set(location_ids)),
100 context=context)180 ('usage', '=', 'internal')],
101 return location_ids181 context=context)
102182
103 def confirm_uninventoried_location_wizard(self, cr, uid, ids, context=None):183 def get_missing_locations(self, cr, uid, ids, context=None):
104 """ Open wizard if inventory is exhautive """184 """Compute the list of location_ids which are missing from the lines
185
186 Here, "missing" means the location is the inventory's location or one
187 of it's children, and the inventory does not contain any line with
188 this location."""
189 inventories = self.browse(cr, uid, ids, context=context)
190 # Find the locations of the inventories
191 inv_location_ids = [i.location_id.id for i in inventories]
192 # Extend to the children locations
193 inv_location_ids = set(self.pool['stock.location'].search(
194 cr, uid, [
195 ('location_id', 'child_of', inv_location_ids),
196 ('usage', '=', 'internal')], context=context))
197 # Find the locations already recorded in inventory lines
198 line_locations_ids = set([l.location_id.id
199 for l in i.inventory_line_id
200 for i in inventories])
201 return list(inv_location_ids - line_locations_ids)
202
203 def confirm_missing_locations(self, cr, uid, ids, context=None):
204 """Open wizard to confirm empty locations on exhaustive inventories"""
105 for inventory in self.browse(cr, uid, ids, context=context):205 for inventory in self.browse(cr, uid, ids, context=context):
106 if not inventory.exhaustive:206 if (self.get_missing_locations(cr, uid, ids, context=context)
107 return self.action_confirm(cr, uid, ids, context=context)207 and inventory.exhaustive):
108 else:
109 context['active_ids'] = ids
110 context['active_id'] = ids[0]
111 return {208 return {
112 'type': 'ir.actions.act_window',209 'type': 'ir.actions.act_window',
113 'view_type': 'form',210 'view_type': 'form',
114 'view_mode': 'form',211 'view_mode': 'form',
115 'res_model': 'stock.inventory.uninventoried.locations',212 'res_model': 'stock.inventory.uninventoried.locations',
116 'target': 'new',213 'target': 'new',
117 'context': context,214 'context': dict(context,
215 active_ids=ids,
216 active_id=ids[0]),
118 'nodestroy': True,217 'nodestroy': True,
119 }218 }
120219 return self.action_confirm(cr, uid, ids, context=context)
121 # XXX : get from stock.py v6.0 patch. Waiting for integration on standard by openerp ... 220
122 def _fill_location_lines(self, cr, uid, inventory_id, location_ids, set_stock_zero, context=None):221
123 """ To Import stock inventory according to products available in the selected locations.222class StockInventoryLine(orm.Model):
124 @param self: The object pointer.
125 @param cr: A database cursor
126 @param uid: ID of the user currently logged in
127 @param location_ids: the location ID or list of location IDs if we want more than one
128 @param inventory_id: the inventory ID
129 @param set_stock_zero: indicate if all the lines will be imported with zero quantity
130 @param context: A standard dictionary
131 @return:
132 """
133 inventory_line_obj = self.pool.get('stock.inventory.line')
134 move_obj = self.pool.get('stock.move')
135 uom_obj = self.pool.get('product.uom')
136 res = []
137 flag = False
138 for location in location_ids:
139 datas = {}
140 move_ids = move_obj.search(cr, uid, ['|', ('location_dest_id', '=', location),
141 ('location_id', '=', location),
142 ('state', '=', 'done')], context=context)
143 for move in move_obj.browse(cr, uid, move_ids, context=context):
144 if move.location_dest_id.id == move.location_id.id:
145 continue
146 lot_id = move.prodlot_id.id
147 prod_id = move.product_id.id
148 if move.location_dest_id.id == location:
149 qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
150 else:
151 qty = -uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
152 if datas.get((prod_id, lot_id)):
153 qty = qty + datas[(prod_id, lot_id)]['product_qty']
154
155 datas[(prod_id, lot_id)] = {'product_id': prod_id,
156 'location_id': location,
157 # Floating point sum could introduce some tiny rounding errors.
158 # The uom are the same on input and output to use api for rounding.
159 'product_qty': uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_id.uom_id),
160 'product_uom': move.product_id.uom_id.id,
161 'prod_lot_id': lot_id,
162 'default_code': move.product_id.default_code,
163 'prodlot_name': move.prodlot_id.name}
164 if datas:
165 flag = True
166 res.append(datas)
167 if not flag:
168 raise osv.except_osv(_('Warning !'), _('No product in this location.'))
169
170 stock_moves = []
171 for stock_move in res:
172 prod_lots = sorted(stock_move, key=lambda k: (stock_move[k]['default_code'], stock_move[k]['prodlot_name']))
173 for prod_lot in prod_lots:
174 stock_move_details = stock_move.get(prod_lot)
175
176 if stock_move_details['product_qty'] == 0:
177 continue # ignore product if stock equal 0
178
179 stock_move_details.update({'inventory_id': inventory_id})
180
181 if set_stock_zero:
182 stock_move_details.update({'product_qty': 0})
183
184 domain = [(field, '=', stock_move_details[field])
185 for field in ['location_id',
186 'product_id',
187 'prod_lot_id']
188 ]
189 # children_inventory_ids is present if stock_inventory_hierarchical_location module has been installed
190 inventory_ids = context.get('children_inventory_ids', False)
191 if inventory_ids:
192 domain.append(('inventory_id', 'child_of', inventory_ids))
193 else:
194 domain.append(('inventory_id', '=', stock_move_details['inventory_id']))
195
196 line_ids = inventory_line_obj.search(cr, uid, domain, context=context)
197 if not line_ids:
198 stock_moves.append(stock_move_details)
199 return stock_moves
200
201
202class StockInventoryLine(osv.osv):
203 """Only allow the Inventory's Locations"""223 """Only allow the Inventory's Locations"""
204224
205 _inherit = 'stock.inventory.line'225 _inherit = 'stock.inventory.line'
206226
207 def onchange_location_id(self, cr, uid, ids, location_ids, exhaustive, location_id, context=None):227 def _default_stock_location(self, cr, uid, context=None):
208 """ Raise exception if Location is not internal, or location_id not in locations list for this inventory """228 res = super(StockInventoryLine, self)._default_stock_location(
209 location_ids = location_ids[0][2]229 cr, uid, context=context)
210 if not exhaustive or not location_ids:230 if context is None or not context.get('location_id', False):
211 return True # don't check if partial inventory231 return res
232 else:
233 return context['location_id']
234
235 _defaults = {
236 'location_id': _default_stock_location
237 }
238
239 def onchange_location_id(self, cr, uid, ids, inventory_location_id,
240 exhaustive, location_id, context=None):
241 """Warn if the new is not in the location list for this inventory."""
242 if not exhaustive:
243 # Don't check if partial inventory
244 return True
212245
213 # search children of location246 # search children of location
214 location_ids = self.pool.get('stock.location').search(cr, uid,247 if location_id not in self.pool['stock.location'].search(
215 [('location_id', 'child_of', location_ids)], context=context)248 cr, uid, [('location_id', 'child_of', inventory_location_id)],
216 if location_id not in location_ids:249 context=context):
217 return {'value': {'location_id': False},250 return {
218 'warning': {'title': _('Warning: Wrong location'),251 'value': {'location_id': False},
219 'message': _("You cannot add this location to inventory line.\n"252 'warning': {
220 "You must add this location on the locations list")}253 'title': _('Warning: Wrong location'),
221 }254 'message': _(
255 "You cannot record an Inventory Line for this "
256 "Location.\n"
257 "You must first add this Location to the list of "
258 "affected Locations on the Inventory form.")}
259 }
222 return True260 return True
223261
224262
225class StockLocation(osv.osv):263class StockLocation(orm.Model):
226 """Refuse changes during exhaustive Inventories"""264 """Refuse changes during exhaustive Inventories"""
227 _inherit = 'stock.location'265 _inherit = 'stock.location'
228 _order = 'name'266 _order = 'name'
229267
268 # TODOv7: why not put this in an ORM "_constraint" instead?
230 def _check_inventory(self, cr, uid, ids, context=None):269 def _check_inventory(self, cr, uid, ids, context=None):
231 """Raise an error if an exhaustive Inventory is being conducted on this Location"""270 """Error if an exhaustive Inventory is being conducted here"""
232 inventory_obj = self.pool.get('stock.inventory')271 inv_obj = self.pool['stock.inventory']
233 location_inventory_open_ids = inventory_obj._get_locations_open_inventories(cr, uid, context=context)272 location_inventory_open_ids = inv_obj._get_locations_open_inventories(
273 cr, uid, context=context)
234 if not isinstance(ids, Iterable):274 if not isinstance(ids, Iterable):
235 ids = [ids]275 ids = [ids]
236 for id in ids:276 for inv_id in ids:
237 if id in location_inventory_open_ids:277 if inv_id in location_inventory_open_ids:
238 raise osv.except_osv(_('Error! Location on inventory'),278 raise ExhaustiveInventoryException(
239 _('A Physical Inventory is being conducted at this location'))279 _('Error: Location locked down'),
280 _('A Physical Inventory is being conducted at this '
281 'location'))
240 return True282 return True
241283
242 def write(self, cr, uid, ids, vals, context=None):284 def write(self, cr, uid, ids, vals, context=None):
@@ -245,16 +287,19 @@
245 if not isinstance(ids, Iterable):287 if not isinstance(ids, Iterable):
246 ids = [ids]288 ids = [ids]
247 ids_to_check = ids289 ids_to_check = ids
248 # If we are changing the parent, there must be no inventory must conducted there either290 # If changing the parent, no inventory must conducted there either
249 if vals.get('location_id'):291 if vals.get('location_id'):
250 ids_to_check.append(vals['location_id'])292 ids_to_check.append(vals['location_id'])
251 self._check_inventory(cr, uid, ids_to_check, context=context)293 self._check_inventory(cr, uid, ids_to_check, context=context)
252 return super(StockLocation, self).write(cr, uid, ids, vals, context=context)294 return super(StockLocation, self).write(cr, uid, ids, vals,
295 context=context)
253296
254 def create(self, cr, uid, vals, context=None):297 def create(self, cr, uid, vals, context=None):
255 """Refuse create if an inventory is being conducted at the parent"""298 """Refuse create if an inventory is being conducted at the parent"""
256 self._check_inventory(cr, uid, vals.get('location_id'), context=context)299 self._check_inventory(cr, uid, vals.get('location_id'),
257 return super(StockLocation, self).create(cr, uid, vals, context=context)300 context=context)
301 return super(StockLocation, self).create(cr, uid, vals,
302 context=context)
258303
259 def unlink(self, cr, uid, ids, context=None):304 def unlink(self, cr, uid, ids, context=None):
260 """Refuse unlink if an inventory is being conducted"""305 """Refuse unlink if an inventory is being conducted"""
@@ -262,34 +307,45 @@
262 return super(StockLocation, self).unlink(cr, uid, ids, context=context)307 return super(StockLocation, self).unlink(cr, uid, ids, context=context)
263308
264309
265class StockMove(osv.osv):310class StockMove(orm.Model):
266 """Refuse Moves during exhaustive Inventories"""311 """Refuse Moves during exhaustive Inventories"""
267312
268 _inherit = 'stock.move'313 _inherit = 'stock.move'
269314
315 # TODOv7: adapt this to match trunk-wms
270 def _check_open_inventory_location(self, cr, uid, ids, context=None):316 def _check_open_inventory_location(self, cr, uid, ids, context=None):
271 """ Check if location is not in opened inventory317 """
272 Don't check on partial inventory (checkbox "Exhaustive" not checked).318 Check that the locations are not locked by an open inventory
319
320 Standard inventories are not checked.
321
322 @raise ExhaustiveInventoryException: an error is raised if locations
323 are locked, instead of returning False, in order to pass an
324 extensive error message back to users.
273 """325 """
274 message = ""326 message = ""
275 inventory_obj = self.pool.get('stock.inventory')327 inv_obj = self.pool['stock.inventory']
276 location_inventory_open_ids = inventory_obj._get_locations_open_inventories(cr, uid, context=context)328 locked_location_ids = inv_obj._get_locations_open_inventories(
277 if not location_inventory_open_ids:329 cr, uid, context=context)
278 return True # Nothing to verify330 if not locked_location_ids:
331 # Nothing to verify
332 return True
279 for move in self.browse(cr, uid, ids, context=context):333 for move in self.browse(cr, uid, ids, context=context):
280 if (move.location_id.usage != 'inventory'334 if (move.location_id.usage != 'inventory'
281 and move.location_dest_id.id in location_inventory_open_ids):335 and move.location_dest_id.id in locked_location_ids):
282 message += " - %s\n" % (move.location_dest_id.name)336 message += " - %s\n" % (move.location_dest_id.name)
283
284 if (move.location_dest_id.usage != 'inventory'337 if (move.location_dest_id.usage != 'inventory'
285 and move.location_id.id in location_inventory_open_ids):338 and move.location_id.id in locked_location_ids):
286 message += " - %s\n" % (move.location_id.name)339 message += " - %s\n" % (move.location_id.name)
287 if message:340 if message:
288 raise osv.except_osv(_('Error! Location on inventory'),341 raise ExhaustiveInventoryException(
289 _('One or more locations are inventoried :\n%s') % message)342 _('Error: Location locked down'),
343 _('A Physical Inventory is being conducted at the following '
344 'location(s):\n%s') % message)
290 return True345 return True
291346
292 _constraints = [347 _constraints = [
293 (_check_open_inventory_location,348 (_check_open_inventory_location,
294 "A Physical Inventory is being conducted at this location", ['location_id', 'location_dest_id']),349 "A Physical Inventory is being conducted at this location",
295 ]350 ['location_id', 'location_dest_id']),
351 ]
296352
=== modified file 'stock_inventory_location/stock_inventory_location_demo.xml'
--- stock_inventory_location/stock_inventory_location_demo.xml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/stock_inventory_location_demo.xml 2014-06-11 15:04:27 +0000
@@ -1,25 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<openerp>2<openerp>
3 <data noupdate="0">3 <data noupdate="0">
4 <!-- Record a non exhaustive inventory -->
5 <record id="inventory_standard" model="stock.inventory">
6 <field name="name">Standard inventory</field>
7 <field name="state">draft</field>
8 </record>
49
5 <!-- Record inventories we can use in the tests. -->
6 <!-- We need them in the demo data because test data is rolled back
7 whenever an exception is raised. -->
8
9 <!-- Record an non exhaustive inventory -->
10 <record id="stock_inventory_location_0" model="stock.inventory">
11 <field name="name">Location test inventory</field>
12 <field name="state">draft</field>
13 <field name="date">2020-03-01 00:00:00</field>
14 </record>
15
16 <!-- Record an exhaustive inventory -->10 <!-- Record an exhaustive inventory -->
17 <record id="stock_inventory_location_1" model="stock.inventory">11 <record id="inventory_exhaustive" model="stock.inventory">
18 <field name="name">Location test exhaustive inventory</field>12 <field name="name">Exhaustive inventory</field>
19 <field name="state">draft</field>13 <field name="state">draft</field>
20 <field name="date">2020-03-15 00:00:00</field>14 <field name="exhaustive">True</field>
21 <field name="exhaustive">True</field> 15 <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
22 <field name="location_ids" model="stock.location" search="[('name', '=', 'Shelf 2')]" />16 </record>
17
18 <!-- Record an inventory dated in the future -->
19 <!-- TODOv8: remove this entry, only useful for a test already in trunk-wms -->
20 <record id="inventory_future" model="stock.inventory">
21 <field name="name">Inventory in the future</field>
22 <field name="state">draft</field>
23 <field name="date">2020-03-01 00:00:00</field>
23 </record>24 </record>
24 </data>25 </data>
25</openerp>26</openerp>
2627
=== modified file 'stock_inventory_location/stock_inventory_location_view.xml'
--- stock_inventory_location/stock_inventory_location_view.xml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/stock_inventory_location_view.xml 2014-06-11 15:04:27 +0000
@@ -1,59 +1,59 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<openerp>2<openerp>
3 <data>3 <data>
4 4
5 <record model="ir.ui.view" id="stock_inventory_location_form_view">5 <record model="ir.ui.view" id="stock_inventory_location_form_view">
6 <field name="name">stock.inventory.location.form</field>6 <field name="name">stock.inventory.location.form</field>
7 <field name="model">stock.inventory</field>7 <field name="model">stock.inventory</field>
8 <field name="inherit_id" ref="stock.view_inventory_form"/>8 <field name="inherit_id" ref="stock.view_inventory_form"/>
9 <field name="priority" eval="10"/>9 <field name="priority" eval="10"/>
10 <field name="arch" type="xml">10 <field name="arch" type="xml">
11 <!-- Add type of inventory : partial or complete. -->11 <!-- Show the state 'done' in the statusbar -->
12 <xpath expr="/form//field[@name='date']" position="after">12 <xpath expr="/form//field[@name='state']" position="attributes">
13 <attribute name="statusbar_visible">draft,open,confirm</attribute>
14 </xpath>
15
16 <!-- TODO v8 place "exhaustive" next to "location_id" -->
17 <!-- Add type of inventory: standard or exhaustive. -->
18 <xpath expr="/form//field[@name='name']" position="after">
13 <field name="exhaustive"/>19 <field name="exhaustive"/>
14 </xpath>20 <field name="location_id" groups="stock.group_locations"
1521 domain="[('usage','in',('view', 'internal'))]"
16 <!-- Add the list of Locations on exhaustive inventories --> 22 attrs="{'invisible':[('exhaustive','!=',True)], 'required':[('exhaustive','=',True)]}"/>
17 <xpath expr="/form//field[@name='inventory_line_id']" position="before">23 </xpath>
18 <!-- Add the locations list for inventory -->24
19 <field colspan="1" nolabel="1" name="location_ids" domain="[('usage','in',('view', 'internal'))]" attrs="{'invisible':[('exhaustive','!=',True)]}">25 <!-- Enable Fill Inventory button when state is open -->
20 <tree string="Locations" editable="bottom">26 <xpath expr="//button[@string='Fill Inventory']" position="attributes">
21 <field name="name"/>27 <attribute name="states">open</attribute>
22 </tree>28 <attribute name="context">{'default_exhaustive': exhaustive}</attribute>
23 </field>29 </xpath>
24 </xpath> 30
2531 <!-- Control locations added by user on inventory line -->
26 <!-- Add Fill Inventory button when state is open -->32 <xpath expr="/form//field[@name='inventory_line_id']"
27 <xpath expr="//button[@string='Fill Inventory']" position="attributes">33 position="attributes">
28 <attribute name="states">draft,open,confirm</attribute>34 <attribute name="context">{'location_id': location_id}</attribute>
29 </xpath>35 </xpath>
3036 <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']"
31 <!-- Reduce the colspan of the lines to make room for the Locations-->37 position="attributes">
32 <xpath expr="/form//field[@name='inventory_line_id']" position="attributes">38 <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
33 <attribute name="colspan">3</attribute>39 </xpath>
34 </xpath> 40 <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']"
3541 position="attributes">
36 <!-- Control locations added by user on inventory line --> 42 <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
37 <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']" position="attributes">43 </xpath>
38 <attribute name="on_change">onchange_location_id(parent.location_ids, parent.exhaustive, location_id)</attribute>44
39 </xpath>
40 <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']" position="attributes">
41 <attribute name="on_change">onchange_location_id(parent.location_ids, parent.exhaustive, location_id)</attribute>
42 </xpath>
43
44 <!-- #XXX change the attributes instead and add the button -->
45 <!-- #XXX enlarge the group's colspan? -->
46 <!-- Add button to open an inventory. Call wizard on confirm inventory -->45 <!-- Add button to open an inventory. Call wizard on confirm inventory -->
47 <xpath expr="/form//button[@name='action_cancel_inventory']" position="replace">46 <xpath expr="/form//button[@name='action_cancel_inventory']" position="before">
48 <button name="action_cancel_inventory" states="draft,open,confirm,done" string="Cancel Inventory" type="object" icon="gtk-cancel"/>47 <button name="action_open" states="draft" string="Open Inventory" type="object" class="oe_highlight" groups="stock.group_stock_user"/>
49 <button name="action_open" states="draft" string="Open Inventory" type="object" icon="gtk-go-forward"/>48 </xpath>
50 </xpath>49 <!-- Show the "cancel" button in state "open" -->
51 50 <xpath expr="/form//button[@name='action_cancel_inventory']" position="attributes">
52 <!-- replace action_confirm button -->51 <attribute name="states">draft,open,confirm,done</attribute>
53 <!-- #XXX change the attributes instead -->52 </xpath>
54 <xpath expr="/form//button[@name='action_confirm']" position="replace">53 <!-- hijack the "confirm" button -->
55 <button name="confirm_uninventoried_location_wizard" 54 <xpath expr="/form//button[@name='action_confirm']" position="attributes">
56 string="Confirm Inventory" type="object" states="open" icon="gtk-apply"/> 55 <attribute name="states">open</attribute>
56 <attribute name="name">confirm_missing_locations</attribute>
57 </xpath>57 </xpath>
58 </field>58 </field>
59 </record>59 </record>
@@ -62,16 +62,16 @@
62 <record model="ir.ui.view" id="view_inventory_complete_filter">62 <record model="ir.ui.view" id="view_inventory_complete_filter">
63 <field name="name">complete.inventory.filter</field>63 <field name="name">complete.inventory.filter</field>
64 <field name="model">stock.inventory</field>64 <field name="model">stock.inventory</field>
65 <field name="type">search</field>
66 <field name="inherit_id" ref="stock.view_inventory_filter"/>65 <field name="inherit_id" ref="stock.view_inventory_filter"/>
67 <field name="arch" type="xml">66 <field name="arch" type="xml">
68 <xpath expr="//field[@name='name']" position="before">67 <xpath expr="//field[@name='name']" position="before">
69 <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]" help="Only select inventories that have no parents." />68 <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]"
69 help="Only select exhaustive Inventories." />
70 <separator orientation="vertical"/>70 <separator orientation="vertical"/>
71 </xpath>71 </xpath>
72 </field>72 </field>
73 </record>73 </record>
74 74
75 <!-- Show exhaustive inventories by default -->75 <!-- Show exhaustive inventories by default -->
76 <record id="stock.action_inventory_form" model="ir.actions.act_window">76 <record id="stock.action_inventory_form" model="ir.actions.act_window">
77 <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>77 <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>
7878
=== renamed file 'stock_inventory_location/test/location_exhaustive_inventory_test.yml' => 'stock_inventory_location/test/inventory_exhaustive_test.yml'
--- stock_inventory_location/test/location_exhaustive_inventory_test.yml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/test/inventory_exhaustive_test.yml 2014-06-11 15:04:27 +0000
@@ -3,99 +3,114 @@
3 I will call open_action method and check if state of inventories are 'open'.3 I will call open_action method and check if state of inventories are 'open'.
4-4-
5 !python {model: stock.inventory}: |5 !python {model: stock.inventory}: |
6 from osv import orm, osv 6 self.action_open(cr, uid, [ref('inventory_exhaustive')])
7 self.action_open(cr, uid, [ref('stock_inventory_location_1')]) 7 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
8 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']8 assert inventory_state == 'open', "Inventory in state '%s'. It should be 'open'" % inventory_state
9 assert inventory_state == 'open', "Parent inventory have '%s' state. It should be 'open'" % inventory_state9-
1010 I create a wizard record for stock_confirm_uninventoried_location to verify that it contains the uninventoried locations
11-11-
12 In order, i add products to exhaustive inventory.12 !python {model: stock.inventory.uninventoried.locations}: |
13 Don't add mou(product.product_product_25) and keya (product.product_product_24) products.13 ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
14 Adding pc1.14 wizard_id = self.create(cr, uid, {}, context=ctx)
15- 15 wizard = self.browse(cr, uid, wizard_id, context=ctx)
16 assert len(wizard.location_ids) > 0 , "The wizard does not contain any lines."
17-
18 I add products to exhaustive inventory.
19 Adding 17” LCD Monitor.
20-
16 !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:21 !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
17 product_id: product.product_product_1022 product_id: product.product_product_7
18 product_uom: product.product_uom_unit23 product_uom: product.product_uom_unit
19 company_id: base.main_company24 company_id: base.main_company
20 inventory_id: stock_inventory_location_1 25 inventory_id: inventory_exhaustive
21 product_qty: 18.026 product_qty: 18.0
22 location_id: stock.stock_location_1427 location_id: stock.stock_location_14
2328
24-29-
25 Adding pc3.30 Adding USB Keyboard, QWERTY.
26- 31-
27 !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:32 !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
28 product_id: product.product_product_pc333 product_id: product.product_product_8
29 product_uom: product.product_uom_unit34 product_uom: product.product_uom_unit
30 company_id: base.main_company35 company_id: base.main_company
31 inventory_id: stock_inventory_location_1 36 inventory_id: inventory_exhaustive
32 product_qty: 5.037 product_qty: 5.0
33 location_id: stock.stock_location_14 38 location_id: stock.stock_location_14
3439
35-40-
36 I will call the function _get_locations_open_inventories and check the result.41 I will call the function _get_locations_open_inventories and check the result.
37 The function will return only the location_ids of the exhaustive inventory.42 The function will return only the location_id of the exhaustive inventory.
38-43-
39 !python {model: stock.inventory}: |44 !python {model: stock.inventory}: |
40 from osv import orm, osv45 locations = self._get_locations_open_inventories(cr, uid)
41 locations = self._get_locations_open_inventories(cr, uid) 46 assert len(locations) == 1, "Function return wrong results: %s" % locations
42 assert len(locations) == 1, "Function return wrong results : %s" % locations47 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])
43 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]) 48-
44
45-
46 I will call the function onchange_location_id.49 I will call the function onchange_location_id.
47 The function must to return true in the first case, and return a warning dictionnary in the second test. 50 The function must return True in the first case, and return a warning dictionnary in the second test.
48-51-
49 !python {model: stock.inventory.line}: |52 !python {model: stock.inventory.line}: |
50 from osv import orm, osv
51 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_14'))53 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_14'))
52 assert res == True, "Exhaustive : The function 'onchange_location_id' should return True and return '%s'" % res 54 assert res == True, "Exhaustive: The function 'onchange_location_id' should return True and return '%s'" % res
53 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_components'))55 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_components'))
54 assert res.get('warning', False) != False , "Function 'onchange_location_id' : Warning not raise. ('%s)" % res 56 assert res.get('warning', False) != False , "Function 'onchange_location_id': Warning not raise. ('%s)" % res
5557
56-58-
57 I will call _fill_location_lines to simulate confirmation for stock_confirm_uninventoried_location,59 I create a wizard record for stock_confirm_uninventoried_location and validate it
58 and create stock_moves60-
59-61 !python {model: stock.inventory.uninventoried.locations}: |
60 !python {model: stock.inventory}: |62 ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
61 from osv import orm, osv63 wizard_id = self.create(cr, uid, {}, context=ctx)
62 ctx={'location': [ref('stock.stock_location_14')]} 64 wizard = self.browse(cr, uid, wizard_id, context=ctx)
63 lines = self._fill_location_lines(cr, uid, ref('stock_inventory_location_1'), [ref('stock.stock_location_14')], True, context=ctx)65 assert len(wizard.location_ids) == 0 , "The wizard should not contain any lines but contains %s." % wizard.location_ids
64 for line in lines:66 self.confirm_uninventoried_locations(cr, uid, wizard_id, context=ctx)
65 self.pool.get('stock.inventory.line').create(cr, uid, line, context=context) 67-
66 68 Stock moves are not allowed in the locations during the inventory.
67-69-
68 I will confirm exhaustive inventory 70 !python {model: stock.move}: |
69- 71 # TODOv8: remove this test, this is already part of trunk-wms
70 !python {model: stock.inventory}: |72 from stock_inventory_location import ExhaustiveInventoryException
71 from osv import orm, osv 73 try:
72 self.action_confirm(cr, uid, [ref('stock_inventory_location_1')]) 74 self.create(
73 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']75 cr,uid, {
76 'name': 'Bad move',
77 'location_id': ref('stock.stock_location_14'),
78 'location_dest_id': ref('stock.stock_location_3'),
79 'product_id': ref('product.product_product_8'),
80 'product_uom': ref('product.product_uom_unit'),
81 'date_expected': '2020-01-01 00:00:00'
82 })
83 except ExhaustiveInventoryException as e:
84 log("Good! The Stock Move was refused: %s" % e)
85-
86 I will confirm the exhaustive inventory
87-
88 !python {model: stock.inventory}: |
89 self.action_confirm(cr, uid, [ref('inventory_exhaustive')])
90 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
74 assert inventory_state == 'confirm', "Exhaustive inventory have '%s' state. It should be 'confirm'" % inventory_state91 assert inventory_state == 'confirm', "Exhaustive inventory have '%s' state. It should be 'confirm'" % inventory_state
75 92
76-93-
77 I will validate exhaustive inventory94 I will validate the exhaustive inventory
78-95-
79 !python {model: stock.inventory}: |96 !python {model: stock.inventory}: |
80 from osv import orm, osv 97 self.action_done(cr, uid, [ref('inventory_exhaustive')])
81 self.action_done(cr, uid, [ref('stock_inventory_location_1')])98 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
82 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']
83 assert inventory_state == 'done', "Exhaustive inventory have '%s' state. It should be 'done'" % inventory_state99 assert inventory_state == 'done', "Exhaustive inventory have '%s' state. It should be 'done'" % inventory_state
84 100
85-101-
86 I will verify the quantity for each products.102 I will verify the quantity for each products.
87- 103-
88 !python {model: product.product}: |104 !python {model: product.product}: |
89 from osv import orm, osv 105 ctx = dict(context, location=[ref('stock.stock_location_14')])
90 ctx={'location': [ref('stock.stock_location_14')], 'to_date': '2020-12-31 23:59:59'} 106 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_7')], ['qty_available'], context=ctx)[0]['qty_available']
91 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']107 assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0: %s" % prod_qty_avail
92 assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0 : %s" % prod_qty_avail108
93 109 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_8')], ['qty_available'], context=ctx)[0]['qty_available']
94 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_pc3')], ['qty_available'], context=ctx)[0]['qty_available']110 assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0: %s" % prod_qty_avail
95 assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0 : %s" % prod_qty_avail
96111
97 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_24')], ['qty_available'], context=ctx)[0]['qty_available']112 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_24')], ['qty_available'], context=ctx)[0]['qty_available']
98 assert prod_qty_avail == 0.0, "The stock of KEYA was not set to 0 : %s" % prod_qty_avail113 assert prod_qty_avail == 0.0, "The stock of KEYA was not set to 0: %s" % prod_qty_avail
99114
100 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_25')], ['qty_available'], context=ctx)[0]['qty_available']115 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_25')], ['qty_available'], context=ctx)[0]['qty_available']
101 assert prod_qty_avail == 0.0, "The stock of MOU was not set to 0 : %s" % prod_qty_avail116 assert prod_qty_avail == 0.0, "The stock of MOU was not set to 0: %s" % prod_qty_avail
102117
=== added file 'stock_inventory_location/test/inventory_future_test.yml'
--- stock_inventory_location/test/inventory_future_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/test/inventory_future_test.yml 2014-06-11 15:04:27 +0000
@@ -0,0 +1,13 @@
1-
2 Check that an inventory with a date in the future cannot be opened.
3-
4 !python {model: stock.inventory}: |
5 # TODO v8: maybe this is already part of the new WMS
6 from osv.osv import except_osv
7 self.action_open(cr, uid, [ref('inventory_future')])
8 try:
9 self.action_done(cr, uid, [ref('inventory_future')])
10 except except_osv as e:
11 log("Good! The Inventory could not be opened: %s" % e)
12 inventory_state = self.read(cr, uid, [ref('inventory_future')], ['state'])[0]['state']
13 assert inventory_state != 'done', "Future inventory is done."
014
=== renamed file 'stock_inventory_location/test/location_inventory_test.yml' => 'stock_inventory_location/test/inventory_standard_test.yml'
--- stock_inventory_location/test/location_inventory_test.yml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/test/inventory_standard_test.yml 2014-06-11 15:04:27 +0000
@@ -3,61 +3,57 @@
3 I will call open_action method and check if state of inventories are 'open'.3 I will call open_action method and check if state of inventories are 'open'.
4-4-
5 !python {model: stock.inventory}: |5 !python {model: stock.inventory}: |
6 from osv import orm, osv 6 self.action_open(cr, uid, [ref('inventory_standard')])
7 self.action_open(cr, uid, [ref('stock_inventory_location_0')]) 7 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
8 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']8 assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state
9 assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state 9
10
11-10-
12 In order, I add products to inventory.11 In order, I add products to inventory.
13 Adding cpu1.12 Adding Azerty Keyboard.
14-13-
15 !record {model: stock.inventory.line, id: lines_inventory_location_cpu1}:14 !record {model: stock.inventory.line, id: lines_inventory_location_kbaz}:
16 product_id: product.product_product_cpu115 product_id: product.product_product_9
17 product_uom: product.product_uom_unit16 product_uom: product.product_uom_unit
18 company_id: base.main_company17 company_id: base.main_company
19 inventory_id: stock_inventory_location_0 18 inventory_id: inventory_standard
20 product_qty: 18.019 product_qty: 18.0
21 location_id: stock.stock_location_components20 location_id: stock.stock_location_components
2221
23-22-
24 Adding cpu3.23 Adding Optical Mouse.
25-24-
26 !record {model: stock.inventory.line, id: lines_inventory_location_cpu3}:25 !record {model: stock.inventory.line, id: lines_inventory_location_optm}:
27 product_id: product.product_product_cpu326 product_id: product.product_product_10
28 product_uom: product.product_uom_unit27 product_uom: product.product_uom_unit
29 company_id: base.main_company28 company_id: base.main_company
30 inventory_id: stock_inventory_location_0 29 inventory_id: inventory_standard
31 product_qty: 12.030 product_qty: 12.0
32 location_id: stock.stock_location_components31 location_id: stock.stock_location_components
3332
34-33-
35 Adding fan.34 Adding Multimedia Speakers.
36-35-
37 !record {model: stock.inventory.line, id: lines_inventory_location_fan}:36 !record {model: stock.inventory.line, id: lines_inventory_location_grca}:
38 product_id: product.product_product_fan37 product_id: product.product_template_31
39 product_uom: product.product_uom_unit38 product_uom: product.product_uom_unit
40 company_id: base.main_company39 company_id: base.main_company
41 inventory_id: stock_inventory_location_0 40 inventory_id: inventory_standard
42 product_qty: 32.041 product_qty: 32.0
43 location_id: stock.stock_location_components42 location_id: stock.stock_location_components
4443
45-44-
46 I will call the function _get_locations_open_inventories and check the result.45 I will call the function _get_locations_open_inventories and check the result.
47 The function will return no locations.46 The function will return no locations because it's not an exhaustive inventory.
48-47-
49 !python {model: stock.inventory}: |48 !python {model: stock.inventory}: |
50 from osv import orm, osv49 locations = self._get_locations_open_inventories(cr, uid)
51 locations = self._get_locations_open_inventories(cr, uid) 50 assert len(locations) == 0, "Function return wrong results: %s" % locations
52 assert len(locations) == 0, "Function return wrong results : %s" % locations51
53
54-52-
55 I will call the function onchange_location_id.53 I will call the function onchange_location_id.
56 The function must to return true in all test case. 54 The function must to return true in all test case.
57-55-
58 !python {model: stock.inventory.line}: |56 !python {model: stock.inventory.line}: |
59 from osv import orm, osv
60 pass
61 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))57 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))
62 assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res58 assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
63 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))59 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))
@@ -68,42 +64,37 @@
68 The function must return True in all test cases.64 The function must return True in all test cases.
69-65-
70 !python {model: stock.location}: |66 !python {model: stock.location}: |
71 from osv import orm, osv
72 res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))67 res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))
73 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res68 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
74 res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))69 res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))
75 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res70 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
76 71
77-72-
78 I will confirm inventory. 73 I will confirm inventory.
79- 74-
80 !python {model: stock.inventory}: |75 !python {model: stock.inventory}: |
81 from osv import orm, osv 76 self.action_confirm(cr, uid, [ref('inventory_standard')])
82 self.action_confirm(cr, uid, [ref('stock_inventory_location_0')]) 77 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
83 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']
84 assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state78 assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state
8579
86-80-
87 I will validate inventory81 I will validate inventory
88-82-
89 !python {model: stock.inventory}: |83 !python {model: stock.inventory}: |
90 from osv import orm, osv 84 self.action_done(cr, uid, [ref('inventory_standard')])
91 self.action_done(cr, uid, [ref('stock_inventory_location_0')]) 85 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
92 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']86 assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state
93 assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state 87
94
95-88-
96 I will verify the quantity for each products.89 I will verify the quantity for each products.
97- 90-
98 !python {model: product.product}: |91 !python {model: product.product}: |
99 from osv import orm, osv
100 ctx={'location': [ref('stock.stock_location_components')], 'to_date': '2020-12-31 23:59:59'}
101 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_cpu1')], ['qty_available'], context=ctx)[0]['qty_available']
102 assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0 : %s" % prod_qty_avail
103
104 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_cpu3')], ['qty_available'], context=ctx)[0]['qty_available']
105 assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0 : %s" % prod_qty_avail
106
107 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_fan')], ['qty_available'], context=ctx)[0]['qty_available']
108 assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0 : %s" % prod_qty_avail
109
110\ No newline at end of file92\ No newline at end of file
93 ctx={'location': [ref('stock.stock_location_components')]}
94 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_9')], ['qty_available'], context=ctx)[0]['qty_available']
95 assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0: %s" % prod_qty_avail
96
97 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']
98 assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0: %s" % prod_qty_avail
99
100 prod_qty_avail = self.read(cr, uid, [ref('product.product_template_31')], ['qty_available'], context=ctx)[0]['qty_available']
101 assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0: %s" % prod_qty_avail
111102
=== modified file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.py'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-06-11 15:04:27 +0000
@@ -18,78 +18,45 @@
18#18#
19##############################################################################19##############################################################################
2020
21from openerp.osv import fields, osv21from openerp.osv import fields, orm
22from openerp.tools.translate import _22
2323
2424class stock_inventory_uninventoried_location(orm.TransientModel):
25class stock_inventory_uninventoried_location(osv.osv_memory):
26 _name = 'stock.inventory.uninventoried.locations'25 _name = 'stock.inventory.uninventoried.locations'
27 _description = 'Confirm the uninventoried Locations.'26 _description = 'Confirm the uninventoried Locations.'
2827
29 _columns = {28 _columns = {
30 'location_ids': fields.many2many('stock.location',29 'location_ids': fields.many2many(
31 'stock_inventory_uninventoried_location_rel',30 'stock.location',
32 'location_id',31 'stock_inventory_uninventoried_location_rel',
33 'wizard_id',32 'location_id', 'wizard_id',
34 'Uninventoried location', readonly=True),33 'Uninventoried location', readonly=True),
35 }34 }
36
37 def get_locations(self, cr, uid, inventory_id, context=None):
38 """ Get all locations from inventory. """
39 location_ids = self.pool.get('stock.inventory').read(cr, uid, [inventory_id], ['location_ids'], context=context)[0]
40 return self.pool.get('stock.location').search(cr, uid, [
41 ('location_id', 'child_of', location_ids['location_ids']),
42 ('usage', '=', 'internal')], context=context)
43
44 def get_locations_inventoried(self, cr, uid, inventory_id, location_ids, context=None):
45 """ Get all locations on inventory lines. """
46 inventory_line_obj = self.pool.get('stock.inventory.line')
47 inventory_line_ids = inventory_line_obj.search(cr, uid, [('location_id', 'in', location_ids),
48 ('inventory_id', '=', inventory_id)], context=context)
49 inventory_line_locations_ids = inventory_line_obj.read(cr, uid, inventory_line_ids, ['location_id'], context=context)
50 return list(set([_id['location_id'][0] for _id in inventory_line_locations_ids]))
5135
52 def default_locations(self, cr, uid, context=None):36 def default_locations(self, cr, uid, context=None):
53 """ Initialize view with the list of uninventoried locations.37 """Initialize view with the list of uninventoried locations."""
54 Search for children of the location if exists.38 return self.pool['stock.inventory'].get_missing_locations(
55 """39 cr, uid, context['active_ids'], context=context)
56 if context is None:
57 context = {}
58 location_ids = self.get_locations(cr, uid, context['active_id'])
59 inventory_line_locations_ids = self.get_locations_inventoried(cr, uid, context['active_id'], location_ids)
60 return [_id for _id in location_ids if _id not in inventory_line_locations_ids]
6140
62 _defaults = {41 _defaults = {
63 'location_ids': default_locations,42 'location_ids': default_locations,
64 }43 }
6544
66 def confirm_uninventoried_locations(self, cr, uid, ids, context=None):45 def confirm_uninventoried_locations(self, cr, uid, ids, context=None):
67 """ Call action confirm method from stock.inventory """46 """Add the missing inventory lines with qty=0 and confirm inventory"""
68 inventory_ids = context['active_ids']47 inventory_ids = context['active_ids']
69 # call the wizard to add lines for uninventoried locations with zero quantity48 inventory_obj = self.pool['stock.inventory']
70 inventory_obj = self.pool.get('stock.inventory')49 wizard_obj = self.pool['stock.fill.inventory']
71 if not isinstance(inventory_ids, list):50 for inventory in inventory_obj.browse(cr, uid, inventory_ids,
72 inventory_ids = [inventory_ids]51 context=context):
73
74 for inventory in inventory_obj.browse(cr, uid, inventory_ids, context=context):
75 if inventory.exhaustive:52 if inventory.exhaustive:
76 location_ids = self.get_locations(cr, uid, inventory.id, context=context)53 # silently run the import wizard with qty=0
77 # get stock inventory lines54 wizard_id = wizard_obj.create(
78 lines = []55 cr, uid, {'location_id': inventory.location_id.id,
79 try:56 'recursive': True,
80 # create inventory lines with zero qty57 'set_stock_zero': True}, context=context)
81 lines = inventory_obj._fill_location_lines(cr, uid,58 wizard_obj.fill_inventory(cr, uid, [wizard_id],
82 inventory.id,59 context=context)
83 location_ids,
84 True,
85 context=context)
86 except osv.except_osv as e:
87 pass
88
89 for line in lines:
90 self.pool.get('stock.inventory.line').create(cr, uid, line, context=context)
9160
92 inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)61 inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)
93 return {'type': 'ir.actions.act_window_close'}62 return {'type': 'ir.actions.act_window_close'}
94
95
9663
=== modified file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-06-11 15:04:27 +0000
@@ -1,33 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<openerp>2<openerp>
3 <data>3 <data>
4 <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml, 4 <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml,
5 but the code is different. 5 but the code is different.
6 This wizard compare declared locations with locations on inventory lines to present the uninventoried (empty) locations to user. -->6 This wizard compare declared locations with locations on inventory lines to present the uninventoried (empty) locations to user. -->
7 <record id="view_confirm_uninventoried_location" model="ir.ui.view">7 <record id="view_confirm_uninventoried_location" model="ir.ui.view">
8 <field name="name">stock.inventory.uninventoried.locations.form</field>8 <field name="name">stock.inventory.uninventoried.locations.form</field>
9 <field name="model">stock.inventory.uninventoried.locations</field>9 <field name="model">stock.inventory.uninventoried.locations</field>
10 <field name="type">form</field>
11 <field name="arch" type="xml">10 <field name="arch" type="xml">
12 <form string="Confirm uninventoried locations">11 <form string="Confirm empty locations" version="7.0">
13 <group colspan="4" col="1">12 <group colspan="4" col="1">
14 <label string="The following Stock Locations are part of the current Physical Inventory, but not Inventory Line has been recorded for them."/>13 <separator string="The following Locations are empty"/>
14 <label string="The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."/>
15 <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
16 <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
15 <field name="location_ids" nolabel="1">17 <field name="location_ids" nolabel="1">
16 <tree>18 <tree>
17 <field name="name"/>19 <field name="name"/>
18 </tree>20 </tree>
19 </field>21 </field>
20 <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
21 <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
22 <separator string=""/>22 <separator string=""/>
23 </group>23 </group>
24 <group colspan="4" col="2">24 <footer>
25 <button special="cancel" string="Cancel" icon="gtk-cancel" default_focus="1" />25 <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" class="oe_highlight"/>
26 <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" icon="gtk-ok"/> 26 or
27 </group>27 <button string="Cancel" class="oe_link" special="cancel" default_focus="1"/>
28 </footer>
28 </form>29 </form>
29 </field>30 </field>
30 </record>31 </record>
31
32 </data>32 </data>
33</openerp>33</openerp>
34\ No newline at end of file34\ No newline at end of file
3535
=== modified file 'stock_inventory_location/wizard/stock_fill_location_inventory.py'
--- stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-06-11 15:04:27 +0000
@@ -18,80 +18,30 @@
18#18#
19##############################################################################19##############################################################################
2020
21from openerp.osv import fields, osv21from openerp.osv import fields, orm
22from openerp.tools.translate import _22
23from collections import OrderedDict23
2424class FillInventoryWizard(orm.TransientModel):
2525 """Add a field that lets the client make the location read-only"""
26class stock_fill_location_inventory(osv.osv_memory):
27 _inherit = 'stock.fill.inventory'26 _inherit = 'stock.fill.inventory'
2827
29 _columns = {28 _columns = {
30 'location_id': fields.many2one('stock.location', 'Location'),29 'exhaustive': fields.boolean('Exhaustive', readonly=True)
31 #'exhaustive': fields.boolean('stock.inventory', 'Type'),30 }
32 'exhaustive': fields.boolean('stock.inventory'),31
33 }32 def default_get(self, cr, uid, fields, context=None):
3433 """Get 'location_id' and 'exhaustive' from the inventory"""
35 def get_inventory_type(self, cr, uid, context=None):34 if context is None:
36 if context.get('active_id', False):35 context = {}
37 inventory_obj = self.pool.get('stock.inventory')36 inv_id = context.get('active_id')
38 exhaustive = inventory_obj.read(cr, uid, [context.get('active_id')], ['exhaustive'], context=context)[0]['exhaustive']37
39 return exhaustive38 res = super(FillInventoryWizard, self).default_get(
40 return False39 cr, uid, fields, context=context)
4140 if (context.get('active_model') == 'stock.inventory'
42 _defaults = {41 and inv_id
43 'exhaustive': get_inventory_type,42 and 'location_id' in fields):
44 }43 inventory = self.pool['stock.inventory'].browse(
4544 cr, uid, context['active_id'], context=context)
46 def view_init(self, cr, uid, fields_list, context=None):45 res.update({'location_id': inventory.location_id.id,
47 """ inherit from original to add multiple selection of location46 'exhaustive': inventory.exhaustive})
48 and exclude from location list the locations already choosen by another inventory """47 return res
49 if context is None:
50 context = {}
51
52 inventory_obj = self.pool.get('stock.inventory')
53 inventory_state = inventory_obj.read(cr, uid, [context.get('active_id')], ['state'], context=context)[0]
54 if inventory_state['state'] != 'open':
55 raise osv.except_osv(_('Error !'),
56 _('the inventory must be in "Open" state.'))
57
58 nb_inventory = inventory_obj.search(cr, uid, [('id', '=', context.get('active_id'))], count=True, context=context)
59 if nb_inventory == 0:
60 raise osv.except_osv(_('Warning !'),
61 _('No locations found for the inventory.'))
62
63 return super(stock_fill_location_inventory, self).view_init(cr, uid, fields_list, context=context)
64
65 def fill_inventory(self, cr, uid, ids, context=None):
66 """ Fill the inventory only with open locations on the inventory.
67 """
68 if context is None:
69 context = {}
70
71 fill_inventory = self.browse(cr, uid, ids[0], context=context)
72 if not fill_inventory.exhaustive:
73 return super(stock_fill_location_inventory, self).fill_inventory(cr, uid, ids, context=context) # call standard wizard
74
75 location_ids = self.pool.get('stock.inventory').read(cr, uid, [context.get('active_id')], ['location_ids'])[0]
76
77 if not location_ids['location_ids']:
78 raise osv.except_osv(_('Error : Empty location !'), _('No location to import.\nYou must add a location on the locations list.'))
79
80 if fill_inventory.recursive:
81 location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids['location_ids']),
82 ('usage', '=', 'internal')], context=context)
83 else:
84 location_ids = location_ids['location_ids']
85
86 location_ids = list(OrderedDict.fromkeys(location_ids))
87
88 lines = self.pool.get('stock.inventory')._fill_location_lines(cr, uid,
89 context['active_ids'][0],
90 location_ids,
91 fill_inventory.set_stock_zero,
92 context=context)
93
94 inventory_lines_obj = self.pool.get('stock.inventory.line')
95 for line in lines:
96 inventory_lines_obj.create(cr, uid, line, context=context)
97 return {'type': 'ir.actions.act_window_close'}
9848
=== modified file 'stock_inventory_location/wizard/stock_fill_location_inventory_view.xml'
--- stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-06-11 15:04:27 +0000
@@ -10,7 +10,7 @@
10 <field name="exhaustive" invisible="1" />10 <field name="exhaustive" invisible="1" />
11 </xpath>11 </xpath>
12 <xpath expr="//field[@name='location_id']" position="attributes">12 <xpath expr="//field[@name='location_id']" position="attributes">
13 <attribute name="attrs">{'invisible':[('exhaustive','=',True)]}</attribute>13 <attribute name="attrs">{'readonly':[('exhaustive','=',True)]}</attribute>
14 </xpath>14 </xpath>
15 </field>15 </field>
16 </record>16 </record>

Subscribers

People subscribed via source and target branches