Merge lp:~openerp-dev/openobject-addons/trunk-wms-phantom-bom-csn into lp:~openerp-dev/openobject-addons/trunk-wms
- trunk-wms-phantom-bom-csn
- Merge into trunk-wms
Proposed by
Cedric Snauwaert (OpenERP)
Status: | Merged |
---|---|
Merged at revision: | 9565 |
Proposed branch: | lp:~openerp-dev/openobject-addons/trunk-wms-phantom-bom-csn |
Merge into: | lp:~openerp-dev/openobject-addons/trunk-wms |
Diff against target: |
679 lines (+222/-95) (has conflicts) 12 files modified
account_anglo_saxon/test/anglo_saxon.yml (+3/-3) mrp/mrp.py (+6/-5) mrp/mrp_view.xml (+4/-0) mrp/stock.py (+60/-55) procurement/procurement.py (+1/-0) purchase/purchase.py (+1/-1) sale_mrp/test/sale_mrp.yml (+3/-1) sale_mrp/tests/__init__.py (+28/-0) sale_mrp/tests/test_move_explode.py (+59/-0) sale_stock/test/picking_order_policy.yml (+37/-16) stock/procurement.py (+1/-1) stock/stock.py (+19/-13) Text conflict in mrp/mrp_view.xml |
To merge this branch: | bzr merge lp:~openerp-dev/openobject-addons/trunk-wms-phantom-bom-csn |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
qdp (OpenERP) | Pending | ||
Review via email: mp+205783@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'account_anglo_saxon/test/anglo_saxon.yml' |
2 | --- account_anglo_saxon/test/anglo_saxon.yml 2014-01-20 14:42:43 +0000 |
3 | +++ account_anglo_saxon/test/anglo_saxon.yml 2014-02-14 13:47:49 +0000 |
4 | @@ -103,7 +103,7 @@ |
5 | I configure the product with required accounts, and cost method = standard |
6 | - |
7 | !python {model: product.product}: | |
8 | - self.write(cr, uid, [ref('product.product_product_3')], {'list_price': 20.00,'standard_price': 9,'categ_id': ref('product.product_category_4'),'valuation': 'real_time', |
9 | + self.write(cr, uid, [ref('product.product_product_10')], {'list_price': 20.00,'standard_price': 9,'categ_id': ref('product.product_category_4'),'valuation': 'real_time', |
10 | 'property_account_income': ref('account_anglo_income'),'property_account_expense': ref('account_anglo_cogs'), |
11 | 'property_account_creditor_price_difference': ref('account_anglo_price_difference'),'property_stock_account_input': ref('account_anglo_stock_input'), |
12 | 'property_stock_account_output': ref('account_anglo_stock_output'), 'cost_method': 'standard'}) |
13 | @@ -115,7 +115,7 @@ |
14 | location_id: stock.stock_location_stock |
15 | pricelist_id: 1 |
16 | order_line: |
17 | - - product_id: product.product_product_3 |
18 | + - product_id: product.product_product_10 |
19 | product_qty: 1 |
20 | price_unit: 10 |
21 | date_planned: !eval "'%s' % (time.strftime('%Y-%m-%d'))" |
22 | @@ -194,7 +194,7 @@ |
23 | move_lines: |
24 | - company_id: base.main_company |
25 | location_id: stock.stock_location_stock |
26 | - product_id: product.product_product_3 |
27 | + product_id: product.product_product_10 |
28 | product_uom_qty: 1.0 |
29 | product_uom: product.product_uom_unit |
30 | location_dest_id: stock.stock_location_customers |
31 | |
32 | === modified file 'mrp/mrp.py' |
33 | --- mrp/mrp.py 2014-02-14 10:01:57 +0000 |
34 | +++ mrp/mrp.py 2014-02-14 13:47:49 +0000 |
35 | @@ -1035,11 +1035,12 @@ |
36 | 'location_dest_id': destination_location_id, |
37 | 'move_dest_id': production.move_prod_id.id, |
38 | 'company_id': production.company_id.id, |
39 | + 'production_id': production.id, |
40 | } |
41 | move_id = stock_move.create(cr, uid, data, context=context) |
42 | - stock_move.action_confirm(cr, uid, [move_id], context=context) |
43 | - production.write({'move_created_ids': [(6, 0, [move_id])]}, context=context) |
44 | - return move_id |
45 | + #a phantom bom cannot be used in mrp order so it's ok to assume the list returned by action_confirm |
46 | + #is 1 element long, so we can take the first. |
47 | + return stock_move.action_confirm(cr, uid, [move_id], context=context)[0] |
48 | |
49 | def _make_production_consume_line(self, cr, uid, production_line, parent_move_id, source_location_id=False, context=None): |
50 | stock_move = self.pool.get('stock.move') |
51 | @@ -1062,10 +1063,10 @@ |
52 | 'location_dest_id': destination_location_id, |
53 | 'company_id': production.company_id.id, |
54 | 'procure_method': 'make_to_order', |
55 | + 'raw_material_production_id': production.id, |
56 | }) |
57 | stock_move.action_confirm(cr, uid, [move_id], context=context) |
58 | - production.write({'move_lines': [(4, move_id)]}, context=context) |
59 | - return move_id |
60 | + return True |
61 | |
62 | def action_confirm(self, cr, uid, ids, context=None): |
63 | """ Confirms production order. |
64 | |
65 | === modified file 'mrp/mrp_view.xml' |
66 | --- mrp/mrp_view.xml 2014-02-14 10:01:57 +0000 |
67 | +++ mrp/mrp_view.xml 2014-02-14 13:47:49 +0000 |
68 | @@ -687,7 +687,11 @@ |
69 | </div> |
70 | <group> |
71 | <group> |
72 | +<<<<<<< TREE |
73 | <field name="product_id" on_change="product_id_change(product_id, product_qty)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False)]" class="oe_inline" context='{"default_supply_method":"produce", "default_type": "product"}'/> |
74 | +======= |
75 | + <field name="product_id" on_change="product_id_change(product_id)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False),('bom_ids.type','!=','phantom')]" class="oe_inline" context='{"default_type": "product"}'/> |
76 | +>>>>>>> MERGE-SOURCE |
77 | <label for="product_qty"/> |
78 | <div> |
79 | <field name="product_qty" class="oe_inline" on_change="product_id_change(product_id, product_qty)"/> |
80 | |
81 | === modified file 'mrp/stock.py' |
82 | --- mrp/stock.py 2014-02-05 10:43:24 +0000 |
83 | +++ mrp/stock.py 2014-02-14 13:47:49 +0000 |
84 | @@ -22,7 +22,9 @@ |
85 | from openerp.osv import fields |
86 | from openerp.osv import osv |
87 | from openerp.tools.translate import _ |
88 | - |
89 | +from openerp import SUPERUSER_ID |
90 | +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT |
91 | +import time |
92 | |
93 | class StockMove(osv.osv): |
94 | _inherit = 'stock.move' |
95 | @@ -33,6 +35,12 @@ |
96 | 'consumed_for': fields.many2one('stock.move', 'Consumed for', help='Technical field used to make the traceability of produced products'), |
97 | } |
98 | |
99 | + def copy(self, cr, uid, id, default=None, context=None): |
100 | + if not default: |
101 | + default = {} |
102 | + default['production_id'] = False |
103 | + return super(StockMove, self).copy(cr, uid, id, default, context=context) |
104 | + |
105 | def check_tracking(self, cr, uid, move, lot_id, context=None): |
106 | super(StockMove, self).check_tracking(cr, uid, move, lot_id, context=context) |
107 | if move.product_id.track_production and (move.location_id.usage == 'production' or move.location_dest_id.usage == 'production') and not lot_id: |
108 | @@ -40,6 +48,20 @@ |
109 | if move.raw_material_production_id and move.location_dest_id.usage == 'production' and move.raw_material_production_id.product_id.track_production and not move.consumed_for: |
110 | raise osv.except_osv(_('Warning!'), _("Because the product %s requires it, you must assign a serial number to your raw material %s to proceed further in your production. Please use the 'Produce' button to do so.") % (move.raw_material_production_id.product_id.name, move.product_id.name)) |
111 | |
112 | + def _check_phantom_bom(self, cr, uid, move, context=None): |
113 | + """check if product associated to move has a phantom bom |
114 | + return list of ids of mrp.bom for that product """ |
115 | + user_company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id |
116 | + #doing the search as SUPERUSER because a user with the permission to write on a stock move should be able to explode it |
117 | + #without giving him the right to read the boms. |
118 | + return self.pool.get('mrp.bom').search(cr, SUPERUSER_ID, [ |
119 | + ('product_id', '=', move.product_id.id), |
120 | + ('bom_id', '=', False), |
121 | + ('type', '=', 'phantom'), |
122 | + '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), |
123 | + '|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), |
124 | + ('company_id', '=', user_company)], context=context) |
125 | + |
126 | def _action_explode(self, cr, uid, move, context=None): |
127 | """ Explodes pickings. |
128 | @param move: Stock moves |
129 | @@ -49,59 +71,52 @@ |
130 | move_obj = self.pool.get('stock.move') |
131 | procurement_obj = self.pool.get('procurement.order') |
132 | product_obj = self.pool.get('product.product') |
133 | - processed_ids = [move.id] |
134 | - |
135 | - bis = bom_obj.search(cr, uid, [ |
136 | - ('product_id', '=', move.product_id.id), |
137 | - ('bom_id', '=', False), |
138 | - ('type', '=', 'phantom')]) |
139 | + to_explode_again_ids = [] |
140 | + processed_ids = [] |
141 | + bis = self._check_phantom_bom(cr, uid, move, context=context) |
142 | if bis: |
143 | factor = move.product_qty |
144 | - bom_point = bom_obj.browse(cr, uid, bis[0], context=context) |
145 | - res = bom_obj._bom_explode(cr, uid, bom_point, factor, []) |
146 | + bom_point = bom_obj.browse(cr, SUPERUSER_ID, bis[0], context=context) |
147 | + res = bom_obj._bom_explode(cr, SUPERUSER_ID, bom_point, factor, []) |
148 | state = 'confirmed' |
149 | if move.state == 'assigned': |
150 | state = 'assigned' |
151 | for line in res[0]: |
152 | valdef = { |
153 | - 'picking_id': move.picking_id.id, |
154 | + 'picking_id': move.picking_id.id if move.picking_id else False, |
155 | 'product_id': line['product_id'], |
156 | 'product_uom': line['product_uom'], |
157 | 'product_qty': line['product_qty'], |
158 | 'product_uos': line['product_uos'], |
159 | 'product_uos_qty': line['product_uos_qty'], |
160 | - 'move_dest_id': move.id, |
161 | 'state': state, |
162 | 'name': line['name'], |
163 | - 'procurements': [], |
164 | } |
165 | mid = move_obj.copy(cr, uid, move.id, default=valdef) |
166 | - processed_ids.append(mid) |
167 | - prodobj = product_obj.browse(cr, uid, line['product_id'], context=context) |
168 | - proc_id = procurement_obj.create(cr, uid, { |
169 | - 'name': (move.picking_id.origin or ''), |
170 | - 'origin': (move.picking_id.origin or ''), |
171 | - 'date_planned': move.date, |
172 | - 'product_id': line['product_id'], |
173 | - 'product_qty': line['product_qty'], |
174 | - 'product_uom': line['product_uom'], |
175 | - 'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False, |
176 | - 'product_uos': line['product_uos'], |
177 | - 'location_id': move.location_id.id, |
178 | - 'procure_method': prodobj.procure_method, |
179 | - 'move_id': mid, |
180 | - }) |
181 | - procurement_obj.signal_button_confirm(cr, uid, [proc_id]) |
182 | - |
183 | - move_obj.write(cr, uid, [move.id], { |
184 | - 'location_dest_id': move.location_id.id, # dummy move for the kit |
185 | - 'picking_id': False, |
186 | - 'state': 'confirmed' |
187 | - }) |
188 | - procurement_ids = procurement_obj.search(cr, uid, [('move_id', '=', move.id)], context) |
189 | - procurement_obj.signal_button_confirm(cr, uid, procurement_ids) |
190 | - procurement_obj.signal_button_wait_done(cr, uid, procurement_ids) |
191 | - return processed_ids |
192 | + to_explode_again_ids.append(mid) |
193 | + |
194 | + #delete the move with original product which is not relevant anymore |
195 | + move_obj.unlink(cr, SUPERUSER_ID, [move.id], context=context) |
196 | + #check if new moves needs to be exploded |
197 | + if to_explode_again_ids: |
198 | + for new_move in self.browse(cr, uid, to_explode_again_ids, context=context): |
199 | + processed_ids.extend(self._action_explode(cr, uid, new_move, context=context)) |
200 | + #return list of newly created move or the move id otherwise |
201 | + return processed_ids or [move.id] |
202 | + |
203 | + def action_confirm(self, cr, uid, ids, context=None): |
204 | + move_ids = [] |
205 | + for move in self.browse(cr, uid, ids, context=context): |
206 | + #in order to explode a move, we must have a picking_type_id on that move because otherwise the move |
207 | + #won't be assigned to a picking and it would be weird to explode a move into several if they aren't |
208 | + #all grouped in the same picking. |
209 | + if move.picking_type_id: |
210 | + move_ids.extend(self._action_explode(cr, uid, move, context=context)) |
211 | + else: |
212 | + move_ids.append(move.id) |
213 | + |
214 | + #we go further with the list of ids potentially changed by action_explode |
215 | + return super(StockMove, self).action_confirm(cr, uid, move_ids, context=context) |
216 | |
217 | def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False, |
218 | consumed_for=False, context=None): |
219 | @@ -121,9 +136,15 @@ |
220 | |
221 | if product_qty <= 0: |
222 | raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.')) |
223 | + #because of the action_confirm that can create extra moves in case of phantom bom, we need to make 2 loops |
224 | + ids2 = [] |
225 | for move in self.browse(cr, uid, ids, context=context): |
226 | if move.state == 'draft': |
227 | - self.action_confirm(cr, uid, [move.id], context=context) |
228 | + ids2.extend(self.action_confirm(cr, uid, [move.id], context=context)) |
229 | + else: |
230 | + ids2.append(move.id) |
231 | + |
232 | + for move in self.browse(cr, uid, ids2, context=context): |
233 | move_qty = move.product_qty |
234 | uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, product_qty, move.product_uom.id) |
235 | if move_qty <= 0: |
236 | @@ -182,22 +203,6 @@ |
237 | workflow.trg_trigger(uid, 'stock.move', move.id, cr) |
238 | return res |
239 | |
240 | - |
241 | -class StockPicking(osv.osv): |
242 | - _inherit = 'stock.picking' |
243 | - |
244 | - # |
245 | - # Explode picking by replacing phantom BoMs |
246 | - # |
247 | - def action_explode(self, cr, uid, move_ids, *args): |
248 | - """Explodes moves by expanding kit components""" |
249 | - move_obj = self.pool.get('stock.move') |
250 | - todo = move_ids[:] |
251 | - for move in move_obj.browse(cr, uid, move_ids): |
252 | - todo.extend(move_obj._action_explode(cr, uid, move)) |
253 | - return list(set(todo)) |
254 | - |
255 | - |
256 | class stock_warehouse(osv.osv): |
257 | _inherit = 'stock.warehouse' |
258 | _columns = { |
259 | |
260 | === modified file 'procurement/procurement.py' |
261 | --- procurement/procurement.py 2013-12-20 09:26:50 +0000 |
262 | +++ procurement/procurement.py 2014-02-14 13:47:49 +0000 |
263 | @@ -250,6 +250,7 @@ |
264 | def _run(self, cr, uid, procurement, context=None): |
265 | '''This method implements the resolution of the given procurement |
266 | :param procurement: browse record |
267 | + :returns: True if the resolution of the procurement was a success, False otherwise to set it in exception |
268 | ''' |
269 | return True |
270 | |
271 | |
272 | === modified file 'purchase/purchase.py' |
273 | --- purchase/purchase.py 2014-02-14 09:09:34 +0000 |
274 | +++ purchase/purchase.py 2014-02-14 13:47:49 +0000 |
275 | @@ -751,7 +751,7 @@ |
276 | move = stock_move.create(cr, uid, vals, context=context) |
277 | todo_moves.append(move) |
278 | |
279 | - stock_move.action_confirm(cr, uid, todo_moves) |
280 | + todo_moves = stock_move.action_confirm(cr, uid, todo_moves) |
281 | stock_move.force_assign(cr, uid, todo_moves) |
282 | |
283 | def test_moves_done(self, cr, uid, ids, context=None): |
284 | |
285 | === modified file 'sale_mrp/test/sale_mrp.yml' |
286 | --- sale_mrp/test/sale_mrp.yml 2013-11-29 16:56:44 +0000 |
287 | +++ sale_mrp/test/sale_mrp.yml 2014-02-14 13:47:49 +0000 |
288 | @@ -44,7 +44,9 @@ |
289 | I add the routes manufacture and mto to the product |
290 | - |
291 | !python {model: product.product, id: scheduler_product}: | |
292 | - self.write(cr, uid, [ref("product_product_slidermobile0")], {"route_ids": [(4, ref("mrp.route_warehouse0_manufacture")), (4, ref("stock.route_warehouse0_mto"))]}) |
293 | + route_warehouse0_manufacture = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).manufacture_pull_id.route_id.id |
294 | + route_warehouse0_mto = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).mto_pull_id.route_id.id |
295 | + self.write(cr, uid, ref('product_product_slidermobile0'), { 'route_ids': [(6, 0, [route_warehouse0_mto,route_warehouse0_manufacture])]}, context=context) |
296 | - |
297 | I create a Bill of Material record for Slider Mobile |
298 | - |
299 | |
300 | === added directory 'sale_mrp/tests' |
301 | === added file 'sale_mrp/tests/__init__.py' |
302 | --- sale_mrp/tests/__init__.py 1970-01-01 00:00:00 +0000 |
303 | +++ sale_mrp/tests/__init__.py 2014-02-14 13:47:49 +0000 |
304 | @@ -0,0 +1,28 @@ |
305 | +# -*- coding: utf-8 -*- |
306 | +############################################################################## |
307 | +# |
308 | +# OpenERP, Open Source Business Applications |
309 | +# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com> |
310 | +# |
311 | +# This program is free software: you can redistribute it and/or modify |
312 | +# it under the terms of the GNU Affero General Public License as |
313 | +# published by the Free Software Foundation, either version 3 of the |
314 | +# License, or (at your option) any later version. |
315 | +# |
316 | +# This program is distributed in the hope that it will be useful, |
317 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
318 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
319 | +# GNU Affero General Public License for more details. |
320 | +# |
321 | +# You should have received a copy of the GNU Affero General Public License |
322 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
323 | +# |
324 | +############################################################################## |
325 | + |
326 | +from . import test_move_explode |
327 | + |
328 | +checks = [ |
329 | + test_move_explode, |
330 | +] |
331 | + |
332 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
333 | |
334 | === added file 'sale_mrp/tests/test_move_explode.py' |
335 | --- sale_mrp/tests/test_move_explode.py 1970-01-01 00:00:00 +0000 |
336 | +++ sale_mrp/tests/test_move_explode.py 2014-02-14 13:47:49 +0000 |
337 | @@ -0,0 +1,59 @@ |
338 | +# -*- coding: utf-8 -*- |
339 | +############################################################################## |
340 | +# |
341 | +# OpenERP, Open Source Business Applications |
342 | +# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com> |
343 | +# |
344 | +# This program is free software: you can redistribute it and/or modify |
345 | +# it under the terms of the GNU Affero General Public License as |
346 | +# published by the Free Software Foundation, either version 3 of the |
347 | +# License, or (at your option) any later version. |
348 | +# |
349 | +# This program is distributed in the hope that it will be useful, |
350 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
351 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
352 | +# GNU Affero General Public License for more details. |
353 | +# |
354 | +# You should have received a copy of the GNU Affero General Public License |
355 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
356 | +# |
357 | +############################################################################## |
358 | + |
359 | +from openerp.tests import common |
360 | + |
361 | + |
362 | +class TestMoveExplode(common.TransactionCase): |
363 | + |
364 | + def setUp(self): |
365 | + super(TestMoveExplode, self).setUp() |
366 | + cr, uid = self.cr, self.uid |
367 | + |
368 | + # Usefull models |
369 | + self.ir_model_data = self.registry('ir.model.data') |
370 | + self.sale_order_line = self.registry('sale.order.line') |
371 | + self.sale_order = self.registry('sale.order') |
372 | + self.mrp_bom = self.registry('mrp.bom') |
373 | + |
374 | + #product that has a phantom bom |
375 | + self.product_bom_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_4')[1] |
376 | + #bom with that product |
377 | + self.bom_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_24')[1] |
378 | + #partner agrolait |
379 | + self.partner_id = self.ir_model_data.get_object_reference(cr, uid, 'base', 'res_partner_1')[1] |
380 | + |
381 | + def test_00_sale_move_explode(self): |
382 | + """check that when creating a sale order with a product that has a phantom BoM, move explode into content of the |
383 | + BoM""" |
384 | + cr, uid, context = self.cr, self.uid, {} |
385 | + #create sale order with one sale order line containing product with a phantom bom |
386 | + so_id = self.sale_order.create(cr, uid, vals={'partner_id': self.partner_id}, context=context) |
387 | + self.sale_order_line.create(cr, uid, values={'order_id': so_id, 'product_id': self.product_bom_id, 'product_uom_qty': 1}, context=context) |
388 | + #confirm sale order |
389 | + self.sale_order.action_button_confirm(cr, uid, [so_id], context=context) |
390 | + #get all move associated to that sale_order |
391 | + browse_move_ids = self.sale_order.browse(cr, uid, so_id, context=context).picking_ids[0].move_lines |
392 | + move_ids = [x.id for x in browse_move_ids] |
393 | + #we should have same amount of move as the component in the phatom bom |
394 | + bom = self.mrp_bom.browse(cr, uid, self.bom_id, context=context) |
395 | + bom_component_length = self.mrp_bom._bom_explode(cr, uid, bom, 1, []) |
396 | + self.assertEqual(len(move_ids), len(bom_component_length[0])) |
397 | |
398 | === modified file 'sale_stock/test/picking_order_policy.yml' |
399 | --- sale_stock/test/picking_order_policy.yml 2014-01-21 08:04:33 +0000 |
400 | +++ sale_stock/test/picking_order_policy.yml 2014-02-14 13:47:49 +0000 |
401 | @@ -4,6 +4,17 @@ |
402 | !context |
403 | uid: 'res_sale_stock_salesman' |
404 | - |
405 | + Create a new SO to be sure we don't have one with product that can explode in mrp |
406 | +- |
407 | + !record {model: sale.order, id: sale_order_service}: |
408 | + partner_id: base.res_partner_18 |
409 | + partner_invoice_id: base.res_partner_18 |
410 | + partner_shipping_id: base.res_partner_18 |
411 | + user_id: base.user_root |
412 | + pricelist_id: product.list0 |
413 | + warehouse_id: stock.warehouse0 |
414 | + order_policy: picking |
415 | +- |
416 | Add SO line with service type product in SO to check flow which contain service type product in SO(BUG#1167330). |
417 | - |
418 | !record {model: sale.order.line, id: sale_order_1}: |
419 | @@ -12,27 +23,37 @@ |
420 | product_uom_qty: 1.0 |
421 | product_uom: 1 |
422 | price_unit: 150.0 |
423 | - order_id: sale.sale_order_6 |
424 | + order_id: sale_order_service |
425 | +- |
426 | + Add a second SO line with a normal product |
427 | +- |
428 | + !record {model: sale.order.line, id: sale_order_2}: |
429 | + name: 'Mouse Optical' |
430 | + product_id: product.product_product_10 |
431 | + product_uom_qty: 1.0 |
432 | + product_uom: 1 |
433 | + price_unit: 150.0 |
434 | + order_id: sale_order_service |
435 | - |
436 | First I check the total amount of the Quotation before Approved. |
437 | - |
438 | - !assert {model: sale.order, id: sale.sale_order_6, string: The amount of the Quotation is not correctly computed}: |
439 | + !assert {model: sale.order, id: sale_order_service, string: The amount of the Quotation is not correctly computed}: |
440 | - sum([l.price_subtotal for l in order_line]) == amount_untaxed |
441 | - |
442 | I set an explicit invoicing partner that is different from the main SO Customer |
443 | - |
444 | - !python {model: sale.order, id: sale.sale_order_6}: | |
445 | - order = self.browse(cr, uid, ref("sale.sale_order_6")) |
446 | + !python {model: sale.order, id: sale_order_service}: | |
447 | + order = self.browse(cr, uid, ref("sale_order_service")) |
448 | order.write({'partner_invoice_id': ref('base.res_partner_address_29')}) |
449 | - |
450 | I confirm the quotation with Invoice based on deliveries policy. |
451 | - |
452 | - !workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_6} |
453 | + !workflow {model: sale.order, action: order_confirm, ref: sale_order_service} |
454 | - |
455 | I check that invoice should not created before dispatch delivery. |
456 | - |
457 | !python {model: sale.order}: | |
458 | - order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6")) |
459 | + order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service")) |
460 | assert order.state == 'progress', 'Order should be in inprogress.' |
461 | assert len(order.invoice_ids) == False, "Invoice should not created." |
462 | - |
463 | @@ -42,7 +63,7 @@ |
464 | from datetime import datetime, timedelta |
465 | from dateutil.relativedelta import relativedelta |
466 | from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT |
467 | - order = self.browse(cr, uid, ref("sale.sale_order_6")) |
468 | + order = self.browse(cr, uid, ref("sale_order_service")) |
469 | for order_line in order.order_line: |
470 | if order_line.product_id.type == 'product': |
471 | procurement = order_line.procurement_ids[0] |
472 | @@ -74,7 +95,7 @@ |
473 | from datetime import datetime, timedelta |
474 | from dateutil.relativedelta import relativedelta |
475 | from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT |
476 | - sale_order = self.browse(cr, uid, ref("sale.sale_order_6")) |
477 | + sale_order = self.browse(cr, uid, ref("sale_order_service")) |
478 | assert sale_order.picking_ids, "Delivery order is not created." |
479 | for picking in sale_order.picking_ids: |
480 | assert picking.state == "auto" or "confirmed", "Delivery order should be in 'Waitting Availability' state." |
481 | @@ -103,7 +124,7 @@ |
482 | Now, I dispatch delivery order. |
483 | - |
484 | !python {model: stock.picking}: | |
485 | - order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6")) |
486 | + order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service")) |
487 | for pick in order.picking_ids: |
488 | data = pick.force_assign() |
489 | if data == True: |
490 | @@ -117,7 +138,7 @@ |
491 | I check sale order to verify shipment. |
492 | - |
493 | !python {model: sale.order}: | |
494 | - order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6")) |
495 | + order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service")) |
496 | assert order.shipped == True, "Sale order is not Delivered." |
497 | #assert order.state == 'progress', 'Order should be in inprogress.' |
498 | assert len(order.invoice_ids) == False, "Invoice should not created on dispatch delivery order." |
499 | @@ -126,7 +147,7 @@ |
500 | - |
501 | !python {model: stock.invoice.onshipping}: | |
502 | sale = self.pool.get('sale.order') |
503 | - sale_order = sale.browse(cr, uid, ref("sale.sale_order_6")) |
504 | + sale_order = sale.browse(cr, uid, ref("sale_order_service")) |
505 | ship_ids = [x.id for x in sale_order.picking_ids] |
506 | wiz_id = self.create(cr, uid, {'journal_id': ref('account.sales_journal')}, |
507 | {'active_ids': ship_ids, 'active_model': 'stock.picking'}) |
508 | @@ -135,7 +156,7 @@ |
509 | I check the invoice details after dispatched delivery. |
510 | - |
511 | !python {model: sale.order}: | |
512 | - order = self.browse(cr, uid, ref("sale.sale_order_6")) |
513 | + order = self.browse(cr, uid, ref("sale_order_service")) |
514 | assert order.invoice_ids, "Invoice is not created." |
515 | ac = order.partner_invoice_id.property_account_receivable.id |
516 | journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)]) |
517 | @@ -166,7 +187,7 @@ |
518 | I open the Invoice. |
519 | - |
520 | !python {model: sale.order}: | |
521 | - so = self.browse(cr, uid, ref("sale.sale_order_6")) |
522 | + so = self.browse(cr, uid, ref("sale_order_service")) |
523 | account_invoice_obj = self.pool.get('account.invoice') |
524 | for invoice in so.invoice_ids: |
525 | account_invoice_obj.signal_invoice_open(cr, uid, [invoice.id]) |
526 | @@ -175,7 +196,7 @@ |
527 | - |
528 | !python {model: account.invoice}: | |
529 | sale_order = self.pool.get('sale.order') |
530 | - order = sale_order.browse(cr, uid, ref("sale.sale_order_6")) |
531 | + order = sale_order.browse(cr, uid, ref("sale_order_service")) |
532 | journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash'), ('company_id', '=', order.company_id.id)], limit=1) |
533 | for invoice in order.invoice_ids: |
534 | invoice.pay_and_reconcile( |
535 | @@ -192,7 +213,7 @@ |
536 | I check the order after paid invoice. |
537 | - |
538 | !python {model: sale.order}: | |
539 | - order = self.browse(cr, uid, ref("sale.sale_order_6")) |
540 | + order = self.browse(cr, uid, ref("sale_order_service")) |
541 | assert order.invoiced == True, "Sale order is not invoiced." |
542 | assert order.invoiced_rate == 100, "Invoiced progress is not 100%." |
543 | assert order.state == 'done', 'Order should be in closed.' |
544 | @@ -203,7 +224,7 @@ |
545 | import os |
546 | import openerp.report |
547 | from openerp import tools |
548 | - data, format = openerp.report.render_report(cr, uid, [ref('sale.sale_order_6')], 'sale.order', {}, {}) |
549 | + data, format = openerp.report.render_report(cr, uid, [ref('sale_order_service')], 'sale.order', {}, {}) |
550 | if tools.config['test_report_directory']: |
551 | file(os.path.join(tools.config['test_report_directory'], 'sale-sale_order.'+format), 'wb+').write(data) |
552 | |
553 | |
554 | === modified file 'stock/procurement.py' |
555 | --- stock/procurement.py 2014-02-07 11:13:49 +0000 |
556 | +++ stock/procurement.py 2014-02-14 13:47:49 +0000 |
557 | @@ -217,7 +217,7 @@ |
558 | move_dict = self._run_move_create(cr, uid, procurement, context=context) |
559 | move_id = move_obj.create(cr, uid, move_dict, context=context) |
560 | move_obj.action_confirm(cr, uid, [move_id], context=context) |
561 | - return move_id |
562 | + return True |
563 | return super(procurement_order, self)._run(cr, uid, procurement, context) |
564 | |
565 | def _check(self, cr, uid, procurement, context=None): |
566 | |
567 | === modified file 'stock/stock.py' |
568 | --- stock/stock.py 2014-02-13 10:59:13 +0000 |
569 | +++ stock/stock.py 2014-02-14 13:47:49 +0000 |
570 | @@ -830,6 +830,7 @@ |
571 | for pick in self.browse(cr, uid, ids, context=context): |
572 | if pick.state == 'draft': |
573 | self.action_confirm(cr, uid, [pick.id], context=context) |
574 | + pick.refresh() |
575 | #skip the moves that don't need to be checked |
576 | move_ids = [x.id for x in pick.move_lines if x.state not in ('draft', 'cancel', 'done')] |
577 | if not move_ids: |
578 | @@ -864,9 +865,7 @@ |
579 | todo = [] |
580 | for move in pick.move_lines: |
581 | if move.state == 'draft': |
582 | - self.pool.get('stock.move').action_confirm(cr, uid, [move.id], |
583 | - context=context) |
584 | - todo.append(move.id) |
585 | + todo.extend(self.pool.get('stock.move').action_confirm(cr, uid, [move.id], context=context)) |
586 | elif move.state in ('assigned', 'confirmed'): |
587 | todo.append(move.id) |
588 | if len(todo): |
589 | @@ -1769,6 +1768,8 @@ |
590 | """ Confirms stock move or put it in waiting if it's linked to another move. |
591 | @return: List of ids. |
592 | """ |
593 | + if isinstance(ids, (int, long)): |
594 | + ids = [ids] |
595 | states = { |
596 | 'confirmed': [], |
597 | 'waiting': [] |
598 | @@ -1790,7 +1791,7 @@ |
599 | self._create_procurement(cr, uid, move, context=context) |
600 | moves = self.browse(cr, uid, ids, context=context) |
601 | self._push_apply(cr, uid, moves, context=context) |
602 | - return True |
603 | + return ids |
604 | |
605 | def force_assign(self, cr, uid, ids, context=None): |
606 | """ Changes the state to assigned. |
607 | @@ -1898,7 +1899,7 @@ |
608 | pack_op_obj = self.pool.get("stock.pack.operation") |
609 | todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"] |
610 | if todo: |
611 | - self.action_confirm(cr, uid, todo, context=context) |
612 | + ids = self.action_confirm(cr, uid, todo, context=context) |
613 | pickings = set() |
614 | procurement_ids = [] |
615 | #Search operations that are linked to the moves |
616 | @@ -2031,7 +2032,16 @@ |
617 | :param move: browse record |
618 | :param qty: float. quantity to split (given in product UoM) |
619 | :param context: dictionay. can contains the special key 'source_location_id' in order to force the source location when copying the move |
620 | + |
621 | + returns the ID of the backorder move created |
622 | """ |
623 | + if move.state in ('done', 'cancel'): |
624 | + raise osv.except_osv(_('Error'), _('You cannot split a move done')) |
625 | + if move.state == 'draft': |
626 | + #we restrict the split of a draft move because if not confirmed yet, it may be replaced by several other moves in |
627 | + #case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode. |
628 | + raise osv.except_osv(_('Error'), _('You cannot split a draft move. It needs to be confirmed first.')) |
629 | + |
630 | if move.product_qty <= qty or qty == 0: |
631 | return move.id |
632 | |
633 | @@ -2041,9 +2051,6 @@ |
634 | uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty, move.product_uom.id) |
635 | uos_qty = uom_qty * move.product_uos_qty / move.product_uom_qty |
636 | |
637 | - if move.state in ('done', 'cancel'): |
638 | - raise osv.except_osv(_('Error'), _('You cannot split a move done')) |
639 | - |
640 | defaults = { |
641 | 'product_uom_qty': uom_qty, |
642 | 'product_uos_qty': uos_qty, |
643 | @@ -2070,9 +2077,9 @@ |
644 | if move.move_dest_id and move.propagate: |
645 | new_move_prop = self.split(cr, uid, move.move_dest_id, qty, context=context) |
646 | self.write(cr, uid, [new_move], {'move_dest_id': new_move_prop}, context=context) |
647 | - |
648 | - self.action_confirm(cr, uid, [new_move], context=context) |
649 | - return new_move |
650 | + #returning the first element of list returned by action_confirm is ok because we checked it wouldn't be exploded (and |
651 | + #thus the result of action_confirm should always be a list of 1 element length) |
652 | + return self.action_confirm(cr, uid, [new_move], context=context)[0] |
653 | |
654 | |
655 | class stock_inventory(osv.osv): |
656 | @@ -3086,6 +3093,7 @@ |
657 | 'propagate': True, |
658 | 'active': True, |
659 | } |
660 | + |
661 | def _apply(self, cr, uid, rule, move, context=None): |
662 | move_obj = self.pool.get('stock.move') |
663 | newdate = (datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta.relativedelta(days=rule.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) |
664 | @@ -3101,7 +3109,6 @@ |
665 | if rule.location_dest_id.id != old_dest_location: |
666 | #call again push_apply to see if a next step is defined |
667 | move_obj._push_apply(cr, uid, [move], context=context) |
668 | - return move.id |
669 | else: |
670 | move_id = move_obj.copy(cr, uid, move.id, { |
671 | 'location_id': move.location_dest_id.id, |
672 | @@ -3119,7 +3126,6 @@ |
673 | 'move_dest_id': move_id, |
674 | }) |
675 | move_obj.action_confirm(cr, uid, [move_id], context=None) |
676 | - return move_id |
677 | |
678 | class stock_move_putaway(osv.osv): |
679 | _name = 'stock.move.putaway' |