Merge lp:~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations into lp:stock-logistic-warehouse

Proposed by Laetitia Gangloff (Acsone)
Status: Merged
Merge reported by: Laetitia Gangloff (Acsone)
Merged at revision: not available
Proposed branch: lp:~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations
Merge into: lp:stock-logistic-warehouse
Diff against target: 3044 lines (+2811/-0)
38 files modified
stock_inventory_hierarchical/__init__.py (+28/-0)
stock_inventory_hierarchical/__openerp__.py (+46/-0)
stock_inventory_hierarchical/exceptions.py (+26/-0)
stock_inventory_hierarchical/hierarchical_inventory.py (+202/-0)
stock_inventory_hierarchical/hierarchical_inventory_demo.xml (+17/-0)
stock_inventory_hierarchical/hierarchical_inventory_view.xml (+78/-0)
stock_inventory_hierarchical/i18n/fr.po (+112/-0)
stock_inventory_hierarchical/test/hierarchical_inventory_test.yml (+96/-0)
stock_inventory_hierarchical_location/__init__.py (+22/-0)
stock_inventory_hierarchical_location/__openerp__.py (+49/-0)
stock_inventory_hierarchical_location/i18n/fr.po (+127/-0)
stock_inventory_hierarchical_location/inventory_hierarchical_location.py (+100/-0)
stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml (+28/-0)
stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml (+37/-0)
stock_inventory_hierarchical_location/tests/__init__.py (+39/-0)
stock_inventory_hierarchical_location/tests/fill_inventory_test.py (+118/-0)
stock_inventory_hierarchical_location/tests/inventory_hierarchical_location_test.yml (+56/-0)
stock_inventory_hierarchical_location/wizard/__init__.py (+22/-0)
stock_inventory_hierarchical_location/wizard/generate_inventory.py (+134/-0)
stock_inventory_hierarchical_location/wizard/generate_inventory_view.xml (+42/-0)
stock_inventory_hierarchical_location/wizard/stock_fill_location_inventory.py (+89/-0)
stock_inventory_location/__init__.py (+24/-0)
stock_inventory_location/__openerp__.py (+77/-0)
stock_inventory_location/exceptions.py (+26/-0)
stock_inventory_location/i18n/fr.po (+241/-0)
stock_inventory_location/stock_inventory_location.py (+353/-0)
stock_inventory_location/stock_inventory_location_demo.xml (+26/-0)
stock_inventory_location/stock_inventory_location_view.xml (+81/-0)
stock_inventory_location/tests/__init__.py (+39/-0)
stock_inventory_location/tests/inventory_exhaustive_test.yml (+128/-0)
stock_inventory_location/tests/inventory_future_test.yml (+13/-0)
stock_inventory_location/tests/inventory_standard_test.yml (+100/-0)
stock_inventory_location/tests/stock_inventory_location_test.py (+47/-0)
stock_inventory_location/wizard/__init__.py (+22/-0)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.py (+68/-0)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml (+33/-0)
stock_inventory_location/wizard/stock_fill_location_inventory.py (+47/-0)
stock_inventory_location/wizard/stock_fill_location_inventory_view.xml (+18/-0)
To merge this branch: bzr merge lp:~acsone-openerp/stock-logistic-warehouse/7.0-inventory-hierarchical-location-fix-subinventory-locations
Reviewer Review Type Date Requested Status
Stock and Logistic Core Editors Pending
Review via email: mp+224774@code.launchpad.net

Description of the change

Add exhaustive parameter for fill inventory.
And encapsulate in try catch to confirm even if there is no product to add

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'stock_inventory_hierarchical'
=== added file 'stock_inventory_hierarchical/__init__.py'
--- stock_inventory_hierarchical/__init__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/__init__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,28 @@
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
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
26import hierarchical_inventory
27# Bring the main exception into the package's scope for easier reuse
28from .exceptions import HierarchicalInventoryException
029
=== added file 'stock_inventory_hierarchical/__openerp__.py'
--- stock_inventory_hierarchical/__openerp__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/__openerp__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,46 @@
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
21{
22 "name": "Hierarchical Physical Inventory",
23 "version": "1.1",
24 "depends": ["stock"],
25 "author": "Numérigraphe",
26 "category": "Warehouse Management",
27 "description": """
28Hierarchical structure for Physical Inventories and sub-Inventories
29===================================================================
30
31This module adds a parent-child relationship between Physical Inventories, to
32help users manage complex inventories.
33Using several inventories, you can distribute the counting to several persons
34and still keep a clear overview of global Inventory's status.
35
36OpenERP will make sure the status of the Inventory and it's Sub-Inventories are
37consistent.
38""",
39 "data": ["hierarchical_inventory_view.xml"],
40 "test": ["test/hierarchical_inventory_test.yml"],
41 "demo": ["hierarchical_inventory_demo.xml"],
42 "images": [
43 "inventory_form.png",
44 "inventory_form_actions.png",
45 ],
46}
047
=== 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-27 09:18:58 +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
=== added file 'stock_inventory_hierarchical/hierarchical_inventory.py'
--- stock_inventory_hierarchical/hierarchical_inventory.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,202 @@
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, fields
22from openerp.tools.translate import _
23
24from .exceptions import HierarchicalInventoryException
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):
32 _inherit = 'stock.inventory'
33
34 _parent_store = True
35 _parent_order = 'date, name'
36 _order = 'parent_left'
37
38 def name_get(self, cr, uid, ids, context=None):
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)
52 if isinstance(ids, (list, tuple)) and not len(ids):
53 return []
54 if isinstance(ids, (long, int)):
55 ids = [ids]
56 reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context)
57 res = []
58 for record in reads:
59 name = record['name']
60 if record['parent_id']:
61 name = record['parent_id'][1] + ' / ' + name
62 res.append((record['id'], name))
63 return res
64
65 def name_search(self, cr, uid, name='', args=None, operator='ilike',
66 context=None, limit=100):
67 """Enable search on value returned by name_get ("parent / child")"""
68 if not args:
69 args = []
70 if not context:
71 context = {}
72 if name:
73 # Make sure name_search is symmetric to name_get
74 name = name.split(' / ')[-1]
75 ids = self.search(cr, uid, [('name', operator, name)] + args,
76 limit=limit, context=context)
77 else:
78 ids = self.search(cr, uid, args, limit=limit, context=context)
79 return self.name_get(cr, uid, ids, context=context)
80
81 def _complete_name(self, cr, uid, ids, field_name, arg, context=None):
82 """Function-field wrapper to get the complete name from name_get"""
83 res = self.name_get(cr, uid, ids, context=context)
84 return dict(res)
85
86 def _progress_rate(self, cr, uid, ids, field_name, arg, context=None):
87 """Rate of (sub)inventories done/total"""
88 rates = {}
89 for current_id in ids:
90 nb = self.search(
91 cr, uid, [('parent_id', 'child_of', current_id)],
92 context=context, count=True)
93 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
102 return rates
103
104 _columns = {
105 # name_get() only changes the default name of the record, not the
106 # content of the field "name" so we add another field for that
107 'complete_name': fields.function(
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)]}),
116 'parent_left': fields.integer('Parent Left', select=1),
117 'parent_right': fields.integer('Parent Right', select=1),
118 'progress_rate': fields.function(
119 _progress_rate, string='Progress', type='float'),
120 }
121
122 _constraints = [
123 (orm.Model._check_recursion,
124 'Error: You can not create recursive inventories.',
125 ['parent_id']),
126 ]
127
128 def create(self, cr, uid, vals, context=None):
129 """Copy selected values from parent to child"""
130 if vals and vals.get('parent_id'):
131 existing_fields = self.fields_get_keys(cr, uid, context=context)
132 parent_values = self.read(cr, uid, [vals['parent_id']],
133 PARENT_VALUES, context=context)
134 vals = vals.copy()
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)
140
141 def write(self, cr, uid, ids, vals, context=None):
142 """Copy selected values from parent to children"""
143 if context is None:
144 context = {}
145
146 values = super(HierarchicalInventory, self).write(
147 cr, uid, ids, vals, context=context)
148 if not vals or context.get('norecurse', False):
149 return values
150
151 # filter the fields we want to propagate
152 children_values = {
153 field: vals[field] for field in PARENT_VALUES if field in vals
154 }
155 if not children_values:
156 return values
157
158 if not isinstance(ids, list):
159 ids = [ids]
160 # The context disables recursion - children are already included
161 return self.write(
162 cr, uid, self.search(cr, uid, [('parent_id', 'child_of', ids)]),
163 children_values, context=dict(context, norecurse=True))
164
165 def action_cancel_inventory(self, cr, uid, ids, context=None):
166 """Cancel inventory only if all the parents are canceled"""
167 inventories = self.browse(cr, uid, ids, context=context)
168 for inventory in inventories:
169 while inventory.parent_id:
170 inventory = inventory.parent_id
171 if inventory.state != 'cancel':
172 raise HierarchicalInventoryException(
173 _('Warning'),
174 _('One of the parent Inventories is not canceled.'))
175 return super(HierarchicalInventory,
176 self).action_cancel_inventory(cr, uid, ids,
177 context=context)
178
179 def action_confirm(self, cr, uid, ids, context=None):
180 """Confirm inventory only if all the children are confirmed"""
181 children_count = self.search(
182 cr, uid, [('parent_id', 'child_of', ids),
183 ('state', 'not in', ['confirm', 'done'])],
184 context=context, count=True)
185 if children_count > 1:
186 raise HierarchicalInventoryException(
187 _('Warning'),
188 _('Some Sub-inventories are not confirmed.'))
189 return super(HierarchicalInventory, self).action_confirm(
190 cr, uid, ids, context=context)
191
192 def action_done(self, cr, uid, ids, context=None):
193 """Perform validation only if all the children states are 'done'."""
194 children_count = self.search(cr, uid, [('parent_id', 'child_of', ids),
195 ('state', '!=', 'done')],
196 context=context, count=True)
197 if children_count > 1:
198 raise HierarchicalInventoryException(
199 _('Warning'),
200 _('Some Sub-inventories are not validated.'))
201 return super(HierarchicalInventory, self).action_done(
202 cr, uid, ids, context=context)
0203
=== added file 'stock_inventory_hierarchical/hierarchical_inventory_demo.xml'
--- stock_inventory_hierarchical/hierarchical_inventory_demo.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory_demo.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data noupdate="0">
4 <!-- Example Inventory with Sub-Inventories. -->
5 <record id="stock_inventory_parent0" model="stock.inventory">
6 <field name="name">Main Inventory</field>
7 </record>
8 <record id="child_1_id" model="stock.inventory">
9 <field name="name">Sub-Inventory 1</field>
10 <field name="parent_id" ref="stock_inventory_parent0" />
11 </record>
12 <record id="child_2_id" model="stock.inventory">
13 <field name="name">Sub-Inventory 2</field>
14 <field name="parent_id" ref="stock_inventory_parent0" />
15 </record>
16 </data>
17</openerp>
018
=== added file 'stock_inventory_hierarchical/hierarchical_inventory_view.xml'
--- stock_inventory_hierarchical/hierarchical_inventory_view.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/hierarchical_inventory_view.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,78 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <!-- Add parent_id and number of Sub-inventories to form view -->
5 <record model="ir.ui.view" id="stock_inventory_hierarchical_tree_view">
6 <field name="name">hierarchical.inventory.tree</field>
7 <field name="model">stock.inventory</field>
8 <field name="inherit_id" ref="stock.view_inventory_tree" />
9 <field name="field_parent">inventory_ids</field>
10 <field name="arch" type="xml">
11 <xpath expr="//field[@name='name']" position="replace">
12 <field name="complete_name" string="Reference"/>
13 </xpath>
14 <xpath expr="//field[@name='state']" position="after">
15 <field name="progress_rate" widget="progressbar" />
16 </xpath>
17 </field>
18 </record>
19
20 <!-- Add the parent_id filter to search view -->
21 <record model="ir.ui.view" id="view_inventory_subinventories_filter">
22 <field name="name">hierarchical.inventory.filter</field>
23 <field name="model">stock.inventory</field>
24 <field name="inherit_id" ref="stock.view_inventory_filter" />
25 <field name="arch" type="xml">
26 <xpath expr="//field[@name='name']" position="before">
27 <filter icon="terp-check" name="main_inventories" string="Main inventories" domain="[('parent_id', '=', False)]" help="Only select inventories that have no parents." />
28 <separator orientation="vertical"/>
29 </xpath>
30 <xpath expr="//field[@name='date']" position="after">
31 <field name="parent_id" />
32 </xpath>
33 </field>
34 </record>
35 <!-- Show main inventories by default -->
36 <record id="stock.action_inventory_form" model="ir.actions.act_window">
37 <field name="context">{'full':'1', 'search_default_main_inventories':1}</field>
38 </record>
39
40 <record model="ir.ui.view" id="stock_inventory_hierarchical_form_view">
41 <field name="name">hierarchical.inventory.form</field>
42 <field name="model">stock.inventory</field>
43 <field name="inherit_id" ref="stock.view_inventory_form" />
44 <field name="arch" type="xml">
45 <xpath expr="/form//field[@name='name']" position="after">
46 <field name="parent_id"/>
47 </xpath>
48 <xpath expr="/form//field[@name='date']" position="attributes">
49 <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
50 </xpath>
51 <xpath
52 expr="//page[@string='General Information']"
53 position="after">
54 <page string="Sub-inventories">
55 <field name="inventory_ids" nolabel="1" context="{'default_parent_id': active_id}">
56 <tree>
57 <field name="name" />
58 <field name="state" />
59 <field name="progress_rate" widget="progressbar" />
60 </tree>
61 </field>
62 </page>
63 </xpath>
64 </field>
65 </record>
66
67 <!-- Open the children of the current Inventory in a distinct list
68 to let users work in a normal window instead of a popup -->
69 <act_window id="action_view_sub_inventory"
70 name="View Sub-inventories"
71 res_model="stock.inventory"
72 src_model="stock.inventory"
73 view_mode="tree,form"
74 view_type="form"
75 domain="[('parent_id', 'child_of', active_id),('id', '!=', active_id)]"
76 context="{'full':1, 'search_default_main_inventories':0}"/>
77 </data>
78</openerp>
079
=== added directory 'stock_inventory_hierarchical/i18n'
=== added file 'stock_inventory_hierarchical/i18n/fr.po'
--- stock_inventory_hierarchical/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/i18n/fr.po 2014-06-27 09:18:58 +0000
@@ -0,0 +1,112 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * stock_inventory_hierarchical
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 6.0.4\n"
8"Report-Msgid-Bugs-To: support@openerp.com\n"
9"POT-Creation-Date: 2013-09-25 13:43+0000\n"
10"PO-Revision-Date: 2013-09-25 13:43+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: stock_inventory_hierarchical
19#: field:stock.inventory,complete_name:0
20msgid "Complete reference"
21msgstr "Réference complète"
22
23#. module: stock_inventory_hierarchical
24#: field:stock.inventory,progress_rate:0
25msgid "Done"
26msgstr "Terminé"
27
28#. module: stock_inventory_hierarchical
29#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:108
30#: constraint:stock.inventory:0
31#, python-format
32msgid "Error: You can not create recursive inventories."
33msgstr "Erreur : Vous ne pouvez pas créer d'inventaire récursifs."
34
35#. module: stock_inventory_hierarchical
36#: model:ir.model,name:stock_inventory_hierarchical.model_stock_inventory
37msgid "Gestion des stocks"
38msgstr "Gestion des stocks"
39
40#. module: stock_inventory_hierarchical
41#: field:stock.inventory,inventory_ids:0
42msgid "List of Sub-inventories"
43msgstr "Liste des sous-inventaires"
44
45#. module: stock_inventory_hierarchical
46#: view:stock.inventory:0
47msgid "Main inventories"
48msgstr "Inventaires principaux"
49
50#. module: stock_inventory_hierarchical
51#: view:stock.inventory:0
52msgid "Number of Sub-inventories"
53msgstr "Nombre de sous-inventaires"
54
55#. module: stock_inventory_hierarchical
56#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:180
57#, python-format
58msgid "One of the parent Inventories is not canceled."
59msgstr "Un des inventaires pères n'est pas annulé."
60
61#. module: stock_inventory_hierarchical
62#: constraint:stock.inventory:0
63msgid "Other Physical inventories are being conducted using the same Locations."
64msgstr "Certains emplacements sont déjà dans un autre inventaire."
65
66#. module: stock_inventory_hierarchical
67#: field:stock.inventory,parent_id:0
68msgid "Parent"
69msgstr "Parent"
70
71#. module: stock_inventory_hierarchical
72#: field:stock.inventory,parent_left:0
73msgid "Parent Left"
74msgstr "Parent gauche"
75
76#. module: stock_inventory_hierarchical
77#: field:stock.inventory,parent_right:0
78msgid "Parent Right"
79msgstr "Parent droit"
80
81#. module: stock_inventory_hierarchical
82#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:188
83#, python-format
84msgid "Some Sub-inventories are not confirmed."
85msgstr "Certains sous-inventaires ne sont pas confirmés."
86
87#. module: stock_inventory_hierarchical
88#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
89#, python-format
90msgid "Some Sub-inventories are not validated."
91msgstr "Certains sous-inventaires ne sont pas terminés."
92
93#. module: stock_inventory_hierarchical
94#: model:ir.actions.act_window,name:stock_inventory_hierarchical.action_view_sub_inventory
95#: view:stock.inventory:0
96msgid "View Sub-inventories"
97msgstr "Voir les sous-inventaires"
98
99#. module: stock_inventory_hierarchical
100#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:130
101#, python-format
102msgid "Sub-inventory: %s"
103msgstr "Sous-inventaire : %s"
104
105#. module: stock_inventory_hierarchical
106#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:180
107#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:188
108#: code:addons/stock_inventory_hierarchical/hierarchical_inventory.py:196
109#, python-format
110msgid "Warning"
111msgstr "Attention"
112
0113
=== added directory 'stock_inventory_hierarchical/images'
=== added file 'stock_inventory_hierarchical/images/inventory_form.png'
1Binary 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-27 09:18:58 +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-27 09:18:58 +0000 differ
=== added file 'stock_inventory_hierarchical/images/inventory_form_actions.png'
2Binary 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-27 09:18:58 +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-27 09:18:58 +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'
3Binary 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-27 09:18:58 +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-27 09:18:58 +0000 differ
=== added directory 'stock_inventory_hierarchical/test'
=== added file 'stock_inventory_hierarchical/test/hierarchical_inventory_test.yml'
--- stock_inventory_hierarchical/test/hierarchical_inventory_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical/test/hierarchical_inventory_test.yml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,96 @@
1-
2 In this file, i check rules about hierarchical inventories.
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 correct.
5-
6 Check if date of children are the same as the parent's.
7-
8 !python {model: stock.inventory}: |
9 parent_date = self.read(
10 cr, uid, [ref('stock_inventory_parent0')], ['date'])[0]['date']
11 child_1_date = self.read(
12 cr, uid, [ref('child_1_id')], ['date'])[0]['date']
13 assert child_1_date == parent_date, "Date are different: %s - %s" % (parent_date, child_1_date)
14
15 child_2_date = self.read(
16 cr, uid, [ref('child_2_id')], ['date'])[0]['date']
17 assert child_2_date == parent_date, "Date are different: %s - %s" % (parent_date, child_2_date)
18
19-
20 Check if children cannot be canceled if the parent was not canceled.
21 I'll try to cancel both children inventory while parent inventory having "draft" state.
22 After, i'll verify the state of each inventory.
23-
24 !python {model: stock.inventory}: |
25 from stock_inventory_hierarchical import HierarchicalInventoryException
26 try:
27 self.action_cancel_inventory(cr, uid, [ref('child_1_id')])
28 except HierarchicalInventoryException as e:
29 log("Good ! The Inventory could not be canceled: %s" % e)
30 try:
31 self.action_cancel_inventory(cr, uid, [ref('child_2_id')])
32 except HierarchicalInventoryException as e:
33 log("Good ! The Inventory could not be canceled: %s" % e)
34 child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
35 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
36 child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
37 assert child_2_state == 'draft', "Child inventory 2 have '%s' state. It should be 'draft'" % child_2_state
38
39-
40 Check if children inventory have confirm state before confirm parent inventory.
41 To check this, i'll try to confirm parent inventory when children inventory having "draft" state,
42 and i'll check if state is still 'draft'.
43-
44 !python {model: stock.inventory}: |
45 from stock_inventory_hierarchical import HierarchicalInventoryException
46 try:
47 self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
48 except HierarchicalInventoryException as e:
49 log("Good, the inventory could not be confirmed: %s", e)
50 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
51 assert parent_state == 'draft', "Parent inventory have '%s' state. It should be 'draft'" % parent_state
52
53-
54 In order, i'll confirm the children inventories, and the parent inventory after.
55-
56 !python {model: stock.inventory}: |
57 self.action_confirm(cr, uid, [ref('child_1_id')])
58 child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
59 assert child_1_state == 'confirm', "Child inventory 1 have '%s' state. It should be 'confirm'" % child_1_state
60
61 self.action_confirm(cr, uid, [ref('child_2_id')])
62 child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
63 assert child_2_state == 'confirm', "Child inventory 2 have '%s' state. It should be 'confirm'" % child_2_state
64
65 self.action_confirm(cr, uid, [ref('stock_inventory_parent0')])
66 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
67 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
68
69-
70 Check if children inventory have done state before validate parent inventory.
71 I'll try to validate parent inventory before children.
72-
73 !python {model: stock.inventory}: |
74 from stock_inventory_hierarchical import HierarchicalInventoryException
75 try:
76 self.action_done(cr, uid, [ref('stock_inventory_parent0')])
77 except HierarchicalInventoryException as e:
78 log("Good, the inventory could not be validated: %s", e)
79 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
80 assert parent_state == 'confirm', "Parent inventory have '%s' state. It should be 'confirm'" % parent_state
81
82-
83 Now, i'll validate all children inventory before validate the parent.
84-
85 !python {model: stock.inventory}: |
86 self.action_done(cr, uid, [ref('child_1_id')])
87 child_1_state = self.read(cr, uid, [ref('child_1_id')], ['state'])[0]['state']
88 assert child_1_state == 'done', "Child inventory 1 have '%s' state. It should be 'done'" % child_1_state
89
90 self.action_done(cr, uid, [ref('child_2_id')])
91 child_2_state = self.read(cr, uid, [ref('child_2_id')], ['state'])[0]['state']
92 assert child_2_state == 'done', "Child inventory 2 have '%s' state. It should be 'done'" % child_2_state
93
94 self.action_done(cr, uid, [ref('stock_inventory_parent0')])
95 parent_state = self.read(cr, uid, [ref('stock_inventory_parent0')], ['state'])[0]['state']
96 assert parent_state == 'done', "Parent inventory have '%s' state. It should be 'done'" % parent_state
097
=== added directory 'stock_inventory_hierarchical_location'
=== added file 'stock_inventory_hierarchical_location/__init__.py'
--- stock_inventory_hierarchical_location/__init__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/__init__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,22 @@
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 . import inventory_hierarchical_location
22from . import wizard
023
=== added file 'stock_inventory_hierarchical_location/__openerp__.py'
--- stock_inventory_hierarchical_location/__openerp__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/__openerp__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,49 @@
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
21{
22 "name": "Exhaustive and hierarchical Stock Inventories",
23 "version": "1.1",
24 "depends": ["stock_inventory_hierarchical", "stock_inventory_location"],
25 "auto_install": True,
26 "author": u"Numérigraphe",
27 "category": "Hidden",
28 "description": """
29Make exhaustive Inventories aware of their Sub-Inventories.
30===========================================================
31
32This module allows an inventory to contain a general Location,
33and it's sub-inventories to contain some of it's sub-Locations.
34It will prevent you from setting the Inventories and sub-Inventories
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
42 """,
43 "data": [
44 "inventory_hierarchical_location_view.xml",
45 "wizard/generate_inventory_view.xml",
46 ],
47 "test": ["tests/inventory_hierarchical_location_test.yml"],
48 "demo": ["inventory_hierarchical_location_demo.xml"],
49}
050
=== added directory 'stock_inventory_hierarchical_location/i18n'
=== added file 'stock_inventory_hierarchical_location/i18n/fr.po'
--- stock_inventory_hierarchical_location/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/i18n/fr.po 2014-06-27 09:18:58 +0000
@@ -0,0 +1,127 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * stock_inventory_hierarchical_location
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 6.0.4\n"
8"Report-Msgid-Bugs-To: support@openerp.com\n"
9"POT-Creation-Date: 2013-09-25 13:55+0000\n"
10"PO-Revision-Date: 2013-09-25 13:55+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: stock_inventory_hierarchical_location
19#: model:ir.actions.act_window,name:stock_inventory_hierarchical_location.action_view_stock_inventory_missing_location
20msgid "Confirm missing location"
21msgstr "Confirmer les emplacements manquants"
22
23#. module: stock_inventory_hierarchical_location
24#: view:stock.inventory.missing.location:0
25msgid "Confirm missing locations"
26msgstr "Confirmer les emplacements manquants"
27
28#. module: stock_inventory_hierarchical_location
29#: model:ir.model,name:stock_inventory_hierarchical_location.model_stock_inventory_uninventoried_locations
30msgid "Confirm the uninventoried Locations."
31msgstr "Confirmer les emplacements non inventoriés."
32
33#. module: stock_inventory_hierarchical_location
34#: view:stock.inventory.missing.location:0
35msgid "Do you want to continue ?"
36msgstr "Voulez-vous continuer ?"
37
38#. module: stock_inventory_hierarchical_location
39#: constraint:stock.inventory:0
40msgid "Error! You can not create recursive inventories."
41msgstr "Erreur! Vous ne pouvez pas créer un inventaire récursif."
42
43#. module: stock_inventory_hierarchical_location
44#: model:ir.model,name:stock_inventory_hierarchical_location.model_stock_inventory
45msgid "Gestion des stocks"
46msgstr "Gestion des stocks"
47
48#. module: stock_inventory_hierarchical_location
49#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:70
50#, python-format
51msgid "Location missing for inventory \"%s\"."
52msgstr "Emplacement manquants dans l'inventaire \"%s\"."
53
54#. module: stock_inventory_hierarchical_location
55#: view:stock.inventory:0
56msgid "Locations"
57msgstr "Emplacements"
58
59#. module: stock_inventory_hierarchical_location
60#: field:stock.inventory.missing.location,location_ids:0
61msgid "Missing location"
62msgstr "Emplacements manquants"
63
64#. module: stock_inventory_hierarchical_location
65#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
66#, python-format
67msgid "One of the parent inventories is not open."
68msgstr "Un des inventaire parent n'est pas ouvert."
69
70#. module: stock_inventory_hierarchical_location
71#: view:stock.inventory:0
72msgid "Open Inventory"
73msgstr "Ouvrir l'inventaire"
74
75#. module: stock_inventory_hierarchical_location
76#: constraint:stock.inventory:0
77msgid "Other Physical inventories are being conducted using the same Locations."
78msgstr "Erreur: certains emplacements sont déjà dans un autre inventaire."
79
80#. module: stock_inventory_hierarchical_location
81#: model:ir.model,name:stock_inventory_hierarchical_location.model_stock_inventory_missing_location
82msgid "Search on inventory tree for missing declared locations."
83msgstr "Recherche dans les inventaires les emplacements absents."
84
85#. module: stock_inventory_hierarchical_location
86#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:122
87#, python-format
88msgid "Some Sub-inventories are not confirmed."
89msgstr "Au moins un sous-inventaire n'est pas confirmé."
90
91#. module: stock_inventory_hierarchical_location
92#: view:stock.inventory.missing.location:0
93msgid "This is the list of missing locations."
94msgstr "Voici la liste des emplacements manquants."
95
96#. module: stock_inventory_hierarchical_location
97#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:58
98#, python-format
99msgid "This location is not declared on the parent inventory\n"
100"It cannot be added."
101msgstr "Cet emplacement n'est pas déclaré dans l'inventaire parent\n"
102"Vous ne pouvez pas l'ajouter."
103
104#. module: stock_inventory_hierarchical_location
105#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:41
106#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:70
107#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:122
108#, python-format
109msgid "Warning !"
110msgstr "Attention !"
111
112#. module: stock_inventory_hierarchical_location
113#: code:addons/stock_inventory_hierarchical_location/inventory_hierarchical_location.py:57
114#, python-format
115msgid "Warning: Wrong location"
116msgstr "Attention: mauvais emplacement"
117
118#. module: stock_inventory_hierarchical_location
119#: view:stock.inventory.missing.location:0
120msgid "_Cancel"
121msgstr "_Annuler"
122
123#. module: stock_inventory_hierarchical_location
124#: view:stock.inventory.missing.location:0
125msgid "_Confirm missing locations"
126msgstr "_Confirmer les emplacements manquants"
127
0128
=== added file 'stock_inventory_hierarchical_location/inventory_hierarchical_location.py'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,100 @@
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# 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"""
33 _inherit = 'stock.inventory'
34
35 def action_open(self, cr, uid, ids, context=None):
36 """Open only if all the parents are Open."""
37 for inventory in self.browse(cr, uid, ids, context=context):
38 while inventory.parent_id:
39 inventory = inventory.parent_id
40 if inventory.state != 'open':
41 raise HierarchicalInventoryException(
42 _('Warning'),
43 _('One of the parent inventories is not open.'))
44 return super(HierarchicalExhInventory, self).action_open(
45 cr, uid, ids, context=context)
46
47 def get_missing_locations(self, cr, uid, ids, context=None):
48 """Extend the list of inventories with their children"""
49 ids = self.search(
50 cr, uid, [('parent_id', 'child_of', ids)], context=context)
51 missing_ids = set(
52 super(HierarchicalExhInventory, self).get_missing_locations(
53 cr, uid, ids, context=context))
54 # Find the locations already included in sub-inventories
55 inventories = self.browse(cr, uid, ids, context=context)
56 subinv_location_ids = [sub.location_id.id
57 for i in inventories
58 for sub in i.inventory_ids]
59 # Extend to the children locations
60 subinv_location_ids = set(self.pool['stock.location'].search(
61 cr, uid, [
62 ('location_id', 'child_of', subinv_location_ids),
63 ('usage', '=', 'internal')], context=context))
64 return list(missing_ids - subinv_location_ids)
65
66 # TODO v8: probably only keep the state "done"
67 def confirm_missing_locations(self, cr, uid, ids, context=None):
68 """Do something only if children state are confirm or done."""
69 children_count = self.search(
70 cr, uid, [('parent_id', 'child_of', ids),
71 ('id', 'not in', ids),
72 ('state', 'not in', ['confirm', 'done'])],
73 context=context, count=True)
74 if children_count > 0:
75 raise HierarchicalInventoryException(
76 _('Warning'),
77 _('Some Sub-inventories are not confirmed.'))
78 return super(HierarchicalExhInventory,
79 self).confirm_missing_locations(
80 cr, uid, ids, context=context)
81
82 def onchange_location_id(self, cr, uid, ids, location_id, context=None):
83 """Check if location is a child of parent inventory location"""
84 loc_obj = self.pool['stock.location']
85 for inventory in self.browse(cr, uid, ids, context=context):
86 if inventory.parent_id:
87 allowed_location_ids = loc_obj.search(
88 cr, uid, [('location_id', 'child_of',
89 inventory.parent_id.location_id.id)],
90 context=context)
91 if location_id not in allowed_location_ids:
92 return {
93 'location_id': False,
94 'warning': {
95 'title': _('Warning: Wrong location'),
96 'message': _("This location is not declared on "
97 "the parent inventory\n"
98 "It cannot be added.")}
99 }
100 return {}
0101
=== added file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_demo.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data noupdate="0">
4
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 a hierarchical exhaustive inventory -->
10 <record id="parent_inventory" model="stock.inventory">
11 <field name="name">Hierarchical exhaustive inventory</field>
12 <field name="state">draft</field>
13 <field name="date">2020-04-15 00:00:00</field>
14 <field name="exhaustive">True</field>
15 <field name="location_id" model="stock.location" ref="stock.stock_location_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"/>
20 <field name="location_id" model="stock.location" ref="stock.stock_location_14"/>
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"/>
25 <field name="location_id" model="stock.location" ref="stock.stock_location_components"/>
26 </record>
27 </data>
28</openerp>
029
=== added file 'stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml'
--- stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/inventory_hierarchical_location_view.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record model="ir.ui.view" id="stock_inventory_hierarchical_location_form_view">
5 <field name="name">hierarchical.inventory.location.form</field>
6 <field name="model">stock.inventory</field>
7 <field name="inherit_id" ref="stock.view_inventory_form" />
8 <field name="arch" type="xml">
9
10 <xpath expr="/form//field[@name='exhaustive']" position="attributes">
11 <attribute name="attrs">{'readonly':[('parent_id', '!=', False)]}</attribute>
12 </xpath>
13
14 <xpath expr="/form//field[@name='location_id']" position="attributes">
15 <attribute name="on_change">onchange_location_id(location_id)</attribute>
16 </xpath>
17
18 </field>
19 </record>
20
21 <record model="ir.ui.view" id="stock_ihl_exhautive_form_view">
22 <field name="name">hierarchical.inventory.location.exhautive.form</field>
23 <field name="model">stock.inventory</field>
24 <field name="inherit_id" ref="stock_inventory_hierarchical.stock_inventory_hierarchical_form_view" />
25 <field name="arch" type="xml">
26 <xpath expr="//field[@name='inventory_ids']" position="attributes">
27 <attribute name="context">{'default_parent_id': active_id, 'default_exhaustive': exhaustive}</attribute>
28 </xpath>
29 </field>
30 </record>
31
32 <!-- Show hierarchical exhaustive inventories by default -->
33 <record id="stock.action_inventory_form" model="ir.actions.act_window">
34 <field name="context">{'full':'1', 'search_default_exhaustive':1, 'search_default_main_inventories':1}</field>
35 </record>
36 </data>
37</openerp>
038
=== 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'
1Binary 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-27 09:18:58 +0000 differ39Binary 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-27 09:18:58 +0000 differ
=== added directory 'stock_inventory_hierarchical_location/tests'
=== added file 'stock_inventory_hierarchical_location/tests/__init__.py'
--- stock_inventory_hierarchical_location/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/tests/__init__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,39 @@
1# -*- coding: utf-8 -*-
2#
3#
4# Authors: Laetitia Gangloff
5# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
6# All Rights Reserved
7#
8# WARNING: This program as such is intended to be used by professional
9# programmers who take the whole responsibility of assessing all potential
10# consequences resulting from its eventual inadequacies and bugs.
11# End users who are looking for a ready-to-use solution with commercial
12# guarantees and support are strongly advised to contact a Free Software
13# Service Company.
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU Affero General Public License as
17# published by the Free Software Foundation, either version 3 of the
18# License, or (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU Affero General Public License for more details.
24#
25# You should have received a copy of the GNU Affero General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28#
29
30import fill_inventory_test
31
32fast_suite = [
33]
34
35checks = [
36 fill_inventory_test,
37]
38
39# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
040
=== added file 'stock_inventory_hierarchical_location/tests/fill_inventory_test.py'
--- stock_inventory_hierarchical_location/tests/fill_inventory_test.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/tests/fill_inventory_test.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,118 @@
1# -*- coding: utf-8 -*-
2#
3#
4# Authors: Laetitia Gangloff
5# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
6# All Rights Reserved
7#
8# WARNING: This program as such is intended to be used by professional
9# programmers who take the whole responsibility of assessing all potential
10# consequences resulting from its eventual inadequacies and bugs.
11# End users who are looking for a ready-to-use solution with commercial
12# guarantees and support are strongly advised to contact a Free Software
13# Service Company.
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU Affero General Public License as
17# published by the Free Software Foundation, either version 3 of the
18# License, or (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU Affero General Public License for more details.
24#
25# You should have received a copy of the GNU Affero General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28#
29
30import openerp.tests.common as common
31
32DB = common.DB
33ADMIN_USER_ID = common.ADMIN_USER_ID
34
35
36class fill_inventory_test(common.TransactionCase):
37
38 def setUp(self):
39 super(fill_inventory_test, self).setUp()
40
41 def test_missing_location(self):
42 """
43 Test that when confirm a parent inventory, the child location are not in the confirmation result
44 """
45 parent_inventory_id = self.ref('stock_inventory_hierarchical_location.parent_inventory')
46 self.registry('stock.inventory').action_open(self.cr, self.uid, [parent_inventory_id])
47 # confirm shelf 1 inventory
48 inventory_id = self.ref('stock_inventory_hierarchical_location.child_2_id')
49 self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
50 missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
51 self.assertEqual(len(missing_location), 1, "1 missing location should be find, because the inventory is empty")
52 wizard_id = self.registry('stock.inventory.uninventoried.locations').create(self.cr, self.uid, {}, context={'active_ids': [inventory_id]})
53 self.registry('stock.inventory.uninventoried.locations').confirm_uninventoried_locations(self.cr, self.uid, wizard_id, context={'active_ids': [inventory_id]})
54 missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
55 self.assertEqual(len(missing_location), 0, "No missing location should be find, because the inventory is confirmed")
56 # confirm shelf 2 inventory
57 inventory_id = self.ref('stock_inventory_hierarchical_location.child_1_id')
58 self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
59 missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
60 self.assertEqual(len(missing_location), 1, "1 missing location should be fine, because the inventory is empty")
61 self.registry('stock.inventory.line').create(self.cr, self.uid, {'product_id': self.ref('product.product_product_7'),
62 'product_uom': self.ref('product.product_uom_unit'),
63 'company_id': self.ref('base.main_company'),
64 'inventory_id': inventory_id,
65 'product_qty': 18.0,
66 'location_id': self.ref('stock.stock_location_14')})
67 missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [inventory_id])
68 self.assertEqual(len(missing_location), 0, "No missing location should be find, because the inventory is filled")
69 wizard_id = self.registry('stock.inventory.uninventoried.locations').create(self.cr, self.uid, {}, context={'active_ids': [inventory_id]})
70 wizard = self.registry('stock.inventory.uninventoried.locations').browse(self.cr, self.uid, wizard_id, context={'active_ids': [inventory_id]})
71 self.assertEqual(len(wizard.location_ids), 0, "The wizard should not contain any lines but contains %s." % wizard.location_ids)
72 self.registry('stock.inventory.uninventoried.locations').confirm_uninventoried_locations(self.cr, self.uid, wizard_id, context={'active_ids': [inventory_id]})
73 # confirm parent inventory
74 missing_location = self.registry('stock.inventory').get_missing_locations(self.cr, self.uid, [parent_inventory_id])
75 self.assertEqual(len(missing_location), 1, "Only 1 missing location should be find, because there is some location in child inventory")
76
77 def test_fill_inventory(self):
78 """
79 Test that when fill a parent inventory, the child location are not in the result
80 """
81 parent_inventory_id = self.ref('stock_inventory_hierarchical_location.parent_inventory')
82 self.registry('stock.inventory').action_open(self.cr, self.uid, [parent_inventory_id])
83 # confirm shelf 1 inventory
84 inventory_id = self.ref('stock_inventory_hierarchical_location.child_2_id')
85 self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
86 wizard_id = self.registry('stock.fill.inventory').create(self.cr, self.uid, {'location_id': self.ref('stock.stock_location_components'),
87 'recursive': True,
88 'exhaustive': True,
89 'set_stock_zero': True}, context={'active_ids': [inventory_id]})
90 self.registry('stock.fill.inventory').fill_inventory(self.cr, self.uid, [wizard_id], context={'active_ids': [inventory_id]})
91 inventory_line_ids = self.registry('stock.inventory.line').search(self.cr, self.uid, [('inventory_id', '=', inventory_id)])
92 self.assertEqual(len(inventory_line_ids), 12, "12 inventory line is fount after filling inventory")
93 # confirm shelf 2 inventory
94 inventory_id = self.ref('stock_inventory_hierarchical_location.child_1_id')
95 self.registry('stock.inventory').action_open(self.cr, self.uid, [inventory_id])
96 wizard_id = self.registry('stock.fill.inventory').create(self.cr, self.uid, {'location_id': self.ref('stock.stock_location_14'),
97 'recursive': True,
98 'exhaustive': True,
99 'set_stock_zero': True}, context={'active_ids': [inventory_id]})
100 self.registry('stock.fill.inventory').fill_inventory(self.cr, self.uid, [wizard_id], context={'active_ids': [inventory_id]})
101 inventory_line_ids = self.registry('stock.inventory.line').search(self.cr, self.uid, [('inventory_id', '=', inventory_id)])
102 self.assertEqual(len(inventory_line_ids), 4, "1 inventory line is fount after filling inventory")
103 # confirm parent inventory
104 wizard_id = self.registry('stock.fill.inventory').create(self.cr, self.uid, {'location_id': self.ref('stock.stock_location_stock'),
105 'recursive': True,
106 'exhaustive': True,
107 'set_stock_zero': True}, context={'active_ids': [parent_inventory_id]})
108 try:
109 self.registry('stock.fill.inventory').fill_inventory(self.cr, self.uid, [wizard_id], context={'active_ids': [parent_inventory_id]})
110 except Exception, e:
111 self.assertEqual(e.value, 'No product in this location. Please select a location in the product form.', "The message should be ''No product in this location. Please select a location in the product form.''")
112 exception_happened = True
113 pass
114 self.assertTrue(exception_happened)
115 inventory_line_ids = self.registry('stock.inventory.line').search(self.cr, self.uid, [('inventory_id', '=', parent_inventory_id)])
116 self.assertEqual(len(inventory_line_ids), 0, "No inventory line is fount after filling inventory")
117
118# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
0119
=== added file 'stock_inventory_hierarchical_location/tests/inventory_hierarchical_location_test.yml'
--- stock_inventory_hierarchical_location/tests/inventory_hierarchical_location_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/tests/inventory_hierarchical_location_test.yml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,56 @@
1-
2 Check that the exhaustive field of parent inventory has been propagated to children.
3-
4 !python {model: stock.inventory}: |
5 exhaustive = self.read(cr, uid, [ref("child_1_id")], ['exhaustive'])[0]['exhaustive']
6 assert exhaustive, "Exhaustive field not propagated to child inventory"
7
8-
9 Check that I can't open child inventory while parent inventory is open.
10-
11 !python {model: stock.inventory}: |
12 from stock_inventory_hierarchical import HierarchicalInventoryException
13 parent_state = self.read(cr, uid, [ref("parent_inventory")], ['state'])[0]['state']
14 assert parent_state == 'draft', "Parent inventory in state '%s'. It should be 'draft'" % parent_state
15 try:
16 self.action_open(cr, uid, [ref("child_1_id")])
17 except HierarchicalInventoryException as e:
18 log("Good ! The Inventory could not be opened: %s" % e)
19 child_1_state = self.read(cr, uid, [ref("child_1_id")], ['state'])[0]['state']
20 assert child_1_state == 'draft', "Child inventory 1 have '%s' state. It should be 'draft'" % child_1_state
21
22-
23 I will check that the function get_missing_locations return some locations.
24-
25 !python {model: stock.inventory}: |
26 missing_loc_ids = self.get_missing_locations(cr, uid, [ref('parent_inventory')], context=context)
27 assert len(missing_loc_ids)==3, "get_missing_locations did not return any ID."
28
29-
30 I will fill the inventory and check that the function get_missing_locations return no locations.
31 Adding 17” LCD Monitor.
32-
33 !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
34 product_id: product.product_product_7
35 product_uom: product.product_uom_unit
36 company_id: base.main_company
37 inventory_id: child_1_id
38 product_qty: 18.0
39 location_id: stock.stock_location_14
40
41-
42 Adding USB Keyboard, QWERTY.
43-
44 !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
45 product_id: product.product_product_8
46 product_uom: product.product_uom_unit
47 company_id: base.main_company
48 inventory_id: child_2_id
49 product_qty: 5.0
50 location_id: stock.stock_location_components
51
52-
53 !python {model: stock.inventory}: |
54
55 missing_loc_ids = self.get_missing_locations(cr, uid, [ref('parent_inventory')], context=context)
56 assert set(missing_loc_ids)==set([ref('stock.stock_location_stock')]), "get_missing_locations should return only %s but returned %s" % ([ref('stock.stock_location_stock')], missing_loc_ids)
057
=== added directory 'stock_inventory_hierarchical_location/wizard'
=== added file 'stock_inventory_hierarchical_location/wizard/__init__.py'
--- stock_inventory_hierarchical_location/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/__init__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,22 @@
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 . import stock_fill_location_inventory
22from . import generate_inventory
023
=== added file 'stock_inventory_hierarchical_location/wizard/generate_inventory.py'
--- stock_inventory_hierarchical_location/wizard/generate_inventory.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/generate_inventory.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,134 @@
1# -*- coding: utf-8 -*-
2#
3#
4# Authors: Laetitia Gangloff
5# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
6# All Rights Reserved
7#
8# WARNING: This program as such is intended to be used by professional
9# programmers who take the whole responsibility of assessing all potential
10# consequences resulting from its eventual inadequacies and bugs.
11# End users who are looking for a ready-to-use solution with commercial
12# guarantees and support are strongly advised to contact a Free Software
13# Service Company.
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU Affero General Public License as
17# published by the Free Software Foundation, either version 3 of the
18# License, or (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU Affero General Public License for more details.
24#
25# You should have received a copy of the GNU Affero General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28#
29
30from openerp.osv import fields, orm
31from openerp.tools.translate import _
32
33
34class GenerateInventoryWizard(orm.TransientModel):
35 """ This wizard generate an inventory and all related sub-inventories for the specified location and level
36 Example: location = Stock / level = 1 => 1 inventory on Stock (similar to create)
37 location = Stock / level = 2 => 1 inventory on Stock, 1 sub-inventory on Shelf1, 1 sub-inventory on Shelf2
38 """
39
40 _name = "stock.generate.inventory"
41 _description = "Generate Inventory"
42
43 _columns = {
44 'prefix_inv_name': fields.char('Inventory prefix', help="Optional prefix for all created inventory"),
45 'location_id': fields.many2one('stock.location', 'Location', required=True),
46 'level': fields.integer("Level", help="number of level between inventory on location_id and sub-inventory"),
47 'only_view': fields.boolean('Only view', help="If set, only inventory on view location can be created"),
48 }
49
50 def _default_location(self, cr, uid, ids, context=None):
51 """Default stock location
52
53 @return: id of the stock location of the first warehouse of the
54 default company"""
55 location_id = False
56 company_id = self.pool['res.company']._company_default_get(
57 cr, uid, 'stock.warehouse', context=context)
58 warehouse_id = self.pool['stock.warehouse'].search(
59 cr, uid, [('company_id', '=', company_id)], limit=1)
60 if warehouse_id:
61 location_id = self.pool['stock.warehouse'].read(
62 cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
63 return location_id
64
65 _defaults = {
66 'location_id': _default_location,
67 'level': 1,
68 'only_view': True,
69 }
70
71 _sql_constraints = [
72 ('level', 'CHECK (level>0)', 'Level must be positive!'),
73 ]
74
75 def _create_subinventory(self, cr, uid, inventory_ids, prefix_inv_name, only_view, context):
76 new_inventory_ids = []
77 for inventory_id in inventory_ids:
78 location_id = self.pool['stock.inventory'].read(cr, uid, inventory_id, ['location_id'], context=context)['location_id'][0]
79 domain = [('location_id', '=', location_id)]
80 if only_view:
81 domain.append(('usage', '=', 'view'))
82 location_ids = self.pool['stock.location'].search(cr, uid, domain, context=context)
83 for location_id in location_ids:
84 location_name = self.pool['stock.location'].read(cr, uid, location_id, ['name'], context=context)['name']
85 new_inventory_ids.append(self.pool['stock.inventory'].create(cr, uid, {'name': prefix_inv_name + location_name,
86 'exhaustive': True,
87 'location_id': location_id,
88 'parent_id': inventory_id}, context=context))
89 return new_inventory_ids
90
91 def generate_inventory(self, cr, uid, ids, context=None):
92 """ Generate inventory and sub-inventories for specified location and level
93
94 @param self: The object pointer.
95 @param cr: A database cursor
96 @param uid: ID of the user currently logged in
97 @param ids: the ID or list of IDs if we want more than one
98 @param context: A standard dictionary
99 @return:
100 """
101 if context is None:
102 context = {}
103
104 if ids and len(ids):
105 ids = ids[0]
106 else:
107 return {'type': 'ir.actions.act_window_close'}
108 generate_inventory = self.browse(cr, uid, ids, context=context)
109 # create first level inventory
110 prefix_inv_name = generate_inventory.prefix_inv_name or ''
111 location_id = generate_inventory.location_id.id
112 only_view = generate_inventory.only_view
113 parent_inventory_id = self.pool['stock.inventory'].create(cr, uid, {'name': prefix_inv_name + generate_inventory.location_id.name,
114 'exhaustive': True,
115 'location_id': location_id}, context=context)
116
117 inventory_ids = [parent_inventory_id]
118 for i in range(1, generate_inventory.level):
119 inventory_ids = self._create_subinventory(cr, uid, inventory_ids, prefix_inv_name, only_view, context)
120
121 mod_obj = self.pool['ir.model.data']
122 result = mod_obj.get_object_reference(cr, uid, 'stock', 'view_inventory_form')
123 view_id = result and result[1] or False
124 return {'name': _('Inventory generated'),
125 'view_mode': 'form',
126 'view_type': 'form',
127 'res_model': 'stock.inventory',
128 'type': 'ir.actions.act_window',
129 'view_id': view_id,
130 'res_id': int(parent_inventory_id),
131 }
132
133
134# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
0135
=== added file 'stock_inventory_hierarchical_location/wizard/generate_inventory_view.xml'
--- stock_inventory_hierarchical_location/wizard/generate_inventory_view.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/generate_inventory_view.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,42 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_stock_generate_inventory" model="ir.ui.view">
5 <field name="name">Generate Inventory</field>
6 <field name="model">stock.generate.inventory</field>
7 <field name="arch" type="xml">
8 <form string="Generate Inventory" version="7.0">
9 <separator string="Generate inventory"/>
10 <group>
11 <field name="prefix_inv_name"/>
12 <field name="location_id"/>
13 <field name="only_view"/>
14 <field name="level"/>
15 </group>
16 <footer>
17 <button name="generate_inventory" string="Generate Inventory" type="object" class="oe_highlight"/>
18 or
19 <button string="Cancel" class="oe_link" special="cancel" />
20 </footer>
21 </form>
22 </field>
23 </record>
24
25 <record id="action_view_stock_generate_inventory" model="ir.actions.act_window">
26 <field name="name">Generate Inventory</field>
27 <field name="type">ir.actions.act_window</field>
28 <field name="res_model">stock.generate.inventory</field>
29 <field name="view_type">form</field>
30 <field name="view_mode">form</field>
31 <field name="view_id" ref="view_stock_generate_inventory"/>
32 <field name="target">new</field>
33 </record>
34
35 <menuitem action="action_view_stock_generate_inventory"
36 id="menu_action_stock_generate_inventory_form"
37 parent="stock.menu_stock_inventory_control"
38 sequence="20"
39 groups="stock.group_locations"/>
40
41 </data>
42</openerp>
043
=== added file 'stock_inventory_hierarchical_location/wizard/stock_fill_location_inventory.py'
--- stock_inventory_hierarchical_location/wizard/stock_fill_location_inventory.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_hierarchical_location/wizard/stock_fill_location_inventory.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,89 @@
1# -*- coding: utf-8 -*-
2#
3#
4# Authors: Laetitia Gangloff
5# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
6# All Rights Reserved
7#
8# WARNING: This program as such is intended to be used by professional
9# programmers who take the whole responsibility of assessing all potential
10# consequences resulting from its eventual inadequacies and bugs.
11# End users who are looking for a ready-to-use solution with commercial
12# guarantees and support are strongly advised to contact a Free Software
13# Service Company.
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU Affero General Public License as
17# published by the Free Software Foundation, either version 3 of the
18# License, or (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU Affero General Public License for more details.
24#
25# You should have received a copy of the GNU Affero General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28#
29
30from openerp.osv import orm, osv
31from openerp.tools.translate import _
32
33
34class FillInventoryWizard(orm.TransientModel):
35 """If inventory as sub inventories, do not fill with sub inventories location"""
36 _inherit = 'stock.fill.inventory'
37
38 def fill_inventory(self, cr, uid, ids, context=None):
39 """ To Import stock inventory according to products available in the location and not already in a sub inventory
40
41 We split fill_inventory on many fill_inventory (one for each location)
42 @param self: The object pointer.
43 @param cr: A database cursor
44 @param uid: ID of the user currently logged in
45 @param ids: the ID or list of IDs if we want more than one
46 @param context: A standard dictionary
47 @return:
48 """
49 if context is None:
50 context = {}
51
52 if ids and len(ids):
53 ids = ids[0]
54 else:
55 return {'type': 'ir.actions.act_window_close'}
56 fill_inventory = self.browse(cr, uid, ids, context=context)
57 if fill_inventory.recursive and fill_inventory.exhaustive:
58 exclude_location_ids = []
59 for i in self.pool['stock.inventory'].browse(cr, uid, context['active_ids']):
60 for sub_inventory in i.inventory_ids:
61 # exclude these location
62 exclude_location_ids.append(sub_inventory.location_id.id)
63 domain = [('location_id', 'child_of', [fill_inventory.location_id.id])]
64 if exclude_location_ids:
65 domain.append('!')
66 domain.append(('location_id', 'child_of', exclude_location_ids))
67 location_ids = self.pool['stock.location'].search(cr, uid, domain,
68 order="id",
69 context=context)
70 all_in_exception = 0
71 for location_id in location_ids:
72 try:
73 super(FillInventoryWizard, self).fill_inventory(cr, uid,
74 [self.copy(cr, uid, ids, {'location_id': location_id,
75 'recursive': False, }, context=context)],
76 context=context)
77 except osv.except_osv, e:
78 if e.value == _('No product in this location. Please select a location in the product form.'):
79 all_in_exception = all_in_exception + 1
80 pass
81 else:
82 raise e
83 if all_in_exception == len(location_ids):
84 raise orm.except_orm(_('Warning!'), _('No product in this location. Please select a location in the product form.'))
85 return {'type': 'ir.actions.act_window_close'}
86 else:
87 return super(FillInventoryWizard, self).fill_inventory(cr, uid, [ids], context=context)
88
89# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
090
=== added directory 'stock_inventory_location'
=== added file 'stock_inventory_location/__init__.py'
--- stock_inventory_location/__init__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/__init__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,24 @@
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
21import stock_inventory_location
22import wizard
23# Bring the main exception into the package's scope for easier reuse
24from .exceptions import ExhaustiveInventoryException
025
=== added file 'stock_inventory_location/__openerp__.py'
--- stock_inventory_location/__openerp__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/__openerp__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,77 @@
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
21{
22 "name": "Exhaustive Stock Inventories",
23 "version": "1.1",
24 "depends": ["stock"],
25 "author": u"Numérigraphe",
26 "category": "Warehouse Management",
27 "description": """
28Let users make exhaustive Inventories
29=====================================
30
31Standard Physical Inventories in OpenERP only contain a generic list of
32products by locations, which is well suited to partial Inventories and simple
33warehouses. When the a standard Inventory is confirmed, only the products in
34the inventory are checked. If a Product is present in the computed stock and
35not in the recorded Inventory, OpenERP will consider that it remains unchanged.
36
37But for exhaustive inventories in complex warehouses, it is not practical:
38 - you want to avoid Stock Moves to/from these Locations while counting goods
39 - you must make sure all the locations you want have been counted
40 - you must make sure no other location has been counted by mistake
41 - you want the computed stock to perfectly match the inventory when you
42 confirm it.
43
44This module lets choose whether an Physical Inventory is exhaustive or
45standard.
46For an exhaustive Inventory:
47 - in the state "Draft" you define the Location where goods must be counted.
48 - the new Inventory status "Open" lets you indicate that the list of Locations
49 is final and you are now counting the goods.
50 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.
58""",
59 "data": [
60 "wizard/stock_confirm_uninventoried_location.xml",
61 "stock_inventory_location_view.xml",
62 "wizard/stock_fill_location_inventory_view.xml",
63 ],
64 "test": [
65 "tests/inventory_standard_test.yml",
66 "tests/inventory_exhaustive_test.yml",
67 "tests/inventory_future_test.yml",
68 ],
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"]
77}
078
=== 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-27 09:18:58 +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
=== added directory 'stock_inventory_location/i18n'
=== added file 'stock_inventory_location/i18n/fr.po'
--- stock_inventory_location/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/i18n/fr.po 2014-06-27 09:18:58 +0000
@@ -0,0 +1,241 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * stock_inventory_location
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 6.0.4\n"
8"Report-Msgid-Bugs-To: support@openerp.com\n"
9"POT-Creation-Date: 2013-09-25 13:58+0000\n"
10"PO-Revision-Date: 2013-09-25 13:58+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: stock_inventory_location
19#: code:addons/stock_inventory_location/stock_inventory_location.py:232
20#: constraint:stock.move:0
21#, python-format
22msgid "A Physical Inventory is being conducted at this location"
23msgstr "Un inventaire est en cours à cet emplacement"
24
25#. module: stock_inventory_location
26#: view:stock.inventory.uninventoried.locations:0
27msgid "Cancel"
28msgstr "Annuler"
29
30#. module: stock_inventory_location
31#: view:stock.inventory:0
32msgid "Cancel Inventory"
33msgstr "Annuler l'inventaire"
34
35#. module: stock_inventory_location
36#: help:stock.inventory,exhaustive:0
37msgid "Check the box if you are conducting an exhaustive Inventory.\n"
38"Leave the box unchecked if you are conducting a standard Inventory (partial inventory for example).\n"
39"For an exhaustive Inventory:\n"
40" - the status \"Draft\" lets you define the list of Locations where goods must be counted\n"
41" - the status \"Open\" indicates that the list of Locations is definitive and you are now counting the goods. In that status, no Stock Moves can be recorded in/out of the Inventory's Locations\n"
42" - only the Inventory's Locations can be entered in the Inventory Lines\n"
43" - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory\n"
44" - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory"
45msgstr "Cochez la case si vous effectuez un inventaire exhaustif.\n"
46"Laissez la case non-dochée si vous effectuez un inventaire standard (par exmple un inventaire partiel).\n"
47"Pour les inventaires exhaustifs :\n"
48" - le statut \"Brouillon\" permet d'indiquer la liste des emplacements dont la marchandise doit être comptée\n"
49" - le statut \"Ouvert\" indiqueque la liste des emplacements est définitive, et que le comptage est en cours. Dans ce statut, aucun mouvement de stock ne peut être enregistré depuis/vers les emplacements de l'inventaire\n"
50" - seuls les emplacements de l'inventaire peuvent être saisis dans les lignes d'inventaire\n"
51" - si certains emplacements sont absent des lignes d'inventaire, OpenERP vous en avertit lors de la confirmation de l'inventaire\n"
52" - toutes les marchandises qui ne sont pas dans les lignes d'inventaire sont considérées comme perdues, et sont supprimées lors de la confirmation de l'inventaire"
53
54#. module: stock_inventory_location
55#: view:stock.inventory:0
56msgid "Confirm Inventory"
57msgstr "Confirmer l'inventaire"
58
59#. module: stock_inventory_location
60#: model:ir.model,name:stock_inventory_location.model_stock_inventory_uninventoried_locations
61msgid "Confirm the uninventoried Locations."
62msgstr "Confirmer les emplacements non inventoriés."
63
64#. module: stock_inventory_location
65#: view:stock.inventory.uninventoried.locations:0
66msgid "Confirm empty locations"
67msgstr "Confirmez les emplacements vides"
68
69#. module: stock_inventory_location
70#: model:ir.model,name:stock_inventory_location.model_stock_location
71msgid "Emplacement"
72msgstr "Emplacement"
73
74#. module: stock_inventory_location
75#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:54
76#, python-format
77msgid "Error"
78msgstr "Erreur"
79
80#. module: stock_inventory_location
81#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:75
82#, python-format
83msgid "Error: Empty location"
84msgstr "Erreur : Emplacement vide"
85
86#. module: stock_inventory_location
87#: code:addons/stock_inventory_location/stock_inventory_location.py:231
88#: code:addons/stock_inventory_location/stock_inventory_location.py:282
89#, python-format
90msgid "Error: Location locked down"
91msgstr "Erreur: emplacement en inventaire"
92
93#. module: stock_inventory_location
94#: constraint:stock.inventory:0
95msgid "Error: You can not create recursive inventories."
96msgstr "Erreur: Vous ne pouvez pas créer un inventaire récursif."
97
98#. module: stock_inventory_location
99#: constraint:stock.inventory.line:0
100msgid "Error: duplicates lines"
101msgstr "Erreur: lignes en double"
102
103#. module: stock_inventory_location
104#: view:stock.inventory:0
105#: field:stock.inventory,exhaustive:0
106msgid "Exhaustive"
107msgstr "Exhaustif"
108
109#. module: stock_inventory_location
110#: model:ir.model,name:stock_inventory_location.model_stock_inventory
111msgid "Gestion des stocks"
112msgstr "Gestion des stocks"
113
114#. module: stock_inventory_location
115#: view:stock.inventory.uninventoried.locations:0
116msgid "If you confirm the Inventory, these Locations will be considered empty and their content will be purged."
117msgstr "Si vous confirmez l'inventaire, ces emplacements seront considérés comme vides et leur contenu sera supprimé."
118
119#. module: stock_inventory_location
120#: model:ir.model,name:stock_inventory_location.model_stock_fill_inventory
121msgid "Importer un inventaire"
122msgstr "Importer un inventaire"
123
124#. module: stock_inventory_location
125#: view:stock.inventory.uninventoried.locations:0
126msgid "It could either mean that the Locations are empty, or that the Inventory is not yet complete."
127msgstr "Cela peut vouloir dire soit que l'inventaire n'est pas terminé, soit que ces emplacements sont effectivement vides."
128
129#. module: stock_inventory_location
130#: model:ir.model,name:stock_inventory_location.model_stock_inventory_line
131msgid "Ligne d'inventaire"
132msgstr "Ligne d'inventaire"
133
134#. module: stock_inventory_location
135#: code:addons/stock_inventory_location/stock_inventory_location.py:59
136#, python-format
137msgid "Location missing for this inventory."
138msgstr "Emplacement manquant pour cet inventaire."
139
140#. module: stock_inventory_location
141#: model:ir.model,name:stock_inventory_location.model_stock_move
142msgid "Mouvement de stock"
143msgstr "Mouvement de stock"
144
145#. module: stock_inventory_location
146#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:75
147#, python-format
148msgid "No location to import.\n"
149"You must add a location on the locations list."
150msgstr "Pas d'emplacement à importer.\n"
151"Vous devez ajouter un emplacement dans la liste des emplacements."
152
153#. module: stock_inventory_location
154#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:58
155#, python-format
156msgid "No locations found for the inventory."
157msgstr "Pas d'emplacement trouvé pour cet inventaire."
158
159#. module: stock_inventory_location
160#: code:addons/stock_inventory_location/stock_inventory_location.py:163
161#, python-format
162msgid "No product in this location."
163msgstr "Pas de produit à cet emplacement."
164
165#. module: stock_inventory_location
166#: code:addons/stock_inventory_location/stock_inventory_location.py:283
167#, python-format
168msgid "A Physical Inventory is being conducted at the following location(s):\n"
169"%s"
170msgstr "Les emplacements suivants sont en inventaire :\n"
171"%s"
172
173#. module: stock_inventory_location
174#: view:stock.inventory:0
175msgid "Open Inventory"
176msgstr "Ouvrir l'inventaire"
177
178#. module: stock_inventory_location
179#: constraint:stock.inventory:0
180msgid "Other Physical inventories are being conducted using the same Locations."
181msgstr "Erreur: certains emplacements sont déjà dans un autre inventaire."
182
183#. module: stock_inventory_location
184#: view:stock.inventory.uninventoried.locations:0
185msgid "Purge contents and confirm Inventory"
186msgstr "Supprimer le contenu et confirmer l'inventaire"
187
188#. module: stock_inventory_location
189#: view:stock.inventory.uninventoried.locations:0
190msgid "The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."
191msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."
192
193#. module: stock_inventory_location
194#: field:stock.inventory.uninventoried.locations,location_ids:0
195msgid "Uninventoried location"
196msgstr "Emplacements non inventoriés"
197
198#. module: stock_inventory_location
199#: code:addons/stock_inventory_location/stock_inventory_location.py:59
200#: code:addons/stock_inventory_location/stock_inventory_location.py:163
201#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:58
202#, python-format
203msgid "Warning"
204msgstr "Attention"
205
206#. module: stock_inventory_location
207#: code:addons/stock_inventory_location/stock_inventory_location.py:209
208#, python-format
209msgid "Warning: Wrong location"
210msgstr "Attention: emplacement incorrect"
211
212#. module: stock_inventory_location
213#: code:addons/stock_inventory_location/stock_inventory_location.py:210
214#, python-format
215msgid "You cannot record an Inventory Line for this Location.\n"
216"You must first add this Location to the list of affected Locations on the Inventory form."
217msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"
218"Il ne fait pas partie de la liste des emplacements à inventorier"
219
220#. module: stock_inventory_location
221#: constraint:stock.move:0
222msgid "You must assign a production lot for this product"
223msgstr "Vous devez affecter un lot de fabrication à ce produit."
224
225#. module: stock_inventory_location
226#: constraint:stock.inventory.line:0
227msgid "You must not create same inventory line Product, Location, Lot on the same date"
228msgstr "Vous ne pouvez pas créer plusieurs lignes d'inventaires pour le même produit, lot, emplacement à la même date"
229
230#. module: stock_inventory_location
231#: constraint:stock.move:0
232msgid "You try to assign a lot which is not from the same product"
233msgstr "Vous essayez d'affecter un lot qui n'est pas pour ce produit."
234
235
236#. module: stock_inventory_location
237#: code:addons/stock_inventory_location/wizard/stock_fill_location_inventory.py:54
238#, python-format
239msgid "the inventory must be in \"Open\" state."
240msgstr "l'inventaire doit etre \"Ouvert\"."
241
0242
=== added directory 'stock_inventory_location/images'
=== added file 'stock_inventory_location/images/future_inventory.png'
1Binary 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-27 09:18:58 +0000 differ243Binary 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-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/inventory_empty_locations.png'
2Binary 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-27 09:18:58 +0000 differ244Binary 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-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/inventory_form.png'
3Binary 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-27 09:18:58 +0000 differ245Binary 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-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/location_locked.png'
4Binary 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-27 09:18:58 +0000 differ246Binary 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-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/images/move_error.png'
5Binary 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-27 09:18:58 +0000 differ247Binary 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-27 09:18:58 +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'
6Binary 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-27 09:18:58 +0000 differ248Binary 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-27 09:18:58 +0000 differ
=== added file 'stock_inventory_location/stock_inventory_location.py'
--- stock_inventory_location/stock_inventory_location.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/stock_inventory_location.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,353 @@
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
21import time
22from collections import Iterable
23
24from openerp.osv import orm, fields
25from openerp.tools.translate import _
26# The next 2 imports are only needed for a feature backported from trunk-wms
27# TODOv8! remove, feature is included upstream
28from openerp.osv import osv
29from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
30from openerp import SUPERUSER_ID
31
32from .exceptions import ExhaustiveInventoryException
33
34
35class StockInventory(orm.Model):
36 """Add locations to the inventories"""
37 _inherit = 'stock.inventory'
38
39 INVENTORY_STATE_SELECTION = [
40 ('draft', 'Draft'),
41 ('open', 'Open'),
42 ('done', 'Done'),
43 ('confirm', 'Confirmed'),
44 ('cancel', 'Cancelled')
45 ]
46
47 _columns = {
48 # TODO v8: should we use "confirm" instead of adding "open"?
49 'state': fields.selection(
50 INVENTORY_STATE_SELECTION, 'State', readonly=True, select=True),
51 # TODO v8: remove this, should not be needed anymore
52 # Make the inventory lines read-only in all states except "Open",
53 # to ensure that no unwanted Location can be inserted
54 'inventory_line_id': fields.one2many(
55 'stock.inventory.line', 'inventory_id', 'Inventory lines',
56 readonly=True, states={'open': [('readonly', False)]}),
57 # TODO v8: remove this, it's backported from v8
58 'location_id': fields.many2one(
59 'stock.location', 'Inventoried Location',
60 readonly=True, states={'draft': [('readonly', False)]}),
61 'exhaustive': fields.boolean(
62 'Exhaustive', readonly=True,
63 states={'draft': [('readonly', False)]},
64 help="Check the box if you are conducting an exhaustive "
65 "Inventory.\n"
66 "Leave the box unchecked if you are conducting a standard "
67 "Inventory (partial inventory for example).\n"
68 "For an exhaustive Inventory:\n"
69 " - the status \"Draft\" lets you define the list of "
70 "Locations where goods must be counted\n"
71 " - the status \"Open\" indicates that the list of Locations "
72 "is definitive and you are now counting the goods. In that "
73 "status, no Stock Moves can be recorded in/out of the "
74 "Inventory's Locations\n"
75 " - only the Inventory's Locations can be entered in the "
76 "Inventory Lines\n"
77 " - if some of the Inventory's Locations have not been "
78 "entered in the Inventory Lines, OpenERP warns you "
79 "when you confirm the Inventory\n"
80 " - every good that is not in the Inventory Lines is "
81 "considered lost, and gets moved out of the stock when you "
82 "confirm the Inventory"),
83 }
84
85 def action_open(self, cr, uid, ids, context=None):
86 """Change the state of the inventory to 'open'"""
87 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
88
89 # TODO v8: remove this method? the feature looks already done upstream
90 def action_done(self, cr, uid, ids, context=None):
91 """
92 Don't allow to make an inventory with a date in the future.
93
94 This makes sure no stock moves will be introduced between the
95 moment you finish the inventory and the date of the Stock Moves.
96 Backported from trunk-wms:
97 revid:qdp-launchpad@openerp.com-20140317090656-o7lo22tzm8yuv3r8
98
99 @raise osv.except_osv:
100 We raise this exception on purpose instead of
101 ExhaustiveInventoryException to ensure forward-compatibility
102 with v8.
103 """
104 for inventory in self.browse(cr, uid, ids, context=None):
105 if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
106 raise osv.except_osv(
107 _('Error!'),
108 _('It\'s impossible to confirm an inventory in the '
109 'future. Please change the inventory date to proceed '
110 'further.'))
111 return super(StockInventory, self).action_done(cr, uid, ids,
112 context=context)
113
114 # TODO: remove this in v8
115 def _default_location(self, cr, uid, ids, context=None):
116 """Default stock location
117
118 @return: id of the stock location of the first warehouse of the
119 default company"""
120 location_id = False
121 company_id = self.pool['res.company']._company_default_get(
122 cr, uid, 'stock.warehouse', context=context)
123 warehouse_id = self.pool['stock.warehouse'].search(
124 cr, uid, [('company_id', '=', company_id)], limit=1)
125 if warehouse_id:
126 location_id = self.pool['stock.warehouse'].read(
127 cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
128 return location_id
129
130 _defaults = {
131 'state': 'draft',
132 'exhaustive': False,
133 # TODO: remove this in v8
134 'location_id': _default_location,
135 }
136
137 def _check_location_free_from_inventories(self, cr, uid, ids):
138 """
139 Verify that no other Inventory is being conducted on the same locations
140
141 Open Inventories are matched using the exact Location IDs,
142 excluding children.
143 """
144 # We don't get a context because we're called from a _constraint
145 for inventory in self.browse(cr, uid, ids):
146 if not inventory.exhaustive:
147 # never block standard inventories
148 continue
149 if self.search(cr, uid,
150 [('location_id', '=', inventory.location_id.id),
151 ('id', '!=', inventory.id),
152 ('date', '=', inventory.date),
153 ('exhaustive', '=', True)]):
154 # Quit as soon as one offending inventory is found
155 return False
156 return True
157
158 _constraints = [
159 (_check_location_free_from_inventories,
160 'Other Physical inventories are being conducted using the same '
161 'Locations.',
162 ['location_id', 'date', 'exhaustive'])
163 ]
164
165 def _get_locations_open_inventories(self, cr, uid, context=None):
166 """IDs of location in open exhaustive inventories, with children"""
167 inv_ids = self.search(
168 cr, uid, [('state', '=', 'open'), ('exhaustive', '=', True)],
169 context=context)
170 if not inv_ids:
171 # Early exit if no match found
172 return []
173 # List the Locations - normally all exhaustive inventories have one
174 location_ids = [inventory.location_id.id
175 for inventory in self.browse(cr, uid, inv_ids,
176 context=context)]
177 # Extend to the children Locations
178 return self.pool['stock.location'].search(
179 cr, uid,
180 [('location_id', 'child_of', set(location_ids)),
181 ('usage', '=', 'internal')],
182 context=context)
183
184 def get_missing_locations(self, cr, uid, ids, context=None):
185 """Compute the list of location_ids which are missing from the lines
186
187 Here, "missing" means the location is the inventory's location or one
188 of it's children, and the inventory does not contain any line with
189 this location."""
190 inventories = self.browse(cr, uid, ids, context=context)
191 # Find the locations of the inventories
192 inv_location_ids = [i.location_id.id for i in inventories]
193 # Extend to the children locations
194 inv_location_ids = set(self.pool['stock.location'].search(
195 cr, uid, [
196 ('location_id', 'child_of', inv_location_ids),
197 ('usage', '=', 'internal')], context=context))
198 # Find the locations already recorded in inventory lines
199 line_locations_ids = set([l.location_id.id
200 for i in inventories
201 for l in i.inventory_line_id])
202 return list(inv_location_ids - line_locations_ids)
203
204 def confirm_missing_locations(self, cr, uid, ids, context=None):
205 """Open wizard to confirm empty locations on exhaustive inventories"""
206 for inventory in self.browse(cr, uid, ids, context=context):
207 if (self.get_missing_locations(cr, uid, ids, context=context)
208 and inventory.exhaustive):
209 return {
210 'type': 'ir.actions.act_window',
211 'view_type': 'form',
212 'view_mode': 'form',
213 'res_model': 'stock.inventory.uninventoried.locations',
214 'target': 'new',
215 'context': dict(context,
216 active_ids=ids,
217 active_id=ids[0]),
218 'nodestroy': True,
219 }
220 return self.action_confirm(cr, uid, ids, context=context)
221
222
223class StockInventoryLine(orm.Model):
224 """Only allow the Inventory's Locations"""
225
226 _inherit = 'stock.inventory.line'
227
228 def _default_stock_location(self, cr, uid, context=None):
229 res = super(StockInventoryLine, self)._default_stock_location(
230 cr, uid, context=context)
231 if context is None or not context.get('location_id', False):
232 return res
233 else:
234 return context['location_id']
235
236 _defaults = {
237 'location_id': _default_stock_location
238 }
239
240 def onchange_location_id(self, cr, uid, ids, inventory_location_id,
241 exhaustive, location_id, context=None):
242 """Warn if the new is not in the location list for this inventory."""
243 if not exhaustive:
244 # Don't check if partial inventory
245 return True
246
247 # search children of location
248 if location_id not in self.pool['stock.location'].search(
249 cr, uid, [('location_id', 'child_of', inventory_location_id)],
250 context=context):
251 return {
252 'value': {'location_id': False},
253 'warning': {
254 'title': _('Warning: Wrong location'),
255 'message': _(
256 "You cannot record an Inventory Line for this "
257 "Location.\n"
258 "You must first add this Location to the list of "
259 "affected Locations on the Inventory form.")}
260 }
261 return True
262
263
264class StockLocation(orm.Model):
265 """Refuse changes during exhaustive Inventories"""
266 _inherit = 'stock.location'
267 _order = 'name'
268
269 def _check_inventory(self, cr, uid, ids, context=None):
270 """Error if an exhaustive Inventory is being conducted here"""
271 inv_obj = self.pool['stock.inventory']
272 location_inventory_open_ids = inv_obj._get_locations_open_inventories(
273 cr, SUPERUSER_ID, context=context)
274 if not isinstance(ids, Iterable):
275 ids = [ids]
276 for inv_id in ids:
277 if inv_id in location_inventory_open_ids:
278 raise ExhaustiveInventoryException(
279 _('Error: Location locked down'),
280 _('A Physical Inventory is being conducted at this '
281 'location'))
282 return True
283
284 def write(self, cr, uid, ids, vals, context=None):
285 """Refuse write if an inventory is being conducted"""
286 self._check_inventory(cr, uid, ids, context=context)
287 if not isinstance(ids, Iterable):
288 ids_to_check = [ids]
289 else:
290 # Copy the data to avoid changing 'ids', it would trigger an infinite recursion
291 ids_to_check = list(ids)
292 # If changing the parent, no inventory must conducted there either
293 if vals.get('location_id'):
294 ids_to_check.append(vals['location_id'])
295 self._check_inventory(cr, uid, ids_to_check, context=context)
296 return super(StockLocation, self).write(cr, uid, ids, vals,
297 context=context)
298
299 def create(self, cr, uid, vals, context=None):
300 """Refuse create if an inventory is being conducted at the parent"""
301 self._check_inventory(cr, uid, vals.get('location_id'),
302 context=context)
303 return super(StockLocation, self).create(cr, uid, vals,
304 context=context)
305
306 def unlink(self, cr, uid, ids, context=None):
307 """Refuse unlink if an inventory is being conducted"""
308 self._check_inventory(cr, uid, ids, context=context)
309 return super(StockLocation, self).unlink(cr, uid, ids, context=context)
310
311
312class StockMove(orm.Model):
313 """Refuse Moves during exhaustive Inventories"""
314
315 _inherit = 'stock.move'
316
317 # TODOv7: adapt this to match trunk-wms
318 def _check_open_inventory_location(self, cr, uid, ids, context=None):
319 """
320 Check that the locations are not locked by an open inventory
321
322 Standard inventories are not checked.
323
324 @raise ExhaustiveInventoryException: an error is raised if locations
325 are locked, instead of returning False, in order to pass an
326 extensive error message back to users.
327 """
328 message = ""
329 inv_obj = self.pool['stock.inventory']
330 locked_location_ids = inv_obj._get_locations_open_inventories(
331 cr, SUPERUSER_ID, context=context)
332 if not locked_location_ids:
333 # Nothing to verify
334 return True
335 for move in self.browse(cr, uid, ids, context=context):
336 if (move.location_id.usage != 'inventory'
337 and move.location_dest_id.id in locked_location_ids):
338 message += " - %s\n" % (move.location_dest_id.name)
339 if (move.location_dest_id.usage != 'inventory'
340 and move.location_id.id in locked_location_ids):
341 message += " - %s\n" % (move.location_id.name)
342 if message:
343 raise ExhaustiveInventoryException(
344 _('Error: Location locked down'),
345 _('A Physical Inventory is being conducted at the following '
346 'location(s):\n%s') % message)
347 return True
348
349 _constraints = [
350 (_check_open_inventory_location,
351 "A Physical Inventory is being conducted at this location",
352 ['location_id', 'location_dest_id']),
353 ]
0354
=== added file 'stock_inventory_location/stock_inventory_location_demo.xml'
--- stock_inventory_location/stock_inventory_location_demo.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/stock_inventory_location_demo.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
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>
9
10 <!-- Record an exhaustive inventory -->
11 <record id="inventory_exhaustive" model="stock.inventory">
12 <field name="name">Exhaustive inventory</field>
13 <field name="state">draft</field>
14 <field name="exhaustive">True</field>
15 <field name="location_id" 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>
24 </record>
25 </data>
26</openerp>
027
=== added file 'stock_inventory_location/stock_inventory_location_view.xml'
--- stock_inventory_location/stock_inventory_location_view.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/stock_inventory_location_view.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,81 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record model="ir.ui.view" id="stock_inventory_location_form_view">
6 <field name="name">stock.inventory.location.form</field>
7 <field name="model">stock.inventory</field>
8 <field name="inherit_id" ref="stock.view_inventory_form"/>
9 <field name="priority" eval="10"/>
10 <field name="arch" type="xml">
11 <!-- Show the state 'done' in the statusbar -->
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">
19 <field name="exhaustive"/>
20 <field name="location_id" groups="stock.group_locations"
21 domain="[('usage','in',('view', 'internal'))]"
22 attrs="{'invisible':[('exhaustive','!=',True)], 'required':[('exhaustive','=',True)]}"/>
23 </xpath>
24
25 <!-- Enable Fill Inventory button when state is open -->
26 <xpath expr="//button[@string='Fill Inventory']" position="attributes">
27 <attribute name="states">open</attribute>
28 <attribute name="context">{'default_exhaustive': exhaustive}</attribute>
29 </xpath>
30
31 <!-- Control locations added by user on inventory line -->
32 <xpath expr="/form//field[@name='inventory_line_id']"
33 position="attributes">
34 <attribute name="context">{'location_id': location_id}</attribute>
35 </xpath>
36 <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']"
37 position="attributes">
38 <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
39 </xpath>
40 <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']"
41 position="attributes">
42 <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
43 </xpath>
44
45 <!-- Add button to open an inventory. Call wizard on confirm inventory -->
46 <xpath expr="/form//button[@name='action_cancel_inventory']" position="before">
47 <button name="action_open" states="draft" string="Open Inventory" type="object" class="oe_highlight" groups="stock.group_stock_user"/>
48 </xpath>
49 <!-- Show the "cancel" button in state "open" -->
50 <xpath expr="/form//button[@name='action_cancel_inventory']" position="attributes">
51 <attribute name="states">draft,open,confirm,done</attribute>
52 </xpath>
53 <!-- hijack the "confirm" button -->
54 <xpath expr="/form//button[@name='action_confirm']" position="attributes">
55 <attribute name="states">open</attribute>
56 <attribute name="name">confirm_missing_locations</attribute>
57 </xpath>
58 </field>
59 </record>
60
61 <!-- Add filter for complete or partial inventory -->
62 <record model="ir.ui.view" id="view_inventory_complete_filter">
63 <field name="name">complete.inventory.filter</field>
64 <field name="model">stock.inventory</field>
65 <field name="inherit_id" ref="stock.view_inventory_filter"/>
66 <field name="arch" type="xml">
67 <xpath expr="//field[@name='name']" position="before">
68 <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]"
69 help="Only select exhaustive Inventories." />
70 <separator orientation="vertical"/>
71 </xpath>
72 </field>
73 </record>
74
75 <!-- Show exhaustive inventories by default -->
76 <record id="stock.action_inventory_form" model="ir.actions.act_window">
77 <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>
78 </record>
79
80 </data>
81</openerp>
0\ No newline at end of file82\ No newline at end of file
183
=== added directory 'stock_inventory_location/tests'
=== added file 'stock_inventory_location/tests/__init__.py'
--- stock_inventory_location/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/__init__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,39 @@
1# -*- coding: utf-8 -*-
2#
3#
4# Authors: Laetitia Gangloff
5# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
6# All Rights Reserved
7#
8# WARNING: This program as such is intended to be used by professional
9# programmers who take the whole responsibility of assessing all potential
10# consequences resulting from its eventual inadequacies and bugs.
11# End users who are looking for a ready-to-use solution with commercial
12# guarantees and support are strongly advised to contact a Free Software
13# Service Company.
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU Affero General Public License as
17# published by the Free Software Foundation, either version 3 of the
18# License, or (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU Affero General Public License for more details.
24#
25# You should have received a copy of the GNU Affero General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28#
29
30import stock_inventory_location_test
31
32fast_suite = [
33]
34
35checks = [
36 stock_inventory_location_test,
37]
38
39# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
040
=== added file 'stock_inventory_location/tests/inventory_exhaustive_test.yml'
--- stock_inventory_location/tests/inventory_exhaustive_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/inventory_exhaustive_test.yml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,128 @@
1-
2 This file will test an exhaustive inventory.
3 I will call open_action method and check if state of inventories are 'open'.
4-
5 !python {model: stock.inventory}: |
6 self.action_open(cr, uid, [ref('inventory_exhaustive')])
7 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
8 assert inventory_state == 'open', "Inventory in state '%s'. It should be 'open'" % inventory_state
9-
10 I will check that the function get_missing_locations return some locations.
11-
12 !python {model: stock.inventory}: |
13 missing_loc_ids = self.get_missing_locations(cr, uid, [ref('inventory_exhaustive')], context=context)
14 assert len(missing_loc_ids), "get_missing_locations did not return any ID."
15-
16 I create a wizard record for stock_confirm_uninventoried_location to verify that it contains the uninventoried locations
17-
18 !python {model: stock.inventory.uninventoried.locations}: |
19 ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
20 wizard_id = self.create(cr, uid, {}, context=ctx)
21 wizard = self.browse(cr, uid, wizard_id, context=ctx)
22 assert len(wizard.location_ids) > 0 , "The wizard does not contain any lines."
23-
24 I add products to exhaustive inventory.
25 Adding 17” LCD Monitor.
26-
27 !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
28 product_id: product.product_product_7
29 product_uom: product.product_uom_unit
30 company_id: base.main_company
31 inventory_id: inventory_exhaustive
32 product_qty: 18.0
33 location_id: stock.stock_location_14
34
35-
36 Adding USB Keyboard, QWERTY.
37-
38 !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
39 product_id: product.product_product_8
40 product_uom: product.product_uom_unit
41 company_id: base.main_company
42 inventory_id: inventory_exhaustive
43 product_qty: 5.0
44 location_id: stock.stock_location_14
45
46-
47 I will call the function _get_locations_open_inventories and check the result.
48 The function will return only the location_id of the exhaustive inventory.
49-
50 !python {model: stock.inventory}: |
51 locations = self._get_locations_open_inventories(cr, uid)
52 assert len(locations) == 1, "Function return wrong results: %s" % locations
53 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])
54-
55 I will call the function onchange_location_id.
56 The function must return True in the first case, and return a warning dictionnary in the second test.
57-
58 !python {model: stock.inventory.line}: |
59 res = self.onchange_location_id(cr, uid, [], ref('stock.stock_location_14'), True, ref('stock.stock_location_14'))
60 assert res == True, "Exhaustive: The function 'onchange_location_id' should return True and return '%s'" % res
61 res = self.onchange_location_id(cr, uid, [], ref('stock.stock_location_14'), True, ref('stock.stock_location_components'))
62 assert res.get('warning', False) != False , "Function 'onchange_location_id': Warning not raise. ('%s)" % res
63
64-
65 I will check that the function get_missing_locations does not return any locations.
66-
67 !python {model: stock.inventory}: |
68 missing_loc_ids = self.get_missing_locations(cr, uid, [ref('inventory_exhaustive')], context=context)
69 assert not missing_loc_ids, "get_missing_locations should not return IDs but returned %s" % missing_loc_ids
70-
71 I create a wizard record for stock_confirm_uninventoried_location and validate it
72-
73 !python {model: stock.inventory.uninventoried.locations}: |
74 ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
75 wizard_id = self.create(cr, uid, {}, context=ctx)
76 wizard = self.browse(cr, uid, wizard_id, context=ctx)
77 assert len(wizard.location_ids) == 0 , "The wizard should not contain any lines but contains %s." % wizard.location_ids
78 self.confirm_uninventoried_locations(cr, uid, wizard_id, context=ctx)
79-
80 Stock moves are not allowed in the locations during the inventory.
81-
82 !python {model: stock.move}: |
83 # TODOv8: remove this test, this is already part of trunk-wms
84 from stock_inventory_location import ExhaustiveInventoryException
85 try:
86 self.create(
87 cr,uid, {
88 'name': 'Bad move',
89 'location_id': ref('stock.stock_location_14'),
90 'location_dest_id': ref('stock.stock_location_3'),
91 'product_id': ref('product.product_product_8'),
92 'product_uom': ref('product.product_uom_unit'),
93 'date_expected': '2020-01-01 00:00:00'
94 })
95 except ExhaustiveInventoryException as e:
96 log("Good! The Stock Move was refused: %s" % e)
97-
98 I will confirm the exhaustive inventory
99-
100 !python {model: stock.inventory}: |
101 self.action_confirm(cr, uid, [ref('inventory_exhaustive')])
102 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
103 assert inventory_state == 'confirm', "Exhaustive inventory is in state '%s'. It should be 'confirm'" % inventory_state
104
105-
106 I will validate the exhaustive inventory
107-
108 !python {model: stock.inventory}: |
109 self.action_done(cr, uid, [ref('inventory_exhaustive')])
110 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
111 assert inventory_state == 'done', "Exhaustive inventory is in state '%s'. It should be 'done'" % inventory_state
112
113-
114 I will verify the quantity for each products.
115-
116 !python {model: product.product}: |
117 ctx = dict(context, location=[ref('stock.stock_location_14')])
118 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_7')], ['qty_available'], context=ctx)[0]['qty_available']
119 assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0: %s" % prod_qty_avail
120
121 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_8')], ['qty_available'], context=ctx)[0]['qty_available']
122 assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0: %s" % prod_qty_avail
123
124 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_24')], ['qty_available'], context=ctx)[0]['qty_available']
125 assert prod_qty_avail == 0.0, "The stock of KEYA was not set to 0: %s" % prod_qty_avail
126
127 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_25')], ['qty_available'], context=ctx)[0]['qty_available']
128 assert prod_qty_avail == 0.0, "The stock of MOU was not set to 0: %s" % prod_qty_avail
0129
=== added file 'stock_inventory_location/tests/inventory_future_test.yml'
--- stock_inventory_location/tests/inventory_future_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/inventory_future_test.yml 2014-06-27 09:18:58 +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
=== added file 'stock_inventory_location/tests/inventory_standard_test.yml'
--- stock_inventory_location/tests/inventory_standard_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/inventory_standard_test.yml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,100 @@
1-
2 This file will test a non exhaustive inventory.
3 I will call open_action method and check if state of inventories are 'open'.
4-
5 !python {model: stock.inventory}: |
6 self.action_open(cr, uid, [ref('inventory_standard')])
7 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
8 assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state
9
10-
11 In order, I add products to inventory.
12 Adding Azerty Keyboard.
13-
14 !record {model: stock.inventory.line, id: lines_inventory_location_kbaz}:
15 product_id: product.product_product_9
16 product_uom: product.product_uom_unit
17 company_id: base.main_company
18 inventory_id: inventory_standard
19 product_qty: 18.0
20 location_id: stock.stock_location_components
21
22-
23 Adding Optical Mouse.
24-
25 !record {model: stock.inventory.line, id: lines_inventory_location_optm}:
26 product_id: product.product_product_10
27 product_uom: product.product_uom_unit
28 company_id: base.main_company
29 inventory_id: inventory_standard
30 product_qty: 12.0
31 location_id: stock.stock_location_components
32
33-
34 Adding Multimedia Speakers.
35-
36 !record {model: stock.inventory.line, id: lines_inventory_location_grca}:
37 product_id: product.product_template_31
38 product_uom: product.product_uom_unit
39 company_id: base.main_company
40 inventory_id: inventory_standard
41 product_qty: 32.0
42 location_id: stock.stock_location_components
43
44-
45 I will call the function _get_locations_open_inventories and check the result.
46 The function will return no locations because it's not an exhaustive inventory.
47-
48 !python {model: stock.inventory}: |
49 locations = self._get_locations_open_inventories(cr, uid)
50 assert len(locations) == 0, "Function return wrong results: %s" % locations
51
52-
53 I will call the function onchange_location_id.
54 The function must to return true in all test case.
55-
56 !python {model: stock.inventory.line}: |
57 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))
58 assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
59 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))
60 assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
61
62-
63 I will call the function _check_inventory.
64 The function must return True in all test cases.
65-
66 !python {model: stock.location}: |
67 res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))
68 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
69 res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))
70 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
71
72-
73 I will confirm inventory.
74-
75 !python {model: stock.inventory}: |
76 self.action_confirm(cr, uid, [ref('inventory_standard')])
77 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
78 assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state
79
80-
81 I will validate inventory
82-
83 !python {model: stock.inventory}: |
84 self.action_done(cr, uid, [ref('inventory_standard')])
85 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
86 assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state
87
88-
89 I will verify the quantity for each products.
90-
91 !python {model: product.product}: |
92 ctx={'location': [ref('stock.stock_location_components')]}
93 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_9')], ['qty_available'], context=ctx)[0]['qty_available']
94 assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0: %s" % prod_qty_avail
95
96 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']
97 assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0: %s" % prod_qty_avail
98
99 prod_qty_avail = self.read(cr, uid, [ref('product.product_template_31')], ['qty_available'], context=ctx)[0]['qty_available']
100 assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0: %s" % prod_qty_avail
0101
=== added file 'stock_inventory_location/tests/stock_inventory_location_test.py'
--- stock_inventory_location/tests/stock_inventory_location_test.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/tests/stock_inventory_location_test.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,47 @@
1# -*- coding: utf-8 -*-
2#
3#
4# Authors: Laetitia Gangloff
5# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
6# All Rights Reserved
7#
8# WARNING: This program as such is intended to be used by professional
9# programmers who take the whole responsibility of assessing all potential
10# consequences resulting from its eventual inadequacies and bugs.
11# End users who are looking for a ready-to-use solution with commercial
12# guarantees and support are strongly advised to contact a Free Software
13# Service Company.
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU Affero General Public License as
17# published by the Free Software Foundation, either version 3 of the
18# License, or (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU Affero General Public License for more details.
24#
25# You should have received a copy of the GNU Affero General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28#
29
30import openerp.tests.common as common
31
32DB = common.DB
33ADMIN_USER_ID = common.ADMIN_USER_ID
34
35
36class stock_inventory_location_test(common.TransactionCase):
37
38 def setUp(self):
39 super(stock_inventory_location_test, self).setUp()
40
41 def test_update_parent_location(self):
42 """
43 Test the update of the parent of a location (no inventory in progress
44 """
45 self.registry('stock.location').write(self.cr, self.uid, self.ref('stock.stock_location_5'), {'location_id': self.ref('stock.stock_location_4')})
46
47# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
048
=== added directory 'stock_inventory_location/wizard'
=== added file 'stock_inventory_location/wizard/__init__.py'
--- stock_inventory_location/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/__init__.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,22 @@
1# -*- coding: utf-8 -*-1
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
21import stock_fill_location_inventory
22import stock_confirm_uninventoried_location
023
=== added file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.py'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,68 @@
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 fields, orm, osv
22
23
24class stock_inventory_uninventoried_location(orm.TransientModel):
25 _name = 'stock.inventory.uninventoried.locations'
26 _description = 'Confirm the uninventoried Locations.'
27
28 _columns = {
29 'location_ids': fields.many2many(
30 'stock.location',
31 'stock_inventory_uninventoried_location_rel',
32 'location_id', 'wizard_id',
33 'Uninventoried location', readonly=True),
34 }
35
36 def default_locations(self, cr, uid, context=None):
37 """Initialize view with the list of uninventoried locations."""
38 return self.pool['stock.inventory'].get_missing_locations(
39 cr, uid, context['active_ids'], context=context)
40
41 _defaults = {
42 'location_ids': default_locations,
43 }
44
45 def confirm_uninventoried_locations(self, cr, uid, ids, context=None):
46 """Add the missing inventory lines with qty=0 and confirm inventory"""
47 inventory_ids = context['active_ids']
48 inventory_obj = self.pool['stock.inventory']
49 wizard_obj = self.pool['stock.fill.inventory']
50 for inventory in inventory_obj.browse(cr, uid, inventory_ids,
51 context=context):
52 if inventory.exhaustive:
53 # silently run the import wizard with qty=0
54 try:
55 # on parent inventory it is possible that fill inventory fail with no product
56 wizard_id = wizard_obj.create(
57 cr, uid, {'location_id': inventory.location_id.id,
58 'recursive': True,
59 'set_stock_zero': True,
60 'exhaustive': True}, context=context)
61 wizard_obj.fill_inventory(cr, uid, [wizard_id],
62 context=context)
63 except osv.except_osv, e:
64 if e.value == _('No product in this location. Please select a location in the product form.'):
65 pass
66
67 inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)
68 return {'type': 'ir.actions.act_window_close'}
069
=== added file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml,
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. -->
7 <record id="view_confirm_uninventoried_location" model="ir.ui.view">
8 <field name="name">stock.inventory.uninventoried.locations.form</field>
9 <field name="model">stock.inventory.uninventoried.locations</field>
10 <field name="arch" type="xml">
11 <form string="Confirm empty locations" version="7.0">
12 <group colspan="4" col="1">
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."/>
17 <field name="location_ids" nolabel="1">
18 <tree>
19 <field name="name"/>
20 </tree>
21 </field>
22 <separator string=""/>
23 </group>
24 <footer>
25 <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" class="oe_highlight"/>
26 or
27 <button string="Cancel" class="oe_link" special="cancel" default_focus="1"/>
28 </footer>
29 </form>
30 </field>
31 </record>
32 </data>
33</openerp>
0\ No newline at end of file34\ No newline at end of file
135
=== added file 'stock_inventory_location/wizard/stock_fill_location_inventory.py'
--- stock_inventory_location/wizard/stock_fill_location_inventory.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-06-27 09:18:58 +0000
@@ -0,0 +1,47 @@
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 fields, orm
22
23
24class FillInventoryWizard(orm.TransientModel):
25 """Add a field that lets the client make the location read-only"""
26 _inherit = 'stock.fill.inventory'
27
28 _columns = {
29 'exhaustive': fields.boolean('Exhaustive', readonly=True)
30 }
31
32 def default_get(self, cr, uid, fields, context=None):
33 """Get 'location_id' and 'exhaustive' from the inventory"""
34 if context is None:
35 context = {}
36 inv_id = context.get('active_id')
37
38 res = super(FillInventoryWizard, self).default_get(
39 cr, uid, fields, context=context)
40 if (context.get('active_model') == 'stock.inventory'
41 and inv_id
42 and 'location_id' in fields):
43 inventory = self.pool['stock.inventory'].browse(
44 cr, uid, context['active_id'], context=context)
45 res.update({'location_id': inventory.location_id.id,
46 'exhaustive': inventory.exhaustive})
47 return res
048
=== added file 'stock_inventory_location/wizard/stock_fill_location_inventory_view.xml'
--- stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-06-27 09:18:58 +0000
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="view_stock_fill_inventory_location" model="ir.ui.view">
5 <field name="name">Import Inventory</field>
6 <field name="model">stock.fill.inventory</field>
7 <field name="inherit_id" ref="stock.view_stock_fill_inventory" />
8 <field name="arch" type="xml">
9 <xpath expr="//field[@name='location_id']" position="after">
10 <field name="exhaustive" invisible="1" />
11 </xpath>
12 <xpath expr="//field[@name='location_id']" position="attributes">
13 <attribute name="attrs">{'readonly':[('exhaustive','=',True)]}</attribute>
14 </xpath>
15 </field>
16 </record>
17 </data>
18</openerp>

Subscribers

People subscribed via source and target branches