很多人第一次看 Odoo POS,会把启动流程想得很简单:
打开前台,向后端拉一坨数据,页面就能用了。
这句话只对了一半。
真正的 Odoo POS 更像一个“带本地数据库的前端应用”:它不只会从服务器拿数据,还会缓存、增量更新、筛掉本地失效记录,并在字段关系变化时重建本地结构。
先说结论:Odoo POS 的启动与刷新,不是单纯 RPC 拉全量,而是“字段关系声明 + IndexedDB 缓存 + last_server_date 增量加载 + 本地无效数据清理”的组合拳。 如果你把它只当成一个普通网页,很容易误判很多“刷新后为什么还记得旧数据”或“为什么全量重载后问题又好了”的现象。
第一步:先拉的不是业务数据,而是“怎么读数据”
在前端 data_service.js 里,POS 启动前会先调用 pos.session.load_data_params()。
这一步返回的不是订单、商品本身,而是每个模型的:
fieldsrelations
也就是说,前端先问服务器:
这次 POS 允许加载哪些模型、每个模型有哪些字段、字段之间怎么关联。
为什么这一步重要?因为 POS 前端用的是 related models 体系,本地对象之间不是扁平 JSON,而是有连接关系的。没有这份“数据结构说明书”,后面就没法可靠地把缓存数据恢复成可用对象图。
第二步:POS 真有自己的本地数据库
data_service.js 明确使用 IndexedDB 存服务器数据缓存。
这意味着:
- POS 刷新页面时,不一定立刻丢掉所有主数据;
- 即使网络暂时不好,也可能先从本地缓存恢复;
- 某些模型会被当成 unique models 处理,直接覆盖;
- 某些模型会走合并逻辑,而不是简单 replace。
所以“浏览器一刷新,为什么商品和配置还在”并不神秘,因为它本来就被设计成能从 IndexedDB 恢复。
loadInitialData() 真正在做什么
loadInitialData() 的思路大致是:
- 先从 IndexedDB 读取本地缓存;
- 根据 session 状态、当前 session id、是否从 backend 进入等条件,决定要不要重新从服务器取;
- 若要取服务器数据,则带上
pos_last_server_date和pos_limited_loading; - 服务器按这些上下文决定是做增量加载还是更多数据返回;
- 最后把本地缓存和服务器新数据融合。
这就解释了一个常见现象:
- 有时刷新非常快,因为大部分数据走了本地缓存;
- 有时会明显慢很多,因为触发了更重的数据重载。
pos_last_server_date 在保护什么
pos.load.mixin 里有 _server_date_to_domain():当上下文里存在 pos_last_server_date 且启用了 pos_limited_loading 时,很多模型的 domain 会自动加上 write_date > last_server_date。
这代表 Odoo 的增量加载不是“前端自己猜哪些改过”,而是:
- 由服务器掌握最后同步时间;
- 再按
write_date缩小返回集。
注意一个很关键的边界:
pos.session、pos.config不走这层 limited loading 过滤;- 其他模型大多可以按改动时间做增量。
所以如果你发现配置级变化更容易触发重载,而商品、伙伴、税等模型更适合增量,这正是源码有意为之。
为什么配置变更后会触发“看起来像全量重建”的行为
loadInitialData() 会比较本地 pos.config._data_server_date 和后端 odoo.last_data_change。
如果服务器记录的配置变动比本地缓存新,前端会:
- reset IndexedDB;
- 重新 init 本地结构;
- 重新拉数据。
这很重要,因为字段结构或关键配置变了以后,继续相信旧缓存反而更危险。
所以全量 reload 不是浪费,而是在承认:
有些变化不是补几条 record 就能修好的,必须把本地缓存世界整体重建。
为什么本地旧数据不会永远赖着不走
很多人一看到本地缓存,就担心“那失效记录岂不是会永远留着”。Odoo 其实也考虑到了。
前端会把本地已有记录 id 送给 pos.session.filter_local_data();后端会检查:
- 记录是否还存在;
- 是否因
active=False等原因已不再 relevant; - 某些取消订单是否应强制前端删除。
返回后,前端再把这些记录从 IndexedDB 以及内存数据里清掉。
这套链路的价值在于:POS 的缓存不是只会长胖,不会瘦身。
models.loadConnectedData() 在做什么
相关模型系统不是简单把 JSON 塞进 store。它会:
- 按模型主键判断已有记录还是新记录;
- 对已有记录做 sanitize 与 reconnect;
- 恢复 UI state;
- 在需要时触发 update 事件。
这解释了另一个常见现象:有些数据变化后,前端不是“整页白屏重来”,而是某些对象平滑地被更新。
为什么“Reload Data”有时能救命
pos_store.reloadData(fullReload) 会 reset IndexedDB;若用户选择 full reload,还会在 URL 上加 limited_loading=0,强制下一轮走更重的加载。
所以这个按钮之所以常能解决“数据怪异”,不是玄学,而是它在做两件很实的事:
- 丢掉可能已不可信的本地缓存;
- 让下一次加载不再过度依赖增量条件。
最容易误解的三件事
误区一:POS 刷新就是重新请求一次接口
不对。它先读缓存,再决定是否增量拉取,必要时还会重建 IndexedDB。
误区二:本地缓存意味着旧数据一定会越积越多
不对。filter_local_data() 就是专门拿来清理失效记录的。
误区三:全量 reload 只是“多等一会儿”
也不对。它常常意味着放弃一套可能已不可信的本地数据世界。
实战排错顺序
遇到“POS 刷新后数据不对 / 商品还在但配置不对 / 本地像卡着旧数据”时,建议按这个顺序查:
- 先看
load_data_params()返回的字段与关系是否正确; - 看 IndexedDB 里是否还留着旧缓存;
- 看
pos_last_server_date与odoo.last_data_change谁更新; - 判断这次是否走了 limited loading;
- 看
filter_local_data()是否把失效记录正确清掉; - 必要时执行 full reload,验证问题是否来自旧缓存世界。
最后的结论
Odoo POS 的“像 App 一样”体验,不只是 OWL 前端做得像,而是它真的带着一套本地数据层在运行。
所以当你看到它刷新后仍记得商品、客户、配置、草稿单,不要简单说“浏览器缓存了”。更准确的说法是:
Odoo POS 在用声明式字段关系、IndexedDB、增量同步和本地清理,把自己做成一个能离线容错、能增量更新的前端数据系统。
DISCUSSION
评论区