跳转至内容
  • 版块
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(Flatly)
  • 不使用皮肤
折叠

Odoo 中文社区

  1. 主页
  2. 版块
  3. Odoo 开发与实施交流
  4. (7.0 version)当销售单中包含service或phantom类型的产品时,销售单不能完成的原因分析及解决方案

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

已定时 已固定 已锁定 已移动 Odoo 开发与实施交流
7 帖子 5 发布者 4.8k 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • KevinKongK 离线
    KevinKongK 离线
    KevinKong
    写于 最后由 编辑
    #1

    首先说一下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 />&nbsp; &nbsp; &nbsp; &nbsp; assert mode in (&#039;finished&#039;, &#039;canceled&#039;), _(&quot;invalid mode for test_state&quot;)<br />&nbsp; &nbsp; &nbsp; &nbsp; finished = True<br />&nbsp; &nbsp; &nbsp; &nbsp; canceled = False<br />&nbsp; &nbsp; &nbsp; &nbsp; write_done_ids = &#91;]<br />&nbsp; &nbsp; &nbsp; &nbsp; write_cancel_ids = &#91;]<br />&nbsp; &nbsp; &nbsp; &nbsp; for order in self.browse(cr, uid, ids, context={}):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for line in order.order_line:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (not line.procurement_id) or (line.procurement_id.state==&#039;done&#039;):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if line.state != &#039;done&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write_done_ids.append(line.id)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; finished = False<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if line.procurement_id:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (line.procurement_id.state == &#039;cancel&#039;):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; canceled = True<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if line.state != &#039;exception&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write_cancel_ids.append(line.id)<br />&nbsp; &nbsp; &nbsp; &nbsp; if write_done_ids:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.pool.get(&#039;sale.order.line&#039;).write(cr, uid, write_done_ids, {&#039;state&#039;: &#039;done&#039;})<br />&nbsp; &nbsp; &nbsp; &nbsp; if write_cancel_ids:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.pool.get(&#039;sale.order.line&#039;).write(cr, uid, write_cancel_ids, {&#039;state&#039;: &#039;exception&#039;})<br /><br />&nbsp; &nbsp; &nbsp; &nbsp; if mode == &#039;finished&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return finished<br />&nbsp; &nbsp; &nbsp; &nbsp; elif mode == &#039;canceled&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 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状态时返回True

    def test_state(self, cr, uid, ids, mode, *args):<br />&nbsp; &nbsp; &nbsp; &nbsp; assert mode in (&#039;finished&#039;, &#039;canceled&#039;), _(&quot;invalid mode for test_state&quot;)<br />&nbsp; &nbsp; &nbsp; &nbsp; finished = True<br />&nbsp; &nbsp; &nbsp; &nbsp; canceled = False<br />&nbsp; &nbsp; &nbsp; &nbsp; write_done_ids = &#91;]<br />&nbsp; &nbsp; &nbsp; &nbsp; write_cancel_ids = &#91;]<br />&nbsp; &nbsp; &nbsp; &nbsp; for order in self.browse(cr, uid, ids, context={}):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for line in order.order_line:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (not line.procurement_id) or (line.procurement_id.state==&#039;done&#039;):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if line.state != &#039;done&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write_done_ids.append(line.id)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else:<br />		&nbsp; &nbsp; mrp = self.pool.get(&#039;mrp.bom&#039;).search(cr,uid,[(&#039;product_id&#039;,&#039;=&#039;,line.product_id.id),(&#039;bom_id&#039;,&#039;=&#039;,False),(&#039;type&#039;,&#039;=&#039;,&#039;phantom&#039;)],context={})<br />		&nbsp; &nbsp; if len(mrp):<br />			for move_line in line.move_ids:<br />			&nbsp; &nbsp; move_line.product_id.id,line.product_id.id,move_line.state<br />			&nbsp; &nbsp; if move_line.product_id.id==line.product_id.id:<br />			&nbsp; &nbsp; &nbsp; continue<br />			&nbsp; &nbsp; <br />			&nbsp; &nbsp; if move_line.product_id.id != line.product_id.id and move_line.state!=&#039;done&#039;:<br />				finished=False<br />		&nbsp; &nbsp; else:<br />			finished = False<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if line.procurement_id:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (line.procurement_id.state == &#039;cancel&#039;):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; canceled = True<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if line.state != &#039;exception&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write_cancel_ids.append(line.id)<br />&nbsp; &nbsp; &nbsp; &nbsp; if write_done_ids:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.pool.get(&#039;sale.order.line&#039;).write(cr, uid, write_done_ids, {&#039;state&#039;: &#039;done&#039;})<br />&nbsp; &nbsp; &nbsp; &nbsp; if write_cancel_ids:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.pool.get(&#039;sale.order.line&#039;).write(cr, uid, write_cancel_ids, {&#039;state&#039;: &#039;exception&#039;})<br /><br />&nbsp; &nbsp; &nbsp; &nbsp; if mode == &#039;finished&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return finished<br />&nbsp; &nbsp; &nbsp; &nbsp; elif mode == &#039;canceled&#039;:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return canceled
    


    2.在stock.move的action_done()里触发对应的组合产品的需求单工作流往下走。

    <br />wf_service = netsvc.LocalService(&quot;workflow&quot;)<br />	stock_move = self.browse(cr,uid,ids[0],context=context)<br />	sale_order_ids = self.pool.get(&#039;sale.order&#039;).search(cr,uid,[(&#039;name&#039;,&#039;=&#039;,stock_move.origin)],context=context)<br />	if len(sale_order_ids):<br />	&nbsp; &nbsp; sale_order = self.pool.get(&#039;sale.order&#039;).browse(cr,uid,sale_order_ids[0],context=context)<br />	&nbsp; &nbsp; for line in sale_order.order_line:<br />		mrp = self.pool.get(&#039;mrp.bom&#039;).search(cr,uid,[(&#039;product_id&#039;,&#039;=&#039;,line.product_id.id),(&#039;bom_id&#039;,&#039;=&#039;,False),(&#039;type&#039;,&#039;=&#039;,&#039;phantom&#039;)],context={})<br />		if len(mrp):<br />		&nbsp; &nbsp; p_order_ids = self.pool.get(&#039;procurement.order&#039;).search(cr,uid,[(&#039;origin&#039;,&#039;=&#039;,stock_move.origin),(&#039;product_id&#039;,&#039;=&#039;,line.product_id.id)],context=context)<br />		&nbsp; &nbsp; <br />		&nbsp; &nbsp; if len(p_order_ids):<br />			wf_service.trg_trigger(uid, &#039;procurement.order&#039;, p_order_ids[0],cr)<br />
    



    虽说这样是解决了问题,但感觉方法还不是最好,坐等官方版本,欢迎各位大神提出更好的思路进行分享。

    1 条回复 最后回复
    0
    • 卓忆卓 离线
      卓忆卓 离线
      卓忆
      写于 最后由 编辑
      #2

      楼主多图做出分享,
      十分感谢。

      恬淡

      1 条回复 最后回复
      0
      • W 离线
        W 离线
        wang
        写于 最后由 编辑
        #3

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

        1 条回复 最后回复
        0
        • digitalsatoriD 离线
          digitalsatoriD 离线
          digitalsatori 管理员
          写于 最后由 编辑
          #4

          感谢Kevin图文并茂,认真仔细的分析。
          看了一下,这个问题产生的原因是在‘stock.move'的‘action_done'方法上,action_done中

          <br /><br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if move.move_dest_id.id and (move.state != &#039;done&#039;):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # Downstream move should only be triggered if this move is the last pending upstream move<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; other_upstream_move_ids = self.search(cr, uid, [(&#039;id&#039;,&#039;!=&#039;,move.id),(&#039;state&#039;,&#039;not in&#039;,&#91;&#039;done&#039;,&#039;cancel&#039;]),<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (&#039;move_dest_id&#039;,&#039;=&#039;,move.move_dest_id.id)], context=context)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if not other_upstream_move_ids:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.write(cr, uid, [move.id], {&#039;move_history_ids&#039;: [(4, move.move_dest_id.id)]})<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if move.move_dest_id.state in (&#039;waiting&#039;, &#039;confirmed&#039;):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.force_assign(cr, uid, [move.move_dest_id.id], context=context)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if move.move_dest_id.picking_id:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wf_service.trg_write(uid, &#039;stock.picking&#039;, move.move_dest_id.picking_id.id, cr)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if move.move_dest_id.auto_validate:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 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, [(&#039;id&#039;,&#039;!=&#039;,move.id),(&#039;state&#039;,&#039;not in&#039;,&#91;&#039;done&#039;,&#039;cancel&#039;]), (&#039;move_dest_id&#039;,&#039;=&#039;,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 />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if move.move_dest_id.state in (&#039;waiting&#039;, &#039;confirmed&#039;):<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # FIXME is opw 607970 still present with new WMS?<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # (see commits 1ef2c181033bd200906fb1e5ce35e234bf566ac6<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # and 41c5ceb8ebb95c1b4e98d8dd1f12b8e547a24b1d)<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; other_upstream_move_ids = self.search(cr, uid, [(&#039;id&#039;, &#039;!=&#039;, move.id), (&#039;state&#039;, &#039;not in&#039;, &#91;&#039;done&#039;, &#039;cancel&#039;]),<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (&#039;move_dest_id&#039;, &#039;=&#039;, 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]

          【上海先安科技】(tony AT openerp.cn)

          1 条回复 最后回复
          0
          • JoshuaJ 离线
            JoshuaJ 离线
            Joshua 管理员
            写于 最后由 编辑
            #5

            楼主和校长都分析得很好,学习了 🙂

            【上海先安科技】(joshua AT openerp.cn),欢迎关注公众号:openerp_cn

            1 条回复 最后回复
            0
            • KevinKongK 离线
              KevinKongK 离线
              KevinKong
              写于 最后由 编辑
              #6

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

              1 条回复 最后回复
              0

              • 登录

              • 没有帐号? 注册

              • 登录或注册以进行搜索。
              • 第一个帖子
                最后一个帖子
              0
              • 版块
              • 标签
              • 热门
              • 用户
              • 群组