跳到正文
原文
OpenEPCIS EPCIS 技术博客·· 1 天前精选AI 评分62

OpenEPCIS 发布 Odoo 连接器,把 ERP 数据发布为 EPCIS 2.0 事件

Your ERP Already Knows: OpenEPCIS for Odoo

AI 导读

OpenEPCIS 在 GitHub 公开 openepcis-odoo 连接器,含 7 个面向 Odoo 18 和 19 的插件,采用 LGPL-3 许可。

推荐理由

原文给出 Odoo 内嵌 EPCIS 连接器的模块划分、字段映射与发布队列设计,可为 DPP 数据来源与集成选型提供参考。

中文全文 · AI 翻译

每一场关于数字产品护照的讨论都会走到同一个时刻。数据模型已经达成一致,解析器已经上线,护照页面也能渲染出来,然后有人问:谁来为这一万一千个产品填写品牌、净重、原产国和制造商?答案通常是:一张电子表格和一个实习生。两者都不对,因为数据早就在 ERP 里了。它已经在那里待了好几年。公司正是用它来下单、拣货和开票的。

所以我们写了一个运行在 ERP 内部的连接器,今天它公开了:GitHub 上的 openepcis/openepcis-odoo,面向 Odoo 18 和 19 的七个插件,LGPL-3。

在产品上勾选 Publish to OpenEPCIS,保存,五分钟之内该产品就会成为目录中的一份文档,位于符合 GS1 标准的 Digital Link 解析器之后,其 Digital Link 和二维码会回到 Odoo 表单上以及可打印的标签上。公司联系人也会以 GS1 组织的形式同样发布出去。批次和序列号会跟随其产品一直下探到实例级别——每个批次一份文档,每个单元一份。而一旦货物开始流动,每一次已验证的调拨、每一个打包的托盘、每一次退货和每一张制造订单都会成为仓库中的一条 EPCIS 2.0 事件。扫描一个序列号,就能回答护照存在的两个问题:这是什么,以及它经历了什么。

七个插件,因为 Odoo 是模块化的,我们也是

这个连接器是沿着 Odoo 自身的模块边界切分的,这是刻意的选择,而不是历史遗留的偶然。

基础插件 openepcis_connector 只依赖 product,别无其他。它可以安装在一个完全没有仓库功能的 Odoo 上。所有需要其他 Odoo 应用的功能都是桥接,会在该应用存在时自行安装:

插件依赖新增功能
openepcis_connectorproduct将产品和联系人作为 GS1 主数据、Draw GTIN、字段映射、首次加载向导
openepcis_connector_stockInventory批次作为 /10/<lot>,序列号作为 /21/<serial>
openepcis_connector_product_expiryExpiration Dates批次上的到期日期,使用其 GS1 名称
openepcis_connector_events上述两者调拨、打包、退货和提货作为 EPCIS 事件;用于接收合作伙伴事件的收件箱
openepcis_connector_events_mrpManufacturing一张制造订单作为一条 TransformationEvent
openepcis_connector_events_posPoint of Sale收银机的订单被读取为销售,而非发货
auth_oauth_end_sessionauth_oauth退出 Odoo 也会退出 Keycloak

数据库永远不会携带它未运行的应用的代码,也没有人需要决定安装哪些桥接。每个插件在文档中都有自己的页面,而它在 Odoo 应用列表中的磁贴上的 Learn More 按钮打开的正是那个页面。

连接器描述起来很简单,但要把它做对却很厚重。以下这些地方,正确的答案并不是显而易见的那个。

保存从不等待网络。产品在保存时入队,并由计划任务在后台发布。验证调拨时,会向发件箱写入一行然后返回。按下 Validate 的人对仓库宕机无能为力,而因此阻塞他们,就会把别人的故障变成停摆的装货区。

发件箱不相信 202。EPCIS 捕获是异步的:仓库回复已接受,之后再验证,并且仍可能拒绝。一个在收到 202 时就删除其行的队列,会为几分钟后被丢弃的事件报告成功。因此,一行会一直保留,直到仓库确认它已捕获该事件,只有到那时才会说明真正发生了什么。

字段映射是数据,不是代码。哪个 Odoo 字段对应哪个 GS1 术语,是一份由管理员编辑的记录列表,因为没有任何两个 Odoo 数据库会把品牌放在同一个地方。随附的行只是起点,升级不会撤销你的编辑。

生成 GTIN 和注册 GTIN 是两回事。向 GS1 注册无法撤销——一个背后没有产品数据的标识符既不能删除也不能停用。因此 生成 GTIN 会从你的公司前缀中取一个号码,并且只在产品保存时才注册它。为某个表单生成、随后被放弃的号码会被交还,而不是被烧掉。

表单在你发布之前就告诉你注册机构想要什么,而不是之后。解析器会报告每个发布渠道坚持要求哪些术语,产品表单会在你输入时保持该列表最新。它只告知,不阻止。护照是随着时间由多个人填写的,未满足的要求绝不能扣留数据作为人质。

ERP 中不存密码。Odoo 持有一个 OIDC 离线令牌,每次调用时用它铸造一个短期访问令牌,并从解析器自身的元数据(RFC 9728)中发现 Keycloak 领域。访问权限在 Keycloak 中撤销,而不是在 Odoo 中。

批次不是序列号。序列号标识一个单元,并进入事件的 epcList。批次标识一个集合,并进入 quantityList。未跟踪的货物也是一个集合——即贸易项目本身——这就是为什么一个什么都不跟踪的仓库仍然能产生有用的事件。

读取点不是可选项。EPCIS 允许事件不带读取点;我们不允许。一个说某事发生了却不说明在哪里发生的事件,只是半个答案,而半个答案比没有答案更糟,因为它们看起来是完整的。GLN 放在仓库或装货区上,其下的每个位置都继承它,而在没有 GLN 的位置之间转移会在 chatter 中报告,而不是在存储库中。

收件箱从不移动库存。合作伙伴关于我们货物的事件会显示在批次和包装上;它们是观察,不是单据,把观察过账到有值的库存中会让结账依赖于第三方的数据质量。唯一的例外是双重锁定:一个传入事件可以验证一个已经打开、已预留并正等待该确认的转移,并且仅当操作类型允许它并且合作伙伴受信任并且有人在阅读了一周的演练日志后关闭了仅观察模式。

文档走过了一个仓库的一天:六十公斤咖啡到货,四十八公斤装入两个箱子,箱子放到一个托盘上。一个箱子被取下并发货;一周后两公斤被退回。另一个箱子被清空,十公斤被研磨成另一种商品的四十袋,而那笔交付结果从未发生。九个屏幕,十二个事件,每个都从截图中实例的发件箱复制而来——包括撤回,因为事件无法从存储库中取出,而 EPCIS 有恰当的方式说“这没有发生”。

截图不是手工拍摄的。一个脚本驱动无头浏览器走过相同的屏幕,因此当插件变化时它们可以更新,而不是悄悄过时。每个标识符都在 GS1 952 测试范围内,每个主机都是占位符,并且该实例不向任何人发布。

如果你的系统不是 Odoo

关键不在于 PIM 与 ERP 之争,而在于连接器运行在哪里。Odoo 是唯一一个连接器位于宿主内部的系统:插件安装在 Odoo 中,保存时排队,并从那里发布。对于我们支持的其他所有系统——UnoPim PIM,以及 ERPNext 和 metasfresh ERP——连接器是我们的一项服务,它轮询系统的 REST API,将发现的内容映射到 GS1 术语并发布。宿主中完全不需要安装任何东西。

每条路线发布什么,取决于宿主知道什么。PIM 了解模型:UnoPim 提供产品和批次。ERP 还了解单个商品:ERPNext 提供产品、批次、序列号和参与方,metasfresh 提供产品和参与方。Odoo 更进一步,因为它还运行仓库——它是目前唯一将移动、包装、生产和销售作为事件报告的路线。

对于 UnoPim,有一个可选的 PHP 包,在产品表单上方放置一个面板——平台当前对该产品的说明,抽取一个编号,存入一个你已有的编号。该包是开源的:openepcis/openepcis-unopim,MIT。各条路线的共同点和差异点在于连接器如何工作。

git clone https://github.com/openepcis/openepcis-odoo.git
cd openepcis-odoo
docker compose up -d
# http://localhost:8069 — create a database, install "OpenEPCIS Connector"

然后是 Settings → General Settings → OpenEPCIS:解析器的 URL,以及在 OpenEPCIS Web 界面中签发的离线令牌。

README 中以粗体重复了一个警告,本文也将如此。不存在 GS1 沙盒。平台的 GS1 凭据就是生产凭据,注册的标识符一经注册便永久有效。请针对开发部署进行测试,开启操作员的 dry-run 标志,并使用 GS1 专为此保留的 952 范围内的标识符。

代码采用 LGPL-3,issue 公开,两个分支遵循 Odoo 的惯例——18.0 用于长期支持版本,19.0 用于最新版本。如果你的 Odoo 将品牌存放在我们未预料到的地方,映射由你自行编辑。如果它保存了我们没想到的内容,请告诉我们。

来源:OpenEPCIS EPCIS 技术博客 · openepcis.io