Odoo中文社区可以通过以下三个域名访问:shine-it.net , odoocn.org,odoo.net.cn

原论坛用户的基本信息和发帖这里都予以保留,请注意:原论坛用户无需重新注册新用户,但是您的密码需要重置

开发人员可以登录gitter讨论组: http://gitter.im/odoo-china/Talk, 需要github账号

如果您登录系统碰到问题,请在微信公众号留言:

(7.0 version)当销售单中包含service或phantom类型的产品时,销售单不能完成的原因分析及解决方案



  • 首先说一下service类型的产品,由于该类型的产品不需要发货,所以当在销售订单确认了后,销售单直接变成了等待开票的状态,但当开票的流程结束后,订单却还是停在销售单的状态上,该问题的解决方案是安装Tasks on SO模块,这个不是本文的重点,本文的重点是当产品类型为stockable,需求方式为Make to Stock且bom type为Sets/Phantom时,销售订单不能完成的问题。<br /><br />当销售单中包含phantom类型的组合产品时,在销售订单的order_line中,你能看到move_ids包含了组成该组合产品的若干产品和该组合产品,并且,当销售单发货完成后,再看该销售订单会发现,该组合产品仍未waiting available的状态,其他产品均为done状态。<br />[img]http://images.cnitblog.com/i/396990/201407/041103124187005.png[/img]<br /><br />再来看销售订单的流程:<br />[img]http://images.cnitblog.com/i/396990/201407/041055089492003.png[/img]<br /><br />在流程图上可以看出,送货的流stuck在ship到ship_end这个结点上,因此test_state('finished')的值肯定是false的,找到test_state('finished')的代码:<br />[code]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[/code]<br />经测试,该组合产品会生成一个需求单,并且该需求单的状态一直为waiting.这就是为什么ship这个工作流一直走不下去的原因了。<br /><br />[img]http://images.cnitblog.com/i/396990/201407/041116002468947.png[/img]<br /><br />再查看需求单的工作流:<br />[img]http://images.cnitblog.com/i/396990/201407/041119060438443.png[/img]<br />问题应该出在check_move_done()这里,但该组合产品在库中的确没有库存,没有发货,那么如何确定该组合产品的状态呢?<br /><br />(这个bug在官方论坛上有人提出来过,但不知道为什么到现在还没解决,我试过8.0的版本,依然存在这个问题)<br /><br />没有找到官方的解决方案,又看着销售订单里那一排排的“销售订单”状态十分不爽,用自己的方式来解决这个问题:<br /><br />1.我尝试过手动改掉组合产品的状态,强制变为done状态,无效。<br />2.尝试过程中发现,销售订单确认的时候会调用一边test_state方法,然后到发货完成都没再调用<br />3.当手动点order_line中的Process Entirely按钮后,工作流完成了。<br /><br />于是,我想着手动触发这个工作流继续往下走。<br /><br />1.重写test_state()方法,使之在判断出是phantom类型的产品且所有子产品全为done状态时返回True<br />[code]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 />     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[/code]<br />2.在stock.move的action_done()里触发对应的组合产品的需求单工作流往下走。<br />[code]<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 />[/code]<br /><br />虽说这样是解决了问题,但感觉方法还不是最好,坐等官方版本,欢迎各位大神提出更好的思路进行分享。



  • 首先说一下service类型的产品,由于该类型的产品不需要发货,所以当在销售订单确认了后,销售单直接变成了等待开票的状态,但当开票的流程结束后,订单却还是停在销售单的状态上,该问题的解决方案是安装Tasks on SO模块,这个不是本文的重点,本文的重点是当产品类型为stockable,需求方式为Make to Stock且bom type为Sets/Phantom时,销售订单不能完成的问题。<br /><br />当销售单中包含phantom类型的组合产品时,在销售订单的order_line中,你能看到move_ids包含了组成该组合产品的若干产品和该组合产品,并且,当销售单发货完成后,再看该销售订单会发现,该组合产品仍未waiting available的状态,其他产品均为done状态。<br />[img]http://images.cnitblog.com/i/396990/201407/041103124187005.png[/img]<br /><br />再来看销售订单的流程:<br />[img]http://images.cnitblog.com/i/396990/201407/041055089492003.png[/img]<br /><br />在流程图上可以看出,送货的流stuck在ship到ship_end这个结点上,因此test_state('finished')的值肯定是false的,找到test_state('finished')的代码:<br />[code]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[/code]<br />经测试,该组合产品会生成一个需求单,并且该需求单的状态一直为waiting.这就是为什么ship这个工作流一直走不下去的原因了。<br /><br />[img]http://images.cnitblog.com/i/396990/201407/041116002468947.png[/img]<br /><br />再查看需求单的工作流:<br />[img]http://images.cnitblog.com/i/396990/201407/041119060438443.png[/img]<br />问题应该出在check_move_done()这里,但该组合产品在库中的确没有库存,没有发货,那么如何确定该组合产品的状态呢?<br /><br />(这个bug在官方论坛上有人提出来过,但不知道为什么到现在还没解决,我试过8.0的版本,依然存在这个问题)<br /><br />没有找到官方的解决方案,又看着销售订单里那一排排的“销售订单”状态十分不爽,用自己的方式来解决这个问题:<br /><br />1.我尝试过手动改掉组合产品的状态,强制变为done状态,无效。<br />2.尝试过程中发现,销售订单确认的时候会调用一边test_state方法,然后到发货完成都没再调用<br />3.当手动点order_line中的Process Entirely按钮后,工作流完成了。<br /><br />于是,我想着手动触发这个工作流继续往下走。<br /><br />1.重写test_state()方法,使之在判断出是phantom类型的产品且所有子产品全为done状态时返回True<br />[code]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 />     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[/code]<br />2.在stock.move的action_done()里触发对应的组合产品的需求单工作流往下走。<br />[code]<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 />[/code]<br /><br />虽说这样是解决了问题,但感觉方法还不是最好,坐等官方版本,欢迎各位大神提出更好的思路进行分享。



  • 楼主多图做出分享,<br />十分感谢。



  • 拜读下!!先评价再仔细的看下


  • 管理员

    感谢Kevin图文并茂,认真仔细的分析。<br />看了一下,这个问题产生的原因是在‘stock.move'的‘action_done'方法上,action_done中<br />[code]<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 />[/code]<br />这部分代码的用意是当一个库存移动有关联的连锁移动时(move_dest_id有值时),检查当前move是否是与move_dest_id关联的所有上游连锁move中最后一个完成(done)的move,如果是则将move_dest_id也改为完成(done)状态。<br /><pre><br />M1-------|<br />M2-------|(move_dest_id.id)<br />Mx-------|------------------------>M[sub]downstream[/sub]<br />:            |<br />Mn-------|<br /></pre><br />即如果有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]<br /><br /><br />问题是,如果M1~Mn同时进入完成状态时,即action_done函数调用时其传入的ids值中包含M1~Mn的id值,因为action_done是在最后一次性改成done状态的,而不是每处理一个move就改变它的状态,所以<br />[code]<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 />[/code]<br />[font=verdana]所以other_upstream_move_ids始终有值,无法执行'[/font][font=verdana]if not other_upstream_move_ids:'下的代码, 一个解决方法是将当前的move的状态改写为‘done’的write方法放在[/font]<br />[code]<br />for move in self.browse(cr, uid, ids, context=context): <br />[/code][font=verdana]<br />[size=1em]for 循环中。这样每个move分别改变其完成状态,这样就能判断是否是最后一个变成done的库存移动了。[/size][/font][size=1em]<br />[font=verdana]当然还有一个方法就是,仍然保持最后一次性修改所有move为done状态,但是判断出是否已经处理了所有的上游move:[/font]<br />[font=verdana][url=http://bazaar.launchpad.net/~openerp-dev/openobject-addons/7.0-opw-607970-acl/revision/9462#stock/stock.py]http://bazaar.launchpad.net/~openerp-dev/openobject-addons/7.0-opw-607970-acl/revision/9462#stock/stock.py[/url][/font][/size]<br />该修改还没有并入7.0分支,在master分支中也已经意识到了该问题,请参见代码中的备注信息:<br />[code]<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 />[/code][font=verdana]<br />所以,官方应该已经意识到你发现的问题,目前可以暂时使用[size=x-small][url=http://bazaar.launchpad.net/~openerp-dev/openobject-addons/7.0-opw-607970-acl/revision/9462#stock/stock.py]http://bazaar.launchpad.net/~openerp-dev/openobject-addons/7.0-opw-607970-acl/revision/9462#stock/stock.py[/url]的修改[/size][/font]


  • 管理员

    楼主和校长都分析得很好,学习了 :-)



  • 谢谢校长的回复,按照您的方法,完美解决该问题 ;)


登录后回复
 

与 Odoo 中文社区 的连接断开,我们正在尝试重连,请耐心等待