先说结论
Odoo POS 的“关班前不能有挂单”并不是一句粗暴规则。
源码实际上把两类 draft 单分开处理:
- 今天就该处理的 draft 单,会直接阻止关班;
- 未来日期的预订单,可以脱离当前 session 留到后面再处理。
所以它的本质不是“有 draft 就一刀切”,而是:
按订单的时间语义,决定它到底属于当前收银班次,还是属于未来业务。
一、为什么 _check_if_no_draft_orders() 看起来严格得近乎不讲理
pos_session.py 里的 _check_if_no_draft_orders() 很直接:
- 找出 session 内 state = draft 的订单;
- 只要还有,就不允许验证 session;
- 还会把订单名列出来,要求收银员先支付或取消。
如果只看这段代码,你会觉得 Odoo 很死板:
- draft 就是不行,没得商量。
但问题是,这段逻辑保护的是:
- 当前班次资金、订单、库存与会计闭环。
只要“属于今天这一班”的草稿单还没落地,它就意味着:
- 订单责任尚未结束;
- 付款状态不完整;
- 后续关班数据可能不干净。
所以这条规则不是针对“草稿单很烦”,而是针对:
- 别把一个还没结清的当前班次事务硬塞进已关闭班次。
二、为什么 future preset order 又能例外
关键例外在 action_pos_order_cancel()。
这个方法会把 draft 单分成两组:
today_orders:没有preset_time或时间不晚于今天;next_days_orders:preset_time落在未来日期,且仍是 draft。
然后处理方式完全不同:
today_orders直接写成cancel;next_days_orders则把session_id = False,从当前 session 脱开。
这说明 Odoo 对未来预订单的理解,不是“未完成的当前挂单”,而是:
- 本来就属于未来业务时间窗的草稿订单。
既然它本来就不应该计入今天这班的结算闭环,那么在关班前把它从当前 session 脱离,就是合理的。
三、为什么未来预订单不能直接取消
源码里还有一个很容易被忽略的保护:
- 如果选中的订单
preset_time.date() > today,会抛错:不能取消未来的 delivery / pickup 订单。
这很有代表性。
Odoo 不希望收银员在关班压力下,顺手把未来预订单当普通挂单清掉。
因为从业务上看,这些订单常常代表:
- 预约取餐;
- 未来配送;
- 按时制作的餐饮订单;
- 需要保留的承诺。
所以系统给它的处理不是“取消”,而是“脱离当前班次”。
四、前端 closing popup 为什么也要展示 next days 信息
closing_popup.js 里有个 orderForNextDays 计算:
- 统计
preset_time > now且 state = draft 且有行的订单数量。
这说明前端关班弹窗不是只展示收银金额,它也在提醒操作者:
- 当前还有多少未来订单挂在系统里。
这一步的价值在于让收银员心里有数:
- 有些订单不是漏处理;
- 它们是未来订单,应该按未来业务继续存在。
也就是说,弹窗承担的不是纯粹会计确认,而是:
- 帮助人区分“必须清理的今天挂单”和“应保留的未来订单”。
五、为什么这套边界很适合餐饮和预约场景
如果所有 draft 单都必须当场支付或取消,很多带预定能力的 POS 就会很难用。
例如:
- 餐厅今天晚上为明天下午接的预订;
- 门店今晚录入了明天自提的订单;
- 预约型业务允许先占坑、后结账。
这些单据的共同点是:
- 它们确实是 draft;
- 但它们不属于当前收银会话的结算责任。
Odoo 用 preset_time 把这类订单从“今日挂单”里切出来,本质上是在给 POS 加一层时间维度。
六、最容易误解的几个点
误解 1:关班前只要有 draft 单就说明出错了
不对。未来预订单可能是正常业务结果。
误解 2:未来预订单就应该直接取消
不对。源码明确禁止把未来配送/自提订单当普通挂单清掉。
误解 3:session closing 只关心钱
不对。它也关心哪些订单在业务上属于当前班次。
误解 4:前端弹窗只是展示金额
不对。它也在帮助区分 today draft 与 next-day draft。
七、做定制时最该保留什么
如果你要改 POS 关班逻辑,最值得保留的是:
- 今天挂单与未来预订单分层处理;
- 未来订单优先脱离 session,而不是粗暴取消;
- 关班 UI 里继续把这两类订单区分给操作者看。
否则你很容易把“时间上合理存在的未完成订单”和“真正该清掉的挂单”混为一谈。
最后一句
理解 Odoo POS 的关班边界,重点不是“draft 单能不能存在”,而是看懂这条主链:
当前 session draft 单阻止关班 → future preset draft 单脱离 session → closing popup 把两类语义显式区分。
看懂以后你就会知道,Odoo 不是在容忍未完成订单,而是在按时间归属给它们分班。
DISCUSSION
评论区