先说结论
Odoo 企业版里,把一张 helpdesk.ticket 转成 project.task,并不是“原样平移”。
从 project_helpdesk、helpdesk_timesheet、helpdesk_sale_timesheet 三层源码叠起来看,官方其实在守三条边界:
- 任务语义要继承:名称、描述、客户、目标项目、目标阶段会带过去;
- 计费语义可以继承:如果启用了 helpdesk sale timesheet,
sale_line_id也会跟着转到 task; - 工时归属不会自动改挂:已有
timesheet_ids仍留在 ticket 侧,不会因为“转任务”自动改成 task 工时。
所以更准确的理解是:
Odoo 的工单转任务,本质是新建一个 task 作为后续执行载体,同时保留 ticket 作为历史来源;它不会替你偷偷重写既有工时账本。
这恰恰是协同办公里很重要的一种设计克制:
- 执行对象可以切换;
- 计费线索可以延续;
- 但已记录的时间,不会被系统默默改写归属。
一、真正的“转任务”不是改类型,而是创建新 task 并归档 ticket
在 enterprise/project_helpdesk/wizard/helpdesk_ticket_convert_wizard.py 里,action_convert() 做的不是把 ticket 直接变形,而是:
- 读取上下文里的
to_convert; - 用
_get_task_values(ticket)组装任务值; - 批量
create()出新的project.task; - 然后把原 ticket 设为
active = False; - 最后双方各发一条消息,保留来源链路。
这说明官方建模思路很明确:
- ticket 仍然是 ticket;
- task 是新建出来的后续执行对象;
- 两者通过消息追溯保持来源关系,而不是靠“同一条记录换个模型壳”。
这点很关键,因为只要理解成“新建 + 归档 + 留痕”,后面很多行为就说得通了:
- 为什么旧工单还能作为历史依据;
- 为什么不会自动把所有关联对象强行搬家;
- 为什么消息链比字段覆盖更重要。
二、基础向导真正继承了哪些字段
_get_task_values() 在基础版 project_helpdesk 中给 task 填的核心字段非常克制:
namedescriptionproject_idstage_idpartner_id
也就是说,默认继承的是:
1. 执行标题
工单标题直接变成任务标题,方便协作团队继续沿着同一问题推进。
2. 问题描述
ticket 的 description 会进入 task,避免服务台与项目团队二次抄写。
3. 承接项目与阶段
这不是“自动猜测一个任务该去哪”,而是显式由向导决定:
- 转到哪个
project_id - 落到哪个
stage_id
4. 客户上下文
partner_id 一起过去,说明任务并不是脱离客户语境的纯内部卡片。
因此,工单转任务继承的是 执行上下文,而不是一切关联数据。
三、为什么 helpdesk_timesheet 会优先给你默认“原 team 对应项目”
如果装了 helpdesk_timesheet,同名向导会覆写 _default_project_id()。
逻辑非常简单但很有意味:
- 先取待转换 tickets 的
project_id; - 如果这些 ticket 都来自同一个项目,就把那个项目设成默认值;
- 如果有多个项目,就按 sequence 取第一个;
- 测试
test_wizard_default_project也明确验证了:默认项目应当是 helpdesk team 绑定的 project。
这意味着官方并不把“转任务”理解成一次完全脱离 helpdesk 现场的跳转。
相反,它默认假设:
如果这个 helpdesk team 本来就把工单工时记到某个项目里,那么转任务时,最自然的承接项目仍是那个项目。
这是一种很典型的企业协同设计:
- 服务台先接单;
- 工时先沉到 team 绑定项目;
- 真要升级成项目执行项时,默认也还是回到这条项目语义主线。
四、sale line 为什么能继承,但 timesheet 为什么不能自动继承
这里最值得写清。
sale line 的继承:是“计费语义延续”
在 helpdesk_sale_timesheet/wizard/helpdesk_ticket_convert_wizard.py,企业版只多做了一件事:
- 在
_get_task_values(ticket)上追加sale_line_id
对应测试 test_convert_ticket_with_sol_to_task 也明确断言:
- 转出来的 task,应该和 ticket 拥有同一个
sale_line_id。
这说明 Odoo 认可一件事:
- 这张工单背后的可计费合同/服务订单项,往往也是后续任务的计费依据。
所以 sale_line_id 可以跟过去。
timesheet 不自动继承:是“时间账本不重写”
但你通读整个 ticket 转 task 向导会发现:
- 没有任何地方在迁移
timesheet_ids; - 没有任何地方把
helpdesk_ticket_id批量改成task_id; - 也没有任何地方做“历史工时改挂任务”的后处理。
这不是遗漏,而是和 account.analytic.line 的约束设计严格一致。
helpdesk_timesheet/models/account_analytic_line.py 明确规定:
- 一条 timesheet 不能同时连
task_id和helpdesk_ticket_id; _check_no_link_task_and_ticket()会直接报错;- 当你给 timesheet 设
helpdesk_ticket_id时,task_id会被清掉; - 反过来有
task_id时,helpdesk_ticket_id也会被清掉。
所以系统态度非常明确:
一条工时要么属于 ticket,要么属于 task,不能两边都算。
既然如此,转任务时不自动迁移历史 timesheet,反而是更安全的选择。
因为一旦自动迁移,就等于系统替企业改写了:
- 历史服务支持工时的归属对象;
- 可能已经参与统计或审核的项目归类;
- 甚至潜在的客户计费依据。
五、这意味着“转任务”更像前向切换,不像历史回填
很多实施方的直觉是:
- 既然工单升级成任务,旧工时也应该自动算到任务里。
但源码展示的官方心智更像:
- 从现在开始,这件事适合在 task 上继续推进;
- 至于之前已经记在 ticket 上的工时,仍代表支持阶段的历史事实。
这就是“前向切换”而不是“历史回填”。
协同上它非常合理:
- 一线支持先响应客户;
- 确认需要项目团队介入后,再生成 task;
- 后续新工时记在 task 还是继续记在 ticket,应由流程显式决定;
- 但旧工时不该被系统悄悄重写。
六、ticket 工时本身为什么已经和 project 强绑定
再往下看 helpdesk_timesheet,你会发现 ticket 工时并不是“松散附着”。
当 account.analytic.line 带上 helpdesk_ticket_id 时:
- create/write 会自动补
project_id; - 还会补
account_id(来自 ticket 的 analytic account / project account); - 默认
company_id也会尽量对齐 ticket 对应项目公司; - partner 也会跟 ticket 客户走。
换句话说,ticket 工时一旦落账,背后已经站着一整套:
- 项目
- 分析账户
- 公司
- 客户
所以它不是 UI 上一条“顺手打的工时”,而是已经进入成本/计费语义的业务记录。
这就更能解释:
为什么转任务不会自动重挂历史工时。
因为那样改的不是一个显示字段,而是可能连项目归集、分析维度、计费逻辑一起改。
七、team 变化时,Odoo 只自动改“未验证草稿工时”
还有一个细节,特别能看出官方边界感。
在 helpdesk_timesheet.models.helpdesk_ticket.write() 和对应测试里:
- 如果 ticket 切到新的 helpdesk team,且新 team 仍支持 timesheet;
- 未验证 的工时会改到新项目;
- 已验证 的工时则保留原项目不动。
这和“转任务不自动迁移工时”其实是一致的设计哲学:
- 草稿、未确认的数据可以顺着流程调整;
- 已验证、已落定的数据不能随便改写。
因此,官方只在非常明确、风险较低的范围内做自动重挂:
- 换 team 时,改未验证工时项目;
- 转 task 时,不碰既有工时归属。
八、计费 ticket 转 task 后,后续 billable 逻辑为什么还能接得上
装了 helpdesk_sale_timesheet 后,ticket 上的 sale_line_id 本来就承担着:
- 默认可计费订单项;
- 计算剩余工时;
- 决定 ticket 工时默认
so_line; - 把 portal / invoice 视图接回销售单。
而转换向导把 sale_line_id 直接带到 task,意味着:
- 这项工作从“支持工单”升级为“项目任务”后,
- 后续任务工时仍可沿着同一条销售订单项继续计费。
也就是说,Odoo 在转换时保留的是:
- 未来工时该如何计费 的线索;
而不是:
- 过去已经记过的工时要改算到哪里。
这个分层非常合理。
九、对实施与二开的真正启发
1. 不要把“转任务”理解成数据搬家按钮
它首先是流程切换按钮。
2. 如果你要迁移历史工时,必须当成显式业务动作设计
因为你在改写的是分析与计费归属,不是界面展示。
3. sale_line_id 能继承,不代表 timesheet_ids 也应该继承
一个是未来计费锚点,一个是历史工时账本,语义不同。
4. 需要可审计性时,官方默认做法其实更稳
ticket 归档、task 新建、消息留痕,比“原记录改模型 + 关联全搬”更容易审计。
5. 若要做自动迁移,至少先区分 validated 与 draft 工时
源码已经用 team 变更场景说明:
- draft 可以调;
- validated 不能乱动。
一句话记忆法
Odoo 企业版把 helpdesk ticket 转成 task 时,继承的是执行上下文和未来计费锚点,不会自动改写既有 timesheet 的历史归属。
参考源码
enterprise/project_helpdesk/wizard/helpdesk_ticket_convert_wizard.pyenterprise/project_helpdesk/tests/test_tasks_tickets_conversion.pyenterprise/helpdesk_timesheet/wizard/helpdesk_ticket_convert_wizard.pyenterprise/helpdesk_timesheet/models/account_analytic_line.pyenterprise/helpdesk_timesheet/models/helpdesk_ticket.pyenterprise/helpdesk_timesheet/tests/test_ticket_conversion.pyenterprise/helpdesk_sale_timesheet/wizard/helpdesk_ticket_convert_wizard.pyenterprise/helpdesk_sale_timesheet/tests/test_tasks_tickets_conversion_sol.pyenterprise/helpdesk_sale_timesheet/models/helpdesk_ticket.py
DISCUSSION
评论区