POS 厨打分发

Odoo POS 厨打为什么不只是“点单后打一张纸”:kitchen printer、preparation display 与变更分发讲透

很多餐饮团队把 POS 厨房链路理解成“订单送厨房、打印一张单子”,但 Odoo 真正处理的是“哪些品类该发到哪个 printer、改数量和改备注时到底发新增还是撤销、别的终端改过单据时如何防止重复厨打,以及有无 preparation display 时同步策略为什么不同”。本文结合 point_of_sale 源码,把厨打分发的真实机制讲透。

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

餐饮门店最容易把 POS 厨房链路想简单。

常见想法是:

  • 前台点单,
  • 厨房收到,
  • 打印机打一张单,
  • 事情结束。

但 Odoo 源码告诉我们的恰恰相反。

它真正要解决的问题是:

  • 哪些商品该送去哪个厨房设备;
  • 一张单后续被修改时,到底该发新增、撤销还是备注更新;
  • 多终端同时操作同一桌单时,怎样避免重复打印;
  • 如果门店用的是 preparation display 而不只是纸质厨打,同步策略又该怎么变。

所以这条链真正重要的不是“能不能打印”,而是:

厨房收到的必须是正确的增量变更,而不是一堆重复或过期的整单快照。

先说结论

结合 pos.printerorder_change.jspos_store.js 相关代码,可以先抓住六个结论:

  1. 厨打不是对全部商品一股脑打印,而是按 pos.category 路由到不同 printer。
  2. Odoo 发送给厨房的核心数据不是“订单全文”,而是“相对上次发送结果的变化量”。
  3. 数量增加、数量减少、整行删除、备注变化,会被当成不同类型的厨房事件。
  4. combo 商品不会简单按父套餐路由,子行和父行都会参与 preparation category 判断。
  5. 如果别的终端先改过单,系统会先警告 order outdated,再决定如何同步。
  6. 是否配置 preparation display,会影响发送后要不要立刻再 sync 一次。

厨打路由的核心不是打印机,而是商品分类

pos_printer.py 里定义 pos.printer 时,很关键的字段是:

  • product_categories_ids
  • printer_type
  • proxy_ip / epson_printer_ip

这说明 Odoo 对“厨房打印机”的理解不是“某台硬件能不能连上”,而是:

  • 这台设备负责哪类商品;
  • 它的输出类型是什么;
  • POS 配置把哪些品类路由给它。

这就是为什么厨房分发的第一层问题永远是分类,不是网络。

如果一个 printer 只负责:

  • 饮品,
  • 或炸物,
  • 或甜点,

那么前台修改订单时,Odoo 不会把整张单都发过去,而是先按分类过滤出这个设备该知道的那部分。

厨房收到的不是“整张单”,而是变化量

order_change.js 的注释写得非常直白:

这套逻辑是根据 last_order_preparation_change,重新计算要发送给 preparation tools 的内容。

也就是说,厨房链路并不是每次都打印“当前订单完整内容”。

它真正做的是:

  • 记住上次已经发给厨房的状态;
  • 比较当前订单和上次状态;
  • 只把差异部分整理成要新增或要撤销的变化。

这点极其关键。

因为餐饮门店的桌单经常会发生:

  • 加菜,
  • 退菜,
  • 备注变化,
  • 同一桌多次补点。

如果每次都整单重打,厨房很快就会乱套。

Odoo 的目标不是“多打一份总没错”,而是“尽量只把新的事实发出去”。

数量增加、减少、删除,为什么会被视为不同厨房事件

changesToOrder() 中,Odoo 会把变化拆成:

  • new
  • cancelled
  • noteUpdate

这很像厨房世界里的三种完全不同语义:

1)new

这是“新增要做的东西”。

可能是:

  • 新增一份面;
  • 同一份菜数量加 1;
  • 补点一杯饮料。

2)cancelled

这不是普通修改,而是“之前发过的东西,现在要撤回”。

这对厨房尤其重要,因为撤销单和新单的处理方式完全不同。

3)noteUpdate

如果数量不变,但备注变了,例如:

  • 不放葱;
  • 少冰;
  • 加辣;

那它也不该被当成整行新单重打一遍。

Odoo 会专门把这种变化作为备注更新处理。

这说明系统在厨房链路里真正保护的是:

厨房要收到“业务语义准确”的变更,而不是模糊的重复文本。

combo 为什么会让厨打更复杂

getOrderChanges() 里有个很值得注意的判断:

  • 不只看当前行商品是否属于 preparation category;
  • 还会看 combo_parent_id 的商品;
  • 还会看 combo_line_ids 里的子项。

这说明 combo 场景下,厨房路由不是只看“父套餐”就完了。

因为厨房真正需要知道的是:

  • 这份套餐里到底有哪些制作项;
  • 哪些子项属于本打印机负责的品类;
  • 父套餐是否需要和子项一起被识别为一组变化。

这也是为什么餐饮套餐在 POS 前台看起来是一笔单,厨房侧却必须更细地理解。

多终端改单时,为什么系统会先说订单过期了

checkPreparationStateAndSentOrderInPreparation() 里有个很重要的保护:

如果本地保存的 last_order_preparation_change.metadata.serverDate 和服务器最新版本不一致,系统会弹出:

  • Order Outdated
  • 别的设备修改过订单
  • 你的修改可能覆盖别人

这不是多余提示,而是厨打链路里的关键防重机制。

因为如果 A 设备已经把某些变化发去厨房,B 设备还拿着旧状态继续算差异,很容易造成:

  • 重复厨打;
  • 撤销量算错;
  • 备注覆盖错对象。

所以 Odoo 会先让订单状态与服务器对齐,再继续发送后续变化。

厨房场景里,“最新状态是谁说了算”远比普通零售订单更敏感。

有无 preparation display,为什么会影响发送后的 sync

sendOrderInPreparation() 的最后有个很关键的分支:

  • 如果打印成功,
  • 且没有 pos.prep.display
  • 才会 syncAllOrders({ orders: [order] })

注释也写得很清楚:

  • 这样别的设备才能知道这些变化已经发过;
  • 否则多台设备可能重复打印同一变化;
  • 如果已经有 preparation display,没必要做多余同步。

这里能看出 Odoo 对两类厨房工具的不同理解:

纸质厨打场景

打印一旦完成,别的终端必须尽快知道“这些变化已经送出”,否则很容易重复打印。

preparation display 场景

显示屏本身就承担更持续的同步角色,不一定需要用同样方式做额外补 sync。

这就是为什么“厨房有屏和没屏”,在 Odoo 里不只是设备差异,而是分发策略差异。

为什么厨打问题常常不是打印机问题

门店现场一遇到厨房异常,很容易先怪:

  • 打印机坏了;
  • 网络断了;
  • IoT 有问题。

这些当然可能,但源码告诉我们,更常见的问题其实是:

  • 品类没配到正确 printer;
  • 订单变化不是新增而是取消;
  • 备注更新被误解成没发单;
  • 终端之间版本不同步;
  • combo 子项路由和预期不一致。

也就是说,很多“厨打没出来”不是硬件故障,而是系统在按自己的变更分发规则工作,只是门店没有意识到。

实战排查顺序

当厨房打印或 preparation display 行为异常时,建议按这个顺序查:

  1. 该 printer 绑定了哪些 product_categories_ids
  2. 问题商品是否真的落在这些 category 里;
  3. 当前变化是新增、取消,还是备注更新;
  4. last_order_preparation_change 是否已经记录过旧状态;
  5. 是否存在多终端改单导致的 outdated 提示;
  6. combo 商品是否让父子项都参与了分类判断;
  7. 门店是否配置了 preparation display,从而影响发送后的 sync 逻辑。

最后的理解方式

如果只记一句话,我建议记:

Odoo POS 的厨房链路不是“打一张厨房单”,而是“把订单变化按品类、按语义、按设备正确分发出去”。

真正被保护的不是打印动作本身, 而是:

  • 厨房不要漏做,
  • 不要重复做,
  • 不要做错备注版本,
  • 不要把别的终端的旧状态当成最新事实。

这也是为什么 Odoo 的厨打与 preparation display,看起来只是设备功能,实际上却是一整套变更分发机制。

DISCUSSION

评论区

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