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