物流与发货管理
本页是一篇完整方案指南:以 logistics 插件(com.auraboot.logistics,命名空间 lg)为例,从用户场景一路讲到开发实施和典型错误。它是 config 型插件——5 个 model、12 条命令、8 个页面全部是 DSL JSON 声明,没有一行后端 Java;下面每个标识符你都能在插件里 grep 到、对运行实例调得通。
它依赖
product-catalog、inventory、org-management三个插件(见plugin.json的dependencies),先导入它们再导入本插件。
1. 用户场景
一家分销商对外发货,从仓库拣货打包到客户签收要经过多个角色:
- 仓库确认拣货打包 → 新建发货单 → 选承运商、填收发地址;
- 发货单确认 → 承运商揽收(填运单号)→ 全程追加跟踪事件(在途、到达中转、派送中)→ 客户签收;
- 签收时生成送货单(POD)并由收货人签署,作为对账与回款凭据;
- 草稿或已确认的发货单可以取消;运费(
lg_sh_freight_cost)和来源订单号(lg_sh_source_order_id)用于后续对账与溯源。
仓管、协调员、财务、客服看到的发货视图和能做的操作各不相同。
2. 需求痛点
- 状态散在表格和电话里:一张发货单要触达仓库、承运商、收货方、财务,Excel + 电话串起来,容易丢单、运费失控。
- 状态变更无授权无审计:谁在什么时候把发货单推进到了哪一步、为什么,没有记录。
- 跟踪事件难溯源:某张单到底在哪一站、是否异常,事后查不到一条连续的里程碑日志。
- 集成困难:销售/采购订单想自动触发发货,却只能靠人工二次录入。
这些都不是「再加一张表」能解决的,而是受控状态变更的问题。
3. 产品方案
在 AuraBoot 里,每一个发货动作都是一条命令,走统一的 命令管道:
- 发货单的「确认发货」按钮调用
lg:confirm_shipment,而不是裸写发货单表;命令在管道里统一鉴权 → 校验 → 执行 → 审计 → 发事件。 - 发货单状态记在
lg_shipment.lg_sh_status上,状态流转命令用autoSetFields把状态字段设到目标值(draft → confirmed → picked_up → delivered,或cancelled),不允许绕过命令直接PUT表。 - 跟踪里程碑写进独立的
lg_tracking_event,每发生一站就追加一条不可变记录;签收后生成lg_delivery_note作为送达凭据。 - 同一条命令既是 UI 按钮的目标,也能被自动化规则、BPM 流程、AI agent 调用,销售单/采购单因此能自动触发发货动作。
4. 功能设计
4.1 数据模型(5 个)
| Model | 用途 | 关键字段 / 状态 |
|---|---|---|
lg_carrier | 承运商主数据 | lg_cr_name、lg_cr_service_type(express / standard / freight / air / sea)、lg_cr_tracking_url;状态字典 lg_carrier_status:active / inactive |
lg_shipment | 核心发货单 | 编号自动生成 LG-{yyyyMMdd}-{seq}(lg_sh_code);状态 lg_sh_status:draft / confirmed / picked_up / in_transit / delivered / exception / cancelled;lg_sh_freight_cost(运费)、lg_sh_source_order_id(来源订单) |
lg_shipment_line | 发货明细(子表) | 父字段 lg_shl_shipment_id;lg_shl_qty 经 documentConfig.totalFields 汇总到单头 lg_sh_total_qty |
lg_tracking_event | 跟踪事件(追加式) | lg_te_event_type(字典 lg_tracking_event_type:created / picked_up / in_transit / arrived_hub / out_for_delivery / delivered / exception / returned)、lg_te_location、lg_te_event_time |
lg_delivery_note | 送货单(POD) | 编号 DN-{yyyyMMdd}-{seq};状态 lg_dn_status:pending / signed / disputed;lg_dn_signed_by、lg_dn_delivery_date |
lg_shipment 在 extension.documentConfig 里声明了 lineModel: lg_shipment_line、codePattern: LG-{yyyyMMdd}-{seq} 和 totalFields,因此子表数量会自动汇总到单头 lg_sh_total_qty。
4.2 命令与状态机
命令命名是冒号 + 动词_名词:lg:<动词>_<名词>。12 条命令:
| 命令 | type | 作用 |
|---|---|---|
lg:create_shipment / lg:update_shipment | create / update | 新建 / 编辑发货单 |
lg:confirm_shipment | update | 确认发货(→ confirmed) |
lg:pickup_shipment | update | 揽收,填运单号(→ picked_up) |
lg:deliver_shipment | update | 签收(→ delivered) |
lg:cancel_shipment | update | 取消发货(→ cancelled) |
lg:create_tracking_event | create | 追加一条跟踪里程碑 |
lg:create_carrier / lg:update_carrier / lg:delete_carrier | create / update / delete | 承运商增改删 |
lg:create_delivery_note | create | 新建送货单(POD) |
lg:sign_delivery_note | update | 签署送货单(→ signed) |
这个插件的状态流转命令不用 state_transition 类型,而是用 type: update + autoSetFields 把状态字段固定写到目标值。例如确认揽收 lg:pickup_shipment(config/commands/lg_pickup_shipment.json)的真实定义:
{
"code": "lg:pickup_shipment",
"displayName:zh-CN": "揽收",
"displayName:en": "Pick Up Shipment",
"type": "update",
"modelCode": "lg_shipment",
"inputFields": ["lg_sh_tracking_no"],
"autoSetFields": {
"lg_sh_status": { "strategy": "fixed_value", "value": "picked_up" }
},
"permissions": ["lg.logistics.execute"]
}读出来的设计信息:这是一条 update 命令,接收用户输入的运单号 lg_sh_tracking_no,并通过 autoSetFields 把 lg_sh_status 固定设为 picked_up,要求 lg.logistics.execute 权限。新建类命令(如 lg:create_shipment)则用 autoSetFields 的 auto_generate 策略按 LG-{yyyyMMdd}-{seq} 生成编号、把初始状态设为 draft。
4.3 权限与角色
4 个权限码(<模块>.<资源>.<动作>):
lg.logistics.manage 新建/编辑/删除发货单、承运商、送货单
lg.logistics.read 查看发货单、承运商、跟踪事件、送货单
lg.logistics.execute 揽收/签收、记录跟踪事件、签署送货单
lg.dashboard.logistics 查看物流看板
插件自带 1 个角色 lg_logistics_coordinator(物流协调员),持有以上全部 4 个权限。其余角色按你的组织自行配置:例如只读岗只给 lg.logistics.read,一线操作岗给 lg.logistics.execute 但不给 manage。注意命令上绑的是权限码,管理类命令要 manage、作业类命令要 execute——拆角色时按这个粒度发权限。
4.4 页面
8 个页面(config/pages/):每个核心 model 一个 list + 一个 form——发货单、承运商、跟踪事件、送货单各一对;menus.json 把它们挂在「物流管理」目录下,菜单项绑 lg.logistics.read 权限。
5. 具体开发与实施
先掌握基础。本插件没有任何后端 Java,全靠平台的几个核心契约。动手前请先读:Model 与 Field · Command · 命令管道 · Permission · 插件清单 · 纯配置 Plugin · Page Designer。
落地一个像 logistics 这样的 config 插件,步骤是:
- 定义 model 与字段 ——
config/models.json+ 一字段一文件的config/fields/,字段类型用平台dataType(string/decimal/date…),长度走constraints.maxLength而不是写string(50)内联长度。单据型 model 在extension.documentConfig里声明lineModel、codePattern、totalFields。详见 Model 与 Field。 - 声明命令 —— 每个动作一个
config/commands/<code>.json。新建用type: create,状态流转用type: update+autoSetFields(fixed_value写状态、auto_generate生编号),并在permissions绑权限码。详见 Command。 - 配 model-field binding ——
config/bindings/<model>.json,每条声明modelCode/fieldCode/sequence/required/visible/editable/displayConfig,并在plugin.json的resourceDirs.modelFieldBindings指向该目录(见「典型错误」)。 - 设计页面 ——
config/pages/,列表/表单用 Page Designer 出 DSL,并在menus.json用pageKey挂菜单。 - 权限、角色、字典、菜单 ——
config/permissions.json/roles.json/dicts.json/menus.json,在resourceDirs一一注册。 - 声明依赖并打包导入 ——
plugin.json的dependencies列出product-catalog、inventory、org-management;用auraCLI 的import-directory-sync(参数是目录 path)或平台导入接口;校验返回success:true才算导入成功。详见 插件清单。
6. 常见配置
- 承运商与服务类型:在
lg_carrier维护承运商,lg_cr_service_type选express/standard/freight/air/sea;lg_cr_tracking_url存追踪 URL 模板,前端可拼运单号跳转。 - 发货单编号:
lg:create_shipment用auto_generate按LG-{yyyyMMdd}-{seq}自动生成lg_sh_code,不要让用户手填编号。 - 运费对账与溯源:
lg_sh_freight_cost记运费、lg_sh_source_order_id记来源销售/采购订单号(已配searchable),财务可从发货单列表直接搜索回溯。 - 跟踪事件追加:每发生一站就用
lg:create_tracking_event追加一条,lg_te_event_type选picked_up/in_transit/arrived_hub/out_for_delivery/delivered/exception/returned;lg_tracking_event是不可变事务日志,只追加不改。 - 送货单签署:签收时
lg:create_delivery_note生成 POD(初始pending),收货人确认后lg:sign_delivery_note推到signed,争议件标disputed。
7. 典型错误
- 命令码用点号:写成
log.shipment.confirm跑不通——真实是冒号 + 动词_名词:lg:confirm_shipment。模型/字段前缀也是lg_不是log_。 - 把状态命令写成
state_transition:本插件的状态流转命令是type: update+autoSetFields.lg_sh_status(fixed_value),不是state_transition+fromStates/toState。照搬别的插件的命令 shape 会导入失败。 - 绕过命令直接改发货单表:直接
PUT/UPDATElg_shipment会跳过鉴权、审计和事件投递,状态机失效。一切状态变更都走命令。 - bindingRules 写进 commands.json 内联:不会被导入;必须独立 binding 文件并在
resourceDirs注册(本插件用config/bindings/目录),否则报[S-EXT-HANDLER] references unregistered handler。详见 纯配置 Plugin。 - 命令执行 payload 结构:字段放在
{ "payload": { ... }, "operationType": ... },目标记录用targetRecordId(不是recordId)——放错位会出现「执行成功却字段为空」的迷惑性报错。 - 漏装依赖插件:
logistics依赖product-catalog、inventory、org-management,先导入这三个再导入本插件,否则引用校验(validateReferences: true)会失败。