先说结论
很多人第一次接触 Odoo 项目销售联动时,会把它理解成一句话:
- 服务产品确认销售订单
- 系统自动生成项目或任务
但如果你去看 /home/ubuntu/odoo-temp/addons/sale_project/models/product_template.py 和 /home/ubuntu/odoo-temp/addons/sale_project/models/sale_order_line.py,就会发现这不是一个“自动化小功能”,而是一套产品配置驱动的交付生成引擎。
系统至少要先回答这些问题:
- 这个服务产品到底要生成项目、任务,还是两者都生成
- 生成时复用全局项目,还是按订单新建项目
- 是否套用项目模板或任务模板
- 新项目挂哪个分析账户
- 多条销售订单行是在同一个项目里落任务,还是各自拆项目
- 里程碑计费场景要不要顺手把 milestone 一起接上
所以你看到的“自动生成任务”,其实只是这条链最后的可见结果。
第一层:真正的入口不是销售订单,而是产品上的 service_tracking
在 product_template.py 里,sale_project 给服务产品扩展了几个关键 tracking 选项:
task_global_projecttask_in_projectproject_only
这三个选项分别表达三种完全不同的交付建模方式。
1. task_global_project
含义是:
- 不新建项目
- 直接在既有项目里生成任务
这适合长期运作的共享项目,比如“客户支持池”“持续顾问项目”“统一运维项目”。
2. task_in_project
含义是:
- 先按订单生成项目
- 再为当前销售订单行生成任务
这适合“每张订单形成一个项目,但每个服务项还要拆成独立任务”的交付模式。
3. project_only
含义是:
- 只生成项目
- 不自动生成任务
这适合项目经理后续自己按模板或计划手工拆任务的场景。
换句话说,Odoo 不是在问“要不要自动建任务”,而是在问:
销售出去的服务,最终应该落成什么粒度的交付对象。
第二层:service_tracking 不是孤立配置,它会被约束校验
product_template.py 里有一段非常关键的约束:
service_tracking == no时,不能再挂project_id或project_template_idtask_global_project时,不能再选project_template_idtask_in_project/project_only时,不能再选全局project_id
这背后的意思很重要。
Odoo 不允许你同时表达两套相互冲突的语义:
- 既说“我要在已有项目里落任务”
- 又说“请按模板新建一个项目”
从源码角度看,这类限制并不只是为了表单体验,而是为了确保后续 _timesheet_service_generation() 不会面对自相矛盾的输入。
所以产品配置在 Odoo 里不是“建议”,而是后续生成链路的硬前提。
第三层:真正负责生成的是销售订单行,而不是产品自己
到了 sale_order_line.py,核心方法是 _timesheet_service_generation()。
这一步很值得注意:
- 产品定义的是“应该怎么生成”
- 但真正执行生成动作的是销售订单行
为什么要这样设计?
因为交付不是抽象产品在履约,而是具体这条销售订单行在履约。只有销售订单行才同时知道:
- 属于哪张订单
- 当前客户是谁
- 数量是多少
- 是否已确认
- 这条行是不是已经生成过项目/任务
这也是为什么 project_id、task_id 最终都挂回 sale.order.line,而不是只停留在产品模板层。
第四层:同一张订单里的多条服务行,不一定各建各的项目
很多实现者最容易误解的,是 task_in_project / project_only 的“复用逻辑”。
源码里,Odoo 会先搜:
- 同一订单下有没有已经由别的服务行生成过项目
- 如果产品使用项目模板,还会看“同模板项目”是否已经生成
于是它不是简单粗暴地“一条销售行一个项目”,而是:
- 无模板时,同一订单往往复用同一个已生成项目
- 有模板时,会按“订单 + 模板”维度复用项目
这说明 Odoo 的思路是:
项目的生成粒度,取决于交付建模,不取决于你有几条销售订单行。
所以当一张订单同时卖出多种服务时,最后结果可能是:
- 一个项目里多张任务
- 多个基于不同模板的项目
- 只生成一个项目不自动拆任务
这完全取决于产品 tracking 配置。
第五层:分析账户不是补充品,而是项目生成时一起落地的地基
在 _timesheet_create_project_prepare_values() 里,新项目会拿到一个 account_id,来源优先级大致是:
- 上下文传入的
project_account_id - 订单上的
project_account_id - 如果都没有,就现场创建分析账户
这一步非常关键。
它意味着 Odoo 认为:
- 项目一旦由销售履约生成
- 就应该立刻具备可归集成本 / 工时 / 收入的分析基础
也就是说,项目生成从来不只是“创建一个 project.project 记录”,而是同时把后续:
- timesheet
- profitability
- invoice analytic distribution
这些能力的底座一起搭起来。
这也是为什么很多项目类功能最后都会绕回分析账户。
第六层:任务标题、描述、工时预算,都是从销售订单行翻译过来的
_timesheet_create_task_prepare_values() 很值得读,因为它展示了 Odoo 怎么把销售语义翻译成任务语义。
它会处理这些事:
- 把销售行数量转换成
allocated_hours - 从销售行描述中拆出任务标题和任务说明
- 关联客户、项目、销售行、销售单
- 默认不直接分配负责人
这里有两个很典型的设计判断。
1. 数量不只是报价数字,还会变成交付预算
如果产品不是 milestone 型服务,销售行数量会被换算成任务的 allocated_hours。
也就是说,订单上的“卖了多少小时 / 天”,并不会停留在销售模块里,而是直接影响项目模块里的任务预算。
2. 任务文案不是凭空生成,而是尽量复用销售上下文
销售行第一行如果只是产品名,Odoo 会把它拿掉,剩余内容作为更具体的任务标题或描述。这其实是在做一件很实用的事:
让销售承诺尽量无损传到交付执行。
第七层:模板复制不是简单 copy,而是带交付语义的复制
如果产品上配置了:
project_template_id- 或
task_template_id
那么生成就不会走最普通的 create,而会调用模板创建逻辑。
这意味着模板不是“方便你少点几下鼠标”,而是:
- 固定交付结构
- 继承项目经理、阶段、角色、任务树
- 把销售确认后的新项目尽量拉到统一实施标准
尤其是项目模板场景,源码还会额外把复制出来的任务统一补上:
sale_line_idsale_order_idpartner_id
这说明模板复制在 Odoo 眼里,首先是把旧的交付结构嫁接到新的销售履约上下文里。
第八层:系统还会替你补默认阶段,避免项目生成后落到“Undefined”
_timesheet_create_project() 里还有个很容易被忽略但特别像产品化思维的动作:
如果新项目没有任何任务阶段,Odoo 会自动补一组基础阶段:
- To Do
- In Progress
- Done
- Cancelled
这一步很小,但说明官方很清楚:
- “成功创建项目”不等于“这个项目已经可用”
- 如果项目一落地就没有阶段,后续任务体验会立刻变差
所以源码在生成链里顺手把最基础的执行环境也补齐了。
第九层:里程碑服务不是晚点再处理,而是生成当下就一起挂上
在 _handle_milestones() 里,如果产品的 service_policy 是 delivered_milestones,系统会:
- 打开项目的
allow_milestones - 把已有里程碑挂到当前销售行
- 或者直接新建一个 milestone
- 如果是
task_in_project,还会把任务和 milestone 关联起来
这说明 milestone 不是销售确认后的独立后处理动作,而是项目生成链的内建分支。
所以服务产品的 tracking 和 invoicing policy,其实一起决定了交付对象会怎么长出来。
新手最容易误解的 5 件事
1. 以为“自动建任务”是产品的单点功能
不对。产品只负责声明策略,真正落地的是销售订单行生成链。
2. 以为一条服务行一定对应一个项目
不对。Odoo 会按订单、模板和 tracking 规则决定是否复用已有项目。
3. 以为项目生成后再补分析账户也行
源码不是这么想的。项目生成时就尽量把分析账户一起准备好。
4. 以为任务预算和销售数量没什么关系
不对。服务数量会直接参与 allocated_hours 的换算。
5. 以为 milestone 计费只影响开票,不影响项目生成
不对。里程碑会在生成链中一起被接入项目和任务。
实战里最该注意什么
1. 配置服务产品时,先想清楚交付粒度
你卖出去的到底应该长成:
- 全局项目里的任务
- 每单一个项目 + 每行一个任务
- 还是每单一个项目由项目经理再拆任务
这比“能不能自动建”更关键。
2. 自定义时别只改 create,先读 _timesheet_service_generation()
很多人喜欢在确认订单后自己补逻辑,但真正的项目复用、模板复用、分析账户复用,都已经在这里了。贸然绕开,最容易制造重复项目或错绑项目。
3. 如果销售和交付常常脱节,优先检查销售行描述和模板设计
因为 Odoo 已经在努力把销售行信息翻译成任务和项目。如果出来的任务总是不像样,往往不是项目模块的问题,而是销售输入本身太弱。
一句话记忆法
Odoo 服务产品的“自动生成项目/任务”不是一个按钮动作,而是
service_tracking + service_policy + 模板 + 分析账户 + 销售订单行复用规则共同驱动的交付建模链。
DISCUSSION
评论区