POS 餐饮出菜

Odoo 餐饮 POS 为什么不是“同桌就一单到底”:table 归并、course fire 与后厨出菜边界讲透

餐饮场景里,很多人以为 POS 桌台单就是一张不断追加的订单,但 Odoo 真正在管理的是“桌台上下文”和“出菜节奏”两条链。本文结合 pos_restaurant 源码,讲清 open order 为什么会按 table 合并、course 为什么要单独建模、fired/fired_date 为什么重要,以及遇到一桌多单、后厨节奏错乱时该怎么排错。

POS
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多人第一次接触 Odoo 餐饮 POS,会自然地把它理解成:

一张桌台单,不断往里加菜,最后一起结账。

这句话只说对了一半。 真正让餐饮场景和普通零售 POS 拉开差距的,不是“有桌台”这么简单,而是:

  • 同一桌的草稿订单什么时候应当合并;
  • 同一桌点的菜,什么时候应该进同一轮 course;
  • 后厨“已发单 / 未发单”的边界在哪里。

结论先说:Odoo 餐饮 POS 不是把零售订单硬套到餐桌上,而是额外引入了 table 上下文和 restaurant.order.course 节奏层。 这就是为什么它不是“有桌号的普通订单”,而更像“桌面会话 + 出菜批次”的组合。

为什么 open order 会按桌台合并,而不是只按 UUID 认单

pos_restaurant/models/pos_order.py 里,_get_open_order() 对餐饮配置做了特殊处理。

如果订单:

  • 带有 table_id
  • 状态仍是 draft

那么检索条件不是单纯找同 UUID,而是:

  • 同 UUID,或者
  • 同一桌台且仍是 draft 的订单。

这件事非常关键。它意味着在餐饮模式下,系统默认承认一种业务现实:

同一桌台的持续加单,本来就可能不是“一次创建、一口气完成”的单据行为。

这也是为什么实施现场常问:

  • 为什么服务员二次点单时没有新开一张单;
  • 为什么换设备后还能接着同桌点;
  • 为什么桌台还没买单,就继续叠在原单上。

答案通常不是前端缓存,而是后端 open order 识别策略本来就优先保留桌台上下文。

为什么 Odoo 要单独建一个 restaurant.order.course

pos_restaurant/models/restaurant_order_course.py 新建了模型 restaurant.order.course,字段很少,但意义很重:

  • order_id
  • line_ids
  • index
  • fired
  • fired_date
  • uuid

这说明 Odoo 对“餐饮出菜轮次”的理解,不是给订单行多加一个状态而已,而是专门抽出一层 course 对象。

为什么要这样设计?

因为餐饮里真正重要的不是“这道菜属于哪张单”,而是:

  • 它属于第几轮;
  • 这一轮有没有正式 fire 给后厨;
  • 后厨端和前台端能否围绕这轮批次达成一致。

如果只是给 pos.order.line 加一个 sent_to_kitchen=True/False,你就很难优雅表达:

  • 一轮里有哪些菜一起发;
  • 这轮什么时候发出去;
  • 新增菜是补进旧轮还是进入新轮。

而单独建 course 后,这个层次一下就清楚了。

firedfired_date 为什么是餐饮排错的关键字段

create()write() 里,只要 course 被标记为 fired=True,就会自动补 fired_date

看起来只是顺手记个时间,但这其实定义了后厨协作边界:

  • fired=False:这还是前台草拟节奏,没正式交给后厨;
  • fired=True 且有 fired_date:这轮已经成为厨房执行对象。

这和零售 POS 完全不同。零售更关心“是否支付”“是否打印”“是否出库”,而餐饮要在支付之前就先处理一层“是否送厨”。

所以如果一线人员反馈:

  • 菜品已经在桌面订单里了,但后厨没收到;
  • 同桌后加菜进了错误批次;
  • 已送厨的批次又被当作未送厨修改;

你第一反应不应只看订单状态,而要先看 course 是否 fired、何时 fired。

为什么订单行不是直接记 course 文本,而是 Many2one 到 course

pos_order_line.pypos.order.line 新增了 course_id。这说明订单行和 course 是明确的引用关系,而不是字符串标签。

这带来的好处有三点:

  1. 一轮 course 可以挂多行菜品;
  2. 订单行可以稳定跟随后厨批次,而不是跟随前端 UI 文本;
  3. 加单、拆单、同步时,course 仍然是可追踪对象。

换句话说,Odoo 这里在保护的是“出菜批次的可计算性”,不是“前台显示好不好看”。

一桌多设备协同时,系统真正在同步什么

read_pos_data() 会把 restaurant.order.course 一起装进 POS 数据里。这意味着餐饮多设备协同时,并不只是同步订单头和订单行,还要同步 course 层。

这件事很重要,因为很多人以为餐饮多端同步只是在抢同一张单,实际上同步对象至少有三层:

  • 订单头;
  • 订单行;
  • 出菜轮次。

如果只同步前两层,不同步 course,前台就会出现非常典型的问题:

  • A 设备看到这道菜已进第二轮;
  • B 设备却把它当第一轮未发单菜;
  • 后厨节奏与前台显示逐渐脱轨。

最容易误解的四件事

误区一:同桌就应该永远只保留一张单

不对。 系统更准确的说法是:同桌 draft 单倾向于被识别为同一 open order。 一旦结账、换桌、状态变化或业务流程切换,边界就不同了。

误区二:course 只是餐饮版的备注分组

不对。 course 不是 UI 分组,而是后厨节奏对象,带有 fired 状态和时间边界。

误区三:后厨没收到菜,一定是打印机或网络问题

不一定。 更常见的是这轮 course 还没 fire,或新增菜被放进了错误 course。

误区四:只看 pos.order.state 就能判断餐饮流程卡在哪

不对。 在餐饮里,订单是否 draft 只是一层。course 是否 fired 往往更早决定执行状态。

实战排错顺序

如果你遇到“一桌多单混乱 / 加菜进错轮次 / 后厨没收到 / 同桌重复单”,建议按这个顺序查:

  1. 当前 POS 配置是否启用了 module_pos_restaurant
  2. 订单是否仍是 draft
  3. 是否存在同一 table_id 的 open order;
  4. 二次点单时命中的到底是 UUID 还是 table 归并逻辑;
  5. 相关订单行的 course_id 是否正确;
  6. 对应 course 是否已 fired
  7. fired_date 是否合理记录;
  8. 多设备读到的 restaurant.order.course 数据是否一致。

最后的结论

Odoo 餐饮 POS 的深层设计,不是“给订单加张桌号图纸”,而是把餐饮场景拆成两层:

  • 桌台层:解决同桌持续加单、跨设备续单;
  • course 层:解决一桌订单内部的出菜批次与送厨边界。

所以如果你想真正理解餐饮 POS,不该只盯着“订单有没有合并”,而应盯住:

桌台上下文是怎么续上的,course 又是怎么把“已点”变成“已送厨”的。

DISCUSSION

评论区

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