(7.0 version)当销售单中包含service或phantom类型的产品时,销售单不能完成的原因分析及解决方案
-
首先说一下service类型的产品,由于该类型的产品不需要发货,所以当在销售订单确认了后,销售单直接变成了等待开票的状态,但当开票的流程结束后,订单却还是停在销售单的状态上,该问题的解决方案是安装Tasks on SO模块,这个不是本文的重点,本文的重点是当产品类型为stockable,需求方式为Make to Stock且bom type为Sets/Phantom时,销售订单不能完成的问题。
当销售单中包含phantom类型的组合产品时,在销售订单的order_line中,你能看到move_ids包含了组成该组合产品的若干产品和该组合产品,并且,当销售单发货完成后,再看该销售订单会发现,该组合产品仍未waiting available的状态,其他产品均为done状态。
[img http://images.cnitblog.com/i/396990/201407/041103124187005.png /img]
再来看销售订单的流程:
[img http://images.cnitblog.com/i/396990/201407/041055089492003.png /img]
在流程图上可以看出,送货的流stuck在ship到ship_end这个结点上,因此test_state('finished')的值肯定是false的,找到test_state('finished')的代码:def test_state(self, cr, uid, ids, mode, *args):<br /> assert mode in ('finished', 'canceled'), _("invalid mode for test_state")<br /> finished = True<br /> canceled = False<br /> write_done_ids = []<br /> write_cancel_ids = []<br /> for order in self.browse(cr, uid, ids, context={}):<br /> for line in order.order_line:<br /> if (not line.procurement_id) or (line.procurement_id.state=='done'):<br /> if line.state != 'done':<br /> write_done_ids.append(line.id)<br /> else:<br /> finished = False<br /> if line.procurement_id:<br /> if (line.procurement_id.state == 'cancel'):<br /> canceled = True<br /> if line.state != 'exception':<br /> write_cancel_ids.append(line.id)<br /> if write_done_ids:<br /> self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})<br /> if write_cancel_ids:<br /> self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})<br /><br /> if mode == 'finished':<br /> return finished<br /> elif mode == 'canceled':<br /> return canceled
经测试,该组合产品会生成一个需求单,并且该需求单的状态一直为waiting.这就是为什么ship这个工作流一直走不下去的原因了。
[img http://images.cnitblog.com/i/396990/201407/041116002468947.png /img]
再查看需求单的工作流:
[img http://images.cnitblog.com/i/396990/201407/041119060438443.png /img]
问题应该出在check_move_done()这里,但该组合产品在库中的确没有库存,没有发货,那么如何确定该组合产品的状态呢?
(这个bug在官方论坛上有人提出来过,但不知道为什么到现在还没解决,我试过8.0的版本,依然存在这个问题)
没有找到官方的解决方案,又看着销售订单里那一排排的“销售订单”状态十分不爽,用自己的方式来解决这个问题:
1.我尝试过手动改掉组合产品的状态,强制变为done状态,无效。
2.尝试过程中发现,销售订单确认的时候会调用一边test_state方法,然后到发货完成都没再调用
3.当手动点order_line中的Process Entirely按钮后,工作流完成了。
于是,我想着手动触发这个工作流继续往下走。
1.重写test_state()方法,使之在判断出是phantom类型的产品且所有子产品全为done状态时返回Truedef test_state(self, cr, uid, ids, mode, *args):<br /> assert mode in ('finished', 'canceled'), _("invalid mode for test_state")<br /> finished = True<br /> canceled = False<br /> write_done_ids = []<br /> write_cancel_ids = []<br /> for order in self.browse(cr, uid, ids, context={}):<br /> for line in order.order_line:<br /> if (not line.procurement_id) or (line.procurement_id.state=='done'):<br /> if line.state != 'done':<br /> write_done_ids.append(line.id)<br /> else:<br /> mrp = self.pool.get('mrp.bom').search(cr,uid,[('product_id','=',line.product_id.id),('bom_id','=',False),('type','=','phantom')],context={})<br /> if len(mrp):<br /> for move_line in line.move_ids:<br /> move_line.product_id.id,line.product_id.id,move_line.state<br /> if move_line.product_id.id==line.product_id.id:<br /> continue<br /> <br /> if move_line.product_id.id != line.product_id.id and move_line.state!='done':<br /> finished=False<br /> else:<br /> finished = False<br /> if line.procurement_id:<br /> if (line.procurement_id.state == 'cancel'):<br /> canceled = True<br /> if line.state != 'exception':<br /> write_cancel_ids.append(line.id)<br /> if write_done_ids:<br /> self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})<br /> if write_cancel_ids:<br /> self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})<br /><br /> if mode == 'finished':<br /> return finished<br /> elif mode == 'canceled':<br /> return canceled
2.在stock.move的action_done()里触发对应的组合产品的需求单工作流往下走。<br />wf_service = netsvc.LocalService("workflow")<br /> stock_move = self.browse(cr,uid,ids[0],context=context)<br /> sale_order_ids = self.pool.get('sale.order').search(cr,uid,[('name','=',stock_move.origin)],context=context)<br /> if len(sale_order_ids):<br /> sale_order = self.pool.get('sale.order').browse(cr,uid,sale_order_ids[0],context=context)<br /> for line in sale_order.order_line:<br /> mrp = self.pool.get('mrp.bom').search(cr,uid,[('product_id','=',line.product_id.id),('bom_id','=',False),('type','=','phantom')],context={})<br /> if len(mrp):<br /> p_order_ids = self.pool.get('procurement.order').search(cr,uid,[('origin','=',stock_move.origin),('product_id','=',line.product_id.id)],context=context)<br /> <br /> if len(p_order_ids):<br /> wf_service.trg_trigger(uid, 'procurement.order', p_order_ids[0],cr)<br />
虽说这样是解决了问题,但感觉方法还不是最好,坐等官方版本,欢迎各位大神提出更好的思路进行分享。 -
感谢Kevin图文并茂,认真仔细的分析。
看了一下,这个问题产生的原因是在‘stock.move'的‘action_done'方法上,action_done中<br /><br /> if move.move_dest_id.id and (move.state != 'done'):<br /> # Downstream move should only be triggered if this move is the last pending upstream move<br /> other_upstream_move_ids = self.search(cr, uid, [('id','!=',move.id),('state','not in',['done','cancel']),<br /> ('move_dest_id','=',move.move_dest_id.id)], context=context)<br /> if not other_upstream_move_ids:<br /> self.write(cr, uid, [move.id], {'move_history_ids': [(4, move.move_dest_id.id)]})<br /> if move.move_dest_id.state in ('waiting', 'confirmed'):<br /> self.force_assign(cr, uid, [move.move_dest_id.id], context=context)<br /> if move.move_dest_id.picking_id:<br /> wf_service.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)<br /> if move.move_dest_id.auto_validate:<br /> self.action_done(cr, uid, [move.move_dest_id.id], context=context)<br />
这部分代码的用意是当一个库存移动有关联的连锁移动时(move_dest_id有值时),检查当前move是否是与move_dest_id关联的所有上游连锁move中最后一个完成(done)的move,如果是则将move_dest_id也改为完成(done)状态。
<pre>
M1-------|
M2-------|(move_dest_id.id)
Mx-------|------------------------>M[sub]downstream[/sub]
: |
Mn-------|
</pre>
即如果有M1~Mn个move都链接到[font=verdana]M[/font][font=verdana][sub]downstream/sub, 如果Mx是最后一个完成的Move(即当Mx完成后,M1~Mn的所有Move都进入done状态),则系统对[/font][size=10px][font=verdana]M[/font][/size][font=verdana][sub]downstream[/sub]调用action_done方法并将其状态改写为'done'[/font]
问题是,如果M1~Mn同时进入完成状态时,即action_done函数调用时其传入的ids值中包含M1~Mn的id值,因为action_done是在最后一次性改成done状态的,而不是每处理一个move就改变它的状态,所以<br />other_upstream_move_ids = self.search(cr, uid, [('id','!=',move.id),('state','not in',['done','cancel']), ('move_dest_id','=',move.move_dest_id.id)], context=context)<br />if not other_upstream_move_ids:<br />
[font=verdana]所以other_upstream_move_ids始终有值,无法执行'[/font][font=verdana]if not other_upstream_move_ids:'下的代码, 一个解决方法是将当前的move的状态改写为‘done’的write方法放在[/font]<br />for move in self.browse(cr, uid, ids, context=context): <br />
[font=verdana]
[size=1em]for 循环中。这样每个move分别改变其完成状态,这样就能判断是否是最后一个变成done的库存移动了。[/size][/font][size=1em]
[font=verdana]当然还有一个方法就是,仍然保持最后一次性修改所有move为done状态,但是判断出是否已经处理了所有的上游move:[/font]
[font=verdana] http://bazaar.launchpad.net/~openerp-dev/openobject-addons/7.0-opw-607970-acl/revision/9462#stock/stock.py [/font][/size]
该修改还没有并入7.0分支,在master分支中也已经意识到了该问题,请参见代码中的备注信息:<br /><br /> if move.move_dest_id.state in ('waiting', 'confirmed'):<br /> # FIXME is opw 607970 still present with new WMS?<br /> # (see commits 1ef2c181033bd200906fb1e5ce35e234bf566ac6<br /> # and 41c5ceb8ebb95c1b4e98d8dd1f12b8e547a24b1d)<br /> other_upstream_move_ids = self.search(cr, uid, [('id', '!=', move.id), ('state', 'not in', ['done', 'cancel']),<br /> ('move_dest_id', '=', move.move_dest_id.id)], context=context)<br />
[font=verdana]
所以,官方应该已经意识到你发现的问题,目前可以暂时使用[size=x-small] http://bazaar.launchpad.net/~openerp-dev/openobject-addons/7.0-opw-607970-acl/revision/9462#stock/stock.py 的修改[/size][/font]