订阅按工时收费最容易出错的地方,是“记录了工时”不等于“这次就该开票”,更不等于“这张票以后还能追溯到哪几条工时”。
这篇文章主要参考了以下企业版源码与测试入口:
enterprise/sale_subscription_timesheet/models/sale_order_line.pyenterprise/sale_subscription_timesheet/models/account_move.pyenterprise/sale_subscription_timesheet/tests/test_sale_subscription_timesheet.py
一、这个模块真正解决的不是表面动作,而是跨模块语义对齐
sale_subscription_timesheet 解决的是订阅、项目工时和开票三者之间的精确衔接:哪些订阅行算 postpaid、哪些 timesheet 要进入 delivered qty、生成发票后又怎么反向关联回工时记录。
如果只看 UI,很容易把它理解成一个按钮、一张表或一个新视图。但从 sale_subscription_timesheet 的模型、测试和桥接关系看,官方真正关心的是:前台动作发生以后,后端主链路能不能继续保持同一套业务语义。
二、核心机制链路
1. 先判断是不是 postpaid line
sale_order_line._is_postpaid_line() 先把该按后付逻辑处理的订阅行挑出来,避免一次性收费、预付费和按工时后付混成一团。
2. delivered qty 依赖 timesheet 取数范围
_get_timesheet_subscription_lines()、_compute_qty_delivered() 与 account_move._get_range_dates(order) 说明,不是所有 timesheet 都进这期,而是先按订阅账期截窗,再算 delivered。
3. 开票后还要能追溯回来
test_sub_link_timesheet_to_invoice 和 test_invoice_orders_if_delivery_qty 表明,工时进入发票不是终点;后续仍要知道哪几条 timesheet 已被哪张发票消费。
三、最容易被误解的边界
- 把所有 timesheet 都累计到当前订阅周期,忽略账期窗口。
- 不区分 postpaid line 和其他订阅行,导致 delivered qty 混乱。
- 发票开完就断链,后续无法解释为什么这几小时已经计费。
这些误解之所以常见,往往是因为大家只看见“入口动作”,却没有继续追到模型方法、状态切换、聚合口径和测试场景里去看 Odoo 究竟把什么当成事实、把什么当成辅助信息。
四、实施与排查时,建议按这个顺序看
- 先看订阅行是否命中 postpaid 判断。
- 再核对 timesheet 落入的 range dates 是否正确。
- 最后追查 invoice 与 timesheet 的反向链接是否建立。
对企业版功能来说,排查顺序非常重要。很多看似是“结果不对”的问题,真正根因往往更早:字段上下文没带过去、桥接对象没建、状态机没推进、或者权限/公司边界一开始就错了。
五、结论
按工时计费的订阅,真正重要的不是“能不能从工时开发票”,而是 delivered qty、账期窗口与回链都要同时成立。
DISCUSSION
评论区