企业 销售工时订阅

Odoo 企业版销售:按工时订阅为什么只统计当前账期的 timesheet

sale_subscription_timesheet 最关键的不是“工时能开发票”,而是 delivered qty 必须严格落在当期窗口里;否则老工时、冲红工时和本期工时会混在一起,账期边界立刻失真。

企业 销售
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

服务订阅按工时收费时,最容易出现的争议不是单价,而是“这几小时到底算哪一期”。如果系统把所有 timesheet 一把抓进 delivered qty,本月开票、上月补录、退款重算很快就会彻底打架。

这篇文章主要参考了以下企业版源码入口:

  • enterprise/sale_subscription_timesheet/models/sale_order_line.py
  • enterprise/sale_subscription_timesheet/models/account_move.py

一、这篇功能真正解决什么问题

sale_subscription_timesheet 要解决的是工时订阅的账期边界。它不是让 timesheet “能开票”就结束,而是要保证 delivered qty 只反映当前可计费窗口内、尚未被正确消费的工时记录。

二、核心链路怎么走

1. 先挑出真正属于工时订阅的销售行

_get_timesheet_subscription_lines() 只取同时满足 recurring_invoiceqty_delivered_method == 'timesheet' 的行。这样一次性服务、手工交付服务和普通订阅行不会混进来。

2. delivered qty 的 domain 被卡在账期窗口内

_prepare_qty_delivered() 会用 last_invoice_datenext_invoice_date 组出 domain,再叠加“未开票 / 已取消发票 / 与退款相关的已开票工时”条件。换句话说,系统不是“按工时总量开票”,而是“按当前账期可计费工时开票”。

3. 发票日期范围也跟着订阅逻辑改写

account.move._get_range_dates(order) 在遇到含 timesheet 交付的订阅单时,会把范围改成 last_invoice_datenext_invoice_date - 1 day。这让发票和 delivered qty 站在同一张时间尺子上。

三、新手最容易踩的坑

  • 以为 timesheet 一录入,下一张订阅发票就一定全部带上。
  • 以为退款只是财务动作,不影响工时是否还能被再次计入。
  • 以为 delivered qty 是简单累计字段。实际上它背后是严格 domain 运算。

四、实战落地时最该盯的点

  • 月底补录工时时,要先确认它想落在哪个账期,否则业务预期和系统结果会天然不同。
  • 处理退款时不要只看金额,要同步检查相关 timesheet 是否重新回到可计费集合。
  • 如果 delivered qty 看起来不对,先查 last_invoice_date / next_invoice_date,它们往往比工时内容本身更关键。

五、结论

工时订阅之所以难,不是因为 timesheet 复杂,而是因为它必须同时满足账期边界、开票消费和退款回滚三层逻辑。sale_subscription_timesheet 的价值,就是把这条时间边界守得很死。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。