很多团队把 Field Service 任务理解成“工程师上门干完,点一下 Done”。但企业版真正难的是:工时怎么挂到正确销售行、材料怎么在完工瞬间放行、序列号产品怎么跟着出库、为什么销售单常常在你还没正式完工前就被确认。
这条链主要在:
enterprise/industry_fsm_sale/models/project_task.pyenterprise/industry_fsm_sale/models/account_analytic_line.pyenterprise/industry_fsm_stock/models/project_task.pyenterprise/industry_fsm_stock/models/sale_order_line.py
一、为什么 FSM 不是“先做任务,最后再补销售单”
industry_fsm_sale 里,project.task.action_fsm_validate() 在任务完成时不会直接只改状态,而是先判断这张任务是否允许 billable,以及是否存在工时或材料。如果任务需要计费,系统会调用 _fsm_ensure_sale_order(),必要时自动建单,再根据项目计价模式补销售行。
这背后的业务含义很重要:
- 现场服务的计费对象不是 task 本身,而是 SO / SOL;
- task 只是“服务发生过”的业务上下文;
- 真正决定后续开票、交付、利润分析的锚点,仍然是销售单。
所以 Odoo 不会把 FSM 做成一个完全脱离销售模块的闭环。
二、为什么有些销售单会在加材料之前就先 confirm
industry_fsm_stock 对 _fsm_ensure_sale_order() 和 _fsm_create_sale_order() 做了扩展:只要现场任务要走材料流,销售单往往要提前确认。
原因不是“界面看着更正式”,而是仓库与拣货链路已经要提前成立:
- 工程师在任务里点材料面板;
- 系统希望使用当前用户默认仓库;
- 如果 SO 还是 draft,等到后面再 confirm,历史销售行可能会错误继承后续用户的仓库上下文;
- 因此企业版宁可先 confirm,再继续追加现场材料。
这是很多实施里最容易误解的一点:FSM 的 SO 早确认,不代表业务已完全结算;它只是为了让库存路线先稳定下来。
三、工时为什么不会简单地“谁填工时就挂默认产品”
account_analytic_line._timesheet_determine_sale_line() 明确体现出企业版 FSM 的复杂度。
如果项目是 employee_rate:
- 系统先看 task 是否还在 warranty;保内任务可能根本不挂销售行;
- 再按
project.sale.line.employee.map把不同员工映射到不同服务产品与单价; - 如果对应销售行尚未生成,且任务还没真正 done,工时甚至会先不落到最终 SOL 上。
也就是说,企业版不是“工时 = 默认服务产品 × 小时数”,而是允许:
- 不同工程师不同价;
- 任务保内不计费;
- 先记工时,等完工时再统一补单。
四、材料为什么要等到完工时统一 _validate_stock()
industry_fsm_stock/models/project_task.py 里的 _validate_stock() 才是现场材料真正难的地方。
它做了几件事:
- 找出当前 task 相关的 SO 行与 stock move;
- 对序列号 / 批次产品,把
fsm_lot_id补到 move line; - 如果 move line 数量不够,就自动补齐缺口;
- 把非 timesheet / 非 milestone 服务行的
qty_delivered直接推到已订购数量; - 只对“当前任务已经完成、或同单其他任务也已完成”的拣货做统一校验;
- 若 push rule 继续生成后续 picking,还会递归再跑一轮。
这说明 Odoo 的现场材料交付不是“扫了就出”,而是 按任务完成状态做批量放行。这样做有两个好处:
- 避免同一张销售单里,尚未完成的任务把别的材料提前放行;
- 避免序列号产品在移动链条中只改了一半 move line。
五、最容易踩坑的三个误解
1. “SO confirm 了,就该锁单”
不对。FSM 场景里,企业版特意把“确认”和“锁单”拆开。action_fsm_validate() 真正成功后,才会对满足条件的 SO 执行 action_lock()。因为现场过程中还可能继续补材料。
2. “任务保内只是发票不收钱”
也不对。保内任务在源码里经常直接影响工时是否挂 SOL、是否补服务销售行,不只是财务阶段少收一笔。
3. “材料已经生成 picking,就说明可交付”
仍然不对。真正能不能自动 button_validate(),要看是不是当前 task 对应材料、是否存在未完成关联 move、是否涉及 lot / serial 补全。
六、实战建议
- 做 FSM 实施时,先定清楚项目
pricing_type,别让员工价和任务统一价混用后再追溯历史工时。 - 序列号物料一定要验证
fsm_lot_id录入路径,否则完工时最容易卡在 move line 补齐。 - 如果客户坚持“SO 未经审批不能确认”,要提前解释:FSM 的 confirm 很多时候是库存前置动作,不等同于最终商务确认。
- 多任务共享同一销售单时,要特别测试“部分任务 done、部分未 done”的出库边界。
七、结论
Odoo 企业版 FSM 的核心不是“任务完成”这个按钮,而是把现场服务、销售计费、库存放行三条链在 done 时刻收拢到一起。你看到的是一个完成动作,源码里处理的却是销售单锚点、工时映射、序列号出库和锁单时机。
DISCUSSION
评论区