POS 离线同步

Odoo POS 离线单为什么有时会“跑进新会话”:sync_from_ui、_get_valid_session 与 rescue session 讲透

很多人以为 POS 离线单回传服务器时只是“补写一张订单”,但 Odoo 真正在处理的是会话合法性、客户端脏数据、逐单同步和异常兜底。本文从前端 syncAllOrders 到后端 _process_order / _get_valid_session 讲清楚:为什么旧会话关了以后订单还能进来、为什么会出现 rescue session、以及遇到未同步订单时该怎么排错。

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

很多人对 Odoo POS 的第一层理解是:

断网时先存在本地,来网后再补传到服务器。

这句话没错,但太粗了。 真正让人踩坑的,不是“会不会补传”,而是:

  • 为什么有的订单恢复网络后马上出现,有的却拖很久;
  • 为什么原来的 session 已经关了,订单还能进系统;
  • 为什么有时明明是同一个收银台,后台却冒出新的 rescue session;
  • 为什么多台 POS 设备里,一台补传成功,另一台却开始读到一堆“旧单”。

结论先说:Odoo POS 的离线补传不是简单批量重放,而是一套“逐单同步 + 会话纠偏 + 多设备通知”的容错机制。 你看到的 rescue session,不是 bug 名字,而是 Odoo 在“原会话已不可写、但订单又必须落库”时的兜底策略。

先把主链路看清:前端不是一次性全量提交,而是逐单 sync

point_of_sale/static/src/app/services/pos_store.js 里,syncAllOrders() 做了几件很关键的事:

  1. 先判断当前是否离线;
  2. 从 pending 集合里挑出需要 create / update / delete 的订单;
  3. 不是整批原子提交,而是一单一单调用后端 pos.order.sync_from_ui
  4. 某一单失败时,不一定把所有单都废掉;
  5. 若发现 session 被切换,还会把前端手上的 draft 单重新绑定到新 session。

这解释了一个常见误区:

“我恢复网络了,为什么不是所有离线单一起秒到?”

因为源码就是按单同步。这样做的好处是,一张坏单不会拖死整批;坏处是,你会看到“有些单先进来,有些单还在排队”的表象。

后端真正关心的第一件事,不是金额,而是 session 还合不合法

订单到后端后,pos.order._process_order() 会先看 session_id。如果这张单来自的 session 已经是 closing_controlclosed,源码不会硬写,而是走 _get_valid_session()

_get_valid_session() 的逻辑非常直接:

  • 找原 order 关联的关闭会话;
  • 按同一个 config_id 再找一个仍然可用、未关闭的 session;
  • 找到就把订单改挂到这个新 session;
  • 找不到就报错,要求先开新会话。

所以离线订单“跑进新会话”不是随机行为,而是有意设计。 Odoo 在这里 prioritise 的不是“保持历史归属绝对不变”,而是“别让真实成交单丢失”。

rescue session 到底是什么

如果原会话已经不能承接,但同一 POS 配置下又需要一个新的会话来接盘,这时系统就会生成或使用 rescue session。

在源码里,pos.config 会区分:

  • 普通当前会话;
  • rescue=True 的恢复会话;
  • 当前配置已经打开了多少个 rescue session。

前端同步完成后,如果发现服务器返回了新的 session 数据,syncAllOrders() 还会主动把本地持有的 draft 单改绑到最新的 session,对旧 session 做替换清理。

所以 rescue session 的本质不是“多开了一个店”,而是:

给迟到的订单找一个合法的落点。

它解决的是“订单真实发生了,但原账期容器已经关了”的矛盾。

为什么 Odoo 不信前端金额,却还信前端订单骨架

很多人以为客户端 serialize 出来的数据,服务端会原样入库。其实并不会。

_process_payment_lines() 里,Odoo 会重新计算 amount_paid;在处理找零时,也会按后端支付方式再补 return line。源码的态度很明确:

  • 前端负责交出订单结构;
  • 后端负责确认金额语义;
  • 前端不是最终账务真相。

这也是离线架构里一个非常重要的边界:

POS 可以离线收银,但不能把“财务可信度”完全下放给浏览器。

多设备场景下,为什么一台补传后另一台也会被惊动

这部分很多实施顾问没注意。

订单同步成功后,后端会调用 config.notify_synchronisation(),通过 bus 把同步事件推给同配置或 trusted config 下的其他设备。前端的 DevicesSynchronisation 会收到 SYNCHRONISATION 通知,再去服务器读取 open orders、静态记录和被删除记录。

换句话说,离线补传不是“这台机子自己的私事”。 在共享会话或多终端模式下,它会触发整个 POS 前端状态重整。

这就是为什么你经常看到:

  • A 机补传后,B 机单据列表突然刷新;
  • 已取消的旧 draft 会被强制删除;
  • 新 session 信息会被其他设备感知到。

最容易误解的三件事

误区一:rescue session 说明数据坏了

不对。 rescue session 更多是在说明:数据没坏,但时机晚了。

误区二:离线订单一定回原 session 才算“正确”

不对。 如果原 session 已经进入 closing/closed,强塞回去反而会破坏关店与会计边界。

误区三:补传失败一定是网络问题

也不对。 它还可能是:

  • 当前没有可用 open session;
  • 配置或用户权限变了;
  • 客户、产品、支付方式等关联对象已失效;
  • 某张订单本身数据不再满足后端约束。

实战排错顺序

遇到“离线单不同步 / 跑进救援会话 / 多端状态混乱”,建议按这个顺序看:

  1. 浏览器前端是否仍把订单标记为 dirty / pending;
  2. 网络恢复后 syncAllOrders() 是否真的触发;
  3. 后端 sync_from_ui 有没有报错;
  4. 原 session 是否已经 closing_controlclosed
  5. 同一 config_id 下是否存在新的 open session;
  6. 是否生成了 rescue session;
  7. bus 同步后其他设备有没有读到旧 draft / deleted record;
  8. 订单关联的 partner、payment method、journal 是否仍有效。

最后的结论

Odoo POS 的离线同步,本质上不是“本地缓存恢复上传”这么简单,而是三层协作:

  • 前端逐单补传,尽量缩小失败面;
  • 后端检查 session 合法性,必要时改挂新会话;
  • 多设备通过 bus 重新对齐状态。

所以当你看到 rescue session 时,别先想着“系统乱了”,而要先理解这套机制真正想保护的是什么:

不是保护某个旧 session 的纯洁性,而是保护已经发生的交易不要丢。

DISCUSSION

评论区

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