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

Proposed by Lionel Sausin - Initiatives/Numérigraphe
Status: Merged
Merged at revision: 38
Proposed branch: lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location-ls
Merge into: lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location
Diff against target: 1532 lines (+547/-540)
14 files modified
stock_inventory_location/__init__.py (+2/-0)
stock_inventory_location/__openerp__.py (+43/-28)
stock_inventory_location/exceptions.py (+26/-0)
stock_inventory_location/i18n/fr.po (+11/-31)
stock_inventory_location/stock_inventory_location.py (+238/-189)
stock_inventory_location/stock_inventory_location_demo.xml (+18/-17)
stock_inventory_location/stock_inventory_location_view.xml (+48/-48)
stock_inventory_location/test/inventory_exhaustive_test.yml (+65/-49)
stock_inventory_location/test/inventory_future_test.yml (+13/-0)
stock_inventory_location/test/inventory_standard_test.yml (+25/-34)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.py (+22/-58)
stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml (+12/-12)
stock_inventory_location/wizard/stock_fill_location_inventory.py (+23/-73)
stock_inventory_location/wizard/stock_fill_location_inventory_view.xml (+1/-1)
To merge this branch: bzr merge lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-location-ls
Reviewer Review Type Date Requested Status
Loïc Bellier - Numérigraphe Pending
Review via email: mp+222375@code.launchpad.net
To post a comment you must log in.
55. By Numérigraphe

[FIX] don't make the inventory location mandatory when it's invisible (ie. when the inventory is not exhaustive)

56. By Numérigraphe

[FIX] when starting the fill inventory wizard, get 'location_id' and 'exhaustive' from the inventory

57. By Numérigraphe

[IMP] better placement for the new fields in stock_inventory_location

58. By Numérigraphe

[ FIX] Fix onchange_location_id (it still looked for multiple locations in the inventory header), and get the default location for inventory lines from the inventory header if it's an exhaustive one, otherwise the on_change check does not let us enter anything.

59. By Numérigraphe

[REF] improve description, remove TODOs done, remove debugging "print" statements

60. By Numérigraphe

[IMP] don't show the confirmation wizard if all the locations are actually present in inventory lines

61. By Numérigraphe

[REF] pep8

62. By Numérigraphe

[IMP] update screenshots

63. By Laetitia Gangloff (Acsone)

[IMP] make the default location company-sensitive (take the location of the 1st warehouse of the default company - courtesy of Laetitia Gangloff (Acsone)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'stock_inventory_location/__init__.py'
--- stock_inventory_location/__init__.py 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/__init__.py 2014-06-11 15:01:48 +0000
@@ -20,3 +20,5 @@
2020
21import stock_inventory_location21import stock_inventory_location
22import wizard22import wizard
23# Bring the main exception into the package's scope for easier reuse
24from .exceptions import ExhaustiveInventoryException
2325
=== modified file 'stock_inventory_location/__openerp__.py'
--- stock_inventory_location/__openerp__.py 2014-03-28 16:13:01 +0000
+++ stock_inventory_location/__openerp__.py 2014-06-11 15:01:48 +0000
@@ -18,45 +18,60 @@
18#18#
19##############################################################################19##############################################################################
2020
21
22{21{
23 "name": "Exhaustive Stock Inventories",22 "name": "Exhaustive Stock Inventories",
24 "version": "1.0",23 "version": "1.1",
25 "depends": ["stock"],24 "depends": ["stock"],
26 "author": u"Numérigraphe",25 "author": u"Numérigraphe",
27 "category": "Warehouse Management",26 "category": "Warehouse Management",
28 "description": """27 "description": """
29Let users choose between standard and exhaustive Inventories28Let users make exhaustive Inventories
30============================================================29=====================================
3130
32Standard Physical Inventories in OpenERP only contain a generic list of products by locations,31Standard Physical Inventories in OpenERP only contain a generic list of
33which is well suited to partial Inventories and simple warehouses.32products by locations, which is well suited to partial Inventories and simple
34When the a standard Inventory is confirmed, only the products in the inventory are checked.33warehouses. When the a standard Inventory is confirmed, only the products in
35If a Product is present in the computed stock and not in the recorded Inventory, OpenERP will34the inventory are checked. If a Product is present in the computed stock and
36consider that it remains unchanged.35not in the recorded Inventory, OpenERP will consider that it remains unchanged.
3736
38But for exhaustive inventories in complex warehouses, it is not practical:37But for exhaustive inventories in complex warehouses, it is not practical:
39 - you want to avoid Stock Moves in/out of these Locations while you count the goods38 - you want to avoid Stock Moves to/from these Locations while counting goods
40 - you must make sure all the locations you want have been counted39 - you must make sure all the locations you want have been counted
41 - you must make sure no other location has been counted by mistake40 - you must make sure no other location has been counted by mistake
42 - you want the computed stock to perfectly match the inventory when you confirm it.41 - you want the computed stock to perfectly match the inventory when you
42 confirm it.
4343
44This module lets choose whether an Physical Inventory is exhaustive or standard.44This module lets choose whether an Physical Inventory is exhaustive or
45For an exhaustive Inventory, in the state "Draft" you define the list of Locations where goods must be counted.45standard.
46 - a new Inventory status ("Open") lets you indicate that the list of Locations is definitive and you are now counting the goods.46For an exhaustive Inventory:
47 In that status, no Stock Moves can be recorded in/out of the Inventory's Locations.47 - in the state "Draft" you define the Location where goods must be counted.
48 - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory.48 - the new Inventory status "Open" lets you indicate that the list of Locations
49 - only the Inventory's Locations can be entered in the Inventory Lines.49 is final and you are now counting the goods.
50 - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory.avec openerp50 In that status, no Stock Moves can be recorded in/out of the Inventory's
51 Locations.
52 - if the Location or some of it's children have not been entered in the
53 Inventory Lines, OpenERP warns you when you confirm the Inventory.
54 - only the Inventory's Location or its children can be entered in the
55 Inventory Lines.
56 - every good that is not in the Inventory Lines is considered lost, and gets
57 moved out of the stock when you confirm the Inventory.
51""",58""",
52 "update_xml": [59 "data": [
53 "wizard/stock_confirm_uninventoried_location.xml",60 "wizard/stock_confirm_uninventoried_location.xml",
54 "stock_inventory_location_view.xml",61 "stock_inventory_location_view.xml",
55 "wizard/stock_fill_location_inventory_view.xml",62 "wizard/stock_fill_location_inventory_view.xml",
56 ],63 ],
57 "test": ["test/location_inventory_test.yml",64 "test": [
58 "test/location_exhaustive_inventory_test.yml",65 "test/inventory_standard_test.yml",
59 ],66 "test/inventory_exhaustive_test.yml",
60 "demo": ["stock_inventory_location_demo.xml"]67 "test/inventory_future_test.yml",
6168 ],
69 "images": [
70 "images/inventory_form.png",
71 "inventory_empty_locations.png",
72 "images/move_error.png",
73 "images/location_locked.png",
74 "images/future_inventory.png",
75 ],
76 "demo": ["stock_inventory_location_demo.xml"]
62}77}
6378
=== added file 'stock_inventory_location/exceptions.py'
--- stock_inventory_location/exceptions.py 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/exceptions.py 2014-06-11 15:01:48 +0000
@@ -0,0 +1,26 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# This module is copyright (C) 2013 Numérigraphe SARL. All Rights Reserved.
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import orm
22
23
24class ExhaustiveInventoryException(orm.except_orm):
25 """The operation is not possible for an exhaustive inventory"""
26 pass
027
=== modified file 'stock_inventory_location/i18n/fr.po'
--- stock_inventory_location/i18n/fr.po 2014-03-28 16:13:01 +0000
+++ stock_inventory_location/i18n/fr.po 2014-06-11 15:01:48 +0000
@@ -20,7 +20,7 @@
20#: constraint:stock.move:020#: constraint:stock.move:0
21#, python-format21#, python-format
22msgid "A Physical Inventory is being conducted at this location"22msgid "A Physical Inventory is being conducted at this location"
23msgstr "Un inventaire est déjà en cours à cet emplacement"23msgstr "Un inventaire est en cours à cet emplacement"
2424
25#. module: stock_inventory_location25#. module: stock_inventory_location
26#: view:stock.inventory.uninventoried.locations:026#: view:stock.inventory.uninventoried.locations:0
@@ -63,8 +63,8 @@
6363
64#. module: stock_inventory_location64#. module: stock_inventory_location
65#: view:stock.inventory.uninventoried.locations:065#: view:stock.inventory.uninventoried.locations:0
66msgid "Confirm uninventoried locations"66msgid "Confirm empty locations"
67msgstr "Confirmez les emplacements non-inventoriés"67msgstr "Confirmez les emplacements vides"
6868
69#. module: stock_inventory_location69#. module: stock_inventory_location
70#: model:ir.model,name:stock_inventory_location.model_stock_location70#: model:ir.model,name:stock_inventory_location.model_stock_location
@@ -87,7 +87,7 @@
87#: code:addons/stock_inventory_location/stock_inventory_location.py:23187#: code:addons/stock_inventory_location/stock_inventory_location.py:231
88#: code:addons/stock_inventory_location/stock_inventory_location.py:28288#: code:addons/stock_inventory_location/stock_inventory_location.py:282
89#, python-format89#, python-format
90msgid "Error: Location on inventory"90msgid "Error: Location locked down"
91msgstr "Erreur: emplacement en inventaire"91msgstr "Erreur: emplacement en inventaire"
9292
93#. module: stock_inventory_location93#. module: stock_inventory_location
@@ -138,12 +138,6 @@
138msgstr "Emplacement manquant pour cet inventaire."138msgstr "Emplacement manquant pour cet inventaire."
139139
140#. module: stock_inventory_location140#. module: stock_inventory_location
141#: view:stock.inventory:0
142#: field:stock.inventory,location_ids:0
143msgid "Locations"
144msgstr "Emplacements"
145
146#. module: stock_inventory_location
147#: model:ir.model,name:stock_inventory_location.model_stock_move141#: model:ir.model,name:stock_inventory_location.model_stock_move
148msgid "Mouvement de stock"142msgid "Mouvement de stock"
149msgstr "Mouvement de stock"143msgstr "Mouvement de stock"
@@ -171,15 +165,10 @@
171#. module: stock_inventory_location165#. module: stock_inventory_location
172#: code:addons/stock_inventory_location/stock_inventory_location.py:283166#: code:addons/stock_inventory_location/stock_inventory_location.py:283
173#, python-format167#, python-format
174msgid "One or more locations are inventoried:\n"168msgid "A Physical Inventory is being conducted at the following location(s):\n"
175"%s"169"%s"
176msgstr "Un ou plusieurs emplacements sont en inventaire :\n"170msgstr "Les emplacements suivants sont en inventaire :\n"
177"%s"171"%s"
178
179#. module: stock_inventory_location
180#: constraint:stock.move:0
181msgid "One or more lots are awaiting quality control and cannot be moved."
182msgstr "Un ou plusieurs lots sont en attente de contrôle qualité, et ne peuvent pas être déplacés."
183172
184#. module: stock_inventory_location173#. module: stock_inventory_location
185#: view:stock.inventory:0174#: view:stock.inventory:0
@@ -198,19 +187,10 @@
198187
199#. module: stock_inventory_location188#. module: stock_inventory_location
200#: view:stock.inventory.uninventoried.locations:0189#: view:stock.inventory.uninventoried.locations:0
201msgid "The following Stock Locations are part of the current Physical Inventory, but not Inventory Line has been recorded for them."190msgid "The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."
202msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."191msgstr "Les emplacements suivants font partie de l'inventaire en cours, mais aucune ligne d'inventaire les concernant n'a été enregistrée."
203192
204#. module: stock_inventory_location193#. module: stock_inventory_location
205#: help:stock.inventory,location_ids:0
206msgid "This is the list of the Stock Locations that you want to count the goods in.\n"
207"Only these Locations can be entered in the Inventory Lines.\n"
208"If some of them have not been entered in the Inventory Lines, OpenERP will warn you when you confirm the Inventory."
209msgstr "Ceci est la liste des emplacements dont vous voulez compter la marchandise.\n"
210"Seuls ces emplacements peuvent être enregistrés dans les lignes d'inventaire.\n"
211"Si certains ne sont enregistrés sur aucune ligne d'inventaire, OpenERP vous avertit lors de la confirmation de l'inventaire."
212
213#. module: stock_inventory_location
214#: field:stock.inventory.uninventoried.locations,location_ids:0194#: field:stock.inventory.uninventoried.locations,location_ids:0
215msgid "Uninventoried location"195msgid "Uninventoried location"
216msgstr "Emplacements non inventoriés"196msgstr "Emplacements non inventoriés"
@@ -232,8 +212,8 @@
232#. module: stock_inventory_location212#. module: stock_inventory_location
233#: code:addons/stock_inventory_location/stock_inventory_location.py:210213#: code:addons/stock_inventory_location/stock_inventory_location.py:210
234#, python-format214#, python-format
235msgid "You cannot add this location to inventory line.\n"215msgid "You cannot record an Inventory Line for this Location.\n"
236"You must add this location on the locations list"216"You must first add this Location to the list of affected Locations on the Inventory form."
237msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"217msgstr "Vous ne pouvez pas ajouter cet emplacement.\n"
238"Il ne fait pas partie de la liste des emplacements à inventorier"218"Il ne fait pas partie de la liste des emplacements à inventorier"
239219
240220
=== added directory 'stock_inventory_location/images'
=== added file 'stock_inventory_location/images/future_inventory.png'
241Binary files stock_inventory_location/images/future_inventory.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/future_inventory.png 2014-06-11 15:01:48 +0000 differ221Binary files stock_inventory_location/images/future_inventory.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/future_inventory.png 2014-06-11 15:01:48 +0000 differ
=== added file 'stock_inventory_location/images/inventory_empty_locations.png'
242Binary files stock_inventory_location/images/inventory_empty_locations.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/inventory_empty_locations.png 2014-06-11 15:01:48 +0000 differ222Binary files stock_inventory_location/images/inventory_empty_locations.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/inventory_empty_locations.png 2014-06-11 15:01:48 +0000 differ
=== added file 'stock_inventory_location/images/inventory_form.png'
243Binary files stock_inventory_location/images/inventory_form.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/inventory_form.png 2014-06-11 15:01:48 +0000 differ223Binary files stock_inventory_location/images/inventory_form.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/inventory_form.png 2014-06-11 15:01:48 +0000 differ
=== added file 'stock_inventory_location/images/location_locked.png'
244Binary files stock_inventory_location/images/location_locked.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/location_locked.png 2014-06-11 15:01:48 +0000 differ224Binary files stock_inventory_location/images/location_locked.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/location_locked.png 2014-06-11 15:01:48 +0000 differ
=== added file 'stock_inventory_location/images/move_error.png'
245Binary files stock_inventory_location/images/move_error.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/move_error.png 2014-06-11 15:01:48 +0000 differ225Binary files stock_inventory_location/images/move_error.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/images/move_error.png 2014-06-11 15:01:48 +0000 differ
=== added directory 'stock_inventory_location/static'
=== added directory 'stock_inventory_location/static/src'
=== added directory 'stock_inventory_location/static/src/img'
=== added file 'stock_inventory_location/static/src/img/icon.png'
246Binary files stock_inventory_location/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/static/src/img/icon.png 2014-06-11 15:01:48 +0000 differ226Binary files stock_inventory_location/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and stock_inventory_location/static/src/img/icon.png 2014-06-11 15:01:48 +0000 differ
=== modified file 'stock_inventory_location/stock_inventory_location.py'
--- stock_inventory_location/stock_inventory_location.py 2014-03-28 16:13:01 +0000
+++ stock_inventory_location/stock_inventory_location.py 2014-06-11 15:01:48 +0000
@@ -18,192 +18,205 @@
18#18#
19##############################################################################19##############################################################################
2020
21import time
21from collections import Iterable22from collections import Iterable
2223
23from openerp.osv import orm, fields24from openerp.osv import orm, fields
24from openerp.tools.translate import _25from openerp.tools.translate import _
2526# The next 2 imports are only needed for a feature backported from trunk-wms
2627# TODOv8! remove, feature is included upstream
27class EmptyLocationException(orm.except_orm):28from openerp.osv import osv
28 def __init__(self, name, value):29from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
29 self.name = name30
30 self.value = value31from .exceptions import ExhaustiveInventoryException
31 self.args = (name, value)
3232
3333
34class StockInventory(orm.Model):34class StockInventory(orm.Model):
35 """Add locations to the inventories"""35 """Add locations to the inventories"""
36 _inherit = 'stock.inventory'36 _inherit = 'stock.inventory'
37
38 INVENTORY_STATE_SELECTION = [
39 ('draft', 'Draft'),
40 ('open', 'Open'),
41 ('done', 'Done'),
42 ('confirm', 'Confirmed'),
43 ('cancel', 'Cancelled')
44 ]
45
37 _columns = {46 _columns = {
38 # XXX refactor if ever lp:~numerigraphe/openobject-addons/7.0-inventory-states is accepted upstream47 # TODO v8: should we use "confirm" instead of adding "open"?
39 'state': fields.selection((('draft', 'Draft'),48 'state': fields.selection(
40 ('open', 'Open'),49 INVENTORY_STATE_SELECTION, 'State', readonly=True, select=True),
41 ('done', 'Done'),50 # TODO v8: remove this, should not be needed anymore
42 ('confirm', 'Confirmed'),51 # Make the inventory lines read-only in all states except "Open",
43 ('cancel', 'Cancelled')), 'State', readonly=True, select=True),52 # to ensure that no unwanted Location can be inserted
44 # Make the inventory lines read-only in all states except "Open", to ensure that no unwanted Location can be inserted53 'inventory_line_id': fields.one2many(
45 'inventory_line_id': fields.one2many('stock.inventory.line', 'inventory_id', 'Inventory lines', readonly=True, states={'open': [('readonly', False)]}),54 'stock.inventory.line', 'inventory_id', 'Inventory lines',
46 'location_ids': fields.many2many('stock.location', 'stock_inventory_location_rel',55 readonly=True, states={'open': [('readonly', False)]}),
47 'location_id', 'inventory_id',56 # TODO v8: remove this, it's backported from v8
48 'Locations',57 'location_id': fields.many2one(
49 readonly=True, states={'draft': [('readonly', False)]},58 'stock.location', 'Inventoried Location',
50 help="""This is the list of the Stock Locations that you want to count the goods in.59 readonly=True, states={'draft': [('readonly', False)]}),
51Only these Locations can be entered in the Inventory Lines.60 'exhaustive': fields.boolean(
52If some of them have not been entered in the Inventory Lines, OpenERP will warn you when you confirm the Inventory."""),61 'Exhaustive', readonly=True,
53 'exhaustive': fields.boolean('Exhaustive', readonly=True, states={'draft': [('readonly', False)]},62 states={'draft': [('readonly', False)]},
54 help="""Check the box if you are conducting an exhaustive Inventory.63 help="Check the box if you are conducting an exhaustive "
55Leave the box unchecked if you are conducting a standard Inventory (partial inventory for example).64 "Inventory.\n"
56For an exhaustive Inventory:65 "Leave the box unchecked if you are conducting a standard "
57 - the status "Draft" lets you define the list of Locations where goods must be counted66 "Inventory (partial inventory for example).\n"
58 - the status "Open" indicates that the list of Locations is definitive and you are now counting the goods. In that status, no Stock Moves can be recorded in/out of the Inventory's Locations67 "For an exhaustive Inventory:\n"
59 - only the Inventory's Locations can be entered in the Inventory Lines68 " - the status \"Draft\" lets you define the list of "
60 - if some of the Inventory's Locations have not been entered in the Inventory Lines, OpenERP warns you when you confirm the Inventory69 "Locations where goods must be counted\n"
61 - every good that is not in the Inventory Lines is considered lost, and gets moved out of the stock when you confirm the Inventory"""),70 " - the status \"Open\" indicates that the list of Locations "
71 "is definitive and you are now counting the goods. In that "
72 "status, no Stock Moves can be recorded in/out of the "
73 "Inventory's Locations\n"
74 " - only the Inventory's Locations can be entered in the "
75 "Inventory Lines\n"
76 " - if some of the Inventory's Locations have not been "
77 "entered in the Inventory Lines, OpenERP warns you "
78 "when you confirm the Inventory\n"
79 " - every good that is not in the Inventory Lines is "
80 "considered lost, and gets moved out of the stock when you "
81 "confirm the Inventory"),
62 }82 }
6383
64 def action_open(self, cr, uid, ids, context=None):84 def action_open(self, cr, uid, ids, context=None):
65 """Open the inventory: open all locations, import and print inventory sheet become possible"""85 """Change the state of the inventory to 'open'"""
66 # verify if exhaustive inventory have locations before open inventory
67 for inventory in self.browse(cr, uid, ids, context=None):
68 if inventory.exhaustive:
69 if not inventory.location_ids:
70 raise orm.except_orm(_('Warning'), _('Location missing for this inventory.'))
71 return self.write(cr, uid, ids, {'state': 'open'}, context=context)86 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
7287
73 # XXX refactor if ever lp:~numerigraphe/openobject-addons/7.0-inventory-states is accepted upstream88 # TODO v8: remove this method? the feature looks already done upstream
89 def action_done(self, cr, uid, ids, context=None):
90 """
91 Don't allow to make an inventory with a date in the future.
92
93 This makes sure no stock moves will be introduced between the
94 moment you finish the inventory and the date of the Stock Moves.
95 Backported from trunk-wms:
96 revid:qdp-launchpad@openerp.com-20140317090656-o7lo22tzm8yuv3r8
97
98 @raise osv.except_osv:
99 We raise this exception on purpose instead of
100 ExhaustiveInventoryException to ensure forward-compatibility
101 with v8.
102 """
103 for inventory in self.browse(cr, uid, ids, context=None):
104 if inventory.date > time.strftime(DEFAULT_SERVER_DATETIME_FORMAT):
105 raise osv.except_osv(
106 _('Error!'),
107 _('It\'s impossible to confirm an inventory in the '
108 'future. Please change the inventory date to proceed '
109 'further.'))
110 return super(StockInventory, self).action_done(cr, uid, ids,
111 context=context)
112
113 # TODO: remove this in v8
114 def _default_location(self, cr, uid, ids, context=None):
115 """Default stock location
116
117 @return: id of the stock location of the first warehouse of the
118 default company"""
119 location_id = False
120 company_id = self.pool['res.company']._company_default_get(
121 cr, uid, 'stock.warehouse', context=context)
122 warehouse_id = self.pool['stock.warehouse'].search(
123 cr, uid, [('company_id', '=', company_id)], limit=1)
124 if warehouse_id:
125 location_id = self.pool['stock.warehouse'].read(
126 cr, uid, warehouse_id[0], ['lot_stock_id'])['lot_stock_id'][0]
127 return location_id
128
74 _defaults = {129 _defaults = {
75 'state': 'draft',130 'state': 'draft',
76 'exhaustive': False,131 'exhaustive': False,
132 # TODO: remove this in v8
133 'location_id': _default_location,
77 }134 }
78135
79 def _check_location_free_from_inventories(self, cr, uid, ids):136 def _check_location_free_from_inventories(self, cr, uid, ids):
80 """Verify that no other Inventory is being conducted on the location (exact id, not children)."""137 """
81 for inventory in self.browse(cr, uid, ids, context=None):138 Verify that no other Inventory is being conducted on the same locations
139
140 Open Inventories are matched using the exact Location IDs,
141 excluding children.
142 """
143 # We don't get a context because we're called from a _constraint
144 for inventory in self.browse(cr, uid, ids):
82 if not inventory.exhaustive:145 if not inventory.exhaustive:
83 continue # always accepted on partial inventories146 # never block standard inventories
84 location_ids = [location.id for location in inventory.location_ids]147 continue
85 inv_ids = self.search(cr, uid, [('location_ids', 'in', location_ids),148 if self.search(cr, uid,
86 ('id', '!=', inventory.id),149 [('location_id', '=', inventory.location_id.id),
87 ('date', '=', inventory.date),150 ('id', '!=', inventory.id),
88 ('exhaustive', '=', True), ])151 ('date', '=', inventory.date),
89 if inv_ids:152 ('exhaustive', '=', True)]):
153 # Quit as soon as one offending inventory is found
90 return False154 return False
91 return True155 return True
92156
93 _constraints = [(_check_location_free_from_inventories,157 _constraints = [
94 'Other Physical inventories are being conducted using the same Locations.',158 (_check_location_free_from_inventories,
95 ['location_ids', 'date', 'exhaustive'])]159 'Other Physical inventories are being conducted using the same '
160 'Locations.',
161 ['location_id', 'date', 'exhaustive'])
162 ]
96163
97 def _get_locations_open_inventories(self, cr, uid, context=None):164 def _get_locations_open_inventories(self, cr, uid, context=None):
98 """Search for locations on open inventories (exhaustive inventory only), and their children """165 """IDs of location in open exhaustive inventories, with children"""
99 open_inventories_ids = self.search(cr, uid, [('state', '=', 'open'), ], context=context)166 inv_ids = self.search(
100 location_ids = set()167 cr, uid, [('state', '=', 'open'), ('exhaustive', '=', True)],
101 for open_inventory in self.browse(cr, uid, open_inventories_ids, context=context):168 context=context)
102 location_ids.update([location.id for location in open_inventory.location_ids])169 if not inv_ids:
170 # Early exit if no match found
171 return []
172 # List the Locations - normally all exhaustive inventories have one
173 location_ids = [inventory.location_id.id
174 for inventory in self.browse(cr, uid, inv_ids,
175 context=context)]
103 # Extend to the children Locations176 # Extend to the children Locations
104 if location_ids: # XXX probably works even otherwise177 return self.pool['stock.location'].search(
105 location_ids = self.pool.get('stock.location').search(cr, uid,178 cr, uid,
106 [('location_id', 'child_of', location_ids), ('usage', '=', 'internal')],179 [('location_id', 'child_of', set(location_ids)),
107 context=context)180 ('usage', '=', 'internal')],
108 return location_ids181 context=context)
109182
110 def confirm_uninventoried_location_wizard(self, cr, uid, ids, context=None):183 def get_missing_locations(self, cr, uid, ids, context=None):
111 """ Open wizard if inventory is exhautive """184 """Compute the list of location_ids which are missing from the lines
185
186 Here, "missing" means the location is the inventory's location or one
187 of it's children, and the inventory does not contain any line with
188 this location."""
189 inventories = self.browse(cr, uid, ids, context=context)
190 # Find the locations of the inventories
191 inv_location_ids = [i.location_id.id for i in inventories]
192 # Extend to the children locations
193 inv_location_ids = set(self.pool['stock.location'].search(
194 cr, uid, [
195 ('location_id', 'child_of', inv_location_ids),
196 ('usage', '=', 'internal')], context=context))
197 # Find the locations already recorded in inventory lines
198 line_locations_ids = set([l.location_id.id
199 for l in i.inventory_line_id
200 for i in inventories])
201 return list(inv_location_ids - line_locations_ids)
202
203 def confirm_missing_locations(self, cr, uid, ids, context=None):
204 """Open wizard to confirm empty locations on exhaustive inventories"""
112 for inventory in self.browse(cr, uid, ids, context=context):205 for inventory in self.browse(cr, uid, ids, context=context):
113 if not inventory.exhaustive:206 if (self.get_missing_locations(cr, uid, ids, context=context)
114 return self.action_confirm(cr, uid, ids, context=context)207 and inventory.exhaustive):
115 else:
116 context['active_ids'] = ids
117 context['active_id'] = ids[0]
118 return {208 return {
119 'type': 'ir.actions.act_window',209 'type': 'ir.actions.act_window',
120 'view_type': 'form',210 'view_type': 'form',
121 'view_mode': 'form',211 'view_mode': 'form',
122 'res_model': 'stock.inventory.uninventoried.locations',212 'res_model': 'stock.inventory.uninventoried.locations',
123 'target': 'new',213 'target': 'new',
124 'context': context,214 'context': dict(context,
215 active_ids=ids,
216 active_id=ids[0]),
125 'nodestroy': True,217 'nodestroy': True,
126 }218 }
127219 return self.action_confirm(cr, uid, ids, context=context)
128 # XXX: get from stock.py v6.0 patch. Waiting for integration on standard by openerp ...
129 def _fill_location_lines(self, cr, uid, inventory_id, location_ids, set_stock_zero, context=None):
130 """ To Import stock inventory according to products available in the selected locations.
131 @param self: The object pointer.
132 @param cr: A database cursor
133 @param uid: ID of the user currently logged in
134 @param location_ids: the location ID or list of location IDs if we want more than one
135 @param inventory_id: the inventory ID
136 @param set_stock_zero: indicate if all the lines will be imported with zero quantity
137 @param context: A standard dictionary
138 @return:
139 """
140 inventory_line_obj = self.pool.get('stock.inventory.line')
141 move_obj = self.pool.get('stock.move')
142 uom_obj = self.pool.get('product.uom')
143 res = []
144 flag = False
145 for location in location_ids:
146 datas = {}
147 move_ids = move_obj.search(cr, uid, ['|', ('location_dest_id', '=', location),
148 ('location_id', '=', location),
149 ('state', '=', 'done')], context=context)
150 for move in move_obj.browse(cr, uid, move_ids, context=context):
151 if move.location_dest_id.id == move.location_id.id:
152 continue
153 lot_id = move.prodlot_id.id
154 prod_id = move.product_id.id
155 if move.location_dest_id.id == location:
156 qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
157 else:
158 qty = -uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
159 if datas.get((prod_id, lot_id)):
160 qty = qty + datas[(prod_id, lot_id)]['product_qty']
161
162 datas[(prod_id, lot_id)] = {'product_id': prod_id,
163 'location_id': location,
164 # Floating point sum could introduce some tiny rounding errors.
165 # The uom are the same on input and output to use api for rounding.
166 'product_qty': uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_id.uom_id),
167 'product_uom': move.product_id.uom_id.id,
168 'prod_lot_id': lot_id,
169 'default_code': move.product_id.default_code,
170 'prodlot_name': move.prodlot_id.name}
171 if datas:
172 flag = True
173 res.append(datas)
174 if not flag:
175 raise EmptyLocationException(_('Warning'), _('No product in this location.'))
176
177 stock_moves = []
178 for stock_move in res:
179 prod_lots = sorted(stock_move, key=lambda k: (stock_move[k]['default_code'], stock_move[k]['prodlot_name']))
180 for prod_lot in prod_lots:
181 stock_move_details = stock_move.get(prod_lot)
182
183 if stock_move_details['product_qty'] == 0:
184 continue # ignore product if stock equal 0
185
186 stock_move_details.update({'inventory_id': inventory_id})
187
188 if set_stock_zero:
189 stock_move_details.update({'product_qty': 0})
190
191 domain = [(field, '=', stock_move_details[field])
192 for field in ['location_id',
193 'product_id',
194 'prod_lot_id']
195 ]
196 # children_inventory_ids is present if stock_inventory_hierarchical_location module has been installed
197 inventory_ids = context.get('children_inventory_ids', False)
198 if inventory_ids:
199 domain.append(('inventory_id', 'child_of', inventory_ids))
200 else:
201 domain.append(('inventory_id', '=', stock_move_details['inventory_id']))
202
203 line_ids = inventory_line_obj.search(cr, uid, domain, context=context)
204 if not line_ids:
205 stock_moves.append(stock_move_details)
206 return stock_moves
207220
208221
209class StockInventoryLine(orm.Model):222class StockInventoryLine(orm.Model):
@@ -211,21 +224,39 @@
211224
212 _inherit = 'stock.inventory.line'225 _inherit = 'stock.inventory.line'
213226
214 def onchange_location_id(self, cr, uid, ids, location_ids, exhaustive, location_id, context=None):227 def _default_stock_location(self, cr, uid, context=None):
215 """ Raise exception if Location is not internal, or location_id not in locations list for this inventory """228 res = super(StockInventoryLine, self)._default_stock_location(
216 location_ids = location_ids[0][2]229 cr, uid, context=context)
217 if not exhaustive or not location_ids:230 if context is None or not context.get('location_id', False):
218 return True # don't check if partial inventory231 return res
232 else:
233 return context['location_id']
234
235 _defaults = {
236 'location_id': _default_stock_location
237 }
238
239 def onchange_location_id(self, cr, uid, ids, inventory_location_id,
240 exhaustive, location_id, context=None):
241 """Warn if the new is not in the location list for this inventory."""
242 if not exhaustive:
243 # Don't check if partial inventory
244 return True
219245
220 # search children of location246 # search children of location
221 location_ids = self.pool.get('stock.location').search(cr, uid,247 if location_id not in self.pool['stock.location'].search(
222 [('location_id', 'child_of', location_ids)], context=context)248 cr, uid, [('location_id', 'child_of', inventory_location_id)],
223 if location_id not in location_ids:249 context=context):
224 return {'value': {'location_id': False},250 return {
225 'warning': {'title': _('Warning: Wrong location'),251 'value': {'location_id': False},
226 'message': _("You cannot add this location to inventory line.\n"252 'warning': {
227 "You must add this location on the locations list")}253 'title': _('Warning: Wrong location'),
228 }254 'message': _(
255 "You cannot record an Inventory Line for this "
256 "Location.\n"
257 "You must first add this Location to the list of "
258 "affected Locations on the Inventory form.")}
259 }
229 return True260 return True
230261
231262
@@ -234,16 +265,20 @@
234 _inherit = 'stock.location'265 _inherit = 'stock.location'
235 _order = 'name'266 _order = 'name'
236267
268 # TODOv7: why not put this in an ORM "_constraint" instead?
237 def _check_inventory(self, cr, uid, ids, context=None):269 def _check_inventory(self, cr, uid, ids, context=None):
238 """Raise an error if an exhaustive Inventory is being conducted on this Location"""270 """Error if an exhaustive Inventory is being conducted here"""
239 inventory_obj = self.pool.get('stock.inventory')271 inv_obj = self.pool['stock.inventory']
240 location_inventory_open_ids = inventory_obj._get_locations_open_inventories(cr, uid, context=context)272 location_inventory_open_ids = inv_obj._get_locations_open_inventories(
273 cr, uid, context=context)
241 if not isinstance(ids, Iterable):274 if not isinstance(ids, Iterable):
242 ids = [ids]275 ids = [ids]
243 for id in ids:276 for inv_id in ids:
244 if id in location_inventory_open_ids:277 if inv_id in location_inventory_open_ids:
245 raise orm.except_orm(_('Error: Location on inventory'),278 raise ExhaustiveInventoryException(
246 _('A Physical Inventory is being conducted at this location'))279 _('Error: Location locked down'),
280 _('A Physical Inventory is being conducted at this '
281 'location'))
247 return True282 return True
248283
249 def write(self, cr, uid, ids, vals, context=None):284 def write(self, cr, uid, ids, vals, context=None):
@@ -252,16 +287,19 @@
252 if not isinstance(ids, Iterable):287 if not isinstance(ids, Iterable):
253 ids = [ids]288 ids = [ids]
254 ids_to_check = ids289 ids_to_check = ids
255 # If we are changing the parent, there must be no inventory must conducted there either290 # If changing the parent, no inventory must conducted there either
256 if vals.get('location_id'):291 if vals.get('location_id'):
257 ids_to_check.append(vals['location_id'])292 ids_to_check.append(vals['location_id'])
258 self._check_inventory(cr, uid, ids_to_check, context=context)293 self._check_inventory(cr, uid, ids_to_check, context=context)
259 return super(StockLocation, self).write(cr, uid, ids, vals, context=context)294 return super(StockLocation, self).write(cr, uid, ids, vals,
295 context=context)
260296
261 def create(self, cr, uid, vals, context=None):297 def create(self, cr, uid, vals, context=None):
262 """Refuse create if an inventory is being conducted at the parent"""298 """Refuse create if an inventory is being conducted at the parent"""
263 self._check_inventory(cr, uid, vals.get('location_id'), context=context)299 self._check_inventory(cr, uid, vals.get('location_id'),
264 return super(StockLocation, self).create(cr, uid, vals, context=context)300 context=context)
301 return super(StockLocation, self).create(cr, uid, vals,
302 context=context)
265303
266 def unlink(self, cr, uid, ids, context=None):304 def unlink(self, cr, uid, ids, context=None):
267 """Refuse unlink if an inventory is being conducted"""305 """Refuse unlink if an inventory is being conducted"""
@@ -274,29 +312,40 @@
274312
275 _inherit = 'stock.move'313 _inherit = 'stock.move'
276314
315 # TODOv7: adapt this to match trunk-wms
277 def _check_open_inventory_location(self, cr, uid, ids, context=None):316 def _check_open_inventory_location(self, cr, uid, ids, context=None):
278 """ Check if location is not in opened inventory317 """
279 Don't check on partial inventory (checkbox "Exhaustive" not checked).318 Check that the locations are not locked by an open inventory
319
320 Standard inventories are not checked.
321
322 @raise ExhaustiveInventoryException: an error is raised if locations
323 are locked, instead of returning False, in order to pass an
324 extensive error message back to users.
280 """325 """
281 message = ""326 message = ""
282 inventory_obj = self.pool.get('stock.inventory')327 inv_obj = self.pool['stock.inventory']
283 location_inventory_open_ids = inventory_obj._get_locations_open_inventories(cr, uid, context=context)328 locked_location_ids = inv_obj._get_locations_open_inventories(
284 if not location_inventory_open_ids:329 cr, uid, context=context)
285 return True # Nothing to verify330 if not locked_location_ids:
331 # Nothing to verify
332 return True
286 for move in self.browse(cr, uid, ids, context=context):333 for move in self.browse(cr, uid, ids, context=context):
287 if (move.location_id.usage != 'inventory'334 if (move.location_id.usage != 'inventory'
288 and move.location_dest_id.id in location_inventory_open_ids):335 and move.location_dest_id.id in locked_location_ids):
289 message += " - %s\n" % (move.location_dest_id.name)336 message += " - %s\n" % (move.location_dest_id.name)
290
291 if (move.location_dest_id.usage != 'inventory'337 if (move.location_dest_id.usage != 'inventory'
292 and move.location_id.id in location_inventory_open_ids):338 and move.location_id.id in locked_location_ids):
293 message += " - %s\n" % (move.location_id.name)339 message += " - %s\n" % (move.location_id.name)
294 if message:340 if message:
295 raise orm.except_orm(_('Error: Location on inventory'),341 raise ExhaustiveInventoryException(
296 _('One or more locations are inventoried:\n%s') % message)342 _('Error: Location locked down'),
343 _('A Physical Inventory is being conducted at the following '
344 'location(s):\n%s') % message)
297 return True345 return True
298346
299 _constraints = [347 _constraints = [
300 (_check_open_inventory_location,348 (_check_open_inventory_location,
301 "A Physical Inventory is being conducted at this location", ['location_id', 'location_dest_id']),349 "A Physical Inventory is being conducted at this location",
302 ]350 ['location_id', 'location_dest_id']),
351 ]
303352
=== modified file 'stock_inventory_location/stock_inventory_location_demo.xml'
--- stock_inventory_location/stock_inventory_location_demo.xml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/stock_inventory_location_demo.xml 2014-06-11 15:01:48 +0000
@@ -1,25 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<openerp>2<openerp>
3 <data noupdate="0">3 <data noupdate="0">
4 <!-- Record a non exhaustive inventory -->
5 <record id="inventory_standard" model="stock.inventory">
6 <field name="name">Standard inventory</field>
7 <field name="state">draft</field>
8 </record>
49
5 <!-- Record inventories we can use in the tests. -->
6 <!-- We need them in the demo data because test data is rolled back
7 whenever an exception is raised. -->
8
9 <!-- Record an non exhaustive inventory -->
10 <record id="stock_inventory_location_0" model="stock.inventory">
11 <field name="name">Location test inventory</field>
12 <field name="state">draft</field>
13 <field name="date">2020-03-01 00:00:00</field>
14 </record>
15
16 <!-- Record an exhaustive inventory -->10 <!-- Record an exhaustive inventory -->
17 <record id="stock_inventory_location_1" model="stock.inventory">11 <record id="inventory_exhaustive" model="stock.inventory">
18 <field name="name">Location test exhaustive inventory</field>12 <field name="name">Exhaustive inventory</field>
19 <field name="state">draft</field>13 <field name="state">draft</field>
20 <field name="date">2020-03-15 00:00:00</field>14 <field name="exhaustive">True</field>
21 <field name="exhaustive">True</field> 15 <field name="location_id" model="stock.location" search="[('name', '=', 'Shelf 2')]" />
22 <field name="location_ids" model="stock.location" search="[('name', '=', 'Shelf 2')]" />16 </record>
17
18 <!-- Record an inventory dated in the future -->
19 <!-- TODOv8: remove this entry, only useful for a test already in trunk-wms -->
20 <record id="inventory_future" model="stock.inventory">
21 <field name="name">Inventory in the future</field>
22 <field name="state">draft</field>
23 <field name="date">2020-03-01 00:00:00</field>
23 </record>24 </record>
24 </data>25 </data>
25</openerp>26</openerp>
2627
=== modified file 'stock_inventory_location/stock_inventory_location_view.xml'
--- stock_inventory_location/stock_inventory_location_view.xml 2014-03-28 16:13:01 +0000
+++ stock_inventory_location/stock_inventory_location_view.xml 2014-06-11 15:01:48 +0000
@@ -1,59 +1,59 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<openerp>2<openerp>
3 <data>3 <data>
4 4
5 <record model="ir.ui.view" id="stock_inventory_location_form_view">5 <record model="ir.ui.view" id="stock_inventory_location_form_view">
6 <field name="name">stock.inventory.location.form</field>6 <field name="name">stock.inventory.location.form</field>
7 <field name="model">stock.inventory</field>7 <field name="model">stock.inventory</field>
8 <field name="inherit_id" ref="stock.view_inventory_form"/>8 <field name="inherit_id" ref="stock.view_inventory_form"/>
9 <field name="priority" eval="10"/>9 <field name="priority" eval="10"/>
10 <field name="arch" type="xml">10 <field name="arch" type="xml">
11 <!-- Add type of inventory: partial or complete. -->11 <!-- Show the state 'done' in the statusbar -->
12 <xpath expr="/form//field[@name='date']" position="after">12 <xpath expr="/form//field[@name='state']" position="attributes">
13 <attribute name="statusbar_visible">draft,open,confirm</attribute>
14 </xpath>
15
16 <!-- TODO v8 place "exhaustive" next to "location_id" -->
17 <!-- Add type of inventory: standard or exhaustive. -->
18 <xpath expr="/form//field[@name='name']" position="after">
13 <field name="exhaustive"/>19 <field name="exhaustive"/>
14 </xpath>20 <field name="location_id" groups="stock.group_locations"
1521 domain="[('usage','in',('view', 'internal'))]"
16 <!-- Add the list of Locations on exhaustive inventories --> 22 attrs="{'invisible':[('exhaustive','!=',True)], 'required':[('exhaustive','=',True)]}"/>
17 <xpath expr="/form//field[@name='inventory_line_id']" position="before">23 </xpath>
18 <!-- Add the locations list for inventory -->24
19 <field colspan="1" nolabel="1" name="location_ids" domain="[('usage','in',('view', 'internal'))]" attrs="{'invisible':[('exhaustive','!=',True)]}">25 <!-- Enable Fill Inventory button when state is open -->
20 <tree string="Locations" editable="bottom">26 <xpath expr="//button[@string='Fill Inventory']" position="attributes">
21 <field name="name"/>27 <attribute name="states">open</attribute>
22 </tree>28 <attribute name="context">{'default_exhaustive': exhaustive}</attribute>
23 </field>29 </xpath>
24 </xpath> 30
2531 <!-- Control locations added by user on inventory line -->
26 <!-- Add Fill Inventory button when state is open -->32 <xpath expr="/form//field[@name='inventory_line_id']"
27 <xpath expr="//button[@string='Fill Inventory']" position="attributes">33 position="attributes">
28 <attribute name="states">draft,open,confirm</attribute>34 <attribute name="context">{'location_id': location_id}</attribute>
29 </xpath>35 </xpath>
3036 <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']"
31 <!-- Reduce the colspan of the lines to make room for the Locations-->37 position="attributes">
32 <xpath expr="/form//field[@name='inventory_line_id']" position="attributes">38 <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
33 <attribute name="colspan">3</attribute>39 </xpath>
34 </xpath> 40 <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']"
3541 position="attributes">
36 <!-- Control locations added by user on inventory line --> 42 <attribute name="on_change">onchange_location_id(parent.location_id, parent.exhaustive, location_id)</attribute>
37 <xpath expr="/form//field[@name='inventory_line_id']/tree//field[@name='location_id']" position="attributes">43 </xpath>
38 <attribute name="on_change">onchange_location_id(parent.location_ids, parent.exhaustive, location_id)</attribute>44
39 </xpath>
40 <xpath expr="/form//field[@name='inventory_line_id']/form//field[@name='location_id']" position="attributes">
41 <attribute name="on_change">onchange_location_id(parent.location_ids, parent.exhaustive, location_id)</attribute>
42 </xpath>
43
44 <!-- #XXX change the attributes instead and add the button -->
45 <!-- #XXX enlarge the group's colspan? -->
46 <!-- Add button to open an inventory. Call wizard on confirm inventory -->45 <!-- Add button to open an inventory. Call wizard on confirm inventory -->
47 <xpath expr="/form//button[@name='action_cancel_inventory']" position="replace">46 <xpath expr="/form//button[@name='action_cancel_inventory']" position="before">
48 <button name="action_cancel_inventory" states="draft,open,confirm,done" string="Cancel Inventory" type="object" icon="gtk-cancel"/>47 <button name="action_open" states="draft" string="Open Inventory" type="object" class="oe_highlight" groups="stock.group_stock_user"/>
49 <button name="action_open" states="draft" string="Open Inventory" type="object" icon="gtk-go-forward"/>48 </xpath>
50 </xpath>49 <!-- Show the "cancel" button in state "open" -->
51 50 <xpath expr="/form//button[@name='action_cancel_inventory']" position="attributes">
52 <!-- replace action_confirm button -->51 <attribute name="states">draft,open,confirm,done</attribute>
53 <!-- #XXX change the attributes instead -->52 </xpath>
54 <xpath expr="/form//button[@name='action_confirm']" position="replace">53 <!-- hijack the "confirm" button -->
55 <button name="confirm_uninventoried_location_wizard" 54 <xpath expr="/form//button[@name='action_confirm']" position="attributes">
56 string="Confirm Inventory" type="object" states="open" icon="gtk-apply"/> 55 <attribute name="states">open</attribute>
56 <attribute name="name">confirm_missing_locations</attribute>
57 </xpath>57 </xpath>
58 </field>58 </field>
59 </record>59 </record>
@@ -62,16 +62,16 @@
62 <record model="ir.ui.view" id="view_inventory_complete_filter">62 <record model="ir.ui.view" id="view_inventory_complete_filter">
63 <field name="name">complete.inventory.filter</field>63 <field name="name">complete.inventory.filter</field>
64 <field name="model">stock.inventory</field>64 <field name="model">stock.inventory</field>
65 <field name="type">search</field>
66 <field name="inherit_id" ref="stock.view_inventory_filter"/>65 <field name="inherit_id" ref="stock.view_inventory_filter"/>
67 <field name="arch" type="xml">66 <field name="arch" type="xml">
68 <xpath expr="//field[@name='name']" position="before">67 <xpath expr="//field[@name='name']" position="before">
69 <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]" help="Only select inventories that have no parents." />68 <filter icon="terp-check" name="exhaustive" string="Exhaustive" domain="[('exhaustive', '=', True)]"
69 help="Only select exhaustive Inventories." />
70 <separator orientation="vertical"/>70 <separator orientation="vertical"/>
71 </xpath>71 </xpath>
72 </field>72 </field>
73 </record>73 </record>
74 74
75 <!-- Show exhaustive inventories by default -->75 <!-- Show exhaustive inventories by default -->
76 <record id="stock.action_inventory_form" model="ir.actions.act_window">76 <record id="stock.action_inventory_form" model="ir.actions.act_window">
77 <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>77 <field name="context">{'full':'1', 'search_default_exhaustive':1}</field>
7878
=== renamed file 'stock_inventory_location/test/location_exhaustive_inventory_test.yml' => 'stock_inventory_location/test/inventory_exhaustive_test.yml'
--- stock_inventory_location/test/location_exhaustive_inventory_test.yml 2014-03-31 13:19:48 +0000
+++ stock_inventory_location/test/inventory_exhaustive_test.yml 2014-06-11 15:01:48 +0000
@@ -3,93 +3,109 @@
3 I will call open_action method and check if state of inventories are 'open'.3 I will call open_action method and check if state of inventories are 'open'.
4-4-
5 !python {model: stock.inventory}: |5 !python {model: stock.inventory}: |
6 from osv import orm, osv 6 self.action_open(cr, uid, [ref('inventory_exhaustive')])
7 self.action_open(cr, uid, [ref('stock_inventory_location_1')]) 7 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
8 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']8 assert inventory_state == 'open', "Inventory in state '%s'. It should be 'open'" % inventory_state
9 assert inventory_state == 'open', "Parent inventory have '%s' state. It should be 'open'" % inventory_state9-
1010 I create a wizard record for stock_confirm_uninventoried_location to verify that it contains the uninventoried locations
11-11-
12 In order, i add products to exhaustive inventory.12 !python {model: stock.inventory.uninventoried.locations}: |
13 ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
14 wizard_id = self.create(cr, uid, {}, context=ctx)
15 wizard = self.browse(cr, uid, wizard_id, context=ctx)
16 assert len(wizard.location_ids) > 0 , "The wizard does not contain any lines."
17-
18 I add products to exhaustive inventory.
13 Adding 17” LCD Monitor.19 Adding 17” LCD Monitor.
14- 20-
15 !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:21 !record {model: stock.inventory.line, id: lines_inventory_location_pc1}:
16 product_id: product.product_product_722 product_id: product.product_product_7
17 product_uom: product.product_uom_unit23 product_uom: product.product_uom_unit
18 company_id: base.main_company24 company_id: base.main_company
19 inventory_id: stock_inventory_location_1 25 inventory_id: inventory_exhaustive
20 product_qty: 18.026 product_qty: 18.0
21 location_id: stock.stock_location_1427 location_id: stock.stock_location_14
2228
23-29-
24 Adding USB Keyboard, QWERTY.30 Adding USB Keyboard, QWERTY.
25- 31-
26 !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:32 !record {model: stock.inventory.line, id: lines_inventory_location_pc3}:
27 product_id: product.product_product_833 product_id: product.product_product_8
28 product_uom: product.product_uom_unit34 product_uom: product.product_uom_unit
29 company_id: base.main_company35 company_id: base.main_company
30 inventory_id: stock_inventory_location_1 36 inventory_id: inventory_exhaustive
31 product_qty: 5.037 product_qty: 5.0
32 location_id: stock.stock_location_14 38 location_id: stock.stock_location_14
3339
34-40-
35 I will call the function _get_locations_open_inventories and check the result.41 I will call the function _get_locations_open_inventories and check the result.
36 The function will return only the location_ids of the exhaustive inventory.42 The function will return only the location_id of the exhaustive inventory.
37-43-
38 !python {model: stock.inventory}: |44 !python {model: stock.inventory}: |
39 from osv import orm, osv45 locations = self._get_locations_open_inventories(cr, uid)
40 locations = self._get_locations_open_inventories(cr, uid)
41 assert len(locations) == 1, "Function return wrong results: %s" % locations46 assert len(locations) == 1, "Function return wrong results: %s" % locations
42 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]) 47 assert locations[0] == ref('stock.stock_location_14'), "Function '_get_locations_open_inventories' return wrong location_id. Should be '%s': '%s'" % (stock.stock_location_14, locations[0])
43 48-
44-
45 I will call the function onchange_location_id.49 I will call the function onchange_location_id.
46 The function must to return true in the first case, and return a warning dictionnary in the second test. 50 The function must return True in the first case, and return a warning dictionnary in the second test.
47-51-
48 !python {model: stock.inventory.line}: |52 !python {model: stock.inventory.line}: |
49 from osv import orm, osv
50 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_14'))53 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_14'))
51 assert res == True, "Exhaustive: The function 'onchange_location_id' should return True and return '%s'" % res 54 assert res == True, "Exhaustive: The function 'onchange_location_id' should return True and return '%s'" % res
52 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_components'))55 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_14')])], True, ref('stock.stock_location_components'))
53 assert res.get('warning', False) != False , "Function 'onchange_location_id': Warning not raise. ('%s)" % res 56 assert res.get('warning', False) != False , "Function 'onchange_location_id': Warning not raise. ('%s)" % res
5457
55-58-
56 I will call _fill_location_lines to simulate confirmation for stock_confirm_uninventoried_location,59 I create a wizard record for stock_confirm_uninventoried_location and validate it
57 and create stock_moves60-
58-61 !python {model: stock.inventory.uninventoried.locations}: |
59 !python {model: stock.inventory}: |62 ctx = dict(context, active_ids=[ref('inventory_exhaustive')])
60 from osv import orm, osv63 wizard_id = self.create(cr, uid, {}, context=ctx)
61 ctx={'location': [ref('stock.stock_location_14')]} 64 wizard = self.browse(cr, uid, wizard_id, context=ctx)
62 lines = self._fill_location_lines(cr, uid, ref('stock_inventory_location_1'), [ref('stock.stock_location_14')], True, context=ctx)65 assert len(wizard.location_ids) == 0 , "The wizard should not contain any lines but contains %s." % wizard.location_ids
63 for line in lines:66 self.confirm_uninventoried_locations(cr, uid, wizard_id, context=ctx)
64 self.pool.get('stock.inventory.line').create(cr, uid, line, context=context) 67-
65 68 Stock moves are not allowed in the locations during the inventory.
66-69-
67 I will confirm exhaustive inventory 70 !python {model: stock.move}: |
68- 71 # TODOv8: remove this test, this is already part of trunk-wms
69 !python {model: stock.inventory}: |72 from stock_inventory_location import ExhaustiveInventoryException
70 from osv import orm, osv 73 try:
71 self.action_confirm(cr, uid, [ref('stock_inventory_location_1')]) 74 self.create(
72 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']75 cr,uid, {
76 'name': 'Bad move',
77 'location_id': ref('stock.stock_location_14'),
78 'location_dest_id': ref('stock.stock_location_3'),
79 'product_id': ref('product.product_product_8'),
80 'product_uom': ref('product.product_uom_unit'),
81 'date_expected': '2020-01-01 00:00:00'
82 })
83 except ExhaustiveInventoryException as e:
84 log("Good! The Stock Move was refused: %s" % e)
85-
86 I will confirm the exhaustive inventory
87-
88 !python {model: stock.inventory}: |
89 self.action_confirm(cr, uid, [ref('inventory_exhaustive')])
90 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
73 assert inventory_state == 'confirm', "Exhaustive inventory have '%s' state. It should be 'confirm'" % inventory_state91 assert inventory_state == 'confirm', "Exhaustive inventory have '%s' state. It should be 'confirm'" % inventory_state
74 92
75-93-
76 I will validate exhaustive inventory94 I will validate the exhaustive inventory
77-95-
78 !python {model: stock.inventory}: |96 !python {model: stock.inventory}: |
79 from osv import orm, osv 97 self.action_done(cr, uid, [ref('inventory_exhaustive')])
80 self.action_done(cr, uid, [ref('stock_inventory_location_1')])98 inventory_state = self.read(cr, uid, [ref('inventory_exhaustive')], ['state'])[0]['state']
81 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_1')], ['state'])[0]['state']
82 assert inventory_state == 'done', "Exhaustive inventory have '%s' state. It should be 'done'" % inventory_state99 assert inventory_state == 'done', "Exhaustive inventory have '%s' state. It should be 'done'" % inventory_state
83 100
84-101-
85 I will verify the quantity for each products.102 I will verify the quantity for each products.
86- 103-
87 !python {model: product.product}: |104 !python {model: product.product}: |
88 from osv import orm, osv 105 ctx = dict(context, location=[ref('stock.stock_location_14')])
89 ctx={'location': [ref('stock.stock_location_14')], 'to_date': '2020-12-31 23:59:59'}
90 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_7')], ['qty_available'], context=ctx)[0]['qty_available']106 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_7')], ['qty_available'], context=ctx)[0]['qty_available']
91 assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0: %s" % prod_qty_avail107 assert prod_qty_avail == 18.0, "The stock of PC1 was not set to 18.0: %s" % prod_qty_avail
92 108
93 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_8')], ['qty_available'], context=ctx)[0]['qty_available']109 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_8')], ['qty_available'], context=ctx)[0]['qty_available']
94 assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0: %s" % prod_qty_avail110 assert prod_qty_avail == 5.0, "The stock of PC3 was not set to 5.0: %s" % prod_qty_avail
95111
96112
=== added file 'stock_inventory_location/test/inventory_future_test.yml'
--- stock_inventory_location/test/inventory_future_test.yml 1970-01-01 00:00:00 +0000
+++ stock_inventory_location/test/inventory_future_test.yml 2014-06-11 15:01:48 +0000
@@ -0,0 +1,13 @@
1-
2 Check that an inventory with a date in the future cannot be opened.
3-
4 !python {model: stock.inventory}: |
5 # TODO v8: maybe this is already part of the new WMS
6 from osv.osv import except_osv
7 self.action_open(cr, uid, [ref('inventory_future')])
8 try:
9 self.action_done(cr, uid, [ref('inventory_future')])
10 except except_osv as e:
11 log("Good! The Inventory could not be opened: %s" % e)
12 inventory_state = self.read(cr, uid, [ref('inventory_future')], ['state'])[0]['state']
13 assert inventory_state != 'done', "Future inventory is done."
014
=== renamed file 'stock_inventory_location/test/location_inventory_test.yml' => 'stock_inventory_location/test/inventory_standard_test.yml'
--- stock_inventory_location/test/location_inventory_test.yml 2014-03-31 13:19:48 +0000
+++ stock_inventory_location/test/inventory_standard_test.yml 2014-06-11 15:01:48 +0000
@@ -3,11 +3,10 @@
3 I will call open_action method and check if state of inventories are 'open'.3 I will call open_action method and check if state of inventories are 'open'.
4-4-
5 !python {model: stock.inventory}: |5 !python {model: stock.inventory}: |
6 from osv import orm, osv 6 self.action_open(cr, uid, [ref('inventory_standard')])
7 self.action_open(cr, uid, [ref('stock_inventory_location_0')]) 7 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
8 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']8 assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state
9 assert inventory_state == 'open', "Partial inventory have '%s' state. It should be 'open'" % inventory_state 9
10
11-10-
12 In order, I add products to inventory.11 In order, I add products to inventory.
13 Adding Azerty Keyboard.12 Adding Azerty Keyboard.
@@ -16,7 +15,7 @@
16 product_id: product.product_product_915 product_id: product.product_product_9
17 product_uom: product.product_uom_unit16 product_uom: product.product_uom_unit
18 company_id: base.main_company17 company_id: base.main_company
19 inventory_id: stock_inventory_location_0 18 inventory_id: inventory_standard
20 product_qty: 18.019 product_qty: 18.0
21 location_id: stock.stock_location_components20 location_id: stock.stock_location_components
2221
@@ -27,7 +26,7 @@
27 product_id: product.product_product_1026 product_id: product.product_product_10
28 product_uom: product.product_uom_unit27 product_uom: product.product_uom_unit
29 company_id: base.main_company28 company_id: base.main_company
30 inventory_id: stock_inventory_location_0 29 inventory_id: inventory_standard
31 product_qty: 12.030 product_qty: 12.0
32 location_id: stock.stock_location_components31 location_id: stock.stock_location_components
3332
@@ -38,26 +37,23 @@
38 product_id: product.product_template_3137 product_id: product.product_template_31
39 product_uom: product.product_uom_unit38 product_uom: product.product_uom_unit
40 company_id: base.main_company39 company_id: base.main_company
41 inventory_id: stock_inventory_location_0 40 inventory_id: inventory_standard
42 product_qty: 32.041 product_qty: 32.0
43 location_id: stock.stock_location_components42 location_id: stock.stock_location_components
4443
45-44-
46 I will call the function _get_locations_open_inventories and check the result.45 I will call the function _get_locations_open_inventories and check the result.
47 The function will return no locations.46 The function will return no locations because it's not an exhaustive inventory.
48-47-
49 !python {model: stock.inventory}: |48 !python {model: stock.inventory}: |
50 from osv import orm, osv49 locations = self._get_locations_open_inventories(cr, uid)
51 locations = self._get_locations_open_inventories(cr, uid)
52 assert len(locations) == 0, "Function return wrong results: %s" % locations50 assert len(locations) == 0, "Function return wrong results: %s" % locations
53 51
54-52-
55 I will call the function onchange_location_id.53 I will call the function onchange_location_id.
56 The function must to return true in all test case. 54 The function must to return true in all test case.
57-55-
58 !python {model: stock.inventory.line}: |56 !python {model: stock.inventory.line}: |
59 from osv import orm, osv
60 pass
61 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))57 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_components'))
62 assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res58 assert res == True, "The function 'onchange_location_id' should return True and return '%s'" % res
63 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))59 res = self.onchange_location_id(cr, uid, [], [(6,0,[ref('stock.stock_location_components')])], False, ref('stock.stock_location_14'))
@@ -68,42 +64,37 @@
68 The function must return True in all test cases.64 The function must return True in all test cases.
69-65-
70 !python {model: stock.location}: |66 !python {model: stock.location}: |
71 from osv import orm, osv
72 res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))67 res = self._check_inventory(cr, uid, ref('stock.stock_location_components'))
73 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res68 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
74 res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))69 res = self._check_inventory(cr, uid, ref('stock.stock_location_14'))
75 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res70 assert res == True, "The function '_check_inventory' should return True and return '%s'" % res
76 71
77-72-
78 I will confirm inventory. 73 I will confirm inventory.
79- 74-
80 !python {model: stock.inventory}: |75 !python {model: stock.inventory}: |
81 from osv import orm, osv 76 self.action_confirm(cr, uid, [ref('inventory_standard')])
82 self.action_confirm(cr, uid, [ref('stock_inventory_location_0')]) 77 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
83 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']
84 assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state78 assert inventory_state == 'confirm', "Partial inventory have '%s' state. It should be 'confirm'" % inventory_state
8579
86-80-
87 I will validate inventory81 I will validate inventory
88-82-
89 !python {model: stock.inventory}: |83 !python {model: stock.inventory}: |
90 from osv import orm, osv 84 self.action_done(cr, uid, [ref('inventory_standard')])
91 self.action_done(cr, uid, [ref('stock_inventory_location_0')]) 85 inventory_state = self.read(cr, uid, [ref('inventory_standard')], ['state'])[0]['state']
92 inventory_state = self.read(cr, uid, [ref('stock_inventory_location_0')], ['state'])[0]['state']86 assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state
93 assert inventory_state == 'done', "Partial inventory have '%s' state. It should be 'done'" % inventory_state 87
94
95-88-
96 I will verify the quantity for each products.89 I will verify the quantity for each products.
97- 90-
98 !python {model: product.product}: |91 !python {model: product.product}: |
99 from osv import orm, osv 92 ctx={'location': [ref('stock.stock_location_components')]}
100 ctx={'location': [ref('stock.stock_location_components')], 'to_date': '2020-12-31 23:59:59'}
101 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_9')], ['qty_available'], context=ctx)[0]['qty_available']93 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_9')], ['qty_available'], context=ctx)[0]['qty_available']
102 assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0: %s" % prod_qty_avail94 assert prod_qty_avail == 18.0, "The stock of CPU1 was not set to 18.0: %s" % prod_qty_avail
103 95
104 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']96 prod_qty_avail = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)[0]['qty_available']
105 assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0: %s" % prod_qty_avail97 assert prod_qty_avail == 12.0, "The stock of CPU3 was not set to 12.0: %s" % prod_qty_avail
106 98
107 prod_qty_avail = self.read(cr, uid, [ref('product.product_template_31')], ['qty_available'], context=ctx)[0]['qty_available']99 prod_qty_avail = self.read(cr, uid, [ref('product.product_template_31')], ['qty_available'], context=ctx)[0]['qty_available']
108 assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0: %s" % prod_qty_avail100 assert prod_qty_avail == 32.0, "The stock of FAN was not set to 32.0: %s" % prod_qty_avail
109
110\ No newline at end of file101\ No newline at end of file
111102
=== modified file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.py'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-03-28 16:13:01 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.py 2014-06-11 15:01:48 +0000
@@ -19,8 +19,6 @@
19##############################################################################19##############################################################################
2020
21from openerp.osv import fields, orm21from openerp.osv import fields, orm
22from openerp.tools.translate import _
23from stock_inventory_location.stock_inventory_location import EmptyLocationException
2422
2523
26class stock_inventory_uninventoried_location(orm.TransientModel):24class stock_inventory_uninventoried_location(orm.TransientModel):
@@ -28,71 +26,37 @@
28 _description = 'Confirm the uninventoried Locations.'26 _description = 'Confirm the uninventoried Locations.'
2927
30 _columns = {28 _columns = {
31 'location_ids': fields.many2many('stock.location',29 'location_ids': fields.many2many(
32 'stock_inventory_uninventoried_location_rel',30 'stock.location',
33 'location_id',31 'stock_inventory_uninventoried_location_rel',
34 'wizard_id',32 'location_id', 'wizard_id',
35 'Uninventoried location', readonly=True),33 'Uninventoried location', readonly=True),
36 }34 }
37
38 def get_locations(self, cr, uid, inventory_id, context=None):
39 """ Get all locations from inventory. """
40 location_ids = self.pool.get('stock.inventory').read(cr, uid, [inventory_id], ['location_ids'], context=context)[0]
41 return self.pool.get('stock.location').search(cr, uid, [
42 ('location_id', 'child_of', location_ids['location_ids']),
43 ('usage', '=', 'internal')], context=context)
44
45 def get_locations_inventoried(self, cr, uid, inventory_id, location_ids, context=None):
46 """ Get all locations on inventory lines. """
47 inventory_line_obj = self.pool.get('stock.inventory.line')
48 inventory_line_ids = inventory_line_obj.search(cr, uid, [('location_id', 'in', location_ids),
49 ('inventory_id', '=', inventory_id)], context=context)
50 inventory_line_locations_ids = inventory_line_obj.read(cr, uid, inventory_line_ids, ['location_id'], context=context)
51 return list(set([_id['location_id'][0] for _id in inventory_line_locations_ids]))
5235
53 def default_locations(self, cr, uid, context=None):36 def default_locations(self, cr, uid, context=None):
54 """ Initialize view with the list of uninventoried locations.37 """Initialize view with the list of uninventoried locations."""
55 Search for children of the location if exists.38 return self.pool['stock.inventory'].get_missing_locations(
56 """39 cr, uid, context['active_ids'], context=context)
57 if context is None:
58 context = {}
59 location_ids = self.get_locations(cr, uid, context['active_id'])
60 inventory_line_locations_ids = self.get_locations_inventoried(cr, uid, context['active_id'], location_ids)
61 return [_id for _id in location_ids if _id not in inventory_line_locations_ids]
6240
63 _defaults = {41 _defaults = {
64 'location_ids': default_locations,42 'location_ids': default_locations,
65 }43 }
6644
67 def confirm_uninventoried_locations(self, cr, uid, ids, context=None):45 def confirm_uninventoried_locations(self, cr, uid, ids, context=None):
68 """ Call action confirm method from stock.inventory """46 """Add the missing inventory lines with qty=0 and confirm inventory"""
69 if context is None or 'active_ids' not in context:
70 return False
71 inventory_ids = context['active_ids']47 inventory_ids = context['active_ids']
72 # call the wizard to add lines for uninventoried locations with zero quantity48 inventory_obj = self.pool['stock.inventory']
73 inventory_obj = self.pool.get('stock.inventory')49 wizard_obj = self.pool['stock.fill.inventory']
74 if not isinstance(inventory_ids, list):50 for inventory in inventory_obj.browse(cr, uid, inventory_ids,
75 inventory_ids = [inventory_ids]51 context=context):
76
77 for inventory in inventory_obj.browse(cr, uid, inventory_ids, context=context):
78 if inventory.exhaustive:52 if inventory.exhaustive:
79 location_ids = self.get_locations(cr, uid, inventory.id, context=context)53 # silently run the import wizard with qty=0
80 # get stock inventory lines54 wizard_id = wizard_obj.create(
81 lines = []55 cr, uid, {'location_id': inventory.location_id.id,
82 try:56 'recursive': True,
83 # create inventory lines with zero qty57 'set_stock_zero': True}, context=context)
84 lines = inventory_obj._fill_location_lines(cr, uid,58 wizard_obj.fill_inventory(cr, uid, [wizard_id],
85 inventory.id,59 context=context)
86 location_ids,
87 True,
88 context=context)
89 except EmptyLocationException:
90 pass # Don't care about empty location exception
91
92 for line in lines:
93 self.pool.get('stock.inventory.line').create(cr, uid, line, context=context)
9460
95 inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)61 inventory_obj.action_confirm(cr, uid, inventory_ids, context=context)
96 return {'type': 'ir.actions.act_window_close'}62 return {'type': 'ir.actions.act_window_close'}
97
98
9963
=== modified file 'stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml'
--- stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/wizard/stock_confirm_uninventoried_location.xml 2014-06-11 15:01:48 +0000
@@ -1,33 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>1<?xml version="1.0" encoding="utf-8"?>
2<openerp>2<openerp>
3 <data>3 <data>
4 <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml, 4 <!-- The view definition is similar with stock_inventory_hierarchical_location/wizard/stock_inventory_missing_locations_view.xml,
5 but the code is different. 5 but the code is different.
6 This wizard compare declared locations with locations on inventory lines to present the uninventoried (empty) locations to user. -->6 This wizard compare declared locations with locations on inventory lines to present the uninventoried (empty) locations to user. -->
7 <record id="view_confirm_uninventoried_location" model="ir.ui.view">7 <record id="view_confirm_uninventoried_location" model="ir.ui.view">
8 <field name="name">stock.inventory.uninventoried.locations.form</field>8 <field name="name">stock.inventory.uninventoried.locations.form</field>
9 <field name="model">stock.inventory.uninventoried.locations</field>9 <field name="model">stock.inventory.uninventoried.locations</field>
10 <field name="type">form</field>
11 <field name="arch" type="xml">10 <field name="arch" type="xml">
12 <form string="Confirm uninventoried locations">11 <form string="Confirm empty locations" version="7.0">
13 <group colspan="4" col="1">12 <group colspan="4" col="1">
14 <label string="The following Stock Locations are part of the current Physical Inventory, but not Inventory Line has been recorded for them."/>13 <separator string="The following Locations are empty"/>
14 <label string="The following Stock Locations are part of the current Physical Inventory, but no Inventory Line has been recorded for them."/>
15 <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
16 <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
15 <field name="location_ids" nolabel="1">17 <field name="location_ids" nolabel="1">
16 <tree>18 <tree>
17 <field name="name"/>19 <field name="name"/>
18 </tree>20 </tree>
19 </field>21 </field>
20 <label string="It could either mean that the Locations are empty, or that the Inventory is not yet complete."/>
21 <label string="If you confirm the Inventory, these Locations will be considered empty and their content will be purged."/>
22 <separator string=""/>22 <separator string=""/>
23 </group>23 </group>
24 <group colspan="4" col="2">24 <footer>
25 <button special="cancel" string="Cancel" icon="gtk-cancel" default_focus="1" />25 <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" class="oe_highlight"/>
26 <button name="confirm_uninventoried_locations" string="Purge contents and confirm Inventory" type="object" icon="gtk-ok"/> 26 or
27 </group>27 <button string="Cancel" class="oe_link" special="cancel" default_focus="1"/>
28 </footer>
28 </form>29 </form>
29 </field>30 </field>
30 </record>31 </record>
31
32 </data>32 </data>
33</openerp>33</openerp>
34\ No newline at end of file34\ No newline at end of file
3535
=== modified file 'stock_inventory_location/wizard/stock_fill_location_inventory.py'
--- stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-03-28 16:13:01 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory.py 2014-06-11 15:01:48 +0000
@@ -19,79 +19,29 @@
19##############################################################################19##############################################################################
2020
21from openerp.osv import fields, orm21from openerp.osv import fields, orm
22from openerp.tools.translate import _22
23from collections import OrderedDict23
2424class FillInventoryWizard(orm.TransientModel):
2525 """Add a field that lets the client make the location read-only"""
26class stock_fill_location_inventory(orm.TransientModel):
27 _inherit = 'stock.fill.inventory'26 _inherit = 'stock.fill.inventory'
2827
29 _columns = {28 _columns = {
30 'location_id': fields.many2one('stock.location', 'Location'),29 'exhaustive': fields.boolean('Exhaustive', readonly=True)
31 #'exhaustive': fields.boolean('stock.inventory', 'Type'),30 }
32 'exhaustive': fields.boolean('stock.inventory'),31
33 }32 def default_get(self, cr, uid, fields, context=None):
3433 """Get 'location_id' and 'exhaustive' from the inventory"""
35 def get_inventory_type(self, cr, uid, context=None):34 if context is None:
36 if context.get('active_id', False):35 context = {}
37 inventory_obj = self.pool.get('stock.inventory')36 inv_id = context.get('active_id')
38 exhaustive = inventory_obj.read(cr, uid, [context.get('active_id')], ['exhaustive'], context=context)[0]['exhaustive']37
39 return exhaustive38 res = super(FillInventoryWizard, self).default_get(
40 return False39 cr, uid, fields, context=context)
4140 if (context.get('active_model') == 'stock.inventory'
42 _defaults = {41 and inv_id
43 'exhaustive': get_inventory_type,42 and 'location_id' in fields):
44 }43 inventory = self.pool['stock.inventory'].browse(
4544 cr, uid, context['active_id'], context=context)
46 def view_init(self, cr, uid, fields_list, context=None):45 res.update({'location_id': inventory.location_id.id,
47 """ inherit from original to add multiple selection of location46 'exhaustive': inventory.exhaustive})
48 and exclude from location list the locations already choosen by another inventory """47 return res
49 if context is None:
50 context = {}
51
52 inventory_obj = self.pool.get('stock.inventory')
53 inventory_state = inventory_obj.read(cr, uid, [context.get('active_id')], ['state'], context=context)[0]
54 if inventory_state['state'] != 'open':
55 raise orm.except_orm(_('Error'),
56 _('the inventory must be in "Open" state.'))
57
58 nb_inventory = inventory_obj.search(cr, uid, [('id', '=', context.get('active_id'))], count=True, context=context)
59 if nb_inventory == 0:
60 raise orm.except_orm(_('Warning'),
61 _('No locations found for the inventory.'))
62
63 return super(stock_fill_location_inventory, self).view_init(cr, uid, fields_list, context=context)
64
65 def fill_inventory(self, cr, uid, ids, context=None):
66 """ Fill the inventory only with open locations on the inventory.
67 """
68 if context is None:
69 context = {}
70
71 fill_inventory = self.browse(cr, uid, ids[0], context=context)
72 if not fill_inventory.exhaustive:
73 return super(stock_fill_location_inventory, self).fill_inventory(cr, uid, ids, context=context) # call standard wizard
74
75 location_ids = self.pool.get('stock.inventory').read(cr, uid, [context.get('active_id')], ['location_ids'])[0]
76
77 if not location_ids['location_ids']:
78 raise orm.except_orm(_('Error: Empty location !'), _('No location to import.\nYou must add a location on the locations list.'))
79
80 if fill_inventory.recursive:
81 location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids['location_ids']),
82 ('usage', '=', 'internal')], context=context)
83 else:
84 location_ids = location_ids['location_ids']
85
86 location_ids = list(OrderedDict.fromkeys(location_ids))
87
88 lines = self.pool.get('stock.inventory')._fill_location_lines(cr, uid,
89 context['active_ids'][0],
90 location_ids,
91 fill_inventory.set_stock_zero,
92 context=context)
93
94 inventory_lines_obj = self.pool.get('stock.inventory.line')
95 for line in lines:
96 inventory_lines_obj.create(cr, uid, line, context=context)
97 return {'type': 'ir.actions.act_window_close'}
9848
=== modified file 'stock_inventory_location/wizard/stock_fill_location_inventory_view.xml'
--- stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-03-12 14:55:39 +0000
+++ stock_inventory_location/wizard/stock_fill_location_inventory_view.xml 2014-06-11 15:01:48 +0000
@@ -10,7 +10,7 @@
10 <field name="exhaustive" invisible="1" />10 <field name="exhaustive" invisible="1" />
11 </xpath>11 </xpath>
12 <xpath expr="//field[@name='location_id']" position="attributes">12 <xpath expr="//field[@name='location_id']" position="attributes">
13 <attribute name="attrs">{'invisible':[('exhaustive','=',True)]}</attribute>13 <attribute name="attrs">{'readonly':[('exhaustive','=',True)]}</attribute>
14 </xpath>14 </xpath>
15 </field>15 </field>
16 </record>16 </record>

Subscribers

People subscribed via source and target branches