物流与发货管理

本页是一篇完整方案指南:以 logistics 插件(com.auraboot.logistics,命名空间 lg)为例,从用户场景一路讲到开发实施和典型错误。它是 config 型插件——5 个 model、12 条命令、8 个页面全部是 DSL JSON 声明,没有一行后端 Java;下面每个标识符你都能在插件里 grep 到、对运行实例调得通。

它依赖 product-cataloginventoryorg-management 三个插件(见 plugin.jsondependencies),先导入它们再导入本插件。


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_namelg_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_qtydocumentConfig.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_locationlg_te_event_time
lg_delivery_note送货单(POD)编号 DN-{yyyyMMdd}-{seq};状态 lg_dn_status:pending / signed / disputed;lg_dn_signed_bylg_dn_delivery_date

lg_shipmentextension.documentConfig 里声明了 lineModel: lg_shipment_linecodePattern: LG-{yyyyMMdd}-{seq}totalFields,因此子表数量会自动汇总到单头 lg_sh_total_qty

4.2 命令与状态机

命令命名是冒号 + 动词_名词:lg:<动词>_<名词>。12 条命令:

命令type作用
lg:create_shipment / lg:update_shipmentcreate / update新建 / 编辑发货单
lg:confirm_shipmentupdate确认发货(→ confirmed)
lg:pickup_shipmentupdate揽收,填运单号(→ picked_up)
lg:deliver_shipmentupdate签收(→ delivered)
lg:cancel_shipmentupdate取消发货(→ cancelled)
lg:create_tracking_eventcreate追加一条跟踪里程碑
lg:create_carrier / lg:update_carrier / lg:delete_carriercreate / update / delete承运商增改删
lg:create_delivery_notecreate新建送货单(POD)
lg:sign_delivery_noteupdate签署送货单(→ 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,并通过 autoSetFieldslg_sh_status 固定设为 picked_up,要求 lg.logistics.execute 权限。新建类命令(如 lg:create_shipment)则用 autoSetFieldsauto_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 插件,步骤是:

  1. 定义 model 与字段 —— config/models.json + 一字段一文件的 config/fields/,字段类型用平台 dataType(string / decimal / date…),长度走 constraints.maxLength 而不是写 string(50) 内联长度。单据型 model 在 extension.documentConfig 里声明 lineModelcodePatterntotalFields。详见 Model 与 Field
  2. 声明命令 —— 每个动作一个 config/commands/<code>.json。新建用 type: create,状态流转用 type: update + autoSetFields(fixed_value 写状态、auto_generate 生编号),并在 permissions 绑权限码。详见 Command
  3. 配 model-field binding —— config/bindings/<model>.json,每条声明 modelCode / fieldCode / sequence / required / visible / editable / displayConfig,并在 plugin.jsonresourceDirs.modelFieldBindings 指向该目录(见「典型错误」)。
  4. 设计页面 —— config/pages/,列表/表单用 Page Designer 出 DSL,并在 menus.jsonpageKey 挂菜单。
  5. 权限、角色、字典、菜单 —— config/permissions.json / roles.json / dicts.json / menus.json,在 resourceDirs 一一注册。
  6. 声明依赖并打包导入 —— plugin.jsondependencies 列出 product-cataloginventoryorg-management;用 aura CLI 的 import-directory-sync(参数是目录 path)或平台导入接口;校验返回 success:true 才算导入成功。详见 插件清单

6. 常见配置

  • 承运商与服务类型:在 lg_carrier 维护承运商,lg_cr_service_typeexpress / standard / freight / air / sea;lg_cr_tracking_url 存追踪 URL 模板,前端可拼运单号跳转。
  • 发货单编号:lg:create_shipmentauto_generateLG-{yyyyMMdd}-{seq} 自动生成 lg_sh_code,不要让用户手填编号。
  • 运费对账与溯源:lg_sh_freight_cost 记运费、lg_sh_source_order_id 记来源销售/采购订单号(已配 searchable),财务可从发货单列表直接搜索回溯。
  • 跟踪事件追加:每发生一站就用 lg:create_tracking_event 追加一条,lg_te_event_typepicked_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/UPDATE lg_shipment 会跳过鉴权、审计和事件投递,状态机失效。一切状态变更都走命令
  • bindingRules 写进 commands.json 内联:不会被导入;必须独立 binding 文件并在 resourceDirs 注册(本插件用 config/bindings/ 目录),否则报 [S-EXT-HANDLER] references unregistered handler。详见 纯配置 Plugin
  • 命令执行 payload 结构:字段放在 { "payload": { ... }, "operationType": ... },目标记录用 targetRecordId(不是 recordId)——放错位会出现「执行成功却字段为空」的迷惑性报错。
  • 漏装依赖插件:logistics 依赖 product-cataloginventoryorg-management,先导入这三个再导入本插件,否则引用校验(validateReferences: true)会失败。

下一步

  • 系统总览 —— 插件、命令与运行时如何拼到一起
  • 命令管道 —— 上面每条命令都走的执行契约
  • 权限 —— 角色与权限码背后的五层模型
  • 定价 —— 各版本能力对比