Merge lp:~openerp-dev/openobject-addons/trunk-wms-phantom-bom-csn into lp:~openerp-dev/openobject-addons/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
Reviewer Review Type Date Requested Status
qdp (OpenERP) Pending
Review via email: mp+205783@code.launchpad.net
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'