Display track upsert request/response in batch job and order modals, relax price check dimensions with disclaimer UI, and update order guide and design docs.
9018 lines
356 KiB
Markdown
9018 lines
356 KiB
Markdown
# OMS 业务系统 — 表结构与接口设计
|
||
|
||
## 1 导读
|
||
|
||
### 1.1 这份文档解决什么问题?
|
||
|
||
1. **订单**:创建、预报、支付、状态机、费用与版本对齐、模板/批量任务、**客户发件地址簿**、**揽收订单**等。
|
||
2. **渠道**:销售产品、服务渠道、计价与发布、计重、分区、自动触发、标签等。
|
||
3. **操作**:入库、出库、面单打印等仓储作业。
|
||
4. **充值**:充值申请、审核及入账(**客户余额权威在 `t_customer.Balance`**;**余额变动统一写入 `t_customer_account_flow` 资金流水**,审核通过后由 OMS 客户服务更新余额)。
|
||
5. **系统主数据**:国家、字典、仓库地址、**其他配置(含小包轨迹服务)**、通知等 **`/system/*`** 能力。
|
||
|
||
|
||
### 1.2 文档定位
|
||
|
||
**OMS** 为 **业务系统**;认证、RBAC、内部用户、客户主数据等由 **`docs/rbac系统设计-060801.md`** 描述(与线上一致)。业务侧以 **`CustomerId`(`t_customer.Id`)** 表达订单/充值等归属;与 JWT 会话(**`mapId`**、**`loginType`**)的对齐见 **1.4**。**本篇不展开 RBAC 表与 `/rbac/*`、`/customer/*` 接口明细**。
|
||
|
||
### 1.3 业务模块一览
|
||
|
||
| 模块 | 章 | 说明 |
|
||
| --- | --- | --- |
|
||
| **订单** | **4** | 全生命周期、查价、模板与批量任务、发件地址、揽收订单 |
|
||
| **渠道** | **5** | 销售产品、服务渠道、计价、计重、分区、标签等 |
|
||
| **操作** | **6** | 入库、出库、打印 |
|
||
| **充值** | **7** | 充值申请、审核、入账;**客户账户资金流水** |
|
||
| **系统主数据** | **8** | 国家、字典、仓库、通知(`/system/*`) |
|
||
|
||
### 1.4 与客户 / 用户数据的边界
|
||
|
||
**客户主数据、子账号、内部用户、认证与 RBAC** 均在 **`docs/rbac系统设计-060801.md`**;**本篇不写**上述领域的表结构与 `/rbac/*`、`/customer/*` 接口明细。
|
||
|
||
#### 双 ID 与业务归属(与 RBAC 文档一致)
|
||
|
||
| 标识 | 来源 | OMS 用途 |
|
||
| --- | --- | --- |
|
||
| `user.Id` | RBAC GUID | JWT 主体;`/rbac/*` 管理接口 |
|
||
| `user.userid` | RBAC 自增 INT | JWT Claim **`mapId`**;`CurrentUser.GetId<int>()` |
|
||
| `t_customer.UserId` | OMS 客户表 | **等于** `user.userid`,桥接 RBAC 与 OMS |
|
||
| `t_customer.Id` | OMS 客户主键 | 业务表 **`CustomerId`** 外键 |
|
||
|
||
OMS 业务表中的 **`CustomerId`** 指向 **`t_customer.Id`**(客户业务主键),**不是** `user.userid`。
|
||
|
||
#### 登录主体与业务 CustomerId 解析
|
||
|
||
| `loginType`(JWT Claim) | 值 | 登录校验 | OMS 业务上下文 |
|
||
| --- | --- | --- | --- |
|
||
| 内部用户 | 0 | RBAC `user` 账号密码 | `mapId` 可作审计 `CreateUserId`;一般不直接作为 `CustomerId` |
|
||
| 客户(含主/子) | 1 | `t_customer` 手机/邮箱/编号 + MD5 密码 | 先用 **`mapId` 查 `t_customer.UserId`**,再取该行 **`t_customer.Id`** 作为业务 `CustomerId` |
|
||
|
||
**子客户**:`t_customer.ParentCustomerId` 非空表示子客户;登录 Token 的 `mapId` 仍是**该子客户自己的** `user.userid`。订单/充值等若按「主客户归属」落库,须将子客户解析为其 **`ParentCustomerId` 对应的主客户 `t_customer.Id`**(具体接口在实现时二选一:按登录客户自身 Id,或归并到主客户 Id,须在接口章节写死;**禁止**把 `mapId` 直接当作 `t_customer.Id`)。
|
||
|
||
**存在性、状态、余额、子客户权限** 以 **`/customer/*`** 与 RBAC 服务为准;**不得**仅凭请求体 Id 绕过鉴权。**订单模块**(`/admin/order/*`)另须遵守 **§4.4.0** 客户数据隔离。
|
||
|
||
**不在本篇设计的内容**:统一认证、RBAC 管理、内部用户、客户 CRUD、角色权限分配等 —— 见 **`docs/rbac系统设计-060801.md`**。
|
||
|
||
### 1.5 后端实现对照(oms0414-back)
|
||
|
||
> 对照仓库:`C:\Users\wilso\Desktop\oms0414-back`(`SimpleAdmin.sln`)。HTTP 由 **Fake 框架** 将 `ApplicationService` 上的 `[HttpGet]` / `[HttpPost]` 自动暴露为 API,**无手写 Controller**。结构说明见仓库内 **`系统结构说明.md`**。
|
||
|
||
#### 1.5.1 技术栈与路由约定
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 运行时 | .NET 8、SqlSugar + MySQL、JWT(Claim **`mapId`** = `user.userid`)、Swagger |
|
||
| OMS 业务路由 | `/order/*`、`/channel/*`、`/operation/*`、`/recharge/*`、`/system/*`、`/customer/*` |
|
||
| 认证 / RBAC | `/rbac/auth/*`(宿主 `AuthService` 覆盖)、`/rbac/*`(NuGet `Fake.Rbac.Application`) |
|
||
| 微信 | `/wechat/mp/*`、`/wechat/customer/*`、`/wechat/oauth/*` |
|
||
| 分页响应 | `OmsPageResult<T>` → `{ items, total }`;无分页列表 → `OmsItemsResult<T>` → `{ items }` |
|
||
| 分页默认值 | `OmsDocHelper.NormalizePage` / `NormalizePageSize`(默认 pageSize **30**) |
|
||
|
||
#### 1.5.2 ApplicationService 与模块映射
|
||
|
||
| 业务域 | Service 类(`Oms.Application/Services/`) | 路由前缀 |
|
||
| --- | --- | --- |
|
||
| 订单 | `OrderService`(+ `ParcelBilling.cs`、`ParcelQuote.cs`、`Helpers.cs`) | `/order/*` |
|
||
| 算价引擎 | `ParcelBillingCalculator`(静态,无 HTTP) | — |
|
||
| 销售产品 | `SalesProductService` | `/channel/sales-product/*` |
|
||
| 服务渠道 | `ServiceChannelService` | `/channel/service-channel/*` |
|
||
| KPI 分区 | `KpiAreaService` | `/channel/kpi-area/*` |
|
||
| 计重 / 进位 | `WeightRuleService`、`WeightRoundingService` | `/channel/weight-rule/*`、`/channel/weight-rounding/*` |
|
||
| 自动触发 / 标签 | `AutoTriggerService`、`LabelTemplateService` | `/channel/auto-trigger/*`、`/channel/label-template/save` |
|
||
| 问题模板 | `IssueTemplateService` | `/channel/issue-template/*` |
|
||
| 仓内作业 | `InboundRecordService`、`OutboundRecordService`、`LabelPrintService` | `/operation/*` |
|
||
| 充值 | `RechargeService` | `/recharge/*` |
|
||
| 客户账户流水 | `CustomerAccountFlowService`、`CustomerBalanceMutator` | `/customer/account-flow/*`(列表);余额变动由充值审核、订单支付触发 |
|
||
| 系统主数据 | `CountryService`、`DictService`、`NoticeService`、`WarehouseService` | `/system/*` |
|
||
| 客户 | `CustomerService` | `/customer/*`(契约见 RBAC 文档) |
|
||
|
||
#### 1.5.3 会话与客户隔离(`OmsSessionHelper`)
|
||
|
||
| 方法 | 行为 |
|
||
| --- | --- |
|
||
| `ResolveSessionCustomerAsync` | **`loginType=1`**:按 `mapId` 查 `t_customer.UserId`,得到 **`t_customer.Id`**;请求体 `customerId` 若传则须一致,否则 **「客户ID非法」**。**管理员**:须传 `customerId`(或 fallback `mapId` 作 Id 查客户),否则 **「客户ID不能为空」** |
|
||
| `ResolveListCustomerIdAsync` | **客户登录**:强制返回 `t_customer.Id` 用于列表过滤。**管理员**:返回 `null`,**不过滤** `CustomerId`(可查看全部客户订单/任务/充值) |
|
||
| `GetOrderOrThrowAsync` | 存在性校验后,客户登录时 **`order.CustomerId` 须等于会话 Id**,否则 **「订单不存在」** |
|
||
|
||
> **与 §4.4.0 的差异**:设计文档要求客户门户专用;**当前实现允许管理员(`loginType=0`)跨客户查询**,列表接口不注入 `CustomerId` 条件。
|
||
|
||
#### 1.5.4 设计 vs 实现:关键差异速查
|
||
|
||
| 能力 | 设计目标 | 当前实现(oms0414-back) |
|
||
| --- | --- | --- |
|
||
| 快照读取 | Redis + 进程内存为主,DB 降级 | **直接读** `t_sales_product_publish_record`;无发布记录时 **Live 组装**(`BuildLiveRulesSnapshotAsync` / `BuildLivePriceSnapshotAsync`),版本号写 **`LIVE-{salesProductId}`** |
|
||
| 预报应付费用 | §4.3.4.2 排货确认渠道 + 渠道买价 | **创建时不排货**;**入库**时 `TryConfirmServiceChannelAndPayableAsync`(失败 → **`pendingAllocation`** + `AllocationPendingReason`,不阻断收货/inbound 应收) |
|
||
| 入库触发应收/应付 | §6.3.1.1 收货计重后重算 | **已实现**:`/operation/inbound/create` → `ApplyReceiveBillingByWaybillAsync`(inbound 应收 + **可选排货**;成功 → `pendingPayment`,失败 → `pendingAllocation`) |
|
||
| 出库更新状态与发货件重尺 | §6.3.2.1 覆盖 `Type=ship` 并重算;**`OrderStatus=pendingOnline`** | **已实现**:`/operation/outbound/create` → `ApplyShipBillingByWaybillAsync` + 写 `t_outbound_record` |
|
||
| 批量 JSON 下单 | 异步任务 + `taskId` | **同步逐条**调用 `CreateParcelCoreAsync`,返回 **`batchNo` + `items[]`** |
|
||
| Excel 导入 | 解析后等同批量创建 / 异步 | **统一走** `t_order_batch_job`(`action=import-row`);MQ 开启异步入队,关闭则同步逐条处理;返回 **`jobId`** |
|
||
| 批量重算费用 | `POST /order/fee/batch-recalculate` | **未实现**(§4.4.1.10 为设计稿) |
|
||
| 订单支付 | 扣减 `t_customer.Balance` 并写 `t_customer_account_flow` | **已实现**:仅 `pendingPayment` 可支付;应收合计扣款;流水 `BizType=orderPay` |
|
||
| 面单 URL | 可选空 | 预报成功时生成 **HTML** 至 `/uploads/prints/orders/`,响应 **`labelUrl`** |
|
||
| 规则快照下游元数据 | 排货 Provider 含渠道/itfx 对接字段 | **已实现**:规则发布时 **`AllocationProviderSnapshotEnricher`** enrich `Providers`;排货确认写入 **`t_order`** 四字段;**AppSecret 不落快照/订单** |
|
||
| 轨迹 / 下游预报 | 预报后异步轨迹;下游 createOrderOne | **部分**:预报成功异步 UpsertSmallBag;「我的订单」可手动 **`POST /order/track-upsert-small-bag`**;下游预报由前端调 itfx **`ForwardWithSubscribe`**,凭证优先读订单绑定字段 |
|
||
| 创建问题件 / 理赔件 | §4.4.1.13~§4.4.1.15 批量标记 + 问题模板消费 | **已落地**(`OrderService.Exception` + `OrderExceptionWorkflowService`) |
|
||
| 问题件通知 | 按模板 `NotifyMethod` 投递 | **部分**:`inApp`(站内信)、`email`(操作人 SMTP)已接入;`sms/feishu/wechat` 待通道对接 |
|
||
| 自动触发运行时 | 下单/入库/出库消费 `t_auto_trigger` | **部分**:`AutoTriggerRuntimeService` 已挂钩;**问题件**联动问题模板,**拦截/警告**已支持 |
|
||
|
||
完整接口清单与实现状态见 **§10.6**;订单各接口「当前实现」见 **§4.4**;**渠道各接口总览见 §5.3.0**。
|
||
|
||
---
|
||
|
||
## 2 业务设计思路与主流程
|
||
|
||
### 2.1 职责边界:OMS 与 RBAC / 客户域
|
||
|
||
| 侧 | 职责 |
|
||
| --- | --- |
|
||
| **OMS(本文)** | 订单全生命周期、渠道配置与算价支撑、仓内作业、充值业务流水、**客户账户资金流水(`t_customer_account_flow`)**、`/system/*` 业务主数据(国家、字典、仓库地址、**其他配置**、通知等)。 |
|
||
| **RBAC / 客户域(另文)** | JWT 认证(`/rbac/auth/*`)、RBAC 用户/角色/菜单/组织(`/rbac/*`)、客户资料与 RBAC 联动(`/customer/*`)、权限码目录与分配、子客户专属角色 `CustomerSub_{userId}`。 |
|
||
| **协作约定** | OMS 用 **`t_customer.Id`** 作业务 `CustomerId`;会话侧用 **`mapId`(`user.userid`)** 定位客户行;**客户是否存在、状态、子客户权限、余额** 以 **`t_customer` + RBAC 服务** 为准,**禁止**把 `mapId` 直接当 `t_customer.Id`,**禁止**仅凭请求体 Id 当作已鉴权事实。 |
|
||
|
||
### 2.2 主流程 A:配置发布 → 运行时算价(渠道支撑订单)
|
||
|
||
**设计意图**:高并发查价/下单不走「每次打 DB 拉全量规则」;以 **版本化快照 + Redis + 计费实例内存** 为主路径,DB 发布表为审计与降级。
|
||
|
||
1. 运营维护销售产品、卖价/买价、分区(KpiArea)、计重、排货、服务渠道等。
|
||
2. **发布** 产生 **不可变版本**;结构化快照写入 **Redis**,计费服务加载 **本地内存**。
|
||
3. **查价与正式下单共用**:按绑定时刻(如 `bindTime`)选取应对生效的 **rules/price 版本** → 规则匹配(分区、计重、黑名单等)→ 出费用明细;查价响应宜带 **`rulesVersion` / `priceVersion`** 便于与订单对齐。
|
||
|
||
> **当前实现(oms0414-back)**:步骤 2~3 的 Redis/内存路径 **尚未接入**;运行时直接读 **`t_sales_product_publish_record`**,无发布时 **Live 组装**(见 **§4.3.5**)。
|
||
|
||
### 2.3 主流程 B:订单(客户侧业务链)
|
||
|
||
**设计意图**:**先锁「计费契约版本」、再按履约阶段分批落费**;历史订单不因后台新发版被静默改价。
|
||
|
||
1. **查价**(可选)→ **创建 / 预报** → 校验 → 草稿与包裹 → **预报计重完成后**将全部启用卖价项写入 **`t_order_fee`**(`ChargingStage=forecast`)。
|
||
2. **契约固化**:预报等关键节点写入 **`SalesProductRulesVersion`、`SalesProductPriceVersion`**(及渠道侧应付相关版本,若有);非显式「改单重算」流程 **不得** 因后台新发布而改写已存版本号。
|
||
3. **状态机**(概念顺序;枚举见 **`## 10 附录` → `### 10.3`**):如 草稿 → 已预报 → **待排货(可选)** → 待支付 → 待发货 → **待上网** → 运输中 → 派送成功/失败;另有拦截、**问题件**(`problematic`)、**理赔件**(`claimed`)、删除等分支。入库排货失败时进入 **`pendingAllocation`**,写入 **`AllocationPendingReason`**,仍保留收货与 inbound 应收。
|
||
4. **支付**:OMS 侧订单状态与支付动作对接;**扣减 `t_customer.Balance`** 并 **写入 `t_customer_account_flow`(`BizType=orderPay`,金额为负)**;支付金额取订单 **`t_order_fee` 应收合计**。
|
||
5. **分阶段计费(按件重尺类型)**:卖价/买价 **不再配置「收费时机」**;应收在 **`t_order_weight.Type=forecast` 或 `receive` 计重完成** 后触发,全部启用价格项参与;`t_order_fee.ChargingStage` 标记本次算价所用重量阶段(`forecast` / `inbound`)。出库阶段(`ship` 行)待 §6.3.2.1 接入。
|
||
6. **批量下单**:Excel/模板 或 JSON 批量接口 → 异步任务;**单次最多受理 2000 条**。
|
||
7. **揽收订单**(§4.4.4):客户维护 **发件地址簿**(§4.2.10 / §4.4.5)→ 创建时选 **发件地址 + 仓库 + 运输方式(销售产品)+ 揽收时间 + 货物名称/重量** → 服务端组装为小包创建入参 → **复用 `CreateParcelCoreAsync` / §4.4.3.1** 落 `t_order`(`OrderSource=pickup`)。**不是**仓储模块 `C_parcel` / 到货登记另开一套订单主表。
|
||
8. **店铺订单创建物流**(§4.4.7):平台店铺单勾选 → 校对收件人/明细 → 选运输方式 → **批量创建** `t_order`(`OrderSource=platform`)并回写 `LinkedOrderId`;若「物流&代发」则同弹窗内按单绑定包裹/库存(复用 §4.4.6 语义)。与「匹配库存」分流:后者依赖平台 SKU 映射并创建时扣减库存。
|
||
|
||
### 2.4 主流程 C:操作(仓库)
|
||
|
||
1. **入库扫描**:实重、尺寸等落库;**仅更新 `Type=receive` 件重尺**并重算;可触发 **`inbound` 阶段**应收计费。
|
||
2. **出库扫描**:**仅更新 `Type=ship` 发货件重尺**并重算计费重;订单 **待发货 → 待上网**,写入 **`ShipTime`**;可触发 **`outbound` 阶段**应收计费。
|
||
3. **标签打印**:按单号与模板输出,记录打印结果。
|
||
|
||
### 2.5 主流程 D:充值
|
||
|
||
1. 客户提交 → **`t_recharge_record` 待审核**。
|
||
2. 审核通过/驳回;**通过时** 经 **`CustomerBalanceMutator`** 增加 **`t_customer.Balance`** 并写入 **`t_customer_account_flow`(`BizType=recharge`,金额为正)**;**`t_recharge_record`** 保留充值申请与审核状态(**非**统一资金流水表)。
|
||
|
||
### 2.6 主流程 E:系统主数据
|
||
|
||
国家、字典、仓库地址、**其他配置(第三方集成参数,如小包轨迹 `track_service`)**、通知供全业务引用;**与客户/内部员工相关的 DictType** 定义以 **`docs/rbac系统设计-060801.md`** 为准。
|
||
|
||
### 2.7 设计不变量(推断与实现时须一致)
|
||
|
||
| 不变量 | 说明 |
|
||
| --- | --- |
|
||
| 版本锁定 | 订单一旦写入规则/卖价版本字段,后续后台发版 **不自动** 推翻该票货的计费契约。 |
|
||
| Live 快照 | 无发布记录时版本号为 **`LIVE-{salesProductId}`**,仍落库为订单契约(**§4.3.5**)。 |
|
||
| 版本与落费时机解耦 | **版本**在预报等节点锁定;**何时写入 `t_order_fee`** 由 **件重尺类型**(forecast/receive/ship)触发的算价流程决定,与卖价/买价配置项无关。 |
|
||
| 后阶段仍用旧版快照 | 入库/出库触发的算价,加载的是 **该订单已绑定 version** 的快照,不是「当前最新 version」。 |
|
||
| 客户与余额 | 校验客户、余额、子客户关系 → **`t_customer` + `/customer/*`**;OMS 持 `CustomerId`(`t_customer.Id`)与业务流程;**任意余额变动须同步写 `t_customer_account_flow`**,流水含变动前后余额。 |
|
||
| **订单客户隔离** | **`/admin/order/*`(服务内 `/order/*`)** 下全部增删改查须以会话解析的 **`effectiveCustomerId`** 过滤与鉴权;**禁止**跨客户读写订单及相关任务/模板。见 **§4.4.0**。 |
|
||
| 审计字段 | 写操作落 `Create*`/`Update*`;操作者来自 **JWT `mapId`**(`CurrentUser.GetId<int>()`),见 **`### 3.2`、`### 3.3`**。 |
|
||
|
||
### 2.8 本文档章节索引(从流程跳转到细节)
|
||
|
||
| 上述块 | 正文位置 |
|
||
| --- | --- |
|
||
| 文档定位、模块一览、权限边界 | **`## 1 导读` → `### 1.3`~`### 1.5`** |
|
||
| 订单、查价、费用、版本策略 | **`## 4 订单模块`**(**`### 4.3.5` 当前实现**、**`### 4.4.0` 客户隔离**、**`### 4.3` 下单计费与版本策略**、**`§4.4.6` 绑定**、**`§4.4.7` 店铺创建物流/代发**) |
|
||
| 产品/渠道/发布/分区/计重/标签 | **`## 5 渠道模块`**(**`### 5.3.0` 当前实现总览**) |
|
||
| 入库/出库/打印 | **`## 6 操作模块`** |
|
||
| 充值与账户流水 | **`## 7 充值管理`**(含 **`§7.4` 资金流水**) |
|
||
| 国家/字典/仓库/通知 | **`## 8 系统主数据`** |
|
||
| 需求迭代与历史差异 | **`## 9 需求迭代与变更记录`** |
|
||
| 枚举与路径速查 | **`## 10 附录`** |
|
||
|
||
---
|
||
|
||
## 3 公共规范
|
||
|
||
### 3.1 表结构设计公共规范
|
||
|
||
#### 3.1.1 表的字段命名规范
|
||
|
||
| 规范 | 说明 | 示例 |
|
||
| --- | --- | --- |
|
||
| 驼峰命名 | 字段名开头大写+单词驼峰 | `ServiceProviderId` |
|
||
| 单数名词 | 表名用单数,表示实体 | `order` 而非 `orders` |
|
||
| 业务前缀 | 表名按业务模块加前缀 | `t_order`、`t_channel` |
|
||
|
||
|
||
#### 3.1.2 必备字段(所有表)
|
||
|
||
> **强制**:凡继承 `OmsAuditedAggregateRoot` / Fake `AggregateRoot` 的 OMS 实体表,建表与增量迁移 **必须**包含下表全部字段。漏列会导致 SqlSugar 生成 SQL 时报错,例如:`Unknown column 'ConcurrencyStamp' in 'field list'`。
|
||
|
||
| 字段名 | 字段类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| Id | INT / BIGINT | 主键,自增(实现侧多为 `INT` IDENTITY) |
|
||
| IsDeleted | TINYINT | 软删除:0-否,1-是,默认 0 |
|
||
| **ConcurrencyStamp** | **CHAR(36)** | **并发戳(乐观锁版本)**;来自 Fake `AggregateRoot`,ORM 读写自动带上;更新时校验/轮换,防止并发覆盖写。业务代码一般无需手写。库表默认值用**固定 GUID**(见下),**禁止**迁移脚本写 `DEFAULT (UUID())` |
|
||
| CreateUserId | INT | 创建人 |
|
||
| CreateTime | DATETIME | 创建时间 |
|
||
| UpdateUserId | INT | 更新人(可空) |
|
||
| UpdateTime | DATETIME | 更新时间(可空) |
|
||
|
||
**建表 / 迁移检查清单**
|
||
|
||
1. 新建表:`CREATE TABLE` 必须含 `ConcurrencyStamp CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'`(建议放在 `IsDeleted` 之后)。
|
||
2. 增量脚本:若实体已有该属性而库表没有,须 `ALTER TABLE ... ADD COLUMN ConcurrencyStamp ...`(可参考 `sql/fix_concurrency_stamp_missing.sql`)。
|
||
3. **复制安全(Error/Note 1674)**:`DEFAULT (UUID())` / `UPDATE ... SET x = UUID()` 在 STATEMENT/MIXED binlog 下会被判为 unsafe(主从可能得到不同值)。迁移 DDL **一律用常量默认值**;真实 UUID 由应用插入/更新时写入。若必须用 `UUID()` 回填历史行,会话临时 `SET SESSION binlog_format='ROW'`(需权限)或接受仅在 ROW 复制拓扑执行。
|
||
4. 评审:新迁移 PR 对照本节必备字段,禁止再出现「实体有列、库表无列」。
|
||
5. 典型报错定位:接口返回 `Unknown column 'ConcurrencyStamp'` → 查 SQL 中的表名 → 对该表补列后重试。
|
||
|
||
**DDL 示例片段**
|
||
|
||
```sql
|
||
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
|
||
-- 禁止:DEFAULT (UUID()) → 复制环境 1674
|
||
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
|
||
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
|
||
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||
`UpdateUserId` INT NULL COMMENT '更新人',
|
||
`UpdateTime` DATETIME NULL COMMENT '更新时间',
|
||
```
|
||
#### 3.1.3 字段命名规范
|
||
|
||
| 规范 | 说明 | 示例 |
|
||
| --- | --- | --- |
|
||
| 布尔字段 | 用 `IsXxx` 或 `HasXxx` | `IsDeleted`、`IsActive` |
|
||
| 外键字段 | `关联表名Id` 或 `XxxId` | `CustomerId`、`WarehouseId` |
|
||
| 时间字段 | `XxxTime` 或 `XxxAt` | `CreateTime`、`PayTime` |
|
||
| 禁用保留字 | 不用 desc、range、match 等 | 用 `Description` 替代 `Desc` |
|
||
|
||
#### 3.1.4 数据类型规范
|
||
|
||
| 类型 | 说明 |
|
||
| --- | --- |
|
||
| 主键 | BIGINT,单表自增 |
|
||
| 金额/重量 | DECIMAL,禁止 float/double |
|
||
| 字符串 | varchar 长度 ≤ 5000,超出用 TEXT |
|
||
| 枚举 | 小数据量用 TINYINT,可读性要求高用 VARCHAR |
|
||
| 状态标志 | TINYINT |
|
||
|
||
### 3.2 接口设计公共规范
|
||
|
||
| 规范 | 说明 |
|
||
| --- | --- |
|
||
| 操作审计必填 | 任何新增、编辑、删除操作,必须记录操作者与操作时间 |
|
||
| 新增接口 | 创建成功后需落库 `CreateUserId`、`CreateTime` |
|
||
| 编辑接口 | 更新成功后需落库 `UpdateUserId`、`UpdateTime` |
|
||
| 删除接口 | 逻辑删除时需落库 `UpdateUserId`、`UpdateTime`,并更新 `IsDeleted=1` |
|
||
|
||
> 审计字段中的操作者信息优先从当前登录会话的 **`mapId`**(`CurrentUser.GetId<int>()`,即 `user.userid`)获取,不允许由前端直接信任覆盖。
|
||
|
||
### 3.3 OMS 与 RBAC 会话协作
|
||
|
||
与 **`docs/rbac系统设计-060801.md`** 对齐的 OMS 侧约定如下。
|
||
|
||
#### 3.3.1 认证接口(不在本篇逐条列出)
|
||
|
||
| 能力 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| 登录 | `POST /rbac/auth/login` | `type=0` 内部用户,`type=1` 客户 |
|
||
| 刷新令牌 | `POST /rbac/auth/refresh-token` | |
|
||
| 当前用户 | `GET /rbac/auth/get-current-user` | 返回 `roles`、`permissions`、`menus` |
|
||
| 改密 | `POST /rbac/auth/change-password` | 客户改 `t_customer` MD5,内部用户改 RBAC 哈希 |
|
||
|
||
请求头:`Authorization: Bearer {accessToken}`。
|
||
|
||
#### 3.3.2 从会话解析 OMS 业务主体
|
||
|
||
```
|
||
JWT → mapId(user.userid INT)
|
||
→ 查 t_customer WHERE UserId = mapId AND IsDeleted = 0
|
||
→ 得到 t_customer.Id 作为业务 CustomerId
|
||
→ 若 ParentCustomerId 非空则为子客户,按接口约定决定是否归并到主客户 Id
|
||
```
|
||
|
||
#### 3.3.3 权限码与接口鉴权
|
||
|
||
- 权限码格式:`Rbac.Users.Query`、`Api.Interfaces.Query` 等(PascalCase 点分隔),定义见 RBAC 文档 **§5**。
|
||
- 当前后端对 OMS 业务接口主要校验 **已登录**(`[Authorize]`);细粒度 Permission 拦截待 RBAC 包完善。
|
||
- 前端路由 `meta.permission` 与 `userInfo.permissions` 对齐;实现细节见 RBAC 文档 **§9**。
|
||
|
||
#### 3.3.4 客户域 HTTP(OMS 宿主内)
|
||
|
||
客户资料 CRUD、子客户创建(含权限子集校验)见 **`/customer/*`**,契约见 **`docs/rbac系统设计-060801.md` §8.7**,不在本篇展开。
|
||
|
||
#### 3.3.5 订单模块客户数据隔离(`/admin/order/*`)
|
||
|
||
线上网关将 OMS 订单接口挂载为 **`https://api.nanafa.com/admin/order/*`**;服务内路由为 **`/order/*`**(网关前缀 `/admin` 由反向代理剥离或统一转发,二者指同一组接口)。
|
||
|
||
| 项 | 约定 |
|
||
| --- | --- |
|
||
| 隔离键 | 会话解析得到的 **`effectiveCustomerId`**(**`t_customer.Id`**,非 `mapId`) |
|
||
| 解析方式 | `loginType=1`:`mapId` → `t_customer.UserId` → `t_customer.Id`;子客户是否归并主客户 Id 与 **§1.4** 一致,**全订单模块统一** |
|
||
| 查询(R) | 列表/导出/任务列表等:**强制** `WHERE CustomerId = effectiveCustomerId`(及 `IsDeleted=0` 等既有条件);**忽略**请求体中用于越权的其它 `customerId` |
|
||
| 单条读 | 按 `id` 加载后校验 `t_order.CustomerId == effectiveCustomerId`,否则按 **「订单不存在」** 返回(**禁止**泄露他客订单存在性) |
|
||
| 新增(C) | 创建订单/批量任务时 **`CustomerId` 仅取自会话**,写入 `t_order.CustomerId` / `t_order_batch_job.CustomerId`;请求体若带 `customerId` 须与 `effectiveCustomerId` **一致**,否则 **403** |
|
||
| 更新/删除(U/D) | 支付、预报、拦截、删除、打印、费用重算等:先 **归属校验** 再执行业务;批量接口逐单校验,任一不属于当前客户则该单失败或整批拒绝(实现二选一须在 **§4.4.0** 写死) |
|
||
| 关联表 | `t_order_sender`、`t_order_recipient`、`t_order_declaration`、`t_order_weight`、`t_order_fee` 等通过 **`OrderId`** 间接隔离,**禁止**仅凭子表 Id 绕过主表 `CustomerId` 校验;**`t_customer_sender_address`** 以 **`CustomerId`** 直接隔离(§4.2.10) |
|
||
| 内部用户 | `loginType=0` **默认禁止**调用 `/admin/order/*` 客户订单接口;若运营后台需跨客户查单,须 **另设** 内部管理路径与 PermissionCode,**不得**复用客户侧 `/admin/order` 且不带客户隔离 |
|
||
|
||
> **安全原则**:`CustomerId` **不得**由前端单独作为可信事实;**不得**仅按订单 `Id` 操作而不校验归属。
|
||
|
||
## 4 订单模块
|
||
|
||
|
||
### 4.1 核心需求说明
|
||
|
||
- **我的订单**:支持订单全流程管理,包括分页查询、详情查看、支付、预报、拦截/取消拦截、删除、打印、导出;**创建问题件**、**创建理赔件**、处理子状态筛选;可按 **`OrderSource`**(含揽收)筛选;订单状态覆盖草稿→已预报→已收货→待支付→待发货→运输中→派送成功/失败→问题件→理赔件等全生命周期。
|
||
- **我要查价**:根据目的国、运输方式、重量尺寸、货物类型等条件查价,返回可选渠道及费用明细;与正式下单共用「选版本 + 内存匹配」计费逻辑,响应宜携带 `rulesVersion` / `priceVersion` 便于与订单对齐。
|
||
- **我要下单**:支持下载下单模板、上传 Excel 批量下单、在 **批量任务** 页查看导入进度与失败明细;模板分为标准模板与自定义模板。**单次导入订单上限 2000 条**(Excel 有效数据行或 `/order/parcel/batch-create` 请求数组长度,超出拒绝)。
|
||
- **发件地址**(菜单挂 **客户**,接口属订单域):客户维护可复用的发件地址簿,供揽收下单选择;**不**写入订单快照表 `t_order_sender`(见 §4.2.10 / §4.4.5)。
|
||
- **揽收订单**(菜单挂 **订单**):简化创建——选发件地址 + 仓库地址、填揽收时间与货物名称/重量、选运输方式(销售产品)即可成单;后端组装收/发件与申报后复用小包创建全链路(§4.4.4)。
|
||
- **店铺订单**:拉单/同步后,客户可对勾选单据做 **创建物流订单**(仅下物流单)或 **创建物流&代发订单**(下物流单并绑定仓库包裹/库存);交互以「校对防错」为第一优先级(§4.4.7)。另有既有 **匹配库存**(平台 SKU 映射后扣减建单),与上述两入口分流。
|
||
|
||
### 4.2 表结构设计
|
||
|
||
> **命名约定**:表名按业务模块加 t_ 前缀;字段名驼峰命名(开头大写+单词驼峰)。
|
||
|
||
#### 4.2.1 订单表 `t_order`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| ServiceNo | VARCHAR | 64 | - | 是 | 运单号 |
|
||
| ClientNo | VARCHAR | 64 | - | 否 | 客户单号 |
|
||
| TrackNo | VARCHAR | 64 | - | 否 | 跟踪号 |
|
||
| ProductCode | VARCHAR | 128 | - | 否 | 产品code |
|
||
| ChannelCode | VARCHAR | 128 | - | 否 | 渠道code |
|
||
| DownstreamSupplierCode | VARCHAR | 64 | - | 否 | 下游 API 服务商编码(itfx **`supplierCode`**);排货确认渠道时自规则快照 **`Providers`** 写入 |
|
||
| DownstreamAppKey | VARCHAR | 128 | - | 否 | 下游 itfx 应用 **AppKey**;与 **`DownstreamAppId`** 配套 |
|
||
| DownstreamAppId | INT | - | - | 否 | 下游 itfx 应用 Id;**AppSecret 不落库**,运行时按 Id 调 itfx **`/App/GetInfo`** |
|
||
| ChannelForecastCode | VARCHAR | 128 | - | 否 | 渠道预报编码(**ForecastCode** / 轨迹 **ApiCode**);排货确认时固化 |
|
||
| ConsigneeCountry | VARCHAR | 8 | - | 否 | 国家二字码 |
|
||
| CargoAttribute | VARCHAR | 32 | - | 否 | 货物属性:general-普货,electronics-3c&带电,sensitive-敏感货 |
|
||
| OrderStatus | VARCHAR | 32 | - | 是 | 订单状态 |
|
||
| HandleStatus | VARCHAR | 16 | - | 否 | 处理子状态:`pending` 待处理 / `processed` 已处理;仅 **问题件、理赔件** 有效 |
|
||
| IssueType | VARCHAR | 32 | - | 否 | 问题类型 code,见 **§10.3.6** / `IssueTypeCodes` |
|
||
| IssueTitle | VARCHAR | 128 | - | 否 | 问题标题(可来自问题模板) |
|
||
| IssueContent | TEXT | - | - | 否 | 问题内容 |
|
||
| IssueTemplateId | BIGINT | - | 外键 → `t_issue_template.Id` | 否 | 引用的问题模板 |
|
||
| IssueRemark | VARCHAR | 512 | - | 否 | 问题备注(人工补充) |
|
||
| IssueTime | DATETIME | - | - | 否 | 标记问题件时间 |
|
||
| ClaimType | VARCHAR | 32 | - | 否 | 理赔类型 code,见 **§10.3.7** |
|
||
| ClaimAmount | DECIMAL | 18,4 | - | 否 | 索赔金额 |
|
||
| ClaimCurrency | VARCHAR | 16 | - | 否 | 索赔币种 |
|
||
| ClaimReason | VARCHAR | 512 | - | 否 | 理赔原因说明 |
|
||
| ClaimTime | DATETIME | - | - | 否 | 创建理赔件时间 |
|
||
| PreviousOrderStatus | VARCHAR | 32 | - | 否 | 进入异常状态前的订单状态(便于追溯/回滚扩展) |
|
||
| WarehouseId | BIGINT | - | 外键 | 否 | 仓库ID |
|
||
| CustomerId | BIGINT | - | 外键 → `t_customer.Id` | 否 | 订单归属客户业务主键 |
|
||
| OrderSource | VARCHAR | 32 | - | 否 | 订单来源:`manual` 普通/我要下单,`pickup` 揽收订单,`upload` Excel 上传,`batch` JSON 批量,`platform` 平台店;**默认 `manual`**;见 **§10.3.9** |
|
||
| PickupTime | DATETIME | - | - | 否 | 预约揽收时间;仅 **`OrderSource=pickup`** 有业务含义 |
|
||
| CustomerSenderAddressId | BIGINT | - | 外键 → `t_customer_sender_address.Id` | 否 | 创建时选用的发件地址簿 Id(溯源;地址变更不影响订单快照) |
|
||
| SenderId | BIGINT | - | 外键 | 否 | 发件人ID → t_order_sender |
|
||
| RecipientId | BIGINT | - | 外键 | 否 | 收件人ID → t_order_recipient |
|
||
| DeclarationId | BIGINT | - | 外键 | 否 | 申报信息ID → t_order_declaration |
|
||
| ExportCustomsMode | VARCHAR | 16 | - | 否 | 出口报关方式,见 ExportCustomsMode 枚举 |
|
||
| ReceiveTime | DATETIME | - | - | 否 | 收货时间 |
|
||
| ShipTime | DATETIME | - | - | 否 | 发货时间 |
|
||
| PayTime | DATETIME | - | - | 否 | 支付时间 |
|
||
| SignTime | DATETIME | - | - | 否 | 签收时间 |
|
||
| IsCod | TINYINT | - | - | 否 | 是否COD:0-否,1-是 |
|
||
| CodCurrency | VARCHAR | 16 | - | 否 | COD币种 |
|
||
| CodSum | DECIMAL | 18,4 | - | 否 | COD金额 |
|
||
| IsIntercept | TINYINT | - | - | 否 | 是否拦截:0-否,1-是 |
|
||
| IsRemote | TINYINT | - | - | 否 | 是否偏远:0-否,1-是 |
|
||
| PredeclareFailReason | VARCHAR | 512 | - | 否 | 预报失败原因:草稿已落库后环节失败时记录;黑名单命中、快照未命中等不落库场景不写此字段 |
|
||
| AllocationPendingReason | VARCHAR | 512 | - | 否 | 待排货原因:入库排货或渠道买价未匹配时记录(`OrderStatus=pendingAllocation`) |
|
||
| SalesProductRulesVersion | VARCHAR | 32 | - | 否 | 下单绑定的规则版本号(与发布记录 `Version` 一致,`OperatorType=rules`) |
|
||
| SalesProductPriceVersion | VARCHAR | 32 | - | 否 | 下单绑定的价格版本号(与发布记录 `Version` 一致,`OperatorType=price`) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**OrderStatus 枚举**:draft, predeclared, pendingAllocation, received, pendingPayment, pendingShip, pendingOnline, shipping, deliverySuccess, deliveryFailed, problematic, deleted, claimed
|
||
|
||
**OrderSource 枚举**:见 **§10.3.9**(`manual` / `pickup` / `upload` / `batch` / `platform`)
|
||
|
||
**CargoAttribute 枚举**:general-普货,electronics-3c&带电,sensitive-敏感货
|
||
|
||
**ExportCustomsMode 枚举**(存库与下列 code 一致):`9610Simple`(9610简易申报),`9610Summary`(9610汇总申报),`9610Separate`(9610单独申报),`9710`,`9810`,`0110`
|
||
|
||
> **请求约定**:`exportCustomsMode` **可选**;若传入须为上表 **code 本身**。非法示例:`9610`、`1`(字典序号)、`export_customs_mode` 字典项编码等;非法时返回 **「出口报关方式不合法」**。
|
||
|
||
#### 4.2.2 下单模板表 `t_order_template`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| Name | VARCHAR | 64 | - | 是 | 模板名称 |
|
||
| Type | VARCHAR | 16 | - | 是 | standard/custom |
|
||
| TemplateKey | VARCHAR | 64 | - | 否 | 模板标识 |
|
||
| CustomerId | BIGINT | - | 外键 → `t_customer.Id` | 否 | 客户ID(自定义模板) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 4.2.3 订单批量任务表 `t_order_batch_job` / `t_order_batch_job_item`
|
||
|
||
> **说明**:原 `t_order_task` / `t_order_task_fail` 已合并至本组表;Excel 导入、批量支付/预报/打印等异步操作均统一落库于此。
|
||
|
||
**任务头表 `t_order_batch_job`(摘要)**
|
||
|
||
| 字段名 | 说明 |
|
||
| --- | --- |
|
||
| Id | 主键 |
|
||
| Action | 操作类型:`import-row` / `pay` / `pre-declare` / `print` 等 |
|
||
| Status | `pending` / `running` / `completed` / `failed` / `cancelled` |
|
||
| Total / SuccessCount / FailCount | 进度统计 |
|
||
| CustomerId | 客户隔离键 |
|
||
| PayloadJson | 任务级扩展(导入时含 `taskName`、`templateId`、`customerId`) |
|
||
| RequestId | 幂等键 |
|
||
| StartedAt / FinishedAt | 执行时间 |
|
||
|
||
**任务明细表 `t_order_batch_job_item`(摘要)**
|
||
|
||
| 字段名 | 说明 |
|
||
| --- | --- |
|
||
| JobId | 关联任务头 |
|
||
| OrderId | 目标订单(导入行前为 0) |
|
||
| ClientNo / ServiceNo | 客户单号 / 运单号(执行后回填) |
|
||
| Status | `pending` / `running` / `success` / `failed` / `cancelled` |
|
||
| PayloadJson | 子项入参(导入行含 `rowNumber`、`rowDataJson`) |
|
||
| ResultJson / FailReason | 执行结果 / 失败原因 |
|
||
| RetryCount | 系统重试次数 |
|
||
|
||
完整 DDL 见 `oms0414-back/db.sql` 或 `scripts/order_batch_job_tables.sql`。
|
||
|
||
#### 4.2.4 订单发件人表 `t_order_sender`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| OrderId | BIGINT | - | 外键 | 是 | 订单ID |
|
||
| SenderName | VARCHAR | 50 | - | 否 | 姓名 |
|
||
| Company | VARCHAR | 255 | - | 否 | 公司 |
|
||
| Country | INT | - | - | 否 | 国家(存 **`t_country.Id`**;创建小包请求体见 §4.4.3.1,传 ISO 二字码) |
|
||
| Province | VARCHAR | 50 | - | 否 | 州省 |
|
||
| City | VARCHAR | 50 | - | 否 | 城市 |
|
||
| ZipCode | VARCHAR | 50 | - | 否 | 邮编 |
|
||
| StreetAddress1 | VARCHAR | 500 | - | 否 | 地址1 |
|
||
| StreetAddress2 | VARCHAR | 500 | - | 否 | 地址2 |
|
||
| StreetAddress3 | VARCHAR | 500 | - | 否 | 地址3 |
|
||
| HouseNumber | VARCHAR | 50 | - | 否 | 门牌号 |
|
||
| Phone | VARCHAR | 50 | - | 否 | 手机 |
|
||
| TelePhone | VARCHAR | 50 | - | 否 | 电话 |
|
||
| Email | VARCHAR | 60 | - | 否 | 邮箱 |
|
||
| IsDefault | TINYINT | - | - | 否 | 是否默认:0-否,1-是 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 4.2.5 订单收件人表 `t_order_recipient`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| OrderId | BIGINT | - | 外键 | 是 | 订单ID |
|
||
| RecipientsName | VARCHAR | 30 | - | 否 | 收件人名称 |
|
||
| CompanyName | VARCHAR | 60 | - | 否 | 收件人公司名称 |
|
||
| Country | INT | - | - | 否 | 国家(存 **`t_country.Id`**;创建小包请求体见 §4.4.3.1,传 ISO 二字码) |
|
||
| Phone | VARCHAR | 20 | - | 否 | 收件人手机 |
|
||
| TelePhone | VARCHAR | 20 | - | 否 | 收件人电话 |
|
||
| ZipCode | VARCHAR | 15 | - | 否 | 收件人邮编 |
|
||
| Province | VARCHAR | 25 | - | 否 | 收件人省/州 |
|
||
| City | VARCHAR | 25 | - | 否 | 收件人城市 |
|
||
| StreetNumber | VARCHAR | 20 | - | 否 | 收件人门牌号 |
|
||
| StreetAddress1 | VARCHAR | 255 | - | 否 | 收件人地址1 |
|
||
| StreetAddress2 | VARCHAR | 255 | - | 否 | 收件人地址2 |
|
||
| StreetAddress3 | VARCHAR | 255 | - | 否 | 收件人地址3 |
|
||
| Email | VARCHAR | 60 | - | 否 | 收件人邮箱 |
|
||
| TaxType | INT | - | - | 否 | 税号类型 |
|
||
| TaxNo | VARCHAR | 35 | - | 否 | 税号 |
|
||
| CertificateType | INT | - | - | 否 | 证件类型 |
|
||
| CertificateNo | VARCHAR | 30 | - | 否 | 证件号码 |
|
||
| Eori | VARCHAR | 255 | - | 否 | EORI |
|
||
| Vat | VARCHAR | 255 | - | 否 | Vat |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 4.2.6 订单申报信息表 `t_order_declaration`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| OrderId | BIGINT | - | 外键 | 是 | 订单ID |
|
||
| CnName | VARCHAR | 500 | - | 否 | 品名--中 |
|
||
| EnName | VARCHAR | 500 | - | 否 | 品名--英 |
|
||
| Sku | VARCHAR | 255 | - | 否 | SKU |
|
||
| Unit | VARCHAR | 5 | - | 否 | 单位 |
|
||
| UnitPrice | DECIMAL | 10,2 | - | 否 | 单价 |
|
||
| UnitWeight | DECIMAL | 11,3 | - | 否 | 单重 |
|
||
| Quantity | INT | - | - | 否 | 数量 |
|
||
| Amount | DECIMAL | 11,2 | - | 否 | 总金额 |
|
||
| Weight | DECIMAL | 11,3 | - | 否 | 总重量 |
|
||
| HsCode | VARCHAR | 50 | - | 否 | HsCode |
|
||
| SkuImage | VARCHAR | 255 | - | 否 | 图片 |
|
||
| Category | VARCHAR | 20 | - | 否 | 类别 |
|
||
| Brand | VARCHAR | 50 | - | 否 | 品牌 |
|
||
| BrandType | VARCHAR | 50 | - | 否 | 品牌类型 |
|
||
| IsMagnetic | BIT | - | - | 否 | 是否带磁带电 |
|
||
| Distribution | VARCHAR | 255 | - | 否 | 配货信息 |
|
||
| Material | VARCHAR | 50 | - | 否 | 材质 |
|
||
| EnMaterial | VARCHAR | 64 | - | 否 | 英文材质 |
|
||
| Purpose | VARCHAR | 50 | - | 否 | 用途 |
|
||
| Attributes | VARCHAR | 50 | - | 否 | 属性 |
|
||
| OriginCountry | VARCHAR | 50 | - | 否 | 原产国 |
|
||
| SalesLink | VARCHAR | 255 | - | 否 | 销售链接 |
|
||
| Model | VARCHAR | 64 | - | 否 | 型号 |
|
||
| Specifications | VARCHAR | 50 | - | 否 | 规格 |
|
||
| Comment | VARCHAR | 500 | - | 否 | 备注 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 4.2.7 订单件重尺表 `t_order_weight`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| OrderId | BIGINT | - | 外键 | 是 | 订单ID |
|
||
| Type | VARCHAR | 16 | - | 是 | 类型:forecast/receive/ship/downstream |
|
||
| Length | DECIMAL | 10,2 | - | 否 | 长(cm) |
|
||
| Width | DECIMAL | 10,2 | - | 否 | 宽(cm) |
|
||
| Height | DECIMAL | 10,2 | - | 否 | 高(cm) |
|
||
| ActualWeight | DECIMAL | 10,3 | - | 否 | 实重(kg) |
|
||
| VolumetricWeight | DECIMAL | 10,3 | - | 否 | 体积重(kg) |
|
||
| ChargeableWeight | DECIMAL | 10,3 | - | 否 | 计费重(kg) |
|
||
| Cubic | DECIMAL | 12,6 | - | 否 | 立方(m³) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**Type 枚举**:forecast(预报)、receive(收货)、ship(发货)、downstream(下游)
|
||
|
||
**履约更新**:四行 **`t_order_weight`** 各维护独立件重尺口径,**入库/出库仅覆盖并重算本环节对应 `Type` 行**,**不得**向其它 `Type` 拷贝或同步。**入库** → 仅 **`receive` 行**(§6.3.1.1);**出库** → 仅 **`ship` 行**(§6.3.2.1)。**`forecast`** 保留下单预报口径;**`downstream`** 由下游回传或独立流程更新,**不由**入库/出库流程写入。
|
||
|
||
#### 4.2.8 订单费用表 `t_order_fee`(子表,关联 t_order)
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| OrderId | BIGINT | - | 外键 | 是 | 订单ID |
|
||
| Type | VARCHAR | 16 | - | 是 | 类型:receivable(应收)/payable(应付) |
|
||
| CostItem | VARCHAR | 64 | - | 是 | 费用项 |
|
||
| Amount | DECIMAL | 18,4 | - | 是 | 金额 |
|
||
| Currency | VARCHAR | 16 | - | 是 | 币种:RMB/USD/EUR |
|
||
| DownstreamProvider | VARCHAR | 128 | - | 否 | 下游服务商 |
|
||
| PriceNo | VARCHAR | 64 | - | 否 | 价格编号(建议写入订单绑定的 `SalesProductPriceVersion` 或渠道侧价格版本) |
|
||
| PriceEffectiveTime | DATETIME | - | - | 否 | 价格版本生效时间(对应该次计费所用快照的生效时刻) |
|
||
| ChargingStage | VARCHAR | 16 | - | 否 | 本行费用是在哪一重量阶段计入:`forecast`(预报)/`inbound`(入库)/`outbound`(出库),由触发算价时的件重尺类型决定,便于对账 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**Type 枚举**:receivable(应收)、payable(应付)
|
||
|
||
#### 4.2.9 订单异常事件表 `t_order_exception_event`
|
||
|
||
> **用途**:问题件/理赔件操作的 **审计流水**(一单可多次「更新处理子状态」;问题件与理赔件各记一条创建事件)。主表 `t_order` 存 **当前快照**,本表存 **历史**。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增 |
|
||
| OrderId | BIGINT | - | 外键 → `t_order.Id` | 是 | 订单 Id |
|
||
| EventType | VARCHAR | 32 | - | 是 | `mark_problematic` / `mark_claimed` / `handle_processed` |
|
||
| FromStatus | VARCHAR | 32 | - | 否 | 变更前 `OrderStatus` |
|
||
| ToStatus | VARCHAR | 32 | - | 否 | 变更后 `OrderStatus` |
|
||
| FromHandleStatus | VARCHAR | 16 | - | 否 | 变更前 `HandleStatus` |
|
||
| ToHandleStatus | VARCHAR | 16 | - | 否 | 变更后 `HandleStatus` |
|
||
| IssueType | VARCHAR | 32 | - | 否 | 问题类型(创建问题件时) |
|
||
| Title | VARCHAR | 128 | - | 否 | 标题快照 |
|
||
| Content | TEXT | - | - | 否 | 内容快照 |
|
||
| IssueTemplateId | BIGINT | - | - | 否 | 模板 Id |
|
||
| NotifyMethod | VARCHAR | 64 | - | 否 | 通知方式快照(逗号分隔) |
|
||
| ClaimType | VARCHAR | 32 | - | 否 | 理赔类型 |
|
||
| ClaimAmount | DECIMAL | 18,4 | - | 否 | 索赔金额 |
|
||
| ClaimCurrency | VARCHAR | 16 | - | 否 | 索赔币种 |
|
||
| ClaimReason | VARCHAR | 512 | - | 否 | 理赔原因 |
|
||
| Remark | VARCHAR | 512 | - | 否 | 操作备注 |
|
||
| CreateUserId | INT | - | - | 是 | 操作人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 操作时间 |
|
||
|
||
**索引**:`idx_oee_order_time` (`OrderId`, `CreateTime`)。
|
||
|
||
#### 4.2.10 客户发件地址表 `t_customer_sender_address`
|
||
|
||
> **用途**:客户侧 **可复用发件地址簿**(菜单 **客户 → 发件地址**)。创建揽收订单时按 Id 读取并 **快照** 到 `t_order_sender`;此后地址簿修改 **不影响** 已下订单。
|
||
> **与 `t_order_sender` 关系**:`t_order_sender` 仍是 **订单瞬时快照**(每单一行);本表是 **主数据模板**,按 `CustomerId` 隔离。
|
||
> **迁移脚本(待编写)**:`sql/migrate_t_customer_sender_address.sql`;订单扩展字段见 `sql/migrate_t_order_pickup_fields.sql`(`OrderSource` / `PickupTime` / `CustomerSenderAddressId`)。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增 |
|
||
| CustomerId | BIGINT | - | 外键 → `t_customer.Id` | 是 | 归属客户(隔离键) |
|
||
| AddressName | VARCHAR | 64 | - | 否 | 地址别名(如「公司仓」「张三宅」);列表展示用 |
|
||
| SenderName | VARCHAR | 50 | - | 是 | 姓名 |
|
||
| Company | VARCHAR | 255 | - | 否 | 公司 |
|
||
| Country | INT | - | - | 否 | 国家(存 **`t_country.Id`**;接口传 ISO 二字码) |
|
||
| Province | VARCHAR | 50 | - | 否 | 州省 |
|
||
| City | VARCHAR | 50 | - | 否 | 城市 |
|
||
| ZipCode | VARCHAR | 50 | - | 否 | 邮编 |
|
||
| StreetAddress1 | VARCHAR | 500 | - | 是 | 地址1 |
|
||
| StreetAddress2 | VARCHAR | 500 | - | 否 | 地址2 |
|
||
| StreetAddress3 | VARCHAR | 500 | - | 否 | 地址3 |
|
||
| HouseNumber | VARCHAR | 50 | - | 否 | 门牌号 |
|
||
| Phone | VARCHAR | 50 | - | 否 | 手机 |
|
||
| TelePhone | VARCHAR | 50 | - | 否 | 电话 |
|
||
| Email | VARCHAR | 60 | - | 否 | 邮箱 |
|
||
| IsDefault | TINYINT | - | - | 是 | 是否默认:0-否,1-是;**同一客户仅允许一条默认**(设默认时清空其它) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 软删:0-否,1-是,默认 0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**索引**
|
||
|
||
| 索引名 | 类型 | 字段 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| idx_csa_customer | KEY | CustomerId, IsDeleted, Id | 客户列表 |
|
||
| idx_csa_customer_default | KEY | CustomerId, IsDefault | 默认地址查询 |
|
||
|
||
#### 4.2.11 物流单仓储绑定表 `t_order_stock_bind`
|
||
|
||
> **用途**:客户在「我的订单」对**已创建成功**的物流单(`t_order`)绑定仓储 **包裹行** 或 **库存 SKU**,用于代发出库占用。
|
||
> **与店铺链路区别**:
|
||
> - **匹配库存** `match-inventory`:店铺单 → **扣库存** → **创建**物流单;
|
||
> - **创建物流&代发** §4.4.7:店铺单 → **创建**物流单 → 本表 **冻结绑定**(不即时扣实物);
|
||
> - 本表在「我的订单」也可对已有物流单单独挂载包裹/库存。
|
||
> **数量**:绑定数量**不要求**与 `t_order_declaration` 申报件数对齐。
|
||
> **冻结**:绑定保存时冻结(`C_inventory` / `C_parcel_item` 的 `FrozenQty`↑、`AvailableQty`↓);确认出库/预报钩子再扣实物 `Qty`/`Quantity` 并释放冻结。
|
||
> **迁移脚本**:`sql/migrate_t_order_stock_bind.sql`。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | INT | - | 主键 | 是 | 自增 |
|
||
| OrderId | INT | - | 外键 → `t_order.Id` | 是 | 物流单 |
|
||
| CustomerId | INT | - | 外键 → `t_customer.Id` | 是 | 归属客户(隔离键) |
|
||
| WarehouseId | INT | - | - | 是 | 仓库;须与订单 `WarehouseId` 一致(订单无仓库时以本字段写入为准) |
|
||
| BindType | VARCHAR | 20 | - | 是 | `inventory` / `parcelItem` |
|
||
| SkuId | INT | - | - | 否 | 库存必填;包裹行可空(未上架) |
|
||
| InventoryId | INT | - | → `C_inventory.Id` | 否 | 指定库位行;空则按 SKU 自动分摊冻结 |
|
||
| BinLocationId | INT | - | - | 否 | 库位快照 |
|
||
| ParcelId | INT | - | → `C_parcel.Id` | 否 | `BindType=parcelItem` 时必填 |
|
||
| ParcelItemId | INT | - | → `C_parcel_item.Id` | 否 | `BindType=parcelItem` 时必填 |
|
||
| BindQty | INT | - | - | 是 | 绑定数量,>0 |
|
||
| Status | VARCHAR | 20 | - | 是 | `bound` 已冻结 / `deducted` 已扣实物 / `released` 已释放 |
|
||
| SkuCode | VARCHAR | 64 | - | 否 | 展示快照 |
|
||
| SkuName | VARCHAR | 128 | - | 否 | 展示快照 |
|
||
| TrackingNo | VARCHAR | 64 | - | 否 | 包裹运单号快照 |
|
||
| ProductName | VARCHAR | 256 | - | 否 | 包裹品名快照 |
|
||
| ImageUrl | VARCHAR | 512 | - | 否 | 图片快照 |
|
||
| AttributeText | VARCHAR | 256 | - | 否 | 规格快照 |
|
||
| Remark | VARCHAR | 512 | - | 否 | 备注 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 软删,默认 0 |
|
||
| ConcurrencyStamp | CHAR | 36 | - | 是 | 并发戳;见 **§3.1.2**,默认 UUID |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**索引**
|
||
|
||
| 索引名 | 类型 | 字段 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| idx_osb_order | KEY | OrderId, IsDeleted, Status | 订单绑定明细 |
|
||
| idx_osb_parcel_item | KEY | ParcelItemId, Status, IsDeleted | 包裹行占用汇总 |
|
||
| idx_osb_sku | KEY | CustomerId, WarehouseId, SkuId, Status | 库存占用查询 |
|
||
|
||
**仓储表字段增量(同迁移脚本)**
|
||
|
||
| 表 | 新增字段 | 说明 |
|
||
| --- | --- | --- |
|
||
| `C_inventory` | `FrozenQty` INT NOT NULL DEFAULT 0 | 已绑定未出库冻结量 |
|
||
| `C_inventory` | `AvailableQty` INT NOT NULL DEFAULT 0 | 可用 = `Qty - FrozenQty`(应用层维护) |
|
||
| `C_parcel_item` | `FrozenQty` INT NOT NULL DEFAULT 0 | 已被物流单占用数量 |
|
||
| `C_parcel_item` | `AvailableQty` INT NOT NULL DEFAULT 0 | 可用 = `Quantity - FrozenQty`(应用层维护) |
|
||
|
||
**不变量**
|
||
|
||
1. `AvailableQty = Qty(或 Quantity) - FrozenQty`,且三者均 ≥ 0。
|
||
2. 绑定保存:校验 `BindQty ≤ AvailableQty`(对本单原占用先释放再算)。
|
||
3. 包裹 Tab **允许全部生命周期**(`inTransit` / `arrived` / `stored`)。
|
||
4. 已上架包裹行(有 `SkuId`)绑定包裹时**同时**冻结对应库存,避免重复售卖。
|
||
5. 保存采用**全量覆盖**:提交 `items[]` 为最终态。
|
||
|
||
### 4.3 下单计费与版本策略(配置 → 运行时 → 下单)
|
||
|
||
> 本节约定**销售产品**维度的规则/卖价如何进入线上算价链路,以及与 `t_order` 版本字段的对应关系;与渠道模块中「运营发布 → 生成版本」接口衔接。
|
||
|
||
#### 4.3.1 总体流程
|
||
|
||
```text
|
||
运营新建版本 → 录入/维护规则与卖价 → 发布并设为生效
|
||
↓
|
||
系统组装结构化 JSON(规则快照 + 价格快照,均绑定 salesProductId 与 version)
|
||
↓
|
||
写入 Redis(按产品/版本可寻址)→ 计费服务实例订阅或定时拉取 → 载入本地内存(热数据)
|
||
↓
|
||
用户下单 / 预估(高 QPS)
|
||
↓
|
||
内存匹配:按 bindTime 选定生效版本 → 规则(黑名单/偏远/计重/进位/价格分区等)→ 价格明细 → 输出费用
|
||
↓
|
||
订单落库:固化 SalesProductRulesVersion、SalesProductPriceVersion(必要时可另存发布记录 Id 便于追溯,非本表字段)
|
||
↓
|
||
后台上新发布、调价 → 仅刷新 Redis 与各实例内存中的「当前生效」视图
|
||
↓
|
||
新订单:bindTime 落在 new version 生效之后 → 绑定新版本算价
|
||
老订单:已写入的版本号不变 → 复核/对账/理赔仍以当时快照为准,不因后台最新发布而改历史费用
|
||
```
|
||
|
||
#### 4.3.2 设计要点
|
||
|
||
| 要点 | 说明 |
|
||
| --- | --- |
|
||
| 版本不可变 | 订单创建/预报成功时写入 `SalesProductRulesVersion`、`SalesProductPriceVersion`,作为该票货的**计费契约**;非「改单重算」类流程不得静默改写。 |
|
||
| 生效与历史 | 同一 `salesProductId` 在同一时刻仅一条「对外生效」规则+价格组合;历史 version 保留在 **Redis(多版本 key)** 与 **`t_sales_product_publish_record`**,便于审计与回放。 |
|
||
| Redis | 建议按 `salesProductId` + `version` 存完整结构化快照 JSON;可选维护 `current` 指针 key 指向当前生效 version,便于实例 O(1) 发现最新配置。 |
|
||
| 本地内存 | 实例启动或收到变更通知后,将当前需服务的 JSON **反序列化并预热**为内存对象(或编译为匹配结构),下单/预估路径**禁止**主链路同步穿透远程 DB。 |
|
||
| 下单与预估 | `/order/parcel/create` 与「我要查价」类预估接口**共用**同一套选版本与匹配逻辑;预估不落库,但响应中建议返回 `rulesVersion` / `priceVersion` 便于与正式单对齐展示。 |
|
||
| 降级 | Redis/内存未命中时,允许回退查询 **`t_sales_product_publish_record`**(按 `bindTime` 取当时生效的 rules/price 各一条,读 `Content`),命中后**异步回写 Redis**;并打点告警。 |
|
||
|
||
#### 4.3.3 与运营侧发布的衔接
|
||
|
||
- 运营在 Admin 完成卖价、排货规则等配置后,通过 **发布** 生成新版本(参见渠道模块 `/channel/sales-product/publish` 等);发布成功后由**配置同步任务**将结构化结果写入 Redis 并通知计费服务刷新内存。
|
||
- 「设为生效」与版本号 `version`、生效时间 `effectiveStartAt`(或等价字段)应对齐,以便 bindTime 维度解析「当前应绑哪一版」。
|
||
|
||
#### 4.3.4 版本确认时机 vs 费用计算时机(件重尺阶段)
|
||
|
||
> **版本(契约)**:在**下单/预报**流程中(如 `/order/parcel/create` 绑定快照时)即确定 `SalesProductRulesVersion`、`SalesProductPriceVersion`(及渠道侧与应付相关的版本,若有)。后续只要未发生「改单重算」等显式流程,**不得**因后台发布新版本而改写订单已绑定版本。
|
||
> **金额(执行)**:卖价/买价主表 **已移除 `ChargingTime`(收费时机)**;应收在 **`t_order_weight` 对应行计重完成** 后触发,**全部启用价格项**参与;`t_order_fee.ChargingStage` 记录本次重量阶段。
|
||
|
||
| 件重尺 `Type` | 典型触发节点 | 应收(`Type=receivable`) | 应付(`Type=payable`,设计) |
|
||
| --- | --- | --- | --- |
|
||
| `forecast` | 下单预报成功 | **§4.3.4.1**:全部启用卖价项;`ChargingStage=forecast`;**不排货、不写应付** |
|
||
| `receive` | 仓库收货 `/operation/inbound/create` | **§4.3.4.1**:全部启用卖价项;`ChargingStage=inbound` | **§4.3.4.2**:**TryConfirm** 排货 + 渠道买价(失败 → `pendingAllocation`,不抛错);`ChargingStage=inbound`;重量取 **`receive`** |
|
||
| `ship` | 仓库出库 §6.3.2.1 | **§4.3.4.1**(**未实现**) | **§4.3.4.2**(**未实现**) |
|
||
|
||
**规则小结**:先锁版本;按件重尺阶段触发算价;每次对 **全部启用** 价格项算价;后阶段仍读订单绑定快照。
|
||
|
||
##### 4.3.4.1 应收费用重算(`Type=receivable`)
|
||
|
||
1. **筛选**:`status=1` 的全部卖价项。
|
||
2. **重量**:按触发节点取 `forecast` / `receive` / `ship` 行计费重。
|
||
3. **输出**:upsert `t_order_fee`,幂等键 `OrderId + CostItem + ChargingStage`。
|
||
4. **已实现**:`/order/parcel/create`、`ApplyReceiveBillingByWaybillAsync`。
|
||
|
||
##### 4.3.4.2 应付费用重算(`Type=payable`)
|
||
|
||
1. **排货选渠**:读取订单绑定 **规则快照** 内 `Providers` 与 `Scheme`;按 **客户范围**(`CustomerIds` 空=全部)过滤候选服务商;对每个候选 **服务渠道** 试算买价总额,再按方案选型:
|
||
- **`costLowest`**:应付总额最低;
|
||
- **`timeFastest`**:`MinTime` 最小;
|
||
- **`overallBest`**:`Weight` 最大。
|
||
选中后回写 **`t_order.ChannelCode`**,并自选中 **`Providers`** 项固化下游对接字段:**`DownstreamSupplierCode`**、**`DownstreamAppKey`**、**`DownstreamAppId`**、**`ChannelForecastCode`**(实现:`ConfirmServiceChannelAndPayableAsync` → **`AllocationProviderSnapshotEnricher.ApplyDownstreamFieldsToOrder`**)。
|
||
2. **筛选**:`status=1` 的全部买价项(优先读渠道买价发布快照,无则 Live 组装)。
|
||
3. **重量**:预报阶段取 **`downstream`** 行计费重;入库阶段取 **`receive`** 行计费重(收货复量后重新排货)。
|
||
4. **输出**:upsert `t_order_fee`,幂等键 `OrderId + CostItem + ChargingStage`;`PriceNo` 写渠道买价版本。
|
||
5. **调用方**:`/order/parcel/create`(预报)、`ApplyReceiveBillingByWaybillAsync`(入库)、`/order/fee/recalculate-payable`。
|
||
6. **下游运行时读取**:轨迹 **UpsertSmallBag** 的 **`ApiCode`** 优先 **`t_order.ChannelForecastCode`**,缺失时回退 live 查 **`t_service_channel.ForecastCode`**;itfx 下游预报(前端 **`createOrderOne`**)优先读订单 **`DownstreamSupplierCode` / `DownstreamAppKey` / `DownstreamAppId`**,**AppSecret** 仍按 **`DownstreamAppId`** 调 itfx App API。
|
||
|
||
#### 4.3.5 当前实现:快照解析与算价(oms0414-back)
|
||
|
||
> 实现位置:`OrderService.ParcelBilling.cs` → `ResolveSnapshotBindingAsync`;`ParcelBillingCalculator.cs`。
|
||
|
||
**快照绑定(`ResolveSnapshotBindingAsync`)**
|
||
|
||
1. `bindTime = DateTime.Now`。
|
||
2. 分别查询 **`t_sales_product_publish_record`**:`OperatorType = 'rules' | 'price'`,`PublishTime <= bindTime`,按发布时间降序取生效记录。
|
||
3. **有发布记录**:反序列化 `Content` 为规则/价格快照 DTO;价格快照 **`Prices` 为空** → 失败 **「未命中价格快照版本(内存/Redis/DB)」**。
|
||
4. **无发布记录(Live 降级)**:
|
||
- **规则**:从 `t_sales_product` + `t_kpi_area_info` / `t_weight_rule_info` / `t_weight_rounding_info` 实时组装;若产品 **`WeightRuleId` 为空** → 失败 **「未命中规则快照版本(内存/Redis/DB)」**。
|
||
- **价格**:从 `t_sales_product_price`(及 factor、detail)实时组装;**无任何价格行** → 同上价格失败。
|
||
- 版本字段落库:**`LIVE-{salesProductId}`**;生效时间取 `bindTime`。
|
||
5. **Redis / 进程内存**:当前代码路径 **未接入**;错误文案保留「内存/Redis/DB」字样以兼容后续接入。
|
||
|
||
**分区 / 黑名单 / 偏远**
|
||
|
||
- 优先读快照内嵌的 **`BlacklistAreaInfos` / `RemoteAreaInfos` / `PriceAreaInfos`**;缺失时回退 **`t_kpi_area_info`**(按产品或快照上的 `*AreaId`)。
|
||
- 黑名单命中 → **不落库**,抛 **「收件地址命中黑名单区域」**。
|
||
- 偏远命中 → 更新 **`t_order.IsRemote = 1`**,不拦截。
|
||
|
||
**计重与进位(`ApplyChargeableWeightAsync`)**
|
||
|
||
- `effectiveWeightRuleId` / `effectiveWeightRoundingId`:优先 **`t_sales_product`**,其次规则快照上的 ID 字段。
|
||
- 明细:优先快照 **`WeightRuleInfos` / `WeightRoundingInfos`**,否则查 **`t_weight_rule_info` / `t_weight_rounding_info`**。
|
||
- 算法:`ParcelBillingCalculator.CalculateRawBillableWeight` → `ApplyRounding`(`ceil(raw/unit)×unit`,3 位小数)。
|
||
- 预报成功后 **四行** `t_order_weight`(`forecast/receive/ship/downstream`)**同步相同**件重尺与计费重。
|
||
|
||
**卖价算价**
|
||
|
||
| 场景 | 方法 | 说明 |
|
||
| --- | --- | --- |
|
||
| 小包创建 / 查价 | `CalculateReceivableFees` / `CalculateQuoteReceivableFees` | 全部启用卖价项;创建写 `ChargingStage=forecast`(**仅应收**) |
|
||
| 入库收货 | `ApplyReceiveBillingByWaybillAsync` | 仅覆盖 **`Type=receive`** 并重算应收;**TryConfirm** 排货/应付(`ChargingStage=inbound`);成功 → **`pendingPayment`**,失败 → **`pendingAllocation`** + `AllocationPendingReason`;写入 **`ReceiveTime`** |
|
||
| 出库发货 | `ApplyShipBillingByWaybillAsync` | 仅覆盖 **`Type=ship`** 并重算;写 `ChargingStage=outbound`;**`OrderStatus=pendingOnline`**,写入 **`ShipTime`** |
|
||
| 应付 | `TryConfirmServiceChannelAndPayableAsync` / 手动重算 | **入库**或运营重算;成功清 `AllocationPendingReason` 并 → `pendingPayment` |
|
||
|
||
**查价额外逻辑**(`OrderService.ParcelQuote.cs`):遍历 **全部已启用** 销售产品(`shippingMethod` 模糊匹配 `ProductCode` / `ProductName`);失败写入 **`failures[]`**(含 `productId`、`step`、`reason`);成功项带 **`rulesVersion`、`priceVersion`、`isRemote`**。
|
||
|
||
### 4.4 核心接口设计
|
||
|
||
> **网关路径**:生产环境对外为 **`/admin/order/*`**(例:`POST https://api.nanafa.com/admin/order/list`);下文契约路径以 **`/order/*`** 表示服务内路由,与前端 `orderUrl('/order/...')` 拼接规则一致。
|
||
|
||
#### 4.4.0 客户数据隔离(强制)
|
||
|
||
**适用范围**:**§4.4.1 我的订单**、**§4.4.2 我要查价**、**§4.4.3 我要下单** 中列出的全部接口,以及同模块后续增补的订单相关接口。
|
||
|
||
**会话客户上下文**
|
||
|
||
1. 从 JWT 解析 **`mapId`**、**`loginType`**(见 **§3.3.2**)。
|
||
2. 仅 **`loginType=1`(客户登录)** 允许访问本组接口;否则返回 **401/403**。
|
||
3. 查询 `t_customer`:`UserId = mapId AND IsDeleted = 0`,得到 **`effectiveCustomerId = t_customer.Id`**。
|
||
4. 若 **`ParentCustomerId` 非空**(子客户):按 **§1.4** 约定,订单归属使用 **主客户 `t_customer.Id`** 或 **子客户自身 `Id`** 之一;**全模块、全接口统一**,实现与测试须与选定策略一致(推荐:**子客户订单落在主客户 `CustomerId` 下**,子账号仅能通过 RBAC 子客户权限访问主客户数据)。
|
||
|
||
**隔离规则(增删改查)**
|
||
|
||
| 操作类型 | 接口示例 | 强制规则 |
|
||
| --- | --- | --- |
|
||
| **查 · 列表** | `POST /order/list`、`GET /order/batch/jobs`、`GET /order/template/list` | 查询条件 **必须包含** `CustomerId = effectiveCustomerId`;**不得**返回其它客户数据 |
|
||
| **查 · 详情** | `GET /order/detail`、`GET /order/batch/jobs/{id}` | 加载实体后校验 `CustomerId`;不匹配返回业务错误「订单不存在」/「批量任务不存在」或 **403**(**禁止**返回他客数据) |
|
||
| **增** | `POST /order/parcel/create`、`POST /order/parcel/batch-create`、`POST /order/upload` | 落库 `CustomerId` **仅**取 `effectiveCustomerId`;请求体 `customerId` 若存在须 **相等**,否则拒绝 |
|
||
| **改** | `POST /order/pay`、`POST /order/pre-declare`、`POST /order/intercept`、`POST /order/cancel-intercept`、`POST /order/fee/batch-recalculate` | 按 `id` / `orderIds` 加载订单后 **逐单**校验 `CustomerId`;不属于当前客户 **不得**变更状态或费用 |
|
||
| **删** | `POST /order/delete` | 同上,仅允许删除 **`CustomerId = effectiveCustomerId`** 的订单 |
|
||
| **其它写** | `POST /order/print`、`POST /order/export` | 打印/导出前校验归属;导出筛选范围 **限定**当前客户 |
|
||
|
||
**批量与关联资源**
|
||
|
||
- **`POST /order/fee/batch-recalculate`**:`orderIds` 中每一单均须归属 `effectiveCustomerId`;建议 **部分失败** 策略(`failures` 记录越权单,错误码如 `ORDER_FORBIDDEN`),与 **§4.4.1.10** 一致。
|
||
- **`t_order_template`**(自定义模板):`CustomerId` 隔离;标准模板(`Type=standard`)全员可见,**不可**通过模板 Id 间接操作他客订单。
|
||
- **`t_order_batch_job`**:列表/详情 **仅**返回 `CustomerId = effectiveCustomerId` 的任务;子项创建订单时 **继承**同一 `CustomerId`。
|
||
|
||
**查价(§4.4.2)**
|
||
|
||
- `POST /order/price-check` 不读写 `t_order`,但仍须 **已登录客户**;算价所用产品/仓库权限须与当前客户可用范围一致(若产品按客户组/客户 Id 限制,按配置过滤);**禁止**借查价接口探测他客专属价目。
|
||
|
||
**错误码建议**
|
||
|
||
| 场景 | 建议 |
|
||
| --- | --- |
|
||
| 未登录 / Token 无效 | 401 |
|
||
| 非客户身份调用 | 403 |
|
||
| 订单 Id 存在但 `CustomerId` 不匹配 | 404 或 403(文案统一「订单不存在」,避免信息泄露) |
|
||
| 请求体 `customerId` 与会话不一致 | 业务错误 **「客户ID非法」**(HTTP 403 亦可) |
|
||
|
||
**实现检查清单(后端,2026-06-24 已落地)**
|
||
|
||
1. 封装 **`ResolveSessionCustomerAsync` / `ResolveListCustomerIdAsync`**(`OmsSessionHelper`),创建类接口 **先**解析 `effectiveCustomerId` 再校验其它字段;列表/导出/任务列表 **强制** `WHERE CustomerId = effectiveCustomerId`(**禁止**仅用 `CreateUserId` 过滤)。
|
||
2. 列表类 Repository 查询 **统一注入** `CustomerId` 条件,**禁止**仅靠前端传参过滤。
|
||
3. **`GetOrderOrThrowAsync(id)`** 在存在性校验后增加 **`order.CustomerId == effectiveCustomerId`**,不匹配返回 **「订单不存在」**。
|
||
4. 创建类接口(`parcel/create`、`batch-create`、`upload`):**未传** `customerId` 时由会话补全;**已传**则须与会话一致;落库 **仅**写入 `effectiveCustomerId`。
|
||
5. 单测/集成测覆盖:**客户 A 的 Token 无法 list/detail/改/删 客户 B 的订单**。
|
||
|
||
#### 4.4.1 我的订单
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 分页查询订单列表 | POST | /order/list | 支持 status、keyword、createTimeStart/End、**orderSource** 筛选;**强制按会话 `CustomerId` 隔离**(§4.4.0) |
|
||
| 订单详情 | GET | /order/detail | 根据订单ID获取详情;**须校验订单归属当前客户** |
|
||
| 支付 | POST | /order/pay | 订单支付;**须校验订单归属** |
|
||
| 预报 | POST | /order/pre-declare | 订单预报;**须校验订单归属** |
|
||
| 拦截 | POST | /order/intercept | 订单拦截;**须校验订单归属** |
|
||
| 取消拦截 | POST | /order/cancel-intercept | 取消订单拦截;**须校验订单归属** |
|
||
| 删除 | POST | /order/delete | 删除订单;**须校验订单归属** |
|
||
| 打印 | POST | /order/print | 打印订单;**须校验订单归属** |
|
||
| 导出 | POST | /order/export | 导出订单;**仅导出当前客户订单** |
|
||
| 批量重算费用 | POST | /order/fee/batch-recalculate | 入参订单 Id 列表 + `scopes`;**逐单校验 `CustomerId`**(§4.4.0) |
|
||
| 创建轨迹订单 | POST | /order/track-upsert-small-bag | 手动同步调用轨迹 **UpsertSmallBag**(联调/补推);**须校验订单归属** |
|
||
| 创建问题件 | POST | /order/mark-problematic | 批量将订单标记为 **问题件**;消费问题模板(§5.2.21);**须校验订单归属** |
|
||
| 创建理赔件 | POST | /order/mark-claimed | 批量将订单标记为 **理赔件**;**须校验订单归属** |
|
||
| 更新处理子状态 | POST | /order/handle-status/update | 问题件/理赔件 **待处理→已处理**;**须校验订单归属** |
|
||
| 预报下游订单 | —(前端) | itfx **`POST /InterfaceForward/ForwardWithSubscribe`** | 「我的订单」批量操作;`standardCode=createOrderOne`;凭证优先读订单下游绑定字段(§4.4.1.12) |
|
||
|
||
##### 4.4.1.1 分页查询订单列表(POST /order/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/list**(POST,分页参数放 Body)
|
||
|
||
```json
|
||
{
|
||
"page": 1, // 页码,从1开始
|
||
"pageSize": 20, // 每页条数
|
||
"status": "all|draft|predeclared|received|...", // 订单状态筛选
|
||
"handleStatus": "pending|processed", // 处理子状态;仅 status=problematic|claimed 时生效
|
||
"orderSource": "all|manual|pickup|upload|batch|platform", // 订单来源;默认 all 不按来源过滤;揽收菜单可固定传 pickup
|
||
"keyword": "", // 关键词(运单号/客户单号/跟踪号)
|
||
"createTimeStart": "", // 创建时间起
|
||
"createTimeEnd": "" // 创建时间止
|
||
}
|
||
```
|
||
|
||
**响应**(用于 order/myOrders 列表及订单详情抽屉,字段与 parcel/create 对应;**注意**:`sender.countryCode` / `receiver.countryCode` 响应中为 **`t_country.Id`(int)**,与创建请求体的 ISO 二字码不同;列表/详情宜回显 **`orderSource`、`pickupTime`、`customerSenderAddressId`**)
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"clientNo": "CUST...",
|
||
"trackNo": "TRK...",
|
||
"serviceNo": "LP...",
|
||
"productCode": "US_AIR",
|
||
"channelCode": "CH001",
|
||
"downstreamSupplierCode": "SP001",
|
||
"downstreamAppKey": "AKxxxx",
|
||
"downstreamAppId": 12,
|
||
"channelForecastCode": "FORECAST_01",
|
||
"cargoAttribute": "general",
|
||
"exportCustomsMode": "9610Simple",
|
||
"warehouseId": 1,
|
||
"warehouse": "深圳仓",
|
||
"customerId": 1,
|
||
"isCod": false,
|
||
"codCurrency": "",
|
||
"codSum": "",
|
||
"orderStatus": "待支付",
|
||
"status": "pendingPayment",
|
||
"receiveTime": "",
|
||
"shipTime": "",
|
||
"payTime": "",
|
||
"signTime": "",
|
||
"isIntercept": false,
|
||
"isRemote": false,
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"createBy": "",
|
||
"updateTime": "",
|
||
"updateBy": "",
|
||
"sender": {
|
||
"senderName": "发件人",
|
||
"company": "示例公司",
|
||
"phone": "13800138000",
|
||
"telePhone": "",
|
||
"email": "sender@example.com",
|
||
"countryCode": 1,
|
||
"province": "广东省",
|
||
"city": "深圳市",
|
||
"zipCode": "518000",
|
||
"streetAddress1": "示例地址1",
|
||
"streetAddress2": "",
|
||
"streetAddress3": "",
|
||
"houseNumber": ""
|
||
},
|
||
"receiver": {
|
||
"recipientsName": "收件人1",
|
||
"companyName": "",
|
||
"phone": "13800138000",
|
||
"telePhone": "",
|
||
"email": "",
|
||
"countryCode": 1,
|
||
"province": "California",
|
||
"city": "Los Angeles",
|
||
"streetNumber": "",
|
||
"streetAddress1": "123 Main St",
|
||
"streetAddress2": "",
|
||
"streetAddress3": "",
|
||
"zipCode": "90001",
|
||
"taxType": 0,
|
||
"taxNo": "",
|
||
"certificateType": 0,
|
||
"certificateNo": "",
|
||
"eori": "",
|
||
"vat": ""
|
||
},
|
||
"items": [
|
||
{
|
||
"cnName": "示例商品",
|
||
"enName": "Sample Product",
|
||
"sku": "SKU001",
|
||
"unit": "件",
|
||
"unitPrice": 10,
|
||
"unitWeight": 0.5,
|
||
"quantity": 1,
|
||
"amount": 10,
|
||
"weight": 0.5,
|
||
"hsCode": "1234567890",
|
||
"skuImage": "",
|
||
"category": "普货",
|
||
"brand": "示例品牌",
|
||
"brandType": "自有",
|
||
"isMagnetic": false,
|
||
"distribution": "",
|
||
"material": "塑料",
|
||
"enMaterial": "Plastic",
|
||
"purpose": "日用",
|
||
"attributes": "",
|
||
"originCountry": "中国",
|
||
"salesLink": "",
|
||
"model": "",
|
||
"specifications": "",
|
||
"comment": ""
|
||
}
|
||
],
|
||
"weightList": [
|
||
{
|
||
"type": "forecast",
|
||
"length": "20",
|
||
"width": "15",
|
||
"height": "10",
|
||
"actualWeight": "2.50",
|
||
"volumetricWeight": "2.00",
|
||
"chargeableWeight": "2.50",
|
||
"cubic": "0.003"
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"total": 100
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. 解析会话 **`effectiveCustomerId`**(§4.4.0)。
|
||
2. 构造分页查询条件时 **固定追加** `t_order.CustomerId = effectiveCustomerId`(及 `IsDeleted = 0`、状态/关键词/时间等前端筛选)。
|
||
3. **忽略**请求体中任意用于切换客户的 `customerId` 字段(若存在)。
|
||
|
||
###### 状态与失败处理
|
||
|
||
- 未登录或非客户身份:401/403。
|
||
|
||
###### 校验与异常
|
||
|
||
- 不得返回其它客户订单;不得通过 `keyword` 等条件绕过 `CustomerId` 过滤。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.GetPaginatedListAsync`:`ResolveListCustomerIdAsync` — **客户**强制 `CustomerId` 过滤;**管理员** `customerId=null` **不过滤**。
|
||
- 关键词匹配 **`ServiceNo`、`ClientNo`、`TrackNo`**;`status=all` 或不传表示不过滤状态。
|
||
- 排序:`UpdateTime desc, Id desc`;分页默认见 §1.5.1。
|
||
|
||
##### 4.4.1.2 订单详情(GET /order/detail)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/detail**(GET,Query 参数)
|
||
|
||
```
|
||
?id=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"clientNo": "CUST...",
|
||
"trackNo": "TRK...",
|
||
"serviceNo": "LP...",
|
||
"productCode": "US_AIR",
|
||
"channelCode": "CH001",
|
||
"cargoAttribute": "general",
|
||
"exportCustomsMode": "9610Simple",
|
||
"warehouseId": 1,
|
||
"warehouse": "深圳仓",
|
||
"customerId": 1,
|
||
"sender": {},
|
||
"receiver": {},
|
||
"items": [],
|
||
"weightList": []
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. 按 `id` 查询订单;不存在或 `IsDeleted=1` 时返回「订单不存在」。
|
||
2. 校验 `t_order.CustomerId == effectiveCustomerId`(§4.4.0);不匹配时 **同样**返回「订单不存在」(禁止泄露他客订单)。
|
||
3. 映射详情并返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 禁止仅凭 `id` 跨客户读取。
|
||
|
||
##### 4.4.1.3 支付(POST /order/pay)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/pay**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"requestId": ""
|
||
}
|
||
```
|
||
|
||
> requestId 幂等,可选。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. `GetOrderOrThrowAsync(id)`:订单存在且客户隔离通过。
|
||
2. **状态校验**:若已 **`pendingShip` 且已有 `PayTime`** → **「订单已支付」**;若 **`OrderStatus` 非 `pendingPayment` 且非 `received`**(`received` 为入库后历史数据兼容)→ **「当前订单状态不可支付」**。
|
||
3. **应付金额**:汇总该订单 **`t_order_fee`** 中 **`Type=receivable`** 的 **`Amount`**;**≤ 0** → **「订单应收金额无效,无法支付」**。
|
||
4. **扣款**:加载 **`order.CustomerId`** 对应客户;调用 **`CustomerBalanceMutator.DebitOrderPayAsync`**:
|
||
- 校验 **`Balance ≥ payAmount`**,不足 → **「账户余额不足」**;
|
||
- **`t_customer.Balance -= payAmount`**;
|
||
- 插入 **`t_customer_account_flow`**:`BizType=orderPay`,`BizId=order.Id`,`BizNo=ServiceNo`,**`Amount` 为负数**,记录 **`BalanceBefore` / `BalanceAfter`**,流水号 **`FLOW{yyyyMMddHHmmss}{6位随机}`**。
|
||
5. 更新订单:**`PayTime = DateTime.Now`**,**`OrderStatus = pendingShip`**。
|
||
|
||
> 步骤 4~5 在同一 **`ApplicationService` 工作单元**内提交;任一步失败整体回滚。
|
||
|
||
###### 校验与异常
|
||
|
||
| 场景 | 提示 |
|
||
| --- | --- |
|
||
| 订单不存在 / 越权 | **「订单不存在」** |
|
||
| 已支付 | **「订单已支付」** |
|
||
| 状态非待支付 | **「当前订单状态不可支付」** |
|
||
| 无客户 | **「订单未关联客户,无法支付」** |
|
||
| 应收无效 | **「订单应收金额无效,无法支付」** |
|
||
| 客户不存在 | **「客户不存在」** |
|
||
| 余额不足 | **「账户余额不足」** |
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- 实现:`OrderService.PayAsync`(`OrderService.cs`)+ **`CustomerBalanceMutator`**(`CustomerBalanceMutator.cs`)。
|
||
- 与 §2.3 主流程 B「支付扣减余额」**已对齐**;**不修改** `t_order_fee`(费用在预报/入库阶段已写入)。
|
||
|
||
##### 4.4.1.4 预报(POST /order/pre-declare)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/pre-declare**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"requestId": ""
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. `GetOrderOrThrowAsync(id)`。
|
||
2. 直接将 **`OrderStatus` 置为 `predeclared`** 并保存。
|
||
3. **不**重跑计重/算价/黑名单;**不**校验是否已有预报费用。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- 实现:`OrderService.PreDeclareAsync` — 纯状态更新,供运营手工补状态。
|
||
|
||
##### 4.4.1.5 拦截(POST /order/intercept)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/intercept**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"requestId": ""
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. `GetOrderOrThrowAsync(id)` → 设置 **`IsIntercept = 1`** 并保存。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.InterceptAsync`。
|
||
|
||
##### 4.4.1.6 取消拦截(POST /order/cancel-intercept)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/cancel-intercept**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"requestId": ""
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. `GetOrderOrThrowAsync(id)` → 设置 **`IsIntercept = 0`** 并保存。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.CancelInterceptAsync`。
|
||
|
||
##### 4.4.1.7 删除(POST /order/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"requestId": ""
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. `GetOrderOrThrowAsync(id)`。
|
||
2. **`OrderStatus = deleted`**,调用仓储 **软删除**(`IsDeleted=1`)。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.DeleteAsync` — 先更新状态再 `DeleteAsync` 软删。
|
||
|
||
##### 4.4.1.8 打印(POST /order/print)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/print**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"requestId": ""
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"printUrl": ""
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. `GetOrderOrThrowAsync(id)` → 映射订单详情。
|
||
2. `CreateOrderPrintFileAsync`:生成 **HTML** 面单至 `wwwroot/uploads/prints/orders/{serviceNo}-{timestamp}.html`。
|
||
3. 返回 **`printUrl`**(相对路径,如 `/uploads/prints/orders/...`)。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.PrintAsync` + `OrderService.Helpers.CreateOrderPrintFileAsync`(简易 HTML,非渠道标签模板)。
|
||
|
||
##### 4.4.1.9 导出(POST /order/export)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/export**(POST)
|
||
|
||
```json
|
||
{
|
||
"status": "all|draft|...",
|
||
"keyword": "",
|
||
"createTimeStart": "",
|
||
"createTimeEnd": ""
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"downloadUrl": "",
|
||
"expireAt": ""
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. 客户登录时 **`ResolveListCustomerIdAsync`** 限定导出范围。
|
||
2. 按 `status`、`keyword`、`createTimeStart/End` 查询订单(与 list 同 predicate)。
|
||
3. MiniExcel 写入 `wwwroot/uploads/exports/orders/orders-{timestamp}.xlsx`。
|
||
4. 清理 **24 小时**前旧文件;返回 **`downloadUrl`**、`expireAt`(当前时间 + 24h)。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.ExportAsync`。
|
||
|
||
##### 4.4.1.10 批量重算费用(POST /order/fee/batch-recalculate)
|
||
|
||
> **实现状态:未落地**(oms0414-back 无对应 `ApplicationService` 方法)。以下为设计目标,供后续迭代实现;**§10.6** 标记为「设计中」。
|
||
|
||
用于「我的订单」运营/对账场景:按订单 Id 列表 **批量** 重算并回写 **`t_order_fee`**,算法拆分为 **应收** 与 **应付** 两段(**§4.3.4.1**、**§4.3.4.2**),与预报、入库、出库、下游链路 **共用同一套计算单元**。
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/fee/batch-recalculate**(POST)
|
||
|
||
```json
|
||
{
|
||
"orderIds": [1001, 1002, 1003],
|
||
"scopes": ["receivable", "payable"]
|
||
}
|
||
```
|
||
|
||
> 参数说明:`orderIds` **必填**,非空数组,元素为 **`t_order.Id`**。`scopes` **必填**,取值为 `receivable`、`payable` 的子集(可只传其一或两者都传)。**`receivable`**:对列表内订单执行 **§4.3.4.1**。**`payable`**:对列表内订单执行 **§4.3.4.2**(依赖 **`downstream`** 行已存在且有效)。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"total": 3,
|
||
"successCount": 2,
|
||
"failCount": 1,
|
||
"failures": [{ "orderId": 1003, "reason": "缺少 t_order_weight.Type=downstream" }]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. **校验**:`orderIds` 去重、长度上限(如 ≤500,可配置)、订单存在且 **`IsDeleted=0`**;**逐单校验 `t_order.CustomerId = effectiveCustomerId`(§4.4.0)**,越权单记入 `failures` 或整批拒绝;调用方须已登录且为 **`loginType=1` 客户**。
|
||
2. **当 `scopes` 含 `receivable`(重算应收)**
|
||
- 逐单加载:绑定 **`SalesProductPriceVersion`** 卖价快照、`t_order_weight`、`t_order_fee` 既有应收行(若策略为全量重算可先逻辑清理再写)。
|
||
- 按 **§4.3.4.1**:全部启用卖价项;重量取对应 **`t_order_weight` 行** `ChargeableWeight`,**upsert** 应收。
|
||
3. **当 `scopes` 含 `payable`(重算应付)**
|
||
- 逐单校验 **`t_order_weight` 存在 `Type=downstream`** 且可用于买价匹配;加载绑定 **买价** 快照。
|
||
- 按 **§4.3.4.2**:**仅 `downstream`** 行参与重量/件重尺因子,重算并 **upsert** `Type=payable`。
|
||
4. **事务**:建议 **按订单** 单事务提交,避免一批中一单失败影响其他单;`failures` 逐单记录原因。
|
||
5. **与履约链路的关系**:**预报成功、入库完成、出库完成** 应在各自流程内调用 **§4.3.4.1**(重量分别取 **`forecast` / `receive` / `ship` 行**);**`downstream` 行更新后**(下游回传或独立流程)再调用 **§4.3.4.2**。本接口为 **显式批量补算**,不改变订单已绑定版本契约。
|
||
|
||
###### 校验与异常
|
||
|
||
- `orderIds` 为空、`scopes` 为空或不合法、订单无绑定卖价/买价版本、缺 `downstream`(在请求 `payable` 时)等返回业务错误;部分失败时仍返回 `200` + `data.failures`(或与全局错误码策略一致)。
|
||
|
||
##### 4.4.1.11 创建轨迹订单(POST /order/track-upsert-small-bag)
|
||
|
||
> **实现状态:已落地**(oms0414-back `OrderService.TrackUpsertSmallBagAsync`;前端「我的订单」批量按钮)。
|
||
|
||
手动触发小包轨迹 **`POST {TrackBaseUrl}/OrderV2/UpsertSmallBag`**(同步调用,返回完整请求/响应便于联调)。与预报成功后的 **异步** 轨迹推送(`SmallBagTrackOrderNotifier`)共用 **`BuildSmallBagTrackUpsertRequestAsync`** 组装逻辑。
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/track-upsert-small-bag**(POST)
|
||
|
||
```json
|
||
{ "id": 1001 }
|
||
```
|
||
|
||
**响应**(`SmallBagTrackUpsertResult`)
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"skipped": false,
|
||
"skipReason": null,
|
||
"requestUrl": "http://api.nanafa.com/track/OrderV2/UpsertSmallBag",
|
||
"requestBody": { "appKey": "...", "orderNo": "LP...", "apiCode": "FORECAST_01" },
|
||
"responseStatusCode": 200,
|
||
"responseBody": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
1. 校验订单存在、未删除、**归属当前客户**(§4.4.0);**`ServiceNo`** 非空。
|
||
2. 组装 **`SmallBagTrackUpsertRequest`**:客户编码、运单号、跟踪号、渠道、产品、收件人国家/省/邮编等。
|
||
3. **`ApiCode`**:优先 **`t_order.ChannelForecastCode`**;为空且 **`ChannelCode`** 有值时,回退查 **`t_service_channel.ForecastCode`**。
|
||
4. 轨迹凭证读 **`t_system_other_config`**(`ConfigCode=track_service`),缺省回退 **`Oms:TrackService`**(§8.2.7);未启用或参数不全时返回 **`skipped=true`** 及原因。
|
||
5. 联调入口:**`POST /order/debug/track-upsert-small-bag`**(Swagger,可自定义测试字段)。
|
||
|
||
##### 4.4.1.12 预报下游订单(前端 → itfx,非 OMS 后端接口)
|
||
|
||
> **实现状态:已落地**(前端 `order/myOrders` 批量操作 + `forwardCreateOrderOne`;凭证解析 `resolveOrderDownstreamCredentials`)。
|
||
|
||
运营在「我的订单」勾选订单后,浏览器直连 itfx 网关 **`POST /InterfaceForward/ForwardWithSubscribe`**,**`standardCode=createOrderOne`**,Body 为 **§4.4.3.1** 同构的 parcel/create JSON(`orderCreateOneBody.ts` 由订单详情组装)。
|
||
|
||
###### 1 凭证解析优先级
|
||
|
||
| 顺序 | 来源 | 字段 |
|
||
| --- | --- | --- |
|
||
| 1(优先) | **`t_order` 绑定字段**(排货确认时写入) | `downstreamSupplierCode`、`downstreamAppKey`、`downstreamAppId` |
|
||
| 2(回退) | 渠道树 live 查询 | `ChannelCode` → `t_service_channel` → 分组 → itfx App / ServiceProvider |
|
||
|
||
**AppSecret** 不落订单、不落规则快照;无论哪条路径,均按 **`downstreamAppId`(或分组 `AppId`)** 调 itfx **`GET /App/GetInfo`** 读取。
|
||
|
||
###### 2 请求要点
|
||
|
||
- Query:`standardCode=createOrderOne`,`supplierCode={DownstreamSupplierCode}`。
|
||
- Header:`appkey`、`appSecret`、`followName=0`(itfx 网关约定)。
|
||
- Body:camelCase,与 **§4.4.3.1** 创建小包请求体对齐(含 `receiver`、`items` 等)。
|
||
|
||
###### 3 与规则快照的关系
|
||
|
||
- 规则发布时 **`Providers[]`** 已 enrich 渠道/itfx 元数据(§5.3.0.2);排货确认渠道时固化至 **`t_order`**。
|
||
- **历史订单**(确认渠道时尚未写入四字段)仍走凭证解析 **回退路径**;重新发布销售产品规则后,**新排货确认** 的单据将携带完整绑定字段。
|
||
|
||
##### 4.4.1.13 创建问题件(POST /order/mark-problematic)
|
||
|
||
> **实现状态:设计中**(oms0414-back `OrderService` 待新增;前端 `order/myOrders` 批量弹窗)。
|
||
|
||
###### 1 业务目标
|
||
|
||
运营在「我的订单」勾选一条或多条订单,填写问题类型与话术(可引用 **问题模板** §5.2.21),将订单 **`OrderStatus` 置为 `problematic`**,**`HandleStatus` 置为 `pending`**,并写入问题快照字段;供 **问题件** Tab 与 **待处理/已处理** 子 Tab 筛选展示。
|
||
|
||
###### 2 状态准入规则
|
||
|
||
| 规则 | 说明 |
|
||
| --- | --- |
|
||
| **禁止源状态** | `draft`、`deleted`、`problematic`、`claimed` |
|
||
| **允许源状态** | `predeclared`、`received`、`pendingPayment`、`pendingShip`、`pendingOnline`、`shipping`、`deliverySuccess`、`deliveryFailed` |
|
||
| **互斥** | 已为问题件/理赔件/已删除的订单 **不可重复创建**;一期 **不支持** 问题件↔理赔件互转 |
|
||
| **拦截** | `IsIntercept=1` **不阻止** 标记问题件(与拦截标记独立) |
|
||
|
||
变更时写入 **`PreviousOrderStatus`** = 变更前 `OrderStatus`,便于二期回滚或报表。
|
||
|
||
###### 3 请求示例
|
||
|
||
```json
|
||
{
|
||
"orderIds": [1001, 1002],
|
||
"issueTemplateId": 12,
|
||
"issueType": "address",
|
||
"title": "收件地址无法派送",
|
||
"content": "运单 {serviceNo} 收件地址不完整,请补充门牌号与邮编。",
|
||
"remark": "客户已电话确认"
|
||
}
|
||
```
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
| --- | --- | --- |
|
||
| `orderIds` | 是 | 订单 Id 列表;**单次最多 200 条** |
|
||
| `issueTemplateId` | 否 | 引用 `t_issue_template.Id`;传入时 **覆盖** `title`/`content`/`issueType`,并快照 `NotifyMethod` 至事件表 |
|
||
| `issueType` | 条件 | 未传 `issueTemplateId` 时 **必填**;枚举见 **§10.3.6** |
|
||
| `title` | 条件 | 未传模板时 **必填**;传模板时可覆盖模板标题 |
|
||
| `content` | 条件 | 未传模板时 **必填** |
|
||
| `remark` | 否 | 人工备注,写入 `IssueRemark` 与事件表 |
|
||
|
||
**模板解析**:按订单归属客户 **`OrganizationId`**(经 `t_customer` 关联)调用 **`GET /channel/issue-template/options?organizationId=&issueType=`** 同源逻辑;`issueTemplateId` 须为 **启用** 且未删除的模板。
|
||
|
||
**占位符(一期)**:`content` **原样落库**;二期在发通知前渲染 `{serviceNo}`、`{clientNo}`、`{trackNo}` 等。
|
||
|
||
###### 4 响应示例(部分失败)
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"successCount": 1,
|
||
"failures": [
|
||
{ "orderId": 1002, "serviceNo": "LP002", "reason": "仅草稿或已删除订单不可标记问题件" }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 5 落库与副作用
|
||
|
||
1. **更新 `t_order`**:`OrderStatus=problematic`,`HandleStatus=pending`,问题字段、`IssueTime=now`,`PreviousOrderStatus`。
|
||
2. **插入 `t_order_exception_event`**:`EventType=mark_problematic`,快照标题/内容/模板/通知方式。
|
||
3. **通知(二期)**:引用问题模板时读取 `NotifyMethod` 并投递(**`inApp` 站内信、`email` 操作人 SMTP 已接入**;`sms/feishu/wechat` 待通道对接);通知失败 **不回滚** 问题件标记。
|
||
4. **客户隔离**:逐单 `GetOrderOrThrowAsync`;管理员须具备数据权限(§4.4.0、`ApplyInternalOrderDataScopeAsync`)。
|
||
|
||
###### 6 权限
|
||
|
||
| 权限码 | 说明 |
|
||
| --- | --- |
|
||
| `Order.Problematic.Create` | 工具栏「创建问题件」及接口调用 |
|
||
|
||
##### 4.4.1.14 创建理赔件(POST /order/mark-claimed)
|
||
|
||
> **实现状态:设计中**。
|
||
|
||
###### 1 业务目标
|
||
|
||
运营勾选订单,填写理赔类型、索赔金额与原因,将订单 **`OrderStatus` 置为 `claimed`(理赔件)**,**`HandleStatus` 置为 `pending`**。
|
||
|
||
###### 2 状态准入规则
|
||
|
||
与 **§4.4.1.13** 相同:**禁止** `draft`/`deleted`/`problematic`/`claimed`;**允许**其余在途/完结状态(见上表)。
|
||
|
||
> 业务上理赔多发生在运输/派送阶段;技术上与问题件共用同一准入集合,由运营自行判断。
|
||
|
||
###### 3 请求示例
|
||
|
||
```json
|
||
{
|
||
"orderIds": [2001],
|
||
"claimType": "lost",
|
||
"claimAmount": 128.5,
|
||
"claimCurrency": "USD",
|
||
"claimReason": "末端确认丢件,申请运费+货值赔偿",
|
||
"remark": ""
|
||
}
|
||
```
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
| --- | --- | --- |
|
||
| `orderIds` | 是 | 同上,最多 200 条 |
|
||
| `claimType` | 是 | 理赔类型,见 **§10.3.7** |
|
||
| `claimAmount` | 是 | `> 0` |
|
||
| `claimCurrency` | 是 | `RMB` / `USD` / `EUR` 等 |
|
||
| `claimReason` | 是 | 最长 512 |
|
||
| `remark` | 否 | 写入事件表 |
|
||
|
||
###### 4 响应
|
||
|
||
与 **§4.4.1.13** 同构:`successCount` + `failures[]`。
|
||
|
||
###### 5 落库
|
||
|
||
1. **`t_order`**:`OrderStatus=claimed`,`HandleStatus=pending`,理赔字段、`ClaimTime=now`,`PreviousOrderStatus`;**清空**问题件专用字段(若曾有问题字段残留)。
|
||
2. **`t_order_exception_event`**:`EventType=mark_claimed`。
|
||
|
||
###### 6 权限
|
||
|
||
| 权限码 | 说明 |
|
||
| --- | --- |
|
||
| `Order.Claim.Create` | 工具栏「创建理赔件」 |
|
||
|
||
##### 4.4.1.15 更新处理子状态(POST /order/handle-status/update)
|
||
|
||
> **实现状态:设计中**(`HandleStatus` 列与列表筛选 **已落地**,见 `migrate_t_order_handle_status.sql`)。
|
||
|
||
###### 1 业务目标
|
||
|
||
在 **问题件 / 理赔件** Tab 下,将选中订单由 **待处理** 标记为 **已处理**(或一期仅支持 `pending → processed`)。
|
||
|
||
###### 2 准入
|
||
|
||
- 当前 `OrderStatus` 必须为 `problematic` 或 `claimed`。
|
||
- 当前 `HandleStatus` 为 `pending` 或 `NULL`(视为待处理)。
|
||
|
||
###### 3 请求示例
|
||
|
||
```json
|
||
{
|
||
"orderIds": [1001],
|
||
"handleStatus": "processed",
|
||
"remark": "已联系客户并结案"
|
||
}
|
||
```
|
||
|
||
###### 4 落库
|
||
|
||
- 更新 `t_order.HandleStatus`。
|
||
- 插入 `t_order_exception_event`:`EventType=handle_processed`,记录 `FromHandleStatus` / `ToHandleStatus`。
|
||
|
||
###### 5 权限
|
||
|
||
| 权限码 | 说明 |
|
||
| --- | --- |
|
||
| `Order.HandleStatus.Update` | 子 Tab「标记已处理」 |
|
||
|
||
##### 4.4.1.16 前端交互(`order/myOrders`)
|
||
|
||
| 场景 | UI |
|
||
| --- | --- |
|
||
| 非问题件/理赔件 Tab | 工具栏 **创建问题件**、**创建理赔件**(须勾选行;`hasPermission` 控制) |
|
||
| 问题件/理赔件 Tab | 子 Tab **待处理 / 已处理**(已落地);工具栏 **标记已处理**(`Order.HandleStatus.Update`) |
|
||
| 创建问题件弹窗 | `ModalPro` + `FormPro`:问题类型 → 联动 **`issue-template/options`** → 可选模板;标题、内容可编辑;备注 |
|
||
| 创建理赔件弹窗 | 理赔类型、金额、币种、原因 |
|
||
| 批量策略 | 优先调用 **批量接口**;失败行 `Message` 汇总展示 `failures` |
|
||
|
||
列表/详情响应补充字段(实现时):`handleStatus`、`handleStatusName`、`issueType`、`issueTitle`、`claimType`、`claimAmount` 等(与 **§4.2.1** 对齐)。
|
||
|
||
#### 4.4.2 我要查价
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 查价 | POST | /order/price-check | 按销售产品**最新生效卖价版本**试算运费并返回;**须客户登录**(§4.4.0) |
|
||
|
||
##### 4.4.2.1 查价(POST /order/price-check)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/price-check**(POST)
|
||
|
||
```json
|
||
{
|
||
"destinationCountry": "US", // 目的国二字码
|
||
"shippingMethod": "", // 运输方式
|
||
"cargoWeight": "2", // 货物重量(kg)
|
||
"length": "20", // 长(cm)
|
||
"width": "15", // 宽(cm)
|
||
"height": "10", // 高(cm)
|
||
"cargoType": "普货", // 货物类型
|
||
"destinationProvince": "", // 目的省州
|
||
"postalCode": "" // 邮编
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"shippingMethod": "美国空运专线(普货)",
|
||
"warehouse": "一代深圳仓",
|
||
"deliveryTime": "3-7",
|
||
"volumetricWeight": "2",
|
||
"chargeableWeight": "2",
|
||
"costFormula": "freight:11 + handling:20",
|
||
"freight": "11",
|
||
"otherFees": "20",
|
||
"totalCost": "31",
|
||
"maxDeliveryDays": 7,
|
||
"totalCostNum": 31,
|
||
"productCode": "test01",
|
||
"rulesVersion": "RULES-20260601-001",
|
||
"priceVersion": "PRICE-20260601-001",
|
||
"isRemote": false
|
||
}
|
||
],
|
||
"failures": [
|
||
{
|
||
"productId": 2,
|
||
"productCode": "OTHER",
|
||
"productName": "其他产品",
|
||
"step": "zone",
|
||
"reason": "目的国 US 邮编 00000 未匹配产品价格分区(请检查 PriceAreaId 及分区规则)"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. **校验**:`destinationCountry` 必填(ISO 二字码);`cargoWeight` 须 **> 0**;**尺寸可选** — 全空则按实重估算,或长/宽/高三者齐全(部分填写 → 报错)。
|
||
2. **绑定时刻**:`bindTime = DateTime.Now`;解析卖价最新生效快照(`ResolveSnapshotBindingAsync`)。
|
||
3. **产品范围**:未传 `shippingMethod` 时遍历 **全部已启用** 销售产品;有值则按代码/名称过滤;未找到 → `failures[0].step=product`。
|
||
4. **逐产品试算**(按快照明细匹配;**无邮编时跳过黑名单**;分区无匹配时可 **zone 兜底** 取最低价分区,标记 `quoteLevel=estimated`):
|
||
- **分区**:用 **目的国 + 邮编**(及省州/城市若有)匹配;未命中且无兜底 → `step=zone`。
|
||
- **计重**:无尺寸时按实重;有尺寸则体积重参与;失败 → `step=weight`。
|
||
- **货物/仓库**:未筛选时展开 **general/electronics/sensitive × 全部仓库** 组合试算。
|
||
- **算价**:`CalculateQuoteReceivableFees`;无明细命中 → `step=price`。
|
||
5. **结果**:成功写入 `items`(含 **`quoteLevel`、`disclaimers`**);顶层 **`queryHint`** 汇总缺失字段与免责声明;失败写入 `failures`(含 `step` + `reason`),**禁止静默丢弃**。
|
||
|
||
###### failures.step 说明
|
||
|
||
| step | 含义 |
|
||
| --- | --- |
|
||
| `product` | 未找到启用产品 |
|
||
| `snapshot` | 卖价/规则快照解析失败 |
|
||
| `blacklist` | 命中黑名单 |
|
||
| `weight` | 计重规则/进位失败 |
|
||
| `zone` | 国家+邮编未匹配价格分区 |
|
||
| `price` | 分区已匹配但卖价明细未命中(重量区间/货物属性/无启用项) |
|
||
|
||
###### 校验与异常
|
||
|
||
| 场景 | 错误信息 |
|
||
| --- | --- |
|
||
| 目的国为空 | 目的国不能为空 |
|
||
| 重量 ≤ 0 | 货物重量必须大于0 |
|
||
| 尺寸部分填写 | 尺寸请填写完整的长、宽、高,或全部留空 |
|
||
| 目的国不存在 | 目的国不存在 |
|
||
| 全部产品未命中 | `200` + `items: []` + `failures` 逐条原因 |
|
||
|
||
**响应扩展(对客提示)**
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `queryHint.quoteLevel` | `exact` / `estimated`(有缺失字段时为估算) |
|
||
| `queryHint.disclaimers` | 免责声明列表(如未填尺寸、未填邮编) |
|
||
| `items[].quoteLevel` | 单行报价精度 |
|
||
| `items[].disclaimers` | 单行补充说明 |
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.PriceCheckAsync` → `BuildPriceCheckResultAsync`(`OrderService.ParcelQuote.cs`)。
|
||
- **`cargoType`** 经 `ParcelBillingCalculator.MapCargoTypeToAttribute` 映射为 `cargoAttribute`(支持中文「普货」等)。
|
||
- 客户登录时 **`ResolveCurrentCustomerGroupAsync`** 参与卖价 **客户组** 因子;管理员查价时客户组为 `null`。
|
||
- 详见 **§4.3.5**。
|
||
|
||
#### 4.4.3 我要下单
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 模板列表 | GET | /order/template/list | 获取下单模板列表;自定义模板按 **`CustomerId` 隔离** |
|
||
| 下载模板 | GET | /order/template/download | 下载 Excel 模板 |
|
||
| 上传下单 | POST | /order/upload | multipart/form-data;创建 **`import-row`** 批量任务,返回 **`jobId`** |
|
||
| 批量任务列表 | GET | /order/batch/jobs | 批量任务分页列表;**仅当前客户** |
|
||
| 批量任务详情 | GET | /order/batch/jobs/{id} | 任务进度(含 success/fail/pending 计数) |
|
||
| 批量任务子项 | GET | /order/batch/jobs/{id}/items | 子项明细(含 Excel 行号、客户单号、失败原因) |
|
||
| 重试失败子项 | POST | /order/batch/jobs/{id}/retry-failed | 重新投递失败子项 |
|
||
| 取消待处理子项 | POST | /order/batch/jobs/{id}/cancel | 取消 pending 子项 |
|
||
| 创建小包 | POST | /order/parcel/create | 创建小包订单;**`CustomerId` 与会话一致**(§4.4.0) |
|
||
| 批量创建小包 | POST | /order/parcel/batch-create | 批量创建;**同上客户隔离** |
|
||
|
||
##### 4.4.3.1 创建小包(POST /order/parcel/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/parcel/create**(POST)
|
||
|
||
> 字段与 t_order、t_order_sender、t_order_recipient、t_order_declaration、t_order_weight 表对应,采用 camelCase。
|
||
|
||
```json
|
||
{
|
||
"requestId": "string", // 幂等键 t_order.RequestId//可选;重复则直接返回已有订单结果
|
||
"clientNo": "string", // 客户单号 t_order.ClientNo//可选
|
||
"trackNo": "string", // 跟踪号 t_order.TrackNo//可选
|
||
"serviceNo": "string", // 运单号 t_order.ServiceNo//可选;未传时服务端自动生成(IDE 前缀)
|
||
"productCode": "string", // 产品代码 t_order.ProductCode//必填
|
||
"channelCode": "string", // 渠道代码 t_order.ChannelCode
|
||
"cargoAttribute": "string", // 货物属性 t_order.CargoAttribute:general-普货,electronics-3c&带电,sensitive-敏感货//必填
|
||
"exportCustomsMode": "9610Simple", // 出口报关方式 t_order.ExportCustomsMode,见 §4.2.1 ExportCustomsMode 枚举//可选
|
||
"warehouseId": 0, // 仓库ID t_order.WarehouseId//必填
|
||
"customerId": 0, // 客户ID t_order.CustomerId//可选:客户登录由会话补全;管理员代下单必填
|
||
"length": 3, // 长(cm) t_order_weight.Length
|
||
"width": 3, // 宽(cm) t_order_weight.Width
|
||
"height": 3, // 高(cm) t_order_weight.Height
|
||
"actualWeight": 3, // 实重(kg) t_order_weight.ActualWeight//必填
|
||
"isCod": false, // 是否COD
|
||
"codSum": "string", // COD金额
|
||
"codCurrency": "string", // COD币种
|
||
"declaredCurrency": "string", // 申报币种//必填
|
||
"declaredSum": "string", // 申报总金额
|
||
"quantity": "string", // 包裹数量//必填
|
||
"receiver": { // 收件人 t_order_recipient
|
||
"recipientsName": "string", // 收件人名称//必填
|
||
"companyName": "string", // 收件人公司名称
|
||
"phone": "string", // 收件人手机(接口层非必填,前端建议填写)
|
||
"telePhone": "string", // 收件人电话
|
||
"email": "string", // 收件人邮箱
|
||
"countryCode": "US", // 收件国家 ISO 3166-1 alpha-2 二字码(对应 t_country.CountryCode)//必填
|
||
"province": "string", // 收件人省/州
|
||
"city": "string", // 收件人城市
|
||
"streetNumber": "string", // 收件人门牌号
|
||
"streetAddress1": "string", // 收件人地址1//必填
|
||
"streetAddress2": "string", // 收件人地址2
|
||
"streetAddress3": "string", // 收件人地址3
|
||
"zipCode": "string", // 收件人邮编//必填
|
||
"taxType": 0, // 税号类型
|
||
"taxNo": "string", // 税号
|
||
"certificateType": 0, // 证件类型
|
||
"certificateNo": "string", // 证件号码
|
||
"eori": "string", // EORI
|
||
"vat": "string" // Vat
|
||
},
|
||
"sender": { // 发件人 t_order_sender
|
||
"senderName": "string", // 发件人姓名//必填
|
||
"company": "string", // 发件人公司
|
||
"phone": "string", // 发件人手机
|
||
"telePhone": "string", // 发件人电话
|
||
"email": "string", // 发件人邮箱
|
||
"countryCode": "CN", // 发件国家 ISO 二字码//可选
|
||
"province": "string", // 州省
|
||
"city": "string", // 城市
|
||
"zipCode": "string", // 邮编
|
||
"streetAddress1": "string", // 地址1
|
||
"streetAddress2": "string", // 地址2
|
||
"streetAddress3": "string", // 地址3
|
||
"houseNumber": "string" // 门牌号
|
||
},
|
||
"items": [ // 申报明细 t_order_declaration
|
||
{
|
||
"cnName": "string", // 品名-中
|
||
"enName": "string", // 品名-英//必填
|
||
"sku": "string", // SKU
|
||
"unit": "string", // 单位
|
||
"unitPrice": 0, // 单价//必填
|
||
"unitWeight": 0, // 单重//必填
|
||
"quantity": 0, // 数量//必填
|
||
"amount": 0, // 总金额
|
||
"weight": 0, // 总重量
|
||
"imHsCode": "string", // 进口HsCode
|
||
"exHsCode": "string", // 出口HsCode
|
||
"skuImage": "string", // 图片
|
||
"category": "string", // 类别
|
||
"brand": "string", // 品牌
|
||
"brandType": "string", // 品牌类型
|
||
"isMagnetic": false, // 是否带磁带电
|
||
"distribution": "string", // 配货信息
|
||
"material": "string", // 材质
|
||
"enMaterial": "string", // 英文材质
|
||
"purpose": "string", // 用途
|
||
"attributes": "string", // 属性
|
||
"originCountry": "string", // 原产国
|
||
"salesLink": "string", // 销售链接
|
||
"model": "string", // 型号
|
||
"specifications": "string", // 规格
|
||
"comment": "string" // 备注
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"clientNo": "",
|
||
"serviceNo": "",
|
||
"trackNo": "",
|
||
"labelUrl": ""
|
||
}
|
||
}
|
||
```
|
||
|
||
> 幂等时可选传 requestId。
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 本接口用于创建小包订单预报数据,执行顺序固定为:参数校验 -> 查询销售产品与规则/价格快照绑定 -> **黑名单校验(命中则中止,不落库)** -> 创建订单草稿 -> 偏远校验 -> 计费重计算 -> **仅「预报」时机费用计算与落库** -> 更新订单状态 -> 返回结果。(入库/出库时机费用见 §4.3.4,由仓储收货、出库流程触发。)
|
||
- 参数校验失败,或销售产品/快照绑定失败、黑名单校验命中时,均不创建任何订单数据,直接返回业务错误。
|
||
- 进入草稿创建后,先将订单主数据、发件人、收件人、申报明细、预报件重尺数据落库,订单状态固定写入 `draft`。
|
||
- 后续偏远、计费重、费用等流程全部基于已创建的草稿订单继续处理;如任一业务环节失败,订单状态不扩展新枚举,统一保留 `draft`,并在 `t_order.PredeclareFailReason` 记录失败原因。
|
||
|
||
###### 详细业务逻辑
|
||
|
||
1. 参数解析与基础数据校验
|
||
- **会话客户(优先)**:调用 `ResolveSessionCustomerAsync`,将 **`effectiveCustomerId`** 写入待落库 `customerId`;客户登录 **可不传** `customerId`;管理员代下单 **须传** `customerId`,否则返回 **「客户ID不能为空」**。
|
||
- 校验请求体必填字段:`productCode`、`warehouseId`、`length`、`width`、`height`、`actualWeight`、`receiver.recipientsName`、`receiver.countryCode`、`receiver.zipCode`、`receiver.streetAddress1`、`sender.senderName`、`items`(**不含** `customerId`、`trackNo`、`serviceNo`)。
|
||
- 校验数值合法性:`length/width/height/actualWeight` 必须大于 0。
|
||
- 校验枚举合法性:`cargoAttribute` 仅允许 `general`、`electronics`、`sensitive`。
|
||
- 校验枚举合法性:`exportCustomsMode` **可选**;若传入则须为 §4.2.1 **ExportCustomsMode** 枚举 code,否则返回 **「出口报关方式不合法」**。
|
||
- **客户校验**:按 **`effectiveCustomerId`**(**`t_customer.Id`**)确认客户存在且 `Status` 可用;不可用则返回错误。请求体若传 `customerId` 须与 **`effectiveCustomerId` 一致**,否则 **「客户ID非法」**。
|
||
- **客户隔离(§4.4.0)**:落库 `t_order.CustomerId` **仅**写入 `effectiveCustomerId`。
|
||
- 查询 `t_warehouse_address`,条件:`Id = warehouseId and IsDeleted = 0`,返回字段:`Id`、`WarehouseName`;仓库不存在时返回错误。
|
||
- 查询 `t_country` 校验收件人国家:`receiver.countryCode` 为 **string**,优先按 **`CountryCode`(ISO 二字码,大小写不敏感)** 匹配;亦兼容 **数字字符串** 形式的 `t_country.Id`;亦可按中/英文名匹配。不存在时返回 **「收件国家不存在」**。下列字段任一缺失返回 **「收件人信息不完整」**:`receiver`、`recipientsName`、`countryCode`、`zipCode`、`streetAddress1`。
|
||
|
||
2. 查询销售产品配置(用于定位发布记录)
|
||
- 查询 `t_sales_product`,条件:`ProductCode = productCode and IsDeleted = 0 and IsActive = 1`
|
||
- 返回字段:`Id`、`ProductCode`、`ProductName`
|
||
- 如不存在,**不创建订单**,直接返回失败(错误信息需明确「销售产品不存在或未启用」)。
|
||
|
||
3. 绑定销售产品计费快照(强关联:以创建订单当刻绑定的价格与规则为准;**运行时优先 Redis + 本地内存**)
|
||
- 绑定时点:以本次请求处理时间作为 `bindTime`(草稿落库后,`t_order.CreateTime` 应与该时点一致或可由该时点推导)。
|
||
- **主路径(推荐)**:按 `salesProductId = t_sales_product.Id` 与 `bindTime`,从 **Redis / 计费服务本地内存** 解析「当前应对外生效」的 **规则快照** 与 **价格快照**(结构化 JSON,与运营发布组装的版本一致),得到:
|
||
- `rulesSnapshot`:含 `version`、`effectiveStartAt`(或等价字段)、`content`
|
||
- `priceSnapshot`:含 `version`、`effectiveStartAt`、`content`
|
||
- **降级路径**:当 Redis/内存未命中或版本指针异常时,从 **`t_sales_product_publish_record`** 读取:
|
||
- 规则:条件 `SalesProductId = t_sales_product.Id and OperatorType = 'rules' and Status = '生效' and PublishTime <= bindTime`,按 `PublishTime desc` 取 1 条,`Content` 即规则快照正文(与下文 `rulesSnapshot.content` 同构)。
|
||
- 价格:条件 `OperatorType = 'price'`,其余同上,`Content` 即价格快照正文。
|
||
- 命中后应**异步回写 Redis**,并打点监控。
|
||
- 绑定失败处理(**尚未落库订单**):
|
||
- 若未找到 rulesSnapshot:**不创建订单**,直接返回失败(错误信息需明确「未命中规则快照版本(内存/Redis/DB)」)。
|
||
- 若未找到 priceSnapshot:**不创建订单**,直接返回失败(错误信息需明确「未命中价格快照版本(内存/Redis/DB)」)。
|
||
- 版本字段落库:在下一步「创建订单草稿」插入 `t_order` 时写入(强关联字段,**新单新价、老单不改**):
|
||
- `SalesProductRulesVersion = rulesSnapshot.version`(与发布记录 `Version` 一致)
|
||
- `SalesProductPriceVersion = priceSnapshot.version`
|
||
- 性能要求:
|
||
- 下单/预估主链路应在**进程内内存**完成快照读取与匹配;降级读 DB 仅作兜底,应限流并尽快恢复 Redis。
|
||
|
||
4. 黑名单校验(拦截型,来自已绑定的 rules 快照 `content`)
|
||
- 读取 `rulesSnapshot.content.areas.blacklist.rules`(国家/省市/邮编区间规则集合)。
|
||
- 匹配规则:
|
||
- 国家必须匹配参数校验阶段已解析的收件国家编码(与后续写入 `t_order.ConsigneeCountry` 的值一致,来源于 `t_country.CountryCode`)
|
||
- 若规则配置了省/市,则请求体收件人 `receiver` 的省/市也必须匹配
|
||
- 邮编落在任一 `postcodeRanges.left ~ postcodeRanges.right` 区间内即视为命中
|
||
- 命中处理:**不创建订单**,直接返回失败(错误信息需明确「收件地址命中黑名单区域」),不进入草稿落库及后续流程。
|
||
|
||
5. 创建订单草稿
|
||
- **`ServiceNo`**:请求体 `serviceNo` 为空时,服务端自动生成唯一运单号(**`IDE` + `yyMMddHHmmss` + 4 位随机数**,年份取后两位如 2026→26);`trackNo` 为空则落库 `NULL`。
|
||
- 插入 `t_order`:
|
||
- 写入字段:`ServiceNo`、`ClientNo`、`TrackNo`、`ProductCode`、`ChannelCode`、`ConsigneeCountry`、`CargoAttribute`、`ExportCustomsMode`、`OrderStatus`、`WarehouseId`、`CustomerId`、`IsCod`、`CodCurrency`、`CodSum`、`IsIntercept`、`IsRemote`、`PredeclareFailReason`、`SalesProductRulesVersion`、`SalesProductPriceVersion`、审计字段。
|
||
- 处理规则:
|
||
- `OrderStatus` 固定写 `draft`
|
||
- `ConsigneeCountry` 取收件国家对应的 `t_country.CountryCode`
|
||
- `IsIntercept` 初始写 `0`
|
||
- `IsRemote` 初始写 `0`
|
||
- `PredeclareFailReason` 初始写空
|
||
- `SalesProductRulesVersion`、`SalesProductPriceVersion` 取步骤 3 已解析的快照版本
|
||
- `ExportCustomsMode` 取请求体 `exportCustomsMode`(须为 §4.2.1 枚举 code),未传则 `NULL`
|
||
- 插入 `t_order_sender` / `t_order_recipient`:
|
||
- 请求体 `sender.countryCode` / `receiver.countryCode` 为 **ISO 二字码**(见步骤 1);落库 **`Country` 字段为 `t_country.Id`**(int)。
|
||
- 其它字段见下表列名映射。
|
||
- 插入 `t_order_sender`(明细):
|
||
- 条件:使用新生成的 `OrderId`
|
||
- 写入字段:`OrderId`、`SenderName`、`Company`、`Country`、`Province`、`City`、`ZipCode`、`StreetAddress1`、`StreetAddress2`、`StreetAddress3`、`HouseNumber`、`Phone`、`TelePhone`、`Email`
|
||
- 插入 `t_order_recipient`:
|
||
- 写入字段:`OrderId`、`RecipientsName`、`CompanyName`、`Country`、`Phone`、`TelePhone`、`ZipCode`、`Province`、`City`、`StreetNumber`、`StreetAddress1`、`StreetAddress2`、`StreetAddress3`、`Email`、`TaxType`、`TaxNo`、`CertificateType`、`CertificateNo`、`Eori`、`Vat`
|
||
- 插入 `t_order_declaration`:
|
||
- 对 `items` 数组逐条入库
|
||
- 写入字段:`OrderId`、`CnName`、`EnName`、`Sku`、`Unit`、`UnitPrice`、`UnitWeight`、`Quantity`、`Amount`、`Weight`、`HsCode`、`SkuImage`、`Category`、`Brand`、`BrandType`、`IsMagnetic`、`Distribution`、`Material`、`EnMaterial`、`Purpose`、`Attributes`、`OriginCountry`、`SalesLink`、`Model`、`Specifications`、`Comment`
|
||
- 插入 `t_order_weight`:
|
||
- 一次性写入 **4** 条记录,`Type` 依次为 **`forecast`**、**`receive`**、**`ship`**、**`downstream`**(与 §4.2.7 `Type` 枚举一致)
|
||
- 写入字段:`OrderId`、`Type`、`Length`、`Width`、`Height`、`ActualWeight`、`VolumetricWeight`、`ChargeableWeight`、`Cubic`
|
||
- **先**构造 **`Type = forecast`** 行的初始值:
|
||
- `ActualWeight` 取请求值
|
||
- `VolumetricWeight`、`ChargeableWeight` 可先写请求体值或 `0`,后续步骤 7、8 按规则重算并覆盖
|
||
- `Cubic = Length × Width × Height / 1000000`
|
||
- **`Type = receive` / `ship` / `downstream` 三行**:`Length`、`Width`、`Height`、`ActualWeight`、`VolumetricWeight`、`ChargeableWeight`、`Cubic` **均与 `forecast` 行相同**(创建时从 `forecast` 拷贝;后续收货、发货、下游等环节再按各自 `Type` 更新差异化测量)
|
||
|
||
6. 偏远区域校验(标记型,来自已绑定的 rules 快照)
|
||
- 读取 `rulesSnapshot.content.areas.remote.rules`(国家/省市/邮编区间规则集合)。
|
||
- 匹配规则与黑名单一致。
|
||
- 命中则更新 `t_order.IsRemote = 1`;未命中则更新 `t_order.IsRemote = 0`
|
||
- 偏远校验不拦截订单,只影响后续费用匹配。
|
||
|
||
7. 计费重计算(**有效规则 ID:仅销售产品**)
|
||
- **输入**:仅使用 `t_order_weight` 中 **`Type = forecast`** 的件重尺:`Length`、`Width`、`Height`、`ActualWeight`(单位与库表约定一致,一般为 cm / kg)。
|
||
- **解析 `effectiveWeightRuleId`**:取本单绑定 **`t_sales_product.WeightRuleId`**;须**非空**且对应 **`t_weight_rule.IsDeleted=0`**。否则更新 `PredeclareFailReason`(如「未配置计重规则」),保持 `draft`,失败返回。
|
||
- **加载明细集合 `rules`**:`t_weight_rule_info` 中 **`RuleId = effectiveWeightRuleId and IsDeleted=0`**,按 **`Id` 升序**排序;或从 **`rulesSnapshot.content.weightRule`** 读取与 **`effectiveWeightRuleId`** 一致的 **`clientRules`**(结构无 `client` 字段,与表 §5.2.16 对齐)。集合为空则同上失败。
|
||
- **算法(未进位计费重 `rawW`)**:
|
||
1. `gw = ActualWeight`(实重,kg)。
|
||
2. 依次遍历 `rules` 中每一行(已按 `Id` 升序):对该行解析 **`volumetricFactor`** 为正数,计算 **`vw = (Length × Width × Height) / volumetricFactor`**(与产品历史口径一致:体积 cm³ ÷ 系数 → 体积重 kg;若实现使用 m³,须在服务内统一换算)。
|
||
3. 将该行的 **`gw`、`vw`** 代入 **`condition`**(支持 `>`、`>=`、`<`、`<=`、`==` 等,运算对象可为 `gw`、`vw` 及常量;**不支持**跨行交叉引用)。
|
||
4. **第一条**使 `condition` 为**真**的行:**命中行**;若无一命中,失败(`PredeclareFailReason` 建议「计重规则条件均未命中」)。
|
||
5. 命中行的 **`billableWeight`** 取值仅为 **`gw` 或 `vw`**(大小写不敏感):`rawW = (billableWeight == 'gw') ? gw : vw`。
|
||
6. 写回 **`t_order_weight.VolumetricWeight`**:建议取**命中行**对应的 **`vw`**。**预报草稿**将同一 `vw` 写入四行(创建时初始拷贝策略)。**入库**仅写 **`receive` 行**;**出库**仅写 **`ship` 行**;均**不**向其它 `Type` 同步。
|
||
|
||
8. 计费重进位(**有效进位 ID:仅销售产品**)
|
||
- **解析 `effectiveWeightRoundingId`**:取本单绑定 **`t_sales_product.WeightRoundingId`**;须**非空**且 **`t_weight_rounding.IsDeleted=0`**。无效则失败(`PredeclareFailReason` 如「未配置计重进位」)。
|
||
- **加载区间集合 `ranges`**:`t_weight_rounding_info` 中 **`RoundingId = effectiveWeightRoundingId and IsDeleted=0`**;或快照 **`rulesSnapshot.content.weightRounding.ranges`**(仅含 **`start`/`end`/`public`**,与 §5.2.18 对齐)。
|
||
- **匹配区间**:在 `ranges` 中找到满足 **`start <= rawW <= end`** 的区间(多区间重叠时按 **`Id` 升序**取第一条,或产品约定唯一覆盖);**无匹配**则失败。
|
||
- **算法(最终计费重)**:设命中区间 **`public = u`**(正数,进位值 kg)。推荐统一为**按进位值向上取整**:
|
||
- `ChargeableWeight = ceil(rawW / u) × u`
|
||
- `ceil` 为数学向上取整;实现须固定小数位数(如 3 位)避免浮点误差。
|
||
- 更新 **`t_order_weight.ChargeableWeight`**:**预报阶段**四行同步为同一计费重。**入库**仅写 **`receive` 行`**;**出库**仅写 **`ship` 行`**;均**不**向其它 `Type` 同步。若 **`public` 为空或非法**,失败。
|
||
|
||
9. 价格分区匹配(来自已绑定的 rules 快照)
|
||
- 读取 `rulesSnapshot.content.areas.price.rules`,按收件国家/省市/邮编区间匹配得到 `zoneName`(如“美国西部”)。
|
||
- 若未匹配到价格分区,更新 `t_order.PredeclareFailReason = '价格分区未匹配(规则快照)'`,订单状态保持 `draft`,返回失败。
|
||
|
||
10. 费用计算与费用落库(**预报节点仅应收**)
|
||
- **重算应收(§4.3.4.1)**:全部启用卖价项;**重量**取 **`Type=forecast`**;**upsert** `ChargingStage=forecast`。
|
||
- **不在创建阶段排货/写应付**;渠道与应付在 **入库** `ApplyReceiveBillingByWaybillAsync` 中 `TryConfirmServiceChannelAndPayableAsync` 处理(见 **§6.3.1.1**)。
|
||
- **强校验**:存在启用卖价项但未匹配到任何明细时,预报失败。
|
||
|
||
11. 固定报价示例规则(某产品,来自 price 快照的 continuePricing)
|
||
- 示例规则:
|
||
- 首重:1kg 以内,首重价 18 元
|
||
- 续重:超出 1kg 部分,每 0.5kg 为一个续重单位,续重单价 6 元 / 0.5kg
|
||
- 进位规则:不足 0.5kg 按 0.5kg 进位
|
||
- 适用前提:
|
||
- 该产品已匹配到对应价格分区与卖价配置
|
||
- `t_order_weight.ChargeableWeight` 已完成最终进位
|
||
- price 快照明细为 `calculationFormula = 续单价` 且包含 `continuePricing`
|
||
- 计算口径:
|
||
- 当 `ChargeableWeight <= 1` 时,总费用 = 18
|
||
- 当 `ChargeableWeight > 1` 时:
|
||
- 先收首重价 18
|
||
- 超出部分 = `ChargeableWeight - 1`
|
||
- 超出部分按 0.5kg 向上取整到续重单位数
|
||
- 续重费用 = 续重单位数 * 6
|
||
- 总费用 = 18 + 续重费用
|
||
- 示例:
|
||
- `ChargeableWeight = 0.8kg`,进位到 `1.0kg`,总费用 = 18
|
||
- `ChargeableWeight = 1.2kg`,超出 `0.2kg`,按 0.5kg 进 1 个续重单位,总费用 = 18 + 6 = 24
|
||
- `ChargeableWeight = 1.6kg`,超出 `0.6kg`,按 0.5kg 进 2 个续重单位,总费用 = 18 + 12 = 30
|
||
- 该示例说明:计费重来源仅为 `t_order_weight`,不参与 `t_order_declaration` 的商品重量汇总逻辑。
|
||
|
||
12. 成功完成预报
|
||
- 当偏远标记完成、计费重计算完成、**本阶段(预报)应计费用**写入完成后(黑名单已在草稿落库前完成校验):
|
||
- 更新 `t_order.OrderStatus = 'predeclared'`
|
||
- 更新 `t_order.PredeclareFailReason = ''`
|
||
- 更新 `t_order.UpdateUserId`、`t_order.UpdateTime`
|
||
- 返回字段:
|
||
- `clientNo`:取 `t_order.ClientNo`
|
||
- `serviceNo`:取 `t_order.ServiceNo`
|
||
- `trackNo`:取 `t_order.TrackNo`
|
||
- `labelUrl`:若当前流程未生成面单则返回空字符串
|
||
|
||
###### 状态与失败处理
|
||
|
||
- 参数校验失败:不创建订单,直接返回错误。
|
||
- 销售产品不存在或未启用、规则/价格快照未命中、黑名单命中:均**不创建订单**,直接返回错误(与参数校验一致,不落库,不写 `PredeclareFailReason`)。
|
||
- 草稿创建成功但后续业务失败:订单状态统一保留 `draft`。
|
||
- 偏远命中:仅更新 `IsRemote = 1`,不拦截流程。
|
||
- 计重规则、进位规则、价格分区、卖价配置缺失或未匹配:统一写入 `PredeclareFailReason`,订单状态保持 `draft`。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、枚举值、数值范围、客户/仓库/国家/销售产品存在性进行校验,异常场景返回统一错误码与错误信息。
|
||
- 草稿创建后的异常场景需要保留订单数据,并将失败原因写入 `t_order.PredeclareFailReason`,供订单列表和详情页展示。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
与上文 **§4.3.5**、**§1.5.4** 对齐;补充要点:
|
||
|
||
| 项 | 实现细节 |
|
||
| --- | --- |
|
||
| 幂等 | `requestId` 非空时先 `GetByRequestIdAsync`,命中则 **`BuildParcelCreateOutputAsync`** 直接返回 |
|
||
| 会话客户 | `ResolveEffectiveCustomerAsync` → 写 `input.CustomerId = sessionCustomer.Id` |
|
||
| 必填校验 | 另含 **`declaredCurrency`、`quantity`(>0)**、申报明细 **`enName/unitPrice/unitWeight/quantity`**;**`clientNo` 非必填** |
|
||
| `channelCode` | 未传时默认 **`product.ProductCode`** |
|
||
| `serviceNo` | 自定义时查重,冲突 → **「运单号已存在」**;否则 **`IDE{yyMMddHHmmss}{4位随机}`** |
|
||
| 快照 | 见 **§4.3.5**(DB 发布 + Live 降级,**无 Redis**) |
|
||
| 应付/排货 | **创建时不执行**;入库时 **TryConfirm**(失败 → `pendingAllocation`) |
|
||
| 成功 | `OrderStatus=predeclared`;清空 `AllocationPendingReason`;**`labelUrl`** 同 print 逻辑生成 HTML |
|
||
| 失败 | 草稿已建则 **`MarkParcelDraftFailedAsync`**(保持 `draft` + `PredeclareFailReason`)后 **仍抛业务异常** |
|
||
|
||
##### 4.4.3.2 批量创建小包(POST /order/parcel/batch-create)
|
||
|
||
与 **§4.4.3.1 创建小包** 使用同一套单票业务规则(`CreateParcelCoreAsync`)。数组元素字段与单票请求体一致。**差异**:本接口在 **同一 HTTP 请求内同步逐条** 调用单票逻辑,**不创建** 批量任务、**不入队**;HTTP 响应返回 **批次号与逐条明细**(含每票 `serviceNo` / `labelUrl` / 失败原因)。
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/parcel/batch-create**(POST)
|
||
|
||
> 请求体为 **`OrderParcelBatchCreateInput`**:与单票结构相同的对象 **数组**(非裸数组根)。单次 **≤ 2000** 条;超限整批拒绝。每元素可选 **`requestId`** 幂等。
|
||
|
||
```json
|
||
[
|
||
{
|
||
"clientNo": "string", // 客户单号 t_order.ClientNo
|
||
"trackNo": "string", // 跟踪号 t_order.TrackNo//可选
|
||
"serviceNo": "string", // 运单号 t_order.ServiceNo//可选;未传时自动生成
|
||
"productCode": "string", // 产品代码 t_order.ProductCode
|
||
"channelCode": "string", // 渠道代码 t_order.ChannelCode
|
||
"cargoAttribute": "string", // 货物属性 t_order.CargoAttribute:general-普货,electronics-3c&带电,sensitive-敏感货
|
||
"exportCustomsMode": "9610Simple", // 出口报关方式,见 §4.2.1//可选
|
||
"warehouseId": 0, // 仓库ID t_order.WarehouseId
|
||
"customerId": 0, // 客户ID t_order.CustomerId//可选:客户登录由会话补全
|
||
"length": 3, // 长(cm) t_order_weight.Length
|
||
"width": 3, // 宽(cm) t_order_weight.Width
|
||
"height": 3, // 高(cm) t_order_weight.Height
|
||
"actualWeight": 3, // 实重(kg) t_order_weight.ActualWeight
|
||
"volumetricWeight": 3, // 体积重(kg) t_order_weight.VolumetricWeight
|
||
"chargeableWeight": 3, // 计费重(kg) t_order_weight.ChargeableWeight
|
||
"cubic": 0, // 立方(m³) t_order_weight.Cubic
|
||
"isCod": false, // 是否COD
|
||
"codCurrency": "string", // COD币种
|
||
"codSum": "string", // COD金额
|
||
"declaredCurrency": "string", // 申报币种
|
||
"declaredSum": "string", // 申报金额
|
||
"quantity": "string", // 包裹数量
|
||
"receiver": { // 收件人 t_order_recipient
|
||
"recipientsName": "string", // 收件人名称
|
||
"companyName": "string", // 收件人公司名称
|
||
"phone": "string", // 收件人手机
|
||
"telePhone": "string", // 收件人电话
|
||
"email": "string", // 收件人邮箱
|
||
"countryCode": "US", // 收件国家 ISO 二字码//必填
|
||
"province": "string", // 收件人省/州
|
||
"city": "string", // 收件人城市
|
||
"streetNumber": "string", // 收件人门牌号
|
||
"streetAddress1": "string", // 收件人地址1
|
||
"streetAddress2": "string", // 收件人地址2
|
||
"streetAddress3": "string", // 收件人地址3
|
||
"zipCode": "string", // 收件人邮编
|
||
"taxType": 0, // 税号类型
|
||
"taxNo": "string", // 税号
|
||
"certificateType": 0, // 证件类型
|
||
"certificateNo": "string", // 证件号码
|
||
"eori": "string", // EORI
|
||
"vat": "string" // Vat
|
||
},
|
||
"sender": { // 发件人 t_order_sender
|
||
"senderName": "string", // 发件人姓名
|
||
"company": "string", // 发件人公司
|
||
"phone": "string", // 发件人手机
|
||
"telePhone": "string", // 发件人电话
|
||
"email": "string", // 发件人邮箱
|
||
"countryCode": "CN", // 发件国家 ISO 二字码//可选
|
||
"province": "string", // 州省
|
||
"city": "string", // 城市
|
||
"zipCode": "string", // 邮编
|
||
"streetAddress1": "string", // 地址1
|
||
"streetAddress2": "string", // 地址2
|
||
"streetAddress3": "string", // 地址3
|
||
"houseNumber": "string" // 门牌号
|
||
},
|
||
"items": [ // 申报明细 t_order_declaration
|
||
{
|
||
"cnName": "string", // 品名-中
|
||
"enName": "string", // 品名-英
|
||
"sku": "string", // SKU
|
||
"unit": "string", // 单位
|
||
"unitPrice": 0, // 单价
|
||
"unitWeight": 0, // 单重
|
||
"quantity": 0, // 数量
|
||
"amount": 0, // 总金额
|
||
"weight": 0, // 总重量
|
||
"imHsCode": "string", // 进口HsCode
|
||
"exHsCode": "string", // 出口HsCode
|
||
"skuImage": "string", // 图片
|
||
"category": "string", // 类别
|
||
"brand": "string", // 品牌
|
||
"brandType": "string", // 品牌类型
|
||
"isMagnetic": false, // 是否带磁带电
|
||
"distribution": "string", // 配货信息
|
||
"material": "string", // 材质
|
||
"enMaterial": "string", // 英文材质
|
||
"purpose": "string", // 用途
|
||
"attributes": "string", // 属性
|
||
"originCountry": "string", // 原产国
|
||
"salesLink": "string", // 销售链接
|
||
"model": "string", // 型号
|
||
"specifications": "string", // 规格
|
||
"comment": "string" // 备注
|
||
}
|
||
]
|
||
}
|
||
]
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"batchNo": "PCB20240509120000123",
|
||
"successCount": 98,
|
||
"failCount": 2,
|
||
"items": [
|
||
{
|
||
"index": 1,
|
||
"requestId": "optional",
|
||
"clientNo": "C001",
|
||
"serviceNo": "LP202405091200011234",
|
||
"trackNo": null,
|
||
"labelUrl": "/uploads/prints/orders/...",
|
||
"success": true,
|
||
"message": "success"
|
||
},
|
||
{
|
||
"index": 2,
|
||
"requestId": null,
|
||
"clientNo": "C002",
|
||
"serviceNo": null,
|
||
"trackNo": null,
|
||
"labelUrl": null,
|
||
"success": false,
|
||
"message": "销售产品不存在或未启用"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
- **`batchNo`**:服务端生成,`PCB` + 时间戳毫秒。
|
||
- **`successCount` / `failCount`**:与 `items.length` 一致;单条失败 **不中断** 后续条目。
|
||
- 失败条目 **`message`** 为业务异常文案;若草稿已创建且后续失败,单票仍可能落库为 **`draft`** + `PredeclareFailReason`(与 §4.4.3.1 一致)。
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. 校验数组非空、`Count ≤ 2000`(常量 `ParcelBatchMaxCount`)。
|
||
2. 生成 **`batchNo`**,按序 **`for` 循环** 调用 **`CreateParcelCoreAsync`**。
|
||
3. 每条 **`try/catch`**:成功累加 `successCount` 并填充 `items`(含 `labelUrl`);失败写入 `items` 且 `success=false`。
|
||
4. 返回 **`OrderParcelBatchCreateOutput`**(**无 `taskId`**)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 空数组 → **「批量创建请求不能为空」**。
|
||
- 超过 2000 条 → **「单次批量创建不能超过2000条」**。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.BatchCreateParcelAsync`(`OrderService.cs`)。
|
||
- **设计演进说明**:批量 JSON 创建仍为 **同步** `batchNo` + `items[]`;若需异步可改为创建 `parcel-create` 批量任务并入队。
|
||
|
||
##### 4.4.3.3 模板列表(GET /order/template/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/template/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20&type=standard|custom&keyword=
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"name": "标准模板",
|
||
"type": "standard",
|
||
"templateKey": "standard_v1",
|
||
"customerId": 0,
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
],
|
||
"total": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 客户登录:仅返回 **`Type=standard`** 的模板,以及 **`Type=custom` 且 `CustomerId = effectiveCustomerId`** 的自定义模板。
|
||
- 管理员:按 `type`、`keyword` 分页查询,不做客户隔离。
|
||
- 按接口定义完成参数解析与分页返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 客户门户 **不得**通过 `templateId` 下载或上传他客 `custom` 模板(无权限时返回 **「模板不存在」**)。
|
||
|
||
##### 4.4.3.4 下载模板(GET /order/template/download)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/template/download**(GET,Query 参数)
|
||
|
||
```
|
||
?templateId=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
> 直接返回 Excel 文件流,Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 校验模板存在;客户登录时须通过 **§4.4.3.3** 可见性规则(`standard` 或归属当前客户的 `custom`)。
|
||
- 标准模板 Excel 表头建议包含列 **「出口报关方式」**(与 `exportCustomsMode` / `t_order.ExportCustomsMode` 对应),单元格填写 §4.2.1 **ExportCustomsMode** 枚举 code;列缺失或空单元格按 **未传** 处理,落库 `NULL`。
|
||
- 示例行中 **`CustomerId` 列**对客户门户导出为 **`effectiveCustomerId`**(非模板表上的 `CustomerId` 字段)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、状态与数据存在性进行校验,异常场景返回统一错误码与错误信息。
|
||
- 若模板中提供「出口报关方式」列,非空值须为合法枚举 code,否则该行在解析或异步处理阶段失败(错误信息需可定位行号)。
|
||
- 客户访问他客 `custom` 模板时返回 **「模板不存在」**。
|
||
|
||
##### 4.4.3.5 上传下单(POST /order/upload)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /order/upload**(POST,multipart/form-data)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| file | File | Excel 文件 |
|
||
| templateId | number | 模板ID |
|
||
|
||
> **导入条数限制**:按模板解析得到的有效订单数据行(不含表头)**最多 2000 行**;超出则拒绝本次上传并返回错误,不创建任务。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"jobId": 1001,
|
||
"async": true
|
||
}
|
||
}
|
||
```
|
||
|
||
- **`jobId`**:批量任务 ID,前端跳转 **批量任务详情页**(`/order/batchJobs?jobId=`)。
|
||
- **`async`**:`true` 表示 RabbitMQ 异步入队处理;`false` 表示 MQ 未启用、已在当前请求内同步处理完毕。
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. **`ResolveEffectiveCustomerAsync`** → `effectiveCustomerId`;校验模板可见性(`standard` 或归属客户的 `custom`)。
|
||
2. 解析 Excel 有效数据行(**最多 2000 行**),每行映射为 `OrderParcelCreateInput` 字段集。
|
||
3. 创建 **`t_order_batch_job`**(`Action=import-row`,`PayloadJson` 含 `taskName`、`templateId`)及 **`t_order_batch_job_item`**(每行一条,`PayloadJson` 含 `rowNumber`、`rowDataJson`)。
|
||
4. **MQ 开启**:逐子项发布 RabbitMQ,异步调用 **`CreateParcelCoreAsync`**(§4.4.3.1 全链路)。
|
||
5. **MQ 关闭**:同步逐条 `ProcessAsync`,当场更新子项状态与任务汇总。
|
||
6. 返回 **`{ jobId, async }`**;失败明细见 **`GET /order/batch/jobs/{id}/items`**(筛选 `status=failed`)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 文件为空 → **「上传文件不能为空」**。
|
||
- 模板不存在 / 无权限 → **「模板不存在」**。
|
||
- 超过 2000 行 → **「单次最多导入 2000 行」**。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OrderService.ImportOrdersAsync` → `BuildImportBatchJobInputAsync` + `OrderBatchJobCoordinator.CreatePayloadJobAsync` / `CreatePayloadJobAndProcessSyncAsync`。
|
||
- 原 **`t_order_task` / `t_order_task_fail` 及 `/order/task/*` 接口已移除**。
|
||
|
||
##### 4.4.3.6 批量任务(GET /order/batch/jobs、/items 等)
|
||
|
||
> Excel 导入及订单列表批量操作(支付、预报、打印等)均复用本组接口;详见 **§4.2.3**。
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**任务列表** `GET /order/batch/jobs?page=1&pageSize=30&status=&action=&jobId=`
|
||
|
||
**任务详情** `GET /order/batch/jobs/{id}`
|
||
|
||
**子项列表** `GET /order/batch/jobs/{id}/items`
|
||
|
||
**响应(任务详情摘要)**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"jobId": 1001,
|
||
"action": "import-row",
|
||
"status": "completed",
|
||
"total": 100,
|
||
"successCount": 95,
|
||
"failCount": 5,
|
||
"pendingCount": 0,
|
||
"runningCount": 0,
|
||
"cancelledCount": 0,
|
||
"templateId": 1,
|
||
"taskName": "批量下单_20260722120000",
|
||
"startedAt": "2026-07-22 12:00:00",
|
||
"finishedAt": "2026-07-22 12:01:30"
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应(子项摘要)**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"orderId": 9001,
|
||
"clientNo": "CUST-001",
|
||
"rowNumber": 2,
|
||
"status": "failed",
|
||
"failReason": "客户单号重复",
|
||
"resultMessage": "客户单号重复"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 客户登录:查询条件 **强制** `t_order_batch_job.CustomerId = effectiveCustomerId`。
|
||
- 导入类任务(`import-row`):子项 **`clientNo`**、**`rowNumber`** 用于定位 Excel 行;原始行数据在 `PayloadJson.rowDataJson`。
|
||
- 支持 **重试失败子项**、**取消待处理子项**(须相应 PermissionCode)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 禁止跨客户查看批量任务详情;不匹配时返回 **「批量任务不存在」**。
|
||
|
||
#### 4.4.4 揽收订单
|
||
|
||
> **定位**:客户预约「从发件地址揽收货件,送往指定仓库」并绑定运输方式(销售产品)的简化下单入口。
|
||
> **落库结果**:仍是一张 **`t_order`**,走既有预报 / 计费 / 支付 / 入库链路;**`OrderSource = pickup`**。
|
||
> **不要混淆**:
|
||
> - **揽收订单**(本节):跨境/仓配 OMS 订单,入口菜单 **订单 → 揽收订单**。
|
||
> - **仓储小包 / 到货**(`C_parcel`、`C_parcel_arrival` 等,前端 `/warehouse/parcel`):国内快递入仓库存管理,**不是**本节对象。
|
||
|
||
##### 4.4.4.0 与现有能力结合(总览)
|
||
|
||
```text
|
||
客户维护发件地址簿(§4.2.10 / §4.4.5)
|
||
↓ 选择 senderAddressId
|
||
POST /order/pickup/create
|
||
↓ 读取仓库 t_warehouse_address → 组装 receiver
|
||
↓ 货物名称/重量 → 组装 items + actualWeight
|
||
↓ 运输方式 productCode + warehouseId + pickupTime
|
||
↓
|
||
组装 OrderParcelCreateInput(缺省填齐 L/W/H、申报、货物属性等)
|
||
↓
|
||
复用 CreateParcelCoreAsync(§4.4.3.1 全链路:快照绑定/黑名单/计费/预报…)
|
||
↓
|
||
t_order(OrderSource=pickup, PickupTime, CustomerSenderAddressId)
|
||
+ t_order_sender(发件快照)+ t_order_recipient(仓库映射快照)
|
||
+ t_order_declaration / t_order_weight / t_order_fee …
|
||
```
|
||
|
||
| 能力 | 复用方式 |
|
||
| --- | --- |
|
||
| 小包创建 / 预报 / 计费 | **必须**复用 `CreateParcelCoreAsync` / `POST /order/parcel/create` 逻辑;揽收接口只做 **入参适配**,禁止另写一套落单 |
|
||
| 仓库列表 | `GET /system/warehouse/list`(已有);创建时校验 `warehouseId` 同 §4.4.3.1 |
|
||
| 运输方式 | 销售产品启用列表(与「我要下单 / 查价」同源:`ProductCode` + 启用态) |
|
||
| 发件信息 | 自 `t_customer_sender_address` 复制到请求 `sender`,并写 `CustomerSenderAddressId` |
|
||
| 收件信息 | **由仓库地址映射为 `receiver`**(客户表单不填海外收件人) |
|
||
| 我的订单 | 支付/预报/拦截/删除等 **全部沿用** `/order/*`;列表可筛 `orderSource=pickup` |
|
||
| 入库/出库 | 订单进仓后仍走操作模块既有扫描(§6),与来源无关 |
|
||
|
||
##### 4.4.4.1 仓库 → 收件人映射规则
|
||
|
||
`t_warehouse_address` 当前为 **非结构化**收货信息(`Recipient` / `ContactPhone` / `ShippingAddress` TEXT),揽收一期映射约定:
|
||
|
||
| `t_order_recipient` | 取值规则 |
|
||
| --- | --- |
|
||
| `RecipientsName` | `t_warehouse_address.Recipient`;空则用 `WarehouseName` |
|
||
| `Phone` | `ContactPhone` |
|
||
| `StreetAddress1` | `ShippingAddress`(整段写入地址1;省市区邮编可空) |
|
||
| `Country` / `countryCode` | **默认 `CN`**(仓库业务默认中国);若后续仓库表扩展国家字段则优先用仓库国家 |
|
||
| `ZipCode` | 无则写占位 **`000000`**(满足 §4.4.3.1 必填校验;黑名单/分区按产品配置评估) |
|
||
| 其余字段 | 空 |
|
||
|
||
> **风险提示**:目的国为仓所在国(默认 CN)时,销售产品须能匹配该目的国;运营应配置/启用适合「送仓」场景的产品,或二期将仓库地址拆为结构化国家/省/市/邮编后再严格计费分区。
|
||
|
||
##### 4.4.4.2 简化字段 → 小包入参缺省
|
||
|
||
| 揽收表单 | 映射到 `OrderParcelCreateInput` |
|
||
| --- | --- |
|
||
| `productCode` | 同左(运输方式) |
|
||
| `warehouseId` | 同左 |
|
||
| `senderAddressId` | 读地址簿 → `sender.*`;并落库 `CustomerSenderAddressId` |
|
||
| `pickupTime` | 落库 `t_order.PickupTime`(创建核心在写 `t_order` 时写入) |
|
||
| `cargoName` | `items[0].cnName` + `items[0].enName`(英文缺省可同中文或拼音拷贝) |
|
||
| `actualWeight` | `actualWeight`;`items[0].unitWeight` / `weight`;`quantity=1` |
|
||
| (无尺寸) | `length/width/height` **缺省各 `1`**(cm),满足现网必填;入库后以实测量覆盖 |
|
||
| (无申报币种/单价) | `declaredCurrency=CNY`,`declaredSum=1`,`items[0].unitPrice=1`,`cargoAttribute=general`,`quantity`(包裹数)=`1` |
|
||
| `clientNo` | 可选 |
|
||
|
||
##### 4.4.4.3 接口清单
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 创建揽收订单 | POST | /order/pickup/create | 组装后走小包创建;**`CustomerId` 会话隔离** |
|
||
| 揽收订单列表 | POST | /order/list | **复用**;固定或默认 `orderSource=pickup`(前端揽收菜单) |
|
||
|
||
##### 4.4.4.4 创建揽收订单(POST /order/pickup/create)
|
||
|
||
###### 1 请求示例
|
||
|
||
```json
|
||
{
|
||
"requestId": "string", // 可选幂等键,透传小包创建
|
||
"clientNo": "string", // 可选客户单号
|
||
"productCode": "US_AIR", // 运输方式=销售产品编码//必填
|
||
"warehouseId": 1, // 仓库地址 Id//必填
|
||
"senderAddressId": 10, // t_customer_sender_address.Id//必填
|
||
"pickupTime": "2026-07-16 14:00:00", // 预约揽收时间//必填;须为未来时间(实现可放宽到 ≥ 当日 00:00)
|
||
"cargoName": "服装", // 货物名称//必填
|
||
"actualWeight": 2.5, // 重量 kg//必填,> 0
|
||
"cargoAttribute": "general" // 可选;默认 general
|
||
}
|
||
```
|
||
|
||
###### 2 响应
|
||
|
||
与 **`POST /order/parcel/create`** 成功响应同构(至少含 `serviceNo` / `clientNo` / `orderId` 等现有字段);另可回显 `orderSource: "pickup"`、`pickupTime`。
|
||
|
||
###### 3 业务处理
|
||
|
||
1. 解析 **`effectiveCustomerId`**(§4.4.0)。
|
||
2. 校验必填:`productCode`、`warehouseId`、`senderAddressId`、`pickupTime`、`cargoName`、`actualWeight`。
|
||
3. 查询 `t_customer_sender_address`:`Id = senderAddressId AND CustomerId = effectiveCustomerId AND IsDeleted = 0`;否则 **「发件地址不存在」**。
|
||
4. 查询 `t_warehouse_address`:同 §4.4.3.1;不存在则失败;按 §4.4.4.1 组装 `receiver`。
|
||
5. 组装 `OrderParcelCreateInput`(§4.4.4.2)→ 调用 **`CreateParcelCoreAsync`**。
|
||
6. 在写入 `t_order` 时(扩展点):`OrderSource = pickup`,`PickupTime = pickupTime`,`CustomerSenderAddressId = senderAddressId`。
|
||
- 实现建议:给 `CreateParcelCoreAsync` / 草稿插入增加可选「来源上下文」参数,避免复制整段创建代码。
|
||
7. 后续偏远/黑名单/计费/预报与 §4.4.3.1 **完全一致**。
|
||
|
||
###### 4 校验与异常
|
||
|
||
- 禁止使用他客 `senderAddressId`。
|
||
- `actualWeight <= 0` 拒绝。
|
||
- 小包创建既有错误(产品未启用、未命中快照、黑名单等)原样返回。
|
||
|
||
##### 4.4.4.5 前端(itfx-front)
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 路由 | `/order/pickupOrder`,菜单 **订单 → 揽收订单** |
|
||
| 页面 | `src/views/order/pickupOrder/index.vue`(建议:上区创建表单 + 下区揽收订单列表,或独立创建页 + Tab 列表) |
|
||
| 组件 | **FormPro**(创建)+ **TablePro** / HandsontablePro(列表);遵守 `docs/前端页面设计规范.md` |
|
||
| 权限 | `meta.permission`: `Order.Pickup.Query`;创建按钮 `Order.Pickup.Create` |
|
||
| 下拉数据 | 发件地址:`/order/sender-address/list`;仓库:`/system/warehouse/list`;运输方式:销售产品下拉(与下单页同源) |
|
||
| 列表 | `POST /order/list` 且 **`orderSource=pickup`**;详情/支付/预报跳转或复用 **我的订单** 能力 |
|
||
|
||
#### 4.4.5 客户发件地址
|
||
|
||
> 菜单挂在 **客户**,HTTP 挂在 **`/order/sender-address/*`**(享受 §4.4.0 客户隔离;**不**放在 RBAC `/customer/*` 主数据文档内展开)。
|
||
|
||
##### 4.4.5.1 接口清单
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 分页列表 | POST | /order/sender-address/list | 仅当前客户;支持 keyword、仅默认 |
|
||
| 详情 | GET | /order/sender-address/detail | `id`;校验归属 |
|
||
| 新增 | POST | /order/sender-address/create | 写入 `CustomerId=effectiveCustomerId` |
|
||
| 更新 | POST | /order/sender-address/update | 校验归属后更新 |
|
||
| 删除 | POST | /order/sender-address/delete | 软删;**不级联**已下订单 |
|
||
| 设为默认 | POST | /order/sender-address/set-default | 同客户仅一条 `IsDefault=1` |
|
||
|
||
##### 4.4.5.2 新增(POST /order/sender-address/create)
|
||
|
||
```json
|
||
{
|
||
"addressName": "公司仓",
|
||
"senderName": "张三", // 必填
|
||
"company": "",
|
||
"phone": "13800000000",
|
||
"telePhone": "",
|
||
"email": "",
|
||
"countryCode": "CN", // ISO 二字码;默认 CN
|
||
"province": "广东省",
|
||
"city": "深圳市",
|
||
"zipCode": "518000",
|
||
"streetAddress1": "南山区…", // 必填
|
||
"streetAddress2": "",
|
||
"streetAddress3": "",
|
||
"houseNumber": "",
|
||
"isDefault": true
|
||
}
|
||
```
|
||
|
||
**响应**:`{ "id": 10 }`(或含完整对象)。国家落库规则同小包:`countryCode` → `t_country.Id`。
|
||
|
||
##### 4.4.5.3 列表(POST /order/sender-address/list)
|
||
|
||
```json
|
||
{
|
||
"page": 1,
|
||
"pageSize": 30,
|
||
"keyword": "", // 匹配 AddressName / SenderName / Phone / StreetAddress1
|
||
"onlyDefault": false
|
||
}
|
||
```
|
||
|
||
响应:`totalCount` + `items[]`(字段 camelCase,含 `countryCode` 建议回显 ISO 或与详情约定一致)。
|
||
|
||
##### 4.4.5.4 前端(itfx-front)
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 路由 | `/user/senderAddress`,菜单 **客户 → 发件地址** |
|
||
| 页面 | `src/views/user/senderAddress/index.vue` |
|
||
| 组件 | **TablePro** 列表 + **ModalPro** / 抽屉表单;按钮高 32px、`var(--app-primary-color)` |
|
||
| 权限 | `Order.SenderAddress.Query` / `.Create` / `.Update` / `.Delete` |
|
||
| 联动 | 揽收创建页下拉刷新本列表;删除已占用地址 **允许**(订单已快照) |
|
||
|
||
#### 4.4.6 物流单绑定包裹/库存(我的订单)
|
||
|
||
> **入口**:我的订单 → 选中一单 →「绑定包裹/库存」弹窗(左:包裹/库存 Tab;右:已选明细,可调数量)。
|
||
> **权限**:`Order.MyOrders.StockBind`。
|
||
> **实现状态**:设计落地中(oms0414-back `OrderService.StockBind` + 前端 `StockBindModal`)。
|
||
|
||
##### 4.4.6.1 接口清单
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 当前绑定 | GET | /order/stock-bind/detail | `orderId`;右侧已选 |
|
||
| 库存候选 | POST | /order/stock-bind/inventory-candidates | 左侧库存 Tab |
|
||
| 包裹候选 | POST | /order/stock-bind/parcel-candidates | 左侧包裹 Tab(**全部生命周期**) |
|
||
| 保存绑定 | POST | /order/stock-bind/save | 全量覆盖;冻结 |
|
||
| 清空绑定 | POST | /order/stock-bind/clear | 释放全部 `bound` |
|
||
| 确认扣减 | POST | /order/stock-bind/confirm-deduct | 扣实物并 `deducted`;可由预报/出库钩子调用 |
|
||
|
||
##### 4.4.6.2 当前绑定(GET /order/stock-bind/detail)
|
||
|
||
**请求**:`orderId`(必填)。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"orderId": 1001,
|
||
"warehouseId": 3,
|
||
"warehouseName": "华南仓",
|
||
"bindStatus": "bound",
|
||
"items": [
|
||
{
|
||
"id": 11,
|
||
"bindType": "inventory",
|
||
"skuId": 88,
|
||
"skuCode": "SKU-A",
|
||
"skuName": "T恤",
|
||
"inventoryId": null,
|
||
"binLocationId": 5,
|
||
"binCode": "A-01",
|
||
"bindQty": 2,
|
||
"availableQty": 10,
|
||
"imageUrl": null,
|
||
"status": "bound"
|
||
},
|
||
{
|
||
"id": 12,
|
||
"bindType": "parcelItem",
|
||
"parcelId": 50,
|
||
"parcelItemId": 501,
|
||
"trackingNo": "YT123",
|
||
"productName": "配件",
|
||
"attributeText": "红/L",
|
||
"skuId": 88,
|
||
"skuCode": "SKU-A",
|
||
"bindQty": 1,
|
||
"availableQty": 3,
|
||
"imageUrl": "",
|
||
"status": "bound"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
`bindStatus`:`none` / `bound` / `deducted`(存在任意 `deducted` 优先;否则有 `bound` 为 `bound`)。
|
||
|
||
##### 4.4.6.3 库存候选(POST /order/stock-bind/inventory-candidates)
|
||
|
||
```json
|
||
{
|
||
"orderId": 1001,
|
||
"warehouseId": 3,
|
||
"keyword": "",
|
||
"page": 1,
|
||
"pageSize": 30
|
||
}
|
||
```
|
||
|
||
- 默认仓库:入参 `warehouseId` 优先,否则取订单 `WarehouseId`。
|
||
- 仅返回 `AvailableQty > 0` 的 SKU 汇总。
|
||
- 响应:`total` + `items[]`:`skuId`, `skuCode`, `skuName`, `skuImageUrl`, `warehouseId`, `totalQty`, `frozenQty`, `availableQty`, `binCount`。
|
||
|
||
##### 4.4.6.4 包裹候选(POST /order/stock-bind/parcel-candidates)
|
||
|
||
```json
|
||
{
|
||
"orderId": 1001,
|
||
"warehouseId": 3,
|
||
"lifecycleStatus": "",
|
||
"keyword": "",
|
||
"page": 1,
|
||
"pageSize": 20
|
||
}
|
||
```
|
||
|
||
- `lifecycleStatus` 空或 `all`:全部生命周期均可。
|
||
- 包裹下行:`parcelItemId`, `productName`, `attributeText`, `imageUrl`, `skuId`, `skuCode`, `quantity`, `frozenQty`, `availableQty`。
|
||
- 仅下行 `availableQty > 0` 的包裹进入列表(包裹头仍可展示)。
|
||
|
||
##### 4.4.6.5 保存绑定(POST /order/stock-bind/save)
|
||
|
||
```json
|
||
{
|
||
"orderId": 1001,
|
||
"items": [
|
||
{ "bindType": "inventory", "skuId": 88, "bindQty": 2, "inventoryId": null },
|
||
{ "bindType": "parcelItem", "parcelId": 50, "parcelItemId": 501, "bindQty": 1 }
|
||
]
|
||
}
|
||
```
|
||
|
||
**服务端步骤**
|
||
|
||
1. 校验订单归属;`OrderStatus` 为已删除/已取消则拒绝;若已有 `deducted` 明细则拒绝改绑(须先业务回滚,本期不做回滚接口)。
|
||
2. 解析仓库;无仓库且候选依赖仓库时拒绝。
|
||
3. 读取现有 `Status=bound` 明细 → 全部释放冻结 → 软删/标 `released`。
|
||
4. 按 `items[]` 重新冻结并插入 `bound` 明细(空数组等价清空)。
|
||
5. `inventory`:按 `inventoryId` 或 SKU 多库位分摊冻结 `C_inventory`。
|
||
6. `parcelItem`:冻结 `C_parcel_item`;若行上已有 `SkuId`,同步按 SKU 冻结库存。
|
||
7. **不**校验申报明细数量。
|
||
8. 事务提交;响应同 detail。
|
||
|
||
##### 4.4.6.6 清空 / 确认扣减
|
||
|
||
- `POST /order/stock-bind/clear`:`{ "orderId" }`,仅释放 `bound`。
|
||
- `POST /order/stock-bind/confirm-deduct`:对 `bound` 行:`Qty`/`Quantity` 与 `FrozenQty` 同减 `BindQty`,`AvailableQty` 重算,明细改 `deducted`。
|
||
|
||
##### 4.4.6.7 前端(itfx-front)
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 入口 | `/order/myOrders` 工具栏/更多:「绑定包裹/库存」 |
|
||
| 组件 | `StockBindModal.vue`:左右两卡片;左 Tab 包裹/库存;右已选 + 数量步进 |
|
||
| 权限 | `Order.MyOrders.StockBind` |
|
||
| 规范 | `docs/前端页面设计规范.md`;主色 `var(--app-primary-color)` |
|
||
|
||
#### 4.4.7 店铺订单:创建物流订单 / 创建物流&代发订单
|
||
|
||
> **页面**:`/order/platformStoreOrder`(店铺订单)。
|
||
> **设计原则(客户视角)**:弹窗第一目标是 **校对防错**——看清「哪几张平台单、寄给谁、寄什么、用哪条运输方式、(代发时)绑哪件库存/包裹」,再一键提交;宁可多一步确认,也不要静默下错单或绑错仓。
|
||
> **实现状态**:设计稿(2026-07-19);待前后端落地。
|
||
|
||
##### 4.4.7.1 能力分流(与「匹配库存」并列)
|
||
|
||
| 能力 | 入口 | 是否依赖平台 SKU→库内 SKU 映射 | 库存动作 | 产出 |
|
||
| --- | --- | --- | --- | --- |
|
||
| **匹配库存**(既有) | 行内 / 批量匹配 | **是** | 创建时 **扣减** `C_inventory` | 创建 `t_order` + 回写 `LinkedOrderId`,履约→`toShip` |
|
||
| **创建物流订单**(本期) | 工具栏「创建物流订单」 | **否** | 无 | 仅批量创建物流单并关联店铺单 |
|
||
| **创建物流&代发订单**(本期) | 工具栏「创建物流&代发订单」 | **否** | 创建后按 §4.4.6 **冻结绑定**(不即时扣实物) | 创建物流单 + `t_order_stock_bind` |
|
||
|
||
- 三入口互斥前提相同:店铺单 `FulfillStatus=pending`、未搁置、无有效 `LinkedOrderId`。
|
||
- **匹配库存**适合「平台 SKU 已映射、要从仓内 SKU 扣数出货」;本期两入口适合「先按店铺收件人/明细下物流单」,代发场景用绑定占用库存/包裹,出库/预报再扣减。
|
||
|
||
##### 4.4.7.2 用户故事与校对目标
|
||
|
||
作为客户,我勾选若干待履约店铺单后:
|
||
|
||
1. **创建物流订单**:只关心「运输方式是否选对、收件人/国家/地址/电话是否与平台单一致、申报品名数量是否对」。确认后批量生成 OMS 物流单。
|
||
2. **创建物流&代发订单**:在上述校对之外,还要确认「每一张店铺单各自绑了哪些库存 SKU / 包裹行、数量是否够、仓库是否同一仓」。确认后批量建单并冻结绑定。
|
||
|
||
##### 4.4.7.3 前端交互总览
|
||
|
||
**列表工具栏(`toolbar-left`)**
|
||
|
||
| 按钮 | 权限码 | 前置校验(点击时) |
|
||
| --- | --- | --- |
|
||
| 创建物流订单 | `Order.PlatformStoreOrder.CreateLogistics` | 至少勾选 1 单;全部可创建;上限 **50** 单/次(与批量匹配同量级,避免一次校对失控) |
|
||
| 创建物流&代发订单 | `Order.PlatformStoreOrder.CreateFulfill` | 同上;另需后续选仓 |
|
||
|
||
不可创建时:`Message.warning` 列出原因摘要(已关联物流单 / 已搁置 / 非 pending),不打开弹窗。
|
||
|
||
**弹窗组件**
|
||
|
||
| 模式 | 组件(建议) | 宽度 |
|
||
| --- | --- | --- |
|
||
| 仅物流 | `CreateLogisticsModal.vue` | `960px` |
|
||
| 物流&代发 | `CreateLogisticsFulfillModal.vue`(可内嵌复用绑定面板) | `1100px` |
|
||
|
||
共用视觉骨架(`platform-store-create-modal`):
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 标题 + 模式标签(仅物流 / 物流&代发) │
|
||
│ 一句提示:请逐单核对收件人与明细后再提交 │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ 【共享配置条】运输方式* | 发货仓库(代发必填;仅物流建议填) │
|
||
│ 勾选 N 单 · 已就绪 M 单(代发) │
|
||
├──────────────────────┬──────────────────────────────────────┤
|
||
│ 【左:待创建清单】 │ 【右:当前单详情 / 绑定区】 │
|
||
│ 可点选切换当前单 │ · 收件人校对卡 │
|
||
│ 每行:平台单号、国家、 │ · 商品明细表(品名/SKU/数量) │
|
||
│ 收件人姓名、行数摘要 │ · (代发)绑定交互 = StockBind 左右栏 │
|
||
│ 代发:绑定状态徽标 │ │
|
||
├──────────────────────┴──────────────────────────────────────┤
|
||
│ 【底部确认条】摘要:运输方式名 · 仓库名 · N 单 │
|
||
│ 主按钮「确认创建」/「确认创建并绑定」 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**校对优先的交互细则**
|
||
|
||
1. **打开即选中第一单**;右侧默认展示该单收件人 + 明细,强迫用户「看见」再提交。
|
||
2. **收件人校对卡**(只读为主,允许本弹窗内临时改写收件人字段,仅影响本次创建入参,不写回店铺主数据除非另开编辑):
|
||
- 姓名、电话、国家二字码、省/市、邮编、详细地址分行展示;缺关键字段标红「缺项」。
|
||
- 与列表列 `recipientName` / `orderCountry` 同源(店铺单结构化收件字段,见拉单落库)。
|
||
3. **商品明细表**:平台 SKU、品名、数量、单价;排除 `IsShipExcluded` 行;空行则该单不可提交。
|
||
4. **共享运输方式**:顶部单一 `a-select`(销售产品,同匹配库存下拉数据源);**所有勾选单共用**;变更后底部摘要同步刷新。
|
||
5. **仅物流**:仓库可选(有则写入订单 `WarehouseId`;无则创建链路按小包规则处理)。右侧无绑定区,仅校对。
|
||
6. **物流&代发**:
|
||
- 仓库 **必填**;变更仓库时清空各单已选绑定草稿并提示。
|
||
- 右侧绑定区复用「我的订单」`StockBindModal` 交互语义(左候选库存/包裹 Tab + 搜索;右已选 + 数量步进);数据先落在 **前端草稿**(按 `storeOrderId` 分桶),提交时一并传给后端。
|
||
- 左清单每行徽标:`未绑定`(灰)/ `已绑 n 件`(主色)。
|
||
- **提交门槛**:每一单至少 1 条绑定且数量 >0;未就绪单在清单上抖动/标红,点击跳转到该单。
|
||
7. **二次确认**(`Modal.confirm`):展示一行摘要「将创建 N 张物流单,运输方式 XXX;(代发)仓库 YYY,共绑定 Z 件」。确认后才调接口。
|
||
8. **结果反馈**:成功/失败按单展示(对齐 `batch-create` / `batch-match-inventory` 的 `items[]`);部分成功时刷新列表并保留失败单说明,可继续对失败单重试。
|
||
|
||
**样式约定**(对齐 `docs/前端页面设计规范.md`)
|
||
|
||
- 容器 padding、按钮高 32px、圆角 4px、间距 8px;主色 `var(--app-primary-color)`。
|
||
- 左清单选中态:左边框主色 + 浅底,避免「卡片堆」感;校对卡用分区标题(基本信息 / 收件人 / 商品 / 绑定),不用多层阴影。
|
||
- 缺项、未绑定用 `danger` 文字色;已就绪用主色 Tag。
|
||
|
||
##### 4.4.7.4 后端接口
|
||
|
||
> **推荐**:店铺域包装接口(内部逐单 `CreateParcelForBatchAsync` + 回写关联;代发再调 StockBind 保存)。前端 **不** 直接拼 `/order/parcel/batch-create` 后手动回写 `LinkedOrderId`(易漏关联、难做幂等)。
|
||
> 包装接口语义上仍是「批量创建订单」;实现可复用 `PlatformStoreOrderParcelBuilder` 的扩展版(**无映射 SKU 时用平台行申报**)。
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 批量创建物流订单 | POST | `/order/platform-store-order/batch-create-logistics` | 仅建物流单并关联 |
|
||
| 批量创建物流&代发 | POST | `/order/platform-store-order/batch-create-fulfill` | 建单 + 按单保存绑定(冻结) |
|
||
|
||
**共同请求字段**
|
||
|
||
```json
|
||
{
|
||
"customerId": null,
|
||
"productCode": "SP-US-EXPRESS",
|
||
"warehouseId": 3,
|
||
"items": [
|
||
{
|
||
"storeOrderId": 101,
|
||
"receiver": null,
|
||
"bindItems": [
|
||
{ "bindType": "inventory", "skuId": 88, "bindQty": 2, "inventoryId": null },
|
||
{ "bindType": "parcelItem", "parcelId": 50, "parcelItemId": 501, "bindQty": 1 }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
| 字段 | 仅物流 | 物流&代发 |
|
||
| --- | --- | --- |
|
||
| `productCode` | 必填 | 必填 |
|
||
| `warehouseId` | 可选 | **必填** |
|
||
| `items[].storeOrderId` | 必填 | 必填 |
|
||
| `items[].receiver` | 可选覆盖 | 可选覆盖 |
|
||
| `items[].bindItems` | 忽略 | **必填且非空** |
|
||
|
||
**共同响应**(对齐批量匹配)
|
||
|
||
```json
|
||
{
|
||
"batchNo": "PSL20260719153000",
|
||
"successCount": 2,
|
||
"failCount": 1,
|
||
"items": [
|
||
{
|
||
"storeOrderId": 101,
|
||
"success": true,
|
||
"orderId": 9001,
|
||
"serviceNo": "SN9001",
|
||
"message": null
|
||
},
|
||
{
|
||
"storeOrderId": 102,
|
||
"success": false,
|
||
"orderId": null,
|
||
"serviceNo": null,
|
||
"message": "收件人国家缺失"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
##### 4.4.7.5 服务端处理步骤
|
||
|
||
**A. `batch-create-logistics`(单条)**
|
||
|
||
1. 客户隔离 + `EnsureCreatableAsync`(同匹配:未搁置、pending、无有效 LinkedOrder)。
|
||
2. 加载店铺单行(排除 `IsShipExcluded`);用平台行组装申报(`PlatformSku` / `ProductName` / 数量 / 单价;重量缺省最小值)。**不要求** `MappedSkuId`。
|
||
3. 收件人:`receiver` 覆盖 > 店铺结构化收件字段 > `PlatformExtraJson` 解析(与现网 `ParseReceiver` 一致)。
|
||
4. `RequestId = platform-store-order:{storeOrderId}`(与匹配库存共用幂等键,避免重复建单)。
|
||
5. `OrderSource=platform`,`ClientNo=PlatformOrderNo`,`ProductCode`、可选 `WarehouseId`。
|
||
6. `CreateParcelForBatchAsync` → 回写 `LinkedOrderId`、`FulfillStatus=toShip`、`LastSyncTime`;有 TrackNo 可写 `ShippingLabelNo`。
|
||
7. **不**扣减库存。
|
||
|
||
**B. `batch-create-fulfill`(单条)**
|
||
|
||
1. 执行 A;成功后立刻按 `bindItems` 调用与 §4.4.6.5 相同的冻结保存逻辑(`orderId` = 新建物流单 Id,`warehouseId` = 请求仓库)。
|
||
2. 若绑定失败:**回滚本条**(软删/取消新建物流单或标记删除 + 清空 `LinkedOrderId`,并释放已冻冻结),该条记入 `failCount`;**不影响**批次内其他已成功单。
|
||
3. 绑定数量与申报件数 **不强制对齐**(同 §4.4.6)。
|
||
|
||
**批量**:同步逐条;单次 `items.length` ≤ 50;返回 `batchNo` + 逐条结果。
|
||
|
||
##### 4.4.7.6 申报组装说明(相对匹配库存)
|
||
|
||
| 项 | 匹配库存 | 创建物流 / 物流&代发 |
|
||
| --- | --- | --- |
|
||
| 申报 SKU | 库内 `CSku.SkuCode` | 平台 `PlatformSku`(可空则用品名截断) |
|
||
| 品名 | 行品名 / SKU 名 | 行 `ProductName` |
|
||
| 重量尺寸 | 优先 SKU 档案 | 缺省最小件重/默认长宽高(可后续增强) |
|
||
| 库存 | 创建前扣减 | 仅物流:无;代发:绑定冻结 |
|
||
|
||
可在 `PlatformStoreOrderParcelBuilder` 增加 `BuildFromPlatformLines(...)` 重载,避免污染映射扣减路径。
|
||
|
||
##### 4.4.7.7 权限与前端落点
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 权限 | `Order.PlatformStoreOrder.CreateLogistics`、`Order.PlatformStoreOrder.CreateFulfill`(代发另需具备 StockBind 等价能力,或本权限内含绑定) |
|
||
| API | `src/api/order/platformStore.ts` 增加两方法 |
|
||
| 页面 | `platformStoreOrder/index.vue` 工具栏两按钮;勾选态驱动 |
|
||
| 组件 | `CreateLogisticsModal.vue`、`CreateLogisticsFulfillModal.vue`;绑定子区可抽 `StockBindPanel.vue` 供我的订单与店铺共用 |
|
||
| i18n | `order.platformStoreOrder.createLogistics.*` / `createFulfill.*` |
|
||
| 规范 | `docs/前端页面设计规范.md`;主色 `var(--app-primary-color)` |
|
||
|
||
##### 4.4.7.8 验收要点(客户校对场景)
|
||
|
||
1. 缺收件人国家/地址时,右侧标红且无法提交该单(或整批拦截并定位到问题单)。
|
||
2. 运输方式未选,主按钮禁用。
|
||
3. 代发:未绑任何资源时主按钮禁用;换仓后绑定草稿清空。
|
||
4. 提交后列表出现 `linkedServiceNo`,履约状态变为待发货;代发单在「我的订单」打开绑定详情可见冻结行。
|
||
5. 对已关联物流单再次点击两入口,前置校验拒绝。
|
||
6. 与「匹配库存」并行存在,文案不混淆(工具栏分组或次要按钮区分)。
|
||
|
||
## 5 渠道模块
|
||
|
||
### 5.1 核心需求说明
|
||
|
||
- **销售产品**:管理对外销售的产品(专线/快递/邮政),配置时效、黑名单区、偏远分区、价格分区、计重规则、计重进位等;支持价格配置。
|
||
- **产品配置**:产品基础信息(编码、名称、分组、时效、面单、黑名单区、偏远分区、价格分区、计重规则、计重进位等)。
|
||
- **产品卖价**:为每个产品配置多条价格项(运费、保险费、燃油附加费等);每条价格项通过弹窗配置:
|
||
- **基础信息**:费用项、费用单位、币种、收费条件、计费因素(因素+因素值,如货物类型、分区、重量、仓库、客户等)。
|
||
- **价格表**:根据计费因素动态生成价格表,支持分区/货物类型/重量等组合、计算公式(总价/单价/续单价)、计算值、客户组。
|
||
- **排货规则**:选择排货方案(成本最低/时效最快/综合最优),配置服务商列表(服务商名称、服务渠道、权重、指定客户)。
|
||
- **发布记录**:价格与规则的发布历史,支持查看版本内容、回滚到指定版本。
|
||
- **服务渠道**:管理下游服务渠道(支持渠道组+渠道层级),配置预报单号类型、时效、买价等。
|
||
- **渠道分组**:`GET /channel/service-channel/group/list` 查询服务商分组列表(下拉/左侧树筛选);`group/create`、`group/update`、`group/delete` 维护 `t_service_channel_group`。
|
||
- **渠道配置**:渠道基础信息(渠道代码、名称、时效、预报配置、描述、预报前脚本等)。
|
||
- **渠道买价**:为每个渠道配置多条买价项(运费、保险费、燃油附加费等);每条买价项通过弹窗配置:
|
||
- **基础信息**:费用项、费用单位、币种、收费条件、计费因素(因素+因素值)。
|
||
- **价格表**:根据计费因素动态生成价格表,支持分区/货物类型/重量等组合、计算公式、计算值、客户组。
|
||
- **发布记录**:买价与规则的发布历史,支持查看版本内容、回滚到指定版本。
|
||
- **黑名单区/偏远分区/价格分区**:共用 KpiArea 表,仅通过 `AreaType` 区分;支持分区及明细(国家/省/市/邮编区间)的增删改查。
|
||
- **计重规则/计重进位**:配置计费重量规则与进位方式,明细结构与分区类似。
|
||
- **自动触发**:配置下单/入库/出库时的自动拦截、警告、问题件等触发规则;**V2 目标**为订单校验规则引擎(结构化条件 + 全字段 + 预报同步拦截),见 **§5.3.0.6.1**。
|
||
- **问题模板**:按组织维护问题件话术与通知方式,供标记问题件与自动触发消费(一期 CRUD)。
|
||
- **标签设计**:保存 vg-print 导出的标签模板 JSON,支持尾程/客户/入库/出库/sku 等类型。
|
||
|
||
### 5.2 表结构设计
|
||
|
||
#### 5.2.1 销售产品表 `t_sales_product`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| ProductCode | VARCHAR | 64 | - | 是 | 产品编码 |
|
||
| ProductName | VARCHAR | 128 | - | 是 | 产品名称 |
|
||
| ProductGroup | VARCHAR | 32 | - | 否 | 分组(专线/快递/邮政) |
|
||
| MinTimeliness | INT | - | - | 否 | 最小时效(天) |
|
||
| MaxTimeliness | INT | - | - | 否 | 最大时效(天) |
|
||
| CustomerWaybill | VARCHAR | 32 | - | 否 | 系统面单/客户面单 |
|
||
| BlacklistAreaId | BIGINT | - | 外键 | 否 | 黑名单区ID |
|
||
| RemoteAreaId | BIGINT | - | 外键 | 否 | 偏远分区ID |
|
||
| PriceAreaId | BIGINT | - | 外键 | 否 | 价格分区ID(`t_kpi_area`,`AreaType=price`) |
|
||
| WeightRuleId | BIGINT | - | 外键 | 否 | 计重规则ID |
|
||
| WeightRoundingId | BIGINT | - | 外键 | 否 | 计重进位ID |
|
||
| ProductDescription | TEXT | - | - | 否 | 产品描述 |
|
||
| PreStorageScript | TEXT | - | - | 否 | 入库前脚本 |
|
||
| IsActive | TINYINT | - | - | 是 | 是否启用 |
|
||
| Scheme | VARCHAR | 32 | - | 是 | 排货方案:costLowest/timeFastest/overallBest |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.2 产品卖价表 `t_sales_product_price`(子表,关联 t_sales_product)
|
||
|
||
> 产品卖价主表,每个产品可配置多条价格项(运费、保险费、燃油附加费等)。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| SalesProductId | BIGINT | - | 外键 | 是 | 销售产品ID |
|
||
| SerialNumber | INT | - | - | 是 | 序号 |
|
||
| Status | TINYINT | - | - | 是 | 状态:0-禁用,1-启用 |
|
||
| PriceName | VARCHAR | 64 | - | 是 | 价格名称(如运费、保险费) |
|
||
| CostItem | VARCHAR | 64 | - | 是 | 费用项 |
|
||
| CostUnit | VARCHAR | 16 | - | 是 | 费用单位:kg/票/件 |
|
||
| Currency | VARCHAR | 16 | - | 是 | 币种:RMB/USD/EUR |
|
||
| ChargingCondition | VARCHAR | 64 | - | 否 | 收费条件 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.3 产品卖价-计费因素表 `t_sales_product_price_factor`(子表,关联 t_sales_product_price)
|
||
|
||
> 弹窗「基础信息」中的计费因素配置,支持货物类型、分区、重量、仓库、客户等。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| PriceId | BIGINT | - | 外键 | 是 | 产品卖价ID |
|
||
| SerialNumber | INT | - | - | 是 | 序号(决定因素排序) |
|
||
| Factor | VARCHAR | 64 | - | 是 | 因素:货物类型/分区/重量/仓库/客户 |
|
||
| FactorValues | JSON | - | - | 否 | 因素值数组,如 ["普货","敏感货"] |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.4 产品卖价-价格表明细表 `t_sales_product_price_detail`(子表,关联 t_sales_product_price)
|
||
|
||
> 弹窗「价格表」中的价格明细,表头由计费因素动态生成,存储各组合的价格值。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| PriceId | BIGINT | - | 外键 | 是 | 产品卖价ID |
|
||
| FactorValuesJson | JSON | - | - | 否 | 非客户因素组合,如 {"分区":"欧洲","货物类型":"普货","重量":"0-0.5"} |
|
||
| CalculationFormula | VARCHAR | 32 | - | 是 | 计算公式:总价/单价/续单价 |
|
||
| CalculatedValue | DECIMAL | 18,4 | - | 否 | 计算值 |
|
||
| CustomerGroupValues | JSON | - | - | 否 | 客户组对应价格,如 {"客户组1":10,"客户组2":12} |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
|
||
#### 5.2.5 排货规则-服务商表 `t_sales_product_allocation_provider`(子表,关联 t_sales_product_allocation_rule)
|
||
|
||
> 服务商列表:服务商名称、服务渠道、权重、指定客户。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| SalesProductId | BIGINT | - | 外键 | 是 | 销售产品ID |
|
||
| SerialNumber | INT | - | - | 是 | 序号 |
|
||
| ProviderName | VARCHAR | 128 | - | 是 | 服务商名称 |
|
||
| ServiceChannelId | BIGINT | - | 外键 | 否 | 服务渠道ID |
|
||
| Weight | INT | - | - | 是 | 权重(评分用) |
|
||
| CustomerIds | JSON | - | - | 否 | 指定客户 ID 数组(**`t_customer.Id`**),空表示全部 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**Scheme 枚举**:costLowest(成本最低)、timeFastest(时效最快)、overallBest(综合最优)
|
||
|
||
#### 5.2.6 发布记录表 `t_sales_product_publish_record`(子表,关联 t_sales_product)
|
||
|
||
> 价格与规则的发布历史,支持查看版本内容、回滚。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| SalesProductId | BIGINT | - | 外键 | 是 | 销售产品ID |
|
||
| Version | VARCHAR | 32 | - | 是 | 版本号,如 20231227112339 |
|
||
| OperatorType | VARCHAR | 16 | - | 是 | 操作类型:price(价格)/rules(规则) |
|
||
| Status | VARCHAR | 16 | - | 是 | 状态:生效/回滚/无效 |
|
||
| PublishUser | VARCHAR | 64 | - | 是 | 发布人 |
|
||
| PublishTime | DATETIME | - | - | 是 | 发布时间 |
|
||
| Description | VARCHAR | 512 | - | 否 | 版本备注 |
|
||
| Content | JSON | - | - | 否 | 发布内容(价格或规则配置的JSON快照) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
##### 5.2.6.1 发布内容 `Content`(JSON快照)结构规范(用于限制校验与计费)
|
||
|
||
> 设计目标:
|
||
> 1) **强绑定有效期**:订单创建时按当时的生效版本(PublishTime)绑定发布记录;后续计算以绑定版本为准。
|
||
> 2) **计算快**:下单/计费/校验不再联查 `t_kpi_area*`、`t_weight_rule*`、`t_weight_rounding*`、`t_sales_product_price*` 等多表;只读取一次发布记录 `Content`(可缓存)。
|
||
> 3) **可回溯**:任何时候都能根据订单绑定的发布记录ID拿到当时的规则/价格快照。
|
||
>
|
||
> **性能实现要求(强制)**:
|
||
> - 发布后的规则/价格快照以 **`t_sales_product_publish_record` 为权威落库**;同步任务将标准化后的 `Content` 写入 **Redis**(按 `salesProductId` + `OperatorType` + `Version` 或 `current` 指针),并通知计费服务刷新**本地内存**。
|
||
> - **在线下单/预估只读 Redis + 本地内存**;仅在 Redis/内存异常时**降级**直读发布记录表(见订单模块 `/order/parcel/create` 步骤 4),并应异步回填 Redis。
|
||
|
||
##### 5.2.6.2 Redis 与发布同步(在线计费)
|
||
|
||
> **当前实现(oms0414-back)**:发布接口 **仅** 向 `t_sales_product_publish_record` / `t_service_channel_publish_record` 插入记录(`Content` 为完整快照 JSON)。**未**写入 Redis、**未**推送计费实例刷新内存。运行时算价见订单 **§4.3.5**(读发布表或 Live 组装)。以下 Redis 流程为 **目标架构**,待渠道发布与订单模块联调时落地。
|
||
|
||
- **Key 示例**(可按团队规范调整前缀):
|
||
- `oms:pricing:sp:{salesProductId}:rules:{version}` / `...:price:{version}`:存放与 `Content` 同构的 JSON(或已序列化的二进制)。
|
||
- `oms:pricing:sp:{salesProductId}:rules:current` / `...:price:current`:指向当前对外生效的 `version` 字符串,便于 O(1) 定位。
|
||
- **在线匹配数据来源**:计费服务进程内已解析的 `rulesSnapshot` / `priceSnapshot`(来自 Redis 加载或启动预热)。
|
||
|
||
**发布同步流程(必须)**:
|
||
|
||
1. 业务侧完成一次规则或价格发布,写入 `t_sales_product_publish_record`(含 `Version`、`Status`、`PublishTime`、`Content`)。
|
||
2. 将 `Content` 标准化后写入 Redis(对应 `OperatorType` 的 key);更新 `current` 指针指向新版本(与「生效」语义一致)。
|
||
3. 通知各计费实例刷新内存;实例冷启动可从 Redis 全量加载,必要时可从发布表补齐缺失版本。
|
||
4. 若 Redis 写入失败:应**阻断或重试发布**,直至线上可见一致快照,避免「库里有版本但线上算价仍用旧缓存」。
|
||
|
||
###### 1) 通用字段(rules / price 共用)
|
||
|
||
- `schemaVersion`:快照结构版本(从 1 开始)
|
||
- `operatorType`:`rules` 或 `price`(与发布记录 `OperatorType` 保持一致)
|
||
- `salesProduct`:发布时的产品基础信息快照(用于展示与校验,不参与联查)
|
||
- `effective`:
|
||
- `startAt`:生效时间(一般等于 `PublishTime`)
|
||
- `endAt`:失效时间(可为空,表示至今有效;当有新版本发布时可由系统补齐旧版本 `endAt`)
|
||
- `checksum`:可选,用于校验内容是否被篡改(如 sha256)
|
||
|
||
###### 2) `rules` 快照内容(限制 + 计重 + 进位)
|
||
|
||
`Content` 示例(OperatorType=rules):
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operatorType": "rules",
|
||
"salesProduct": {
|
||
"salesProductId": 1,
|
||
"productCode": "US_AIR",
|
||
"productName": "美国空运专线(普货)",
|
||
"cargoAttributes": ["general", "electronics", "sensitive"],
|
||
"scheme": "overallBest"
|
||
},
|
||
"effective": { "startAt": "2026-04-25 14:30:00", "endAt": null },
|
||
"areas": {
|
||
"blacklist": {
|
||
"areaName": "黑名单区1",
|
||
"rules": [
|
||
{
|
||
"countryCode": "US",
|
||
"province": null,
|
||
"city": null,
|
||
"postcodeRanges": [
|
||
{ "left": "90000", "right": "90010" },
|
||
{ "left": "91000", "right": "91099" }
|
||
]
|
||
}
|
||
]
|
||
},
|
||
"remote": {
|
||
"areaName": "偏远分区1",
|
||
"rules": [
|
||
{
|
||
"countryCode": "US",
|
||
"province": "California",
|
||
"city": null,
|
||
"postcodeRanges": [{ "left": "90001", "right": "90099" }]
|
||
}
|
||
]
|
||
},
|
||
"price": {
|
||
"areaName": "价格分区(美国)",
|
||
"rules": [
|
||
{
|
||
"zoneCode": "US_WEST",
|
||
"zoneName": "美国西部",
|
||
"countryCode": "US",
|
||
"province": null,
|
||
"city": null,
|
||
"postcodeRanges": [{ "left": "90000", "right": "96999" }]
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"weightRule": {
|
||
"weightRuleId": 1,
|
||
"name": "标准计重规则",
|
||
"clientRules": [
|
||
{ "volumetricFactor": 6000, "condition": "gw>vw", "billableWeight": "gw" },
|
||
{ "volumetricFactor": 6000, "condition": "gw<=vw", "billableWeight": "vw" }
|
||
]
|
||
},
|
||
"weightRounding": {
|
||
"weightRoundingId": 1,
|
||
"name": "标准进位",
|
||
"ranges": [{ "start": 0, "end": 30, "public": "0.5" }]
|
||
},
|
||
"checksum": ""
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- `areas.blacklist/remote/price` 将 `t_kpi_area_info` 的明细**扁平化内嵌**到快照里,避免下单时联查分区表。
|
||
- `areas.price.rules[*].zoneCode/zoneName` 用于后续计价因子“分区”匹配(对应价格快照的 `factorValuesJson.分区`)。
|
||
- `weightRule.clientRules` 将 `t_weight_rule_info` 直接内嵌(**无 `client` 列**);运行时以 §4.4.3.1 步骤 7 解析的 **`effectiveWeightRuleId`** 为准,与快照中 **`weightRuleId`** 对齐或回表查询。
|
||
- `weightRounding.ranges` 将 `t_weight_rounding_info` 内嵌(**仅 `public` 进位值**);运行时以步骤 8 的 **`effectiveWeightRoundingId`** 为准。
|
||
|
||
###### 3) `price` 快照内容(卖价配置 + 价格表)
|
||
|
||
`Content` 示例(OperatorType=price):
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operatorType": "price",
|
||
"salesProduct": {
|
||
"salesProductId": 1,
|
||
"productCode": "US_AIR",
|
||
"productName": "美国空运专线(普货)"
|
||
},
|
||
"effective": { "startAt": "2026-04-25 14:30:00", "endAt": null },
|
||
"priceItems": [
|
||
{
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [
|
||
{ "serialNumber": 1, "factor": "分区" },
|
||
{ "serialNumber": 2, "factor": "重量" }
|
||
],
|
||
"details": [
|
||
{
|
||
"factorValuesJson": { "分区": "美国西部", "重量": "0-9999" },
|
||
"calculationFormula": "续单价",
|
||
"calculatedValue": 0,
|
||
"customerGroupValues": null,
|
||
"continuePricing": {
|
||
"firstWeightKg": 1,
|
||
"firstPrice": 18,
|
||
"continueUnitKg": 0.5,
|
||
"continueUnitPrice": 6,
|
||
"roundingUnitKg": 0.5
|
||
}
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"checksum": ""
|
||
}
|
||
```
|
||
|
||
说明:
|
||
- 为了支持“**首重/续重/进位**”的可落地配置,当 `calculationFormula = 续单价` 时,使用 `continuePricing` 描述完整计费参数(不再依赖额外表字段)。
|
||
- `roundingUnitKg` 表示计价侧进位值(如 0.5kg)。若同时存在规则侧进位(`rules` 快照里的 `weightRounding`),**计费重进位以规则侧为准**,`roundingUnitKg` 用于价格项自身的续重单位计算兜底(两者一般配置一致)。
|
||
|
||
|
||
#### 5.2.7 服务渠道组表 `t_service_channel_group`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| Name | VARCHAR | 128 | - | 是 | 渠道组名称 |
|
||
| AppId | BIGINT | - | - | 否 | 应用ID(页面新增/编辑服务商时保存) |
|
||
| ApiServiceProviderId | BIGINT | - | - | 否 | API服务商ID(页面新增/编辑服务商时保存) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.8 服务渠道表 `t_service_channel`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| GroupId | BIGINT | - | 外键 | 否 | 渠道组ID(可选) |
|
||
| ChannelCode | VARCHAR | 64 | - | 是 | 渠道编码 |
|
||
| ChannelName | VARCHAR | 128 | - | 是 | 渠道名称 |
|
||
| MinTime | INT | - | - | 否 | 最小时效(天) |
|
||
| MaxTime | INT | - | - | 否 | 最大时效(天) |
|
||
| ForecastType | VARCHAR | 32 | - | 否 | 预报单号类型(customer/waybill/tracking) |
|
||
| ForecastCode | VARCHAR | 128 | - | 否 | 预报单号 |
|
||
| Description | TEXT | - | - | 否 | 描述 |
|
||
| PreScript | TEXT | - | - | 否 | 入库前脚本 |
|
||
| IsActive | TINYINT | - | - | 是 | 是否启用 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.9 渠道买价表 `t_service_channel_price`(子表,关联 t_service_channel)
|
||
|
||
> 渠道买价主表,每个渠道可配置多条买价项(运费、保险费、燃油附加费等)。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| ServiceChannelId | BIGINT | - | 外键 | 是 | 服务渠道ID |
|
||
| SerialNumber | INT | - | - | 是 | 序号 |
|
||
| Status | TINYINT | - | - | 是 | 状态:0-禁用,1-启用 |
|
||
| PriceName | VARCHAR | 64 | - | 是 | 价格名称 |
|
||
| CostItem | VARCHAR | 64 | - | 是 | 费用项 |
|
||
| CostUnit | VARCHAR | 16 | - | 是 | 费用单位:kg/票/件 |
|
||
| Currency | VARCHAR | 16 | - | 是 | 币种:RMB/USD/EUR |
|
||
| ChargingCondition | VARCHAR | 64 | - | 否 | 收费条件 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.10 渠道买价-计费因素表 `t_service_channel_price_factor`(子表,关联 t_service_channel_price)
|
||
|
||
> 弹窗「基础信息」中的计费因素配置。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| PriceId | BIGINT | - | 外键 | 是 | 渠道买价ID |
|
||
| SerialNumber | INT | - | - | 是 | 序号 |
|
||
| Factor | VARCHAR | 64 | - | 是 | 因素:货物类型/分区/重量/仓库/客户 |
|
||
| FactorValues | JSON | - | - | 否 | 因素值数组 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.11 渠道买价-价格表明细表 `t_service_channel_price_detail`(子表,关联 t_service_channel_price)
|
||
|
||
> 弹窗「价格表」中的价格明细。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| PriceId | BIGINT | - | 外键 | 是 | 渠道买价ID |
|
||
| FactorValuesJson | JSON | - | - | 否 | 非客户因素组合 |
|
||
| CalculationFormula | VARCHAR | 32 | - | 是 | 计算公式:总价/单价/续单价 |
|
||
| CalculatedValue | DECIMAL | 18,4 | - | 否 | 计算值 |
|
||
| CustomerGroupValues | JSON | - | - | 否 | 客户组对应价格 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.12 服务渠道发布记录表 `t_service_channel_publish_record`(子表,关联 t_service_channel)
|
||
|
||
> 买价与规则的发布历史。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| ServiceChannelId | BIGINT | - | 外键 | 是 | 服务渠道ID |
|
||
| Version | VARCHAR | 32 | - | 是 | 版本号 |
|
||
| OperatorType | VARCHAR | 16 | - | 是 | 操作类型:price(价格)/rules(规则) |
|
||
| Status | VARCHAR | 16 | - | 是 | 状态:生效/回滚/无效 |
|
||
| PublishUser | VARCHAR | 64 | - | 是 | 发布人 |
|
||
| PublishTime | DATETIME | - | - | 是 | 发布时间 |
|
||
| Description | VARCHAR | 512 | - | 否 | 版本备注 |
|
||
| Content | JSON | - | - | 否 | 发布内容JSON快照 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.13 分区表(黑名单区/偏远分区/价格分区共用)`t_kpi_area`
|
||
|
||
> 三类分区共用本表,仅通过 `AreaType` 区分业务含义。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| AreaType | VARCHAR | 32 | - | 是 | 类型: blacklist/remote/price |
|
||
| Name | VARCHAR | 128 | - | 是 | 分区名称 |
|
||
| SystemName | VARCHAR | 128 | - | 否 | 系统名称 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**AreaType 枚举**:blacklist(黑名单区)、remote(偏远分区)、price(价格分区)
|
||
|
||
#### 5.2.14 分区明细表 `t_kpi_area_info`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| AreaId | BIGINT | - | 外键 | 是 | 分区ID |
|
||
| CountryCode | VARCHAR | 8 | - | 否 | 国家二字码 |
|
||
| CountryName | VARCHAR | 64 | - | 否 | 国家名称 |
|
||
| Province | VARCHAR | 128 | - | 否 | 省份 |
|
||
| City | VARCHAR | 128 | - | 否 | 城市 |
|
||
| LeftPostcode | VARCHAR | 32 | - | 否 | 左侧邮编 |
|
||
| RightPostcode | VARCHAR | 32 | - | 否 | 右侧邮编 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.15 计重规则表 `t_weight_rule`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| Name | VARCHAR | 128 | - | 是 | 规则名称 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.16 计重规则明细表 `t_weight_rule_info`(子表,关联 t_weight_rule)
|
||
|
||
> 计重规则子页面,仅配置**条件表达式**、**计费重取值**、**材积系数**;不再使用「客户/客户组」列。同一 `RuleId` 下多行按 **`Id` 升序**逐条尝试,**第一条**使条件为真的明细生效(算法见 §4.4.3.1 步骤 7)。运行时通过本单绑定 **`t_sales_product.WeightRuleId`** 选用对应 `t_weight_rule`(不同销售产品可绑定不同规则)。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| RuleId | BIGINT | - | 外键 | 是 | 计重规则ID |
|
||
| Condition | VARCHAR | 256 | - | 是 | 条件表达式(如:gw>vw) |
|
||
| BillableWeight | VARCHAR | 64 | - | 是 | 计费重取值(如:vw、gw) |
|
||
| VolumetricFactor | VARCHAR | 32 | - | 是 | 材积系数(如:6000) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.17 计重进位表 `t_weight_rounding`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| Name | VARCHAR | 128 | - | 是 | 进位名称 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.18 计重进位明细表 `t_weight_rounding_info`(子表,关联 t_weight_rounding)
|
||
|
||
> 计重进位子页面,按重量区间配置**单一进位值** `Public`(不再按客户组分列)。运行时通过本单绑定 **`t_sales_product.WeightRoundingId`** 选用对应 `t_weight_rounding`(算法见 §4.4.3.1 步骤 8)。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| RoundingId | BIGINT | - | 外键 | 是 | 计重进位ID |
|
||
| StartValue | DECIMAL | 18,4 | - | 是 | 起始值(kg) |
|
||
| EndValue | DECIMAL | 18,4 | - | 是 | 结束值(kg) |
|
||
| Public | VARCHAR | 32 | - | 否 | 进位值(kg,如 0.5 表示按 0.5kg 向上取整) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.19 自动触发表 `t_auto_trigger`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| Status | TINYINT | - | - | 是 | 是否启用 |
|
||
| Operation | VARCHAR | 32 | - | 否 | 操作: 拦截/警告/问题件 |
|
||
| TriggerTiming | VARCHAR | 32 | - | 否 | 触发时机: 下单/入库/出库 |
|
||
| TriggerCondition | TEXT | - | - | 否 | 触发条件 |
|
||
| PromptMessage | VARCHAR | 512 | - | 否 | 提示信息 |
|
||
| Notifier | VARCHAR | 128 | - | 否 | 通知人 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
|
||
#### 5.2.20 标签模板表 `t_label_template`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| Name | VARCHAR | 128 | - | 是 | 模板名称 |
|
||
| TemplateJson | JSON | - | - | 否 | vg-print 模板 JSON |
|
||
| TemplateType | VARCHAR | 32 | - | 否 | 尾程/客户/入库/出库/sku |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 5.2.21 问题模板表 `t_issue_template`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| OrganizationId | CHAR | 36 | 外键 → `organization.Id` | 否 | 归属组织 GUID;**NULL = 平台通用** |
|
||
| IssueType | VARCHAR | 32 | - | 是 | 问题类型 code,见 §10.3 |
|
||
| Title | VARCHAR | 128 | - | 是 | 问题标题 |
|
||
| Content | TEXT | - | - | 是 | 问题内容(支持占位符 `{serviceNo}` 等,二期渲染) |
|
||
| NotifyMethod | VARCHAR | 64 | - | 是 | 通知方式,逗号分隔:`inApp,email,sms,feishu,wechat` |
|
||
| Status | TINYINT | - | - | 是 | 是否启用:0-否,1-是 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| ConcurrencyStamp | CHAR | 36 | - | 是 | 并发戳(AggregateRoot 约定) |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**唯一性(业务层)**:同一 `OrganizationId`(含 NULL)+ `IssueType` 仅允许一条未删除记录。
|
||
|
||
### 5.3 核心接口设计
|
||
|
||
> **网关前缀**:生产环境渠道接口挂载为 **`https://api.nanafa.com/admin/channel/...`**;下文路径以 **`/channel/*`** 表示服务内路由。
|
||
|
||
#### 5.3.0 当前实现总览(oms0414-back)
|
||
|
||
> 实现位置:`Oms.Application/Services/Channels/`。Swagger 分组 **`OMS-Channel`**。渠道接口 **无客户数据隔离**(运营/管理员使用);须 **已登录**(`[Authorize]`)。
|
||
|
||
##### 5.3.0.1 公共约定
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 分页 | `OmsPageResult` → `{ items, total }`;列表无分页 → `OmsItemsResult` → `{ items }` |
|
||
| 软删除 | 各 `DeleteAsync` 走仓储软删(`IsDeleted=1`) |
|
||
| 唯一性 | 销售产品 **`ProductCode`**、服务渠道 **`ChannelCode`**、KPI 分区 **`AppKey+AreaType+Name`**、渠道分组 **`Name`**、标签模板 **`Name+TemplateType`** |
|
||
| 发布类型 | `operatorType` 仅 **`price`** \| **`rules`**(大小写不敏感,内部归一化小写) |
|
||
| 版本号 | 发布时 **`Version = yyyyMMddHHmmss`**(字符串);`Status` 固定写 **「生效」** |
|
||
| Redis | 发布 **仅写 DB** `t_*_publish_record.Content`;**未**同步 Redis / 未通知计费实例(见 **§5.2.6.2** 实现说明) |
|
||
|
||
##### 5.3.0.2 销售产品(`SalesProductService`)
|
||
|
||
| 接口 | 核心逻辑 |
|
||
| --- | --- |
|
||
| `GET .../list` | 筛选:`productGroup`、`keyword`(匹配 Code/Name)、`isActive`;排序 `UpdateTime desc, Id desc` |
|
||
| `GET .../detail` | 单条;不存在 → **「销售产品不存在」** |
|
||
| `POST .../create` | 校验 Code 唯一 → `InsertAsync`;`Scheme` 空则默认 **`costLowest`** |
|
||
| `POST .../update` | 同 create,带 `Id` |
|
||
| `POST .../delete` | 软删产品 |
|
||
| `POST .../config/save` | 与 update 相同(`[IgnoreApi]`,供前端配置 Tab 专用路径) |
|
||
| `GET .../price/list` | 校验产品存在 → 一次查齐价格项 + factors + details(避免 N+1) |
|
||
| `POST .../price/create` | 插入价格主表 + factor + detail 子表 |
|
||
| `POST .../price/update` | 校验价格项归属 `SalesProductId` → 全量替换 factor/detail |
|
||
| `POST .../price/delete` | 删价格项 |
|
||
| `POST .../price/toggleStatus` | `ToggleStatusInput` 更新启用状态 |
|
||
| `POST .../price/save` | **`ReplacePricesAsync`** 整树替换(临时负数 Id 关联子表);可选 **`publish=true`** 触发价格发布 |
|
||
| `GET .../allocation/get` | 返回 `Scheme` + 排货服务商列表(补 **`ServiceChannelName`**) |
|
||
| `POST .../allocation/save` | 更新产品 `Scheme` + **`ReplaceAllocationProvidersAsync`**;`CustomerIds` 存 JSON;可选 **`publish=true`** 触发规则发布 |
|
||
| `GET .../publish/price\|rules/list` | 分页发布记录;`Content` 反序列化为 JSON 对象返回 |
|
||
| `GET .../publish/preview` | **不落库**预览快照:`BuildPricePublishSnapshotAsync` / `BuildRulesPublishSnapshotAsync` |
|
||
| `POST .../publish` | `CreatePublishRecordAsync`:组装快照 JSON 写入发布表 |
|
||
| `POST .../publish/rollback` | 读指定 `recordId` 的 `Content`:**price** → `SavePriceAsync` 恢复价格树;**rules** → 更新产品配置 + 恢复排货 providers |
|
||
|
||
**规则快照(`operatorType=rules`)组装内容**(`BuildRulesPublishSnapshotAsync`):
|
||
|
||
- 产品基础字段(含 `BlacklistAreaId`、`RemoteAreaId`、`PriceAreaId`、`WeightRuleId`、`WeightRoundingId` 等)
|
||
- 排货 **`Providers`**(**不含** `CreateTime`/`UpdateTime`,与卖价 Slim 策略一致)
|
||
- 通过 `RulesSnapshotHelper` 嵌入:**黑名单/偏远/价格分区明细**、**计重规则明细**、**进位规则明细**
|
||
- 发布前由 **`AllocationProviderSnapshotEnricher`** 对每个 Provider(有 **`ServiceChannelId`**)补充 **下游对接元数据**(见下表)
|
||
|
||
**`Providers[]` 下游 enrich 字段**(写入 `t_sales_product_publish_record.Content`,供排货/轨迹/下游预报读取):
|
||
|
||
| 字段 | 来源 | 说明 |
|
||
| --- | --- | --- |
|
||
| `channelCode` | `t_service_channel.ChannelCode` | 服务渠道编码 |
|
||
| `forecastCode` | `t_service_channel.ForecastCode` | 渠道预报编码(轨迹 **ApiCode**) |
|
||
| `groupId` | `t_service_channel.GroupId` | 服务商分组 Id |
|
||
| `appId` | `t_service_channel_group.AppId` | itfx 应用 Id |
|
||
| `apiServiceProviderId` | `t_service_channel_group.ApiServiceProviderId` | itfx API 服务商 Id |
|
||
| `appKey` | itfx **`GET /App/GetInfo?id={appId}`** | 应用 AppKey |
|
||
| `supplierCode` | itfx **`GET /ServiceProvider/GetServiceProvider?serviceProviderId=`** | API 服务商编码 |
|
||
|
||
**安全与兼容约定**:
|
||
|
||
- **`AppSecret` 不落快照、不落 `t_order`**;运行时按 **`appId` / `downstreamAppId`** 调 itfx App API 获取。
|
||
- itfx 元数据客户端配置:**`Oms:Itfx:BaseUrl`**(默认 `http://api.nanafa.com/itfx`),转发当前请求 **JWT Authorization**。
|
||
- **旧版 rules 快照**(无上述字段):排货时 **`ResolveAllocationProvidersAsync`** 按需 live enrich;确认渠道后仍写入 **`t_order`** 四字段(**`sql/migrate_t_order_downstream_integration.sql`**)。
|
||
|
||
**价格快照(`operatorType=price`)组装内容**(`BuildPricePublishSnapshotAsync`):
|
||
|
||
- 全部卖价项(Slim 后)+ factors + details
|
||
- 通过 `PriceAreaSnapshotHelper` 嵌入 **`PriceAreaInfos`**(价格分区明细,供运行时匹配 zone key)
|
||
|
||
##### 5.3.0.3 服务渠道(`ServiceChannelService`)
|
||
|
||
与销售产品 **对称**,差异要点:
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 列表筛选 | `groupId`、`keyword`(Code/Name)、`isActive`;列表/详情补 **`groupName`** |
|
||
| 分组 | `group/list` 返回 **数组**(非分页);筛选 `keyword`、`appId`、`apiServiceProviderId` |
|
||
| 分组删除 | 分组下仍有渠道 → **「当前分组下仍有关联渠道,无法删除」** |
|
||
| 分组校验 | `Name`、`AppId`、`ApiServiceProviderId` 必填;名称唯一 |
|
||
| `config/save` | 同 update(`[IgnoreApi]`) |
|
||
| 买价 | 与卖价相同 CRUD + **`price/save`** 整树替换 + 可选发布 |
|
||
| 发布/回滚 | 写 **`t_service_channel_publish_record`**;**price** 回滚恢复买价树;**rules** 回滚恢复渠道主配置 |
|
||
| 发布预览 | **`GET .../publish/preview`**(与卖价对称,2026-06 已落地) |
|
||
|
||
##### 5.3.0.4 KPI 分区(`KpiAreaService`)
|
||
|
||
| 接口 | 核心逻辑 |
|
||
| --- | --- |
|
||
| `POST .../list` | 筛选 `appKey`、`areaType`(**blacklist / remote / price**,见 §10.2) |
|
||
| `POST .../create\|update` | 同 **`AppKey+AreaType+Name`** 唯一;update 校验存在 |
|
||
| `POST .../delete` | 软删主表 |
|
||
| `POST .../info/list` | 按 `areaId` 分页明细 |
|
||
| `POST .../info/add\|update\|delete` | 明细 CRUD:国家码、省市区、邮编区间 `LeftPostcode`~`RightPostcode` |
|
||
| `GET .../info/import-template` | 下载 Excel 模板(`KpiAreaInfoImportHelper`) |
|
||
| `POST .../info/import` | multipart:`areaId` + file;逐行解析 → 校验国家码(对照 `t_country`)→ 插入;部分失败返回 **`successCount/failCount/failures[]`** |
|
||
|
||
##### 5.3.0.5 计重规则 / 进位(`WeightRuleService`、`WeightRoundingService`)
|
||
|
||
标准 **主表 + info 子表** CRUD(均为 **POST** 路由):
|
||
|
||
- 主表:list(分页,可按 keyword 筛)、create、update、delete
|
||
- 子表:info/list(按 `ruleId` 或 `roundingId` 分页)、info/add、info/update、info/delete
|
||
- 计重明细字段:`VolumetricFactor`、`Condition`、`BillableWeight`(gw/vw)
|
||
- 进位明细字段:`StartValue`、`EndValue`、`Public`(进位单位 kg)
|
||
|
||
运行时算价见订单 **§4.3.5**(按 `Id` 升序匹配首条计重条件;进位 `start <= raw <= end`)。
|
||
|
||
##### 5.3.0.6 自动触发(`AutoTriggerService`)
|
||
|
||
| 接口 | 核心逻辑 |
|
||
| --- | --- |
|
||
| `GET .../list` | 分页;筛选 `operation`、`triggerTiming` |
|
||
| `POST .../create\|update` | 字段:`Status`、`Operation`、`TriggerTiming`、`TriggerCondition`、`PromptMessage`、`Notifier` |
|
||
| `POST .../delete` | 软删 |
|
||
| `POST .../toggleStatus` | 更新 `Status` |
|
||
|
||
> **运行时(现状)**:`AutoTriggerRuntimeService` **已部分落地**(见 **§9.4.5**):预报成功 / 入库 / 出库后异步执行;条件匹配器仅支持 **关键字子串** 与 **少量 JSON 等值**,**不支持** 表达式、重量尺寸、客户名称等字段;**拦截** 为预报成功后写 `IsIntercept=1`,**不能阻止下单**。
|
||
>
|
||
> **目标(V2 设计稿)**:见 **§5.3.0.6.1**,将自动触发升级为 **订单校验与限制规则引擎**——结构化条件、全量订单字段、比较/集合/逻辑运算符;**预报时机 + 拦截** 在提交前 **同步拒绝** 并返回 `promptMessage`。
|
||
|
||
##### 5.3.0.6.1 自动触发规则引擎 V2(设计稿 · 订单校验与限制)
|
||
|
||
> **状态**:设计稿(2026-07-09),**尚未开发**。本节为 `/channel/autoTrigger` 页面与 `AutoTriggerRuntimeService` 的下一版目标设计,服务于 **订单校验、限制下单、自动预警、自动问题件**。
|
||
>
|
||
> **业务目标**:运营可在渠道模块配置规则,在 **预报 / 入库 / 出库** 等节点,按 **订单全量相关字段**(主表、件重尺、收发件人、申报内件、客户、产品等)组合条件;动作为 **拦截(禁止继续)**、**警告(通知)**、**问题件(走问题模板)**。
|
||
|
||
###### 1)现状与差距
|
||
|
||
| 维度 | 现状(代码) | V2 目标 |
|
||
| --- | --- | --- |
|
||
| 配置页 | 触发时机/条件为 **纯文本**;操作三选一硬编码 | 时机/操作走字典;**可视化条件构建器**(字段 + 运算符 + 值) |
|
||
| 条件语法 | 关键字子串;JSON 仅 `orderStatus/productCode/channelCode/keyword` 等值 | **结构化 AST**(AND/OR/NOT 分组 + 比较表达式) |
|
||
| 可用字段 | 运单号、客户单号、跟踪号、产品/渠道编码 | **订单域全字段**(见 §5.3.0.6.1.4) |
|
||
| 运算符 | 无(仅 contains 式关键字) | `>`、`>=`、`<`、`<=`、`=`、`!=`、`between`、`in`、`notIn`、`contains`、`notContains`、`isEmpty`… |
|
||
| 预报 + 拦截 | 预报 **成功后** 写 `IsIntercept=1` | **预报提交前同步校验**;命中 **拦截** → **拒绝下单/预报**,返回 `promptMessage` |
|
||
| 尺寸/重量 | 未读取 `t_order_weight`、内件 | 支持 **按件重尺类型**(forecast/receive/ship/downstream)及 **内件聚合** |
|
||
| 客户 | 未参与匹配 | 支持 `customer.name` / `customer.code` / `customer.group` 等 |
|
||
| 规则优先级 | 全部启用规则依次执行 | 增加 `Priority`(数字越小越先);**拦截** 命中即短路 |
|
||
|
||
###### 2)核心概念
|
||
|
||
**触发时机(TriggerTiming)** — 字典 `auto_trigger_timing`(扩展):
|
||
|
||
| dictCode | 名称 | 挂钩点 | 执行方式 |
|
||
| --- | --- | --- | --- |
|
||
| `forecast` | 预报 | `POST /order/parcel/create`、批量下单、Excel 导入(均走 `CreateParcelCoreAsync`) | **同步**(提交前) |
|
||
| `inbound` | 入库 | `POST /operation/inbound/create` | 异步(扫描成功后) |
|
||
| `outbound` | 出库 | `POST /operation/outbound/create` | 异步(扫描成功后) |
|
||
|
||
> 兼容:旧数据 `下单`/`order_create`/`预报` 等文本,运行时归一化为 `forecast`。
|
||
|
||
**操作(Operation)** — 字典 `auto_trigger_operation`:
|
||
|
||
| dictCode | 名称 | 预报时机行为 | 入库/出库时机行为 |
|
||
| --- | --- | --- | --- |
|
||
| `intercept` | 拦截 | **拒绝**本次下单/预报,`HTTP` 业务错误文案 = `PromptMessage` | 订单已存在 → 设 `IsIntercept=1` |
|
||
| `warn` | 警告 | 允许成功,发站内信(+ 模板占位符) | 同左 |
|
||
| `problem` | 问题件 | 允许成功,按 `issueType` 匹配问题模板并标记 | 同左 |
|
||
|
||
**两类执行阶段**:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[请求进入] --> B{时机=forecast?}
|
||
B -->|是| C[PreCommit 同步评估]
|
||
C -->|命中 intercept| D[抛 BusinessException\nPromptMessage]
|
||
C -->|未命中或 warn/problem| E[继续预报主流程]
|
||
E --> F[预报成功]
|
||
F --> G[PostCommit 异步评估\nwarn / problem / 入库出库 intercept]
|
||
```
|
||
|
||
- **PreCommit(同步校验)**:仅 `forecast`;用于 **拦截下单**;必须在 `OrderRepository.InsertAsync` **之前**(或至少在写 `predeclared` 之前)调用。
|
||
- **PostCommit(异步处置)**:现有 `AutoTriggerRuntimeService.FireAsync` 演进;用于警告、问题件、以及入库/出库拦截标记。
|
||
|
||
###### 3)示例规则(超尺寸 · 英国 · 指定客户 · 排除产品)
|
||
|
||
**业务描述**(运营可读):
|
||
|
||
- **触发时机**:预报
|
||
- **触发条件**:
|
||
- (单件最大长度 > 60 **或** 单件最大宽度 > 46 **或** 单件长+宽+高 > 150)
|
||
- **且** 收件国家包含 `GB`
|
||
- **且** 客户名称包含 `阿里国际站/深圳一达通`
|
||
- **且** 销售产品 **不包含** `EX_ASP_HKDHL_YD`
|
||
- **操作**:拦截
|
||
- **提示语**:`{serviceNo}超尺寸,禁止下单`(预报前尚无运单号时可用固定文案:`超尺寸,禁止下单`)
|
||
|
||
**结构化条件(`TriggerCondition` JSON · version=2)**:
|
||
|
||
```json
|
||
{
|
||
"version": 2,
|
||
"logic": "and",
|
||
"children": [
|
||
{
|
||
"logic": "or",
|
||
"children": [
|
||
{ "field": "piece.maxLength", "op": "gt", "value": 60 },
|
||
{ "field": "piece.maxWidth", "op": "gt", "value": 46 },
|
||
{ "field": "piece.sumLwh", "op": "gt", "value": 150 }
|
||
]
|
||
},
|
||
{ "field": "receiver.countryCode", "op": "in", "value": ["GB"] },
|
||
{ "field": "customer.name", "op": "contains", "value": "阿里国际站/深圳一达通" },
|
||
{ "field": "productCode", "op": "notIn", "value": ["EX_ASP_HKDHL_YD"] }
|
||
]
|
||
}
|
||
```
|
||
|
||
**字段取值说明(本例)**:
|
||
|
||
| 字段 token | 含义 | 数据来源(预报 PreCommit) |
|
||
| --- | --- | --- |
|
||
| `piece.maxLength` | 单件最大长度(cm) | `max(包裹预报长宽高, 各内件对应尺寸)`;单包裹场景 = `input.Length` |
|
||
| `piece.maxWidth` | 单件最大宽度(cm) | 同上 → `input.Width` |
|
||
| `piece.sumLwh` | 单件长+宽+高(cm) | `Length + Width + Height`(按件取 max 后再比) |
|
||
| `receiver.countryCode` | 收件国家二字码 | `input.Receiver.CountryCode` 归一化后 |
|
||
| `customer.name` | 客户名称 | `t_customer.CustomerName`(`input.CustomerId` 解析) |
|
||
| `productCode` | 销售产品编码 | `input.ProductCode` |
|
||
|
||
###### 4)条件表达式模型
|
||
|
||
**叶子节点(比较)**:
|
||
|
||
```json
|
||
{ "field": "forecast.chargeableWeight", "op": "gt", "value": 30 }
|
||
```
|
||
|
||
| 属性 | 说明 |
|
||
| --- | --- |
|
||
| `field` | 字段 token,见 §5.3.0.6.1.5 |
|
||
| `op` | 运算符 code,见 §5.3.0.6.1.6 |
|
||
| `value` | 标量 / 数组 / 区间 `{ "min": 1, "max": 10 }`(`between`) |
|
||
|
||
**分组节点**:
|
||
|
||
```json
|
||
{ "logic": "and|or", "children": [ /* 叶子或分组 */ ] }
|
||
```
|
||
|
||
**否定(可选)**:
|
||
|
||
```json
|
||
{ "logic": "not", "children": [ { "field": "...", "op": "...", "value": "..." } ] }
|
||
```
|
||
|
||
**存储**:继续复用 `t_auto_trigger.TriggerCondition`(TEXT),内容为 **JSON**;`version: 2` 表示新引擎。`version` 缺失或 `version: 1`/纯文本 → 走 **兼容适配器**(映射到现有关键字匹配器,逐步迁移)。
|
||
|
||
###### 5)运算符一览
|
||
|
||
| op | 名称 | 适用类型 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `eq` | 等于 | 字符串/数字/布尔 | |
|
||
| `ne` | 不等于 | 同上 | |
|
||
| `gt` | 大于 | 数字 | |
|
||
| `gte` | 大于等于 | 数字 | |
|
||
| `lt` | 小于 | 数字 | |
|
||
| `lte` | 小于等于 | 数字 | |
|
||
| `between` | 区间内 | 数字 | `value: { min, max }` 闭区间 |
|
||
| `in` | 在集合中 | 字符串/数字 | `value: []` |
|
||
| `notIn` | 不在集合中 | 字符串/数字 | |
|
||
| `contains` | 包含 | 字符串 | 子串,忽略大小写 |
|
||
| `notContains` | 不包含 | 字符串 | |
|
||
| `startsWith` | 前缀 | 字符串 | |
|
||
| `endsWith` | 后缀 | 字符串 | |
|
||
| `isEmpty` | 为空 | 任意 | 无需 value |
|
||
| `isNotEmpty` | 不为空 | 任意 | |
|
||
|
||
> 页面按字段 **值类型** 过滤可选运算符(如数字字段不展示 `contains`)。
|
||
|
||
###### 6)订单相关字段目录(规则引擎可用 token)
|
||
|
||
> 下列为 **V2 一期** 拟支持字段;页面左侧按分组展示,右侧选运算符与值。带 `[]` 表示内件聚合。
|
||
|
||
**A. 订单主表 `order.*`**
|
||
|
||
| token | 说明 |
|
||
| --- | --- |
|
||
| `order.serviceNo` | 运单号 |
|
||
| `order.clientNo` | 客户单号 |
|
||
| `order.trackNo` | 跟踪号 |
|
||
| `order.productCode` | 销售产品编码 |
|
||
| `order.channelCode` | 服务渠道编码 |
|
||
| `order.orderStatus` | 订单状态 |
|
||
| `order.cargoAttribute` | 货物属性 |
|
||
| `order.exportCustomsMode` | 出口报关方式 |
|
||
| `order.warehouseId` | 仓库 ID |
|
||
| `order.quantity` | 件数 |
|
||
| `order.declaredCurrency` / `order.declaredSum` | 申报币种/总金额 |
|
||
| `order.isCod` / `order.codCurrency` / `order.codSum` | COD |
|
||
| `order.isIntercept` / `order.isRemote` / `order.isPaid` | 标志位 |
|
||
| `order.consigneeCountry` / `order.consigneeCountryName` | 收件国家码/名 |
|
||
| `order.salesProductRulesVersion` / `order.salesProductPriceVersion` | 绑定版本 |
|
||
| `order.predeclareFailReason` | 预报失败原因 |
|
||
|
||
**B. 件重尺 `forecast.*` / `receive.*` / `ship.*` / `downstream.*`**
|
||
|
||
| token | 说明 |
|
||
| --- | --- |
|
||
| `{type}.length` / `width` / `height` | 长/宽/高(cm) |
|
||
| `{type}.actualWeight` | 实重(kg) |
|
||
| `{type}.volumetricWeight` | 体积重(kg) |
|
||
| `{type}.chargeableWeight` | 计费重(kg) |
|
||
| `{type}.cubic` | 体积 |
|
||
| `{type}.sumLwh` | 长+宽+高 |
|
||
|
||
`{type}` ∈ `forecast`(预报,**PreCommit 默认**)、`receive`(收货)、`ship`(发货)、`downstream`(下游)。
|
||
|
||
**C. 包裹/单件聚合 `piece.*`(预报常用)**
|
||
|
||
| token | 说明 |
|
||
| --- | --- |
|
||
| `piece.maxLength` / `piece.maxWidth` / `piece.maxHeight` | 单件最大长宽高 |
|
||
| `piece.sumLwh` | 单件长+宽+高(对当前包裹) |
|
||
| `piece.maxChargeableWeight` | 单件最大计费重 |
|
||
|
||
**D. 收件人 `receiver.*` → `t_order_recipient` / 下单入参**
|
||
|
||
`recipientsName`, `companyName`, `phone`, `telePhone`, `email`, `countryCode`, `province`, `city`, `zipCode`, `streetNumber`, `streetAddress1~3`, `taxType`, `taxNo`, `certificateType`, `certificateNo`, `eori`, `vat`
|
||
|
||
**E. 发件人 `sender.*` → `t_order_sender` / 下单入参**
|
||
|
||
`senderName`, `company`, `phone`, `telePhone`, `email`, `countryCode`, `province`, `city`, `zipCode`, `streetAddress1~3`, `houseNumber`
|
||
|
||
**F. 客户 `customer.*` → `t_customer`**
|
||
|
||
| token | 说明 |
|
||
| --- | --- |
|
||
| `customer.id` | 客户 ID |
|
||
| `customer.code` | 客户编码 |
|
||
| `customer.name` | 客户名称(**本例「客户包含」**) |
|
||
| `customer.group` | 客户组 |
|
||
| `customer.phone` / `customer.email` | 联系方式 |
|
||
| `customer.salesman` / `customer.customerService` | 销售/客服 |
|
||
|
||
**G. 销售产品 `product.*` → `t_sales_product`(按 `productCode` 关联)**
|
||
|
||
`product.code`, `product.name`, `product.group`, `product.minTimeliness`, `product.maxTimeliness`
|
||
|
||
**H. 申报内件聚合 `items.*`**
|
||
|
||
| token | 说明 |
|
||
| --- | --- |
|
||
| `items.count` | 内件行数 |
|
||
| `items.totalQuantity` | 数量合计 |
|
||
| `items.totalWeight` / `items.totalAmount` | 重量/金额合计 |
|
||
| `items.maxUnitWeight` | 最大单重 |
|
||
| `items.anyCnName` / `items.anyEnName` | 任一品名包含(需配合 `contains`) |
|
||
| `items.anySku` / `items.anyHsCode` | SKU/税号 |
|
||
| `items.anyIsMagnetic` | 是否存在带磁内件 |
|
||
|
||
**I. 费用快照(二期,可选)**
|
||
|
||
`fee.receivable.total` / `fee.payable.total` — 当前阶段应收/应付合计。
|
||
|
||
###### 7)提示语占位符
|
||
|
||
`PromptMessage` 支持 `IssuePlaceholderRenderer` 同款占位符,另增尺寸类:
|
||
|
||
| 占位符 | 说明 |
|
||
| --- | --- |
|
||
| `{serviceNo}` / `{运单号}` | 运单号(PreCommit 未生成时可空) |
|
||
| `{clientNo}` / `{客户单号}` | 客户单号 |
|
||
| `{productCode}` / `{产品代码}` | 销售产品 |
|
||
| `{receiverCountry}` / `{收件国家}` | 国家码 |
|
||
| `{pieceMaxLength}` 等 | 触发时计算的聚合值 |
|
||
|
||
本例落地文案:**`超尺寸,禁止下单`** 或 **`{clientNo}超尺寸,禁止下单`**。
|
||
|
||
###### 8)配置页 `/channel/autoTrigger` UI 设计
|
||
|
||
**列表页(保持 TablePro/Handsontable)**,列增加:`规则名称`、`优先级`、条件摘要(人类可读)、启用状态。
|
||
|
||
**新增/编辑弹窗**:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 规则名称 * [ 英国阿里超尺寸拦截 ] │
|
||
│ 状态 (•) 启用 ( ) 禁用 │
|
||
│ 触发时机 * [ 预报 ▼ ] (字典 auto_trigger_timing)│
|
||
│ 操作 * [ 拦截 ▼ ] (字典 auto_trigger_operation)│
|
||
│ 优先级 [ 100 ] 数字越小越先执行 │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 触发条件 * [+ 添加条件组] │
|
||
│ ┌ 满足 [全部 AND ▼] ─────────────────────────────────┐ │
|
||
│ │ ┌ 满足 [任一 OR ▼] ───────────────────────────────┐ │ │
|
||
│ │ │ [单件最大长度 ▼] [大于 ▼] [ 60 ] [删除] │ │ │
|
||
│ │ │ [单件最大宽度 ▼] [大于 ▼] [ 46 ] [删除] │ │ │
|
||
│ │ │ [单件长+宽+高 ▼] [大于 ▼] [ 150 ] [删除] │ │ │
|
||
│ │ └ [+ 添加条件] │ │ │
|
||
│ │ [收件国家 ▼] [在列表中 ▼] [ GB (+) ] [删除]│ │
|
||
│ │ [客户名称 ▼] [包含 ▼] [ 阿里国际站/深圳一达通 ] │ │
|
||
│ │ [销售产品 ▼] [不在列表 ▼] [ EX_ASP_HKDHL_YD ] │ │
|
||
│ └ [+ 添加条件组] │ │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 提示语 * [ 超尺寸,禁止下单 ] │
|
||
│ 通知人 [ 用户多选,仅警告/问题件 ] │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**交互要点**:
|
||
|
||
- 字段下拉分组:订单 / 件重尺 / 收件人 / 发件人 / 客户 / 产品 / 内件聚合。
|
||
- 运算符随字段类型变化;`in`/`notIn` 用多选或标签输入。
|
||
- 底部 **「预览 JSON」** 只读(给研发调试);运营默认不展开。
|
||
- 保存时前端 `condition` 序列化为 §4 JSON,`TriggerTiming`/`Operation` 存 **dictCode**(`forecast`/`intercept`)。
|
||
|
||
###### 9)表结构与接口变更(建议)
|
||
|
||
**`t_auto_trigger` 增字段(迁移脚本)**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `RuleName` | VARCHAR(128) | 规则名称 |
|
||
| `Priority` | INT | 优先级,默认 100 |
|
||
| `ConditionVersion` | TINYINT | 条件版本,默认 2 |
|
||
|
||
**新增接口(可选)**:
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/channel/auto-trigger/meta` | 返回字段目录、运算符、时机/操作字典(供构建器) |
|
||
| POST | `/channel/auto-trigger/test` | 传入样例订单 JSON + 规则 ID,返回是否命中(运营调试) |
|
||
|
||
CRUD 入参/出参增加 `ruleName`、`priority`;`triggerCondition` 存 JSON。
|
||
|
||
###### 10)后端评估器设计
|
||
|
||
**新组件**:
|
||
|
||
| 类 | 职责 |
|
||
| --- | --- |
|
||
| `OrderRuleEvaluationContext` | 聚合 `OrderParcelCreateInput`(PreCommit)或 `Order`+子表(PostCommit) |
|
||
| `OrderRuleFieldResolver` | token → 强类型值 |
|
||
| `OrderRuleConditionEvaluator` | 解析 JSON AST,递归求值 |
|
||
| `OrderAutoTriggerService` | 加载启用规则 → 排序 → PreCommit/PostCommit 分流 |
|
||
|
||
**PreCommit 调用点**(`OrderService.ParcelBilling.CreateParcelCoreAsync`):
|
||
|
||
```
|
||
ValidateParcelCreateInputAsync
|
||
→ 销售产品/黑名单等硬编码校验
|
||
→ OrderAutoTriggerService.EvaluatePreCommitAsync(input, timing=forecast) // 新增
|
||
→ InsertAsync(order) ...
|
||
→ ... 预报成功 ...
|
||
→ OrderAutoTriggerService.EvaluatePostCommitAsync(order, timing=forecast) // 替代原 FireAsync 或内部转调
|
||
```
|
||
|
||
**拦截短路**:PreCommit 命中 `intercept` → `throw new BusinessException(promptMessage)`;批量下单逐行失败,错误写入任务明细。
|
||
|
||
**PostCommit**:保留警告/问题件/入库出库拦截标记;失败 **仅记日志**,不影响主流程(与现网一致)。
|
||
|
||
###### 11)与硬编码校验的关系
|
||
|
||
| 机制 | 定位 |
|
||
| --- | --- |
|
||
| 代码内校验(产品存在、黑名单、分区匹配等) | **系统不变量**,不可由运营关闭 |
|
||
| 自动触发 V2 | **可配置业务规则**,运营可启停;优先级低于安全类硬编码 |
|
||
|
||
黑名单(KPI 分区)与自动触发 **并存**;均命中时 **先执行的** 抛出错误(PreCommit 顺序:硬编码 → 自动触发按 `Priority`)。
|
||
|
||
###### 12)实施分期
|
||
|
||
| 阶段 | 范围 |
|
||
| --- | --- |
|
||
| **P0** | 表字段 + 条件 JSON 存储 + `OrderRuleConditionEvaluator` + 预报 PreCommit 拦截 + 字段目录 API |
|
||
| **P1** | 配置页可视化构建器 + 字典化时机/操作 + 本例超尺寸规则可配置 |
|
||
| **P2** | 入库/出库字段(`receive.*`/`ship.*`)+ 测试接口 + 条件人类可读摘要 |
|
||
| **P3** | 费用字段、正则、规则命中审计日志 `t_auto_trigger_hit_log` |
|
||
|
||
###### 13)验收标准(本例)
|
||
|
||
1. 在 `/channel/autoTrigger` 配置 §3 示例规则并启用。
|
||
2. 预报一单:GB、客户名含「阿里国际站/深圳一达通」、尺寸 61×40×40、产品非 `EX_ASP_HKDHL_YD` → **接口失败**,文案「超尺寸,禁止下单」,**不产生** `predeclared` 订单。
|
||
3. 将产品改为 `EX_ASP_HKDHL_YD` → **放行**。
|
||
4. 尺寸改为 50×40×40 → **放行**。
|
||
|
||
---
|
||
|
||
##### 5.3.0.7 问题模板(`IssueTemplateService`)
|
||
|
||
| 接口 | 核心逻辑 |
|
||
| --- | --- |
|
||
| `GET .../list` | 分页;筛选 `organizationId`、`issueType`、`keyword`、`status` |
|
||
| `GET .../detail` | 单条详情 |
|
||
| `GET .../options` | 启用模板下拉;组织专属优先于平台通用 |
|
||
| `POST .../create\|update` | 字段:`OrganizationId`、`IssueType`、`Title`、`Content`、`NotifyMethod`、`Status`;**OrganizationId+IssueType 唯一** |
|
||
| `POST .../delete` | 软删 |
|
||
| `POST .../toggleStatus` | 更新 `Status` |
|
||
|
||
> **运行时消费**(**§4.4.1.13** 标记问题件发通知、自动触发关联模板)**二期**;当前为配置 CRUD;**`GET .../options`** 供创建问题件弹窗联动。
|
||
|
||
##### 5.3.0.8 标签模板(`LabelTemplateService`)
|
||
|
||
| 接口 | 核心逻辑 |
|
||
| --- | --- |
|
||
| `POST .../label-template/save` | `id>0` 更新,否则新增;**`Name+TemplateType` 唯一**;`templateJson` 序列化存库;返回带 `id` 的完整对象 |
|
||
|
||
下拉供仓内打印见 **`GET /operation/print-label/template-list`**(操作模块)。
|
||
|
||
##### 5.3.0.9 文档有而需对照的实现差异
|
||
|
||
| 设计描述 | 当前实现 |
|
||
| --- | --- |
|
||
| 发布后同步 Redis + 刷新计费内存 | **未实现**;下单读 DB 发布表或 Live 组装(§4.3.5) |
|
||
| 发布时旧版本 `Status` 置失效 | **未显式**更新历史记录状态;算价按 **`PublishTime <= bindTime` 取最新一条** |
|
||
| 服务渠道 `publish/preview` | 与卖价对称 | **已落地**:`GET /channel/service-channel/publish/preview` |
|
||
| 自动触发执行 | **部分落地** | `AutoTriggerRuntimeService` 已挂钩;**V2 规则引擎见 §5.3.0.6.1** |
|
||
|
||
#### 5.3.1 销售产品
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 产品列表 | GET | /channel/sales-product/list | 产品列表 |
|
||
| 产品详情 | GET | /channel/sales-product/detail | 产品详情 |
|
||
| 新增产品 | POST | /channel/sales-product/create | 新增产品 |
|
||
| 更新产品 | POST | /channel/sales-product/update | 更新产品主表 |
|
||
| 保存产品配置 | POST | /channel/sales-product/config/save | 同 update(前端配置 Tab 专用,Swagger 隐藏) |
|
||
| 删除产品 | POST | /channel/sales-product/delete | 删除产品 |
|
||
|
||
##### 5.3.1.1 产品列表(GET /channel/sales-product/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20&productGroup=专线&keyword=&isActive=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"productCode": "US-AIR-P",
|
||
"productName": "美国空运专线(普货)",
|
||
"productGroup": "专线",
|
||
"minTimeliness": 3,
|
||
"maxTimeliness": 7,
|
||
"customerWaybill": "系统面单",
|
||
"blacklistAreaId": 1,
|
||
"remoteAreaId": 2,
|
||
"priceAreaId": 3,
|
||
"weightRuleId": 1,
|
||
"weightRoundingId": 1,
|
||
"productDescription": "美国空运普货专线",
|
||
"isActive": 1,
|
||
"scheme": "costLowest",
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
],
|
||
"total": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 接收分页与筛选参数后构建查询条件(productGroup、keyword、isActive)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 按分页参数查询销售产品列表,返回 `items` 与 `total`。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- 见 **§5.3.0.2** `GET .../list`。
|
||
|
||
##### 5.3.1.2 产品详情(GET /channel/sales-product/detail)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/detail**(GET,Query 参数)
|
||
|
||
```
|
||
?id=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"productCode": "US-AIR-P",
|
||
"productName": "美国空运专线(普货)",
|
||
"productGroup": "专线",
|
||
"minTimeliness": 3,
|
||
"maxTimeliness": 7,
|
||
"customerWaybill": "系统面单",
|
||
"blacklistAreaId": 1,
|
||
"remoteAreaId": 2,
|
||
"priceAreaId": 3,
|
||
"weightRuleId": 1,
|
||
"weightRoundingId": 1,
|
||
"productDescription": "美国空运普货专线",
|
||
"preStorageScript": "",
|
||
"isActive": 1,
|
||
"scheme": "costLowest",
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"createBy": "admin",
|
||
"updateTime": "2024-01-01 12:00:00",
|
||
"updateBy": "admin"
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `id` 查询产品主数据及扩展配置,组装详情对象返回。
|
||
###### 校验与异常
|
||
- 若产品不存在或不可访问,返回业务错误码。
|
||
|
||
##### 5.3.1.3 新增产品(POST /channel/sales-product/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"productCode": "US-AIR-P",
|
||
"productName": "美国空运专线(普货)",
|
||
"productGroup": "专线",
|
||
"minTimeliness": 3,
|
||
"maxTimeliness": 7,
|
||
"customerWaybill": "系统面单",
|
||
"blacklistAreaId": 1,
|
||
"remoteAreaId": 2,
|
||
"priceAreaId": 3,
|
||
"weightRuleId": 1,
|
||
"weightRoundingId": 1,
|
||
"productDescription": "美国空运普货专线",
|
||
"preStorageScript": "",
|
||
"isActive": 1,
|
||
"scheme": "costLowest"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 校验产品编码唯一性与必填字段后新增产品记录。
|
||
###### 校验与异常
|
||
- 初始化产品配置与默认状态,写入审计字段。
|
||
|
||
##### 5.3.1.4 更新产品(POST /channel/sales-product/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"productCode": "US-AIR-P",
|
||
"productName": "美国空运专线(普货)",
|
||
"productGroup": "专线",
|
||
"minTimeliness": 3,
|
||
"maxTimeliness": 7,
|
||
"customerWaybill": "系统面单",
|
||
"blacklistAreaId": 1,
|
||
"remoteAreaId": 2,
|
||
"priceAreaId": 3,
|
||
"weightRuleId": 1,
|
||
"weightRoundingId": 1,
|
||
"productDescription": "美国空运普货专线",
|
||
"preStorageScript": "",
|
||
"isActive": 1,
|
||
"scheme": "costLowest"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `id` 校验产品存在性后更新可变字段(包含产品配置 tab 的保存内容)。
|
||
###### 校验与异常
|
||
- 记录操作人和更新时间,并触发配置缓存刷新。
|
||
|
||
##### 5.3.1.5 删除产品(POST /channel/sales-product/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 校验产品是否可删除(无有效关联配置/任务)后执行逻辑删除。
|
||
###### 校验与异常
|
||
- 更新删除标识及审计字段,避免物理删除带来的历史数据丢失。
|
||
|
||
#### 5.3.2 产品卖价(子表接口)
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 价格项列表 | GET | /channel/sales-product/price/list | 按产品ID获取价格项列表 |
|
||
| 新增价格项 | POST | /channel/sales-product/price/create | 新增价格项(含基础信息+计费因素+价格表) |
|
||
| 更新价格项 | POST | /channel/sales-product/price/update | 更新价格项 |
|
||
| 删除价格项 | POST | /channel/sales-product/price/delete | 删除价格项 |
|
||
| 切换价格项状态 | POST | /channel/sales-product/price/toggleStatus | 启用/禁用价格项 |
|
||
| 批量保存价格项 | POST | /channel/sales-product/price/save | 整树替换卖价;可选 `publish=true` 同步发布 |
|
||
|
||
##### 5.3.2.1 价格项列表(GET /channel/sales-product/price/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/price/list**(GET,Query 参数)
|
||
|
||
```
|
||
?salesProductId=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"salesProductId": 1,
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [
|
||
{
|
||
"id": 1,
|
||
"priceId": 1,
|
||
"serialNumber": 1,
|
||
"factor": "货物类型",
|
||
"factorValues": ["普货", "敏感货"]
|
||
}
|
||
],
|
||
"details": [
|
||
{
|
||
"id": 1,
|
||
"priceId": 1,
|
||
"factorValuesJson": {"分区": "欧洲", "货物类型": "普货", "重量": "0-0.5"},
|
||
"calculationFormula": "单价",
|
||
"calculatedValue": 62,
|
||
"customerGroupValues": {"客户组1": 60, "客户组2": 65}
|
||
}
|
||
],
|
||
"version": "20260425143000",
|
||
"publishTime": "2026-04-25 14:30:00",
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `salesProductId` 查询价格项主表,并聚合返回计费因素与价格明细子数据。
|
||
- 结果按 `serialNumber` 升序组织,便于前端按配置顺序渲染。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `salesProductId` 必填且产品存在;不存在时返回业务错误。
|
||
- 仅返回未删除记录,查询异常返回统一错误码。
|
||
|
||
##### 5.3.2.2 新增价格项(POST /channel/sales-product/price/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/price/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"salesProductId": 1,
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [
|
||
{
|
||
"serialNumber": 1,
|
||
"factor": "货物类型",
|
||
"factorValues": ["普货", "敏感货"]
|
||
},
|
||
{
|
||
"serialNumber": 2,
|
||
"factor": "分区",
|
||
"factorValues": ["欧洲", "美国"]
|
||
}
|
||
],
|
||
"details": [
|
||
{
|
||
"factorValuesJson": {"分区": "欧洲", "货物类型": "普货", "重量": "0-0.5"},
|
||
"calculationFormula": "单价",
|
||
"calculatedValue": 62,
|
||
"customerGroupValues": {"客户组1": 60, "客户组2": 65}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 新增价格项主记录,并写入对应 `factors` 与 `details` 子记录。
|
||
- 同步写入审计字段,保持主从数据一致性。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `salesProductId`、`priceName`、`costItem`、`costUnit` 等必填字段。
|
||
- 校验同产品下 `serialNumber` 不冲突;冲突时返回业务错误。
|
||
|
||
##### 5.3.2.3 更新价格项(POST /channel/sales-product/price/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/price/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"salesProductId": 1,
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [],
|
||
"details": []
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `id` 更新价格项主数据,并按最新请求重建/同步因素与明细子数据。
|
||
- 保留版本追溯能力,更新后可参与后续发布流程。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且归属 `salesProductId`。
|
||
- 校验更新后配置合法(计算公式、阶梯区间、客户组取值等),不合法时返回业务错误。
|
||
|
||
##### 5.3.2.4 删除价格项(POST /channel/sales-product/price/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/price/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按价格项 `id` 执行逻辑删除,并处理关联子表数据的可见性。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且允许删除(如未处于不可删状态)。
|
||
- 删除失败返回统一业务错误码。
|
||
|
||
##### 5.3.2.5 切换价格项状态(POST /channel/sales-product/price/toggleStatus)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/price/toggleStatus**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `id` 更新价格项启用状态(`status`),并刷新相关缓存。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `status` 仅允许合法枚举值(启用/禁用)。
|
||
- 校验 `id` 存在,不存在时返回业务错误。
|
||
|
||
#### 5.3.3 排货规则(子表接口)
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 获取排货规则 | GET | /channel/sales-product/allocation/get | 按产品ID获取排货方案及服务商列表 |
|
||
| 保存排货规则 | POST | /channel/sales-product/allocation/save | 保存排货方案及服务商(保存并可选发布) |
|
||
|
||
##### 5.3.3.1 获取排货规则(GET /channel/sales-product/allocation/get)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/allocation/get**(GET,Query 参数)
|
||
|
||
```
|
||
?salesProductId=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"salesProductId": 1,
|
||
"scheme": "costLowest",
|
||
"providers": [
|
||
{
|
||
"id": 1,
|
||
"salesProductId": 1,
|
||
"serialNumber": 1,
|
||
"providerName": "一代物流",
|
||
"serviceChannelId": 10,
|
||
"serviceChannelName": "DHL美国专线",
|
||
"weight": 100,
|
||
"customerIds": [1, 2],
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `salesProductId` 查询排货方案与服务商列表并按顺序返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `salesProductId` 必填且产品存在;不存在时返回业务错误。
|
||
|
||
##### 5.3.3.2 保存排货规则(POST /channel/sales-product/allocation/save)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/allocation/save**(POST)
|
||
|
||
```json
|
||
{
|
||
"salesProductId": 1,
|
||
"publish": false,
|
||
"scheme": "costLowest",
|
||
"providers": [
|
||
{
|
||
"id": 0,
|
||
"serialNumber": 1,
|
||
"providerName": "一代物流",
|
||
"serviceChannelId": 10,
|
||
"weight": 100,
|
||
"customerIds": [1, 2]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 保存排货策略与服务商配置,按 `id` 判定新增或更新。
|
||
- 当 `publish=true` 时触发规则发布并生成版本记录。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `salesProductId`、`scheme` 与 `providers` 合法性。
|
||
- 保存失败回滚事务并返回统一错误信息。
|
||
|
||
#### 5.3.4 发布记录(子表接口)
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 价格发布记录列表 | GET | /channel/sales-product/publish/price/list | 按产品ID获取价格发布历史 |
|
||
| 规则发布记录列表 | GET | /channel/sales-product/publish/rules/list | 按产品ID获取规则发布历史 |
|
||
| 发布预览 | GET | /channel/sales-product/publish/preview | 预览即将发布的快照 JSON(不落库) |
|
||
| 发布 | POST | /channel/sales-product/publish | 发布当前配置(价格或规则) |
|
||
| 回滚到版本 | POST | /channel/sales-product/publish/rollback | 回滚到指定版本 |
|
||
|
||
##### 5.3.4.1 价格发布记录列表(GET /channel/sales-product/publish/price/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/publish/price/list**(GET,Query 参数)
|
||
|
||
```
|
||
?salesProductId=1&page=1&pageSize=20
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"salesProductId": 1,
|
||
"version": "20231227112339",
|
||
"operatorType": "price",
|
||
"status": "生效",
|
||
"publishUser": "admin",
|
||
"publishTime": "2023-12-27 11:23:39",
|
||
"description": "价格调整",
|
||
"content": {},
|
||
"createTime": "2023-12-27 11:23:39"
|
||
}
|
||
],
|
||
"total": 5
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按产品ID分页查询价格类发布历史,返回版本与发布人信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `salesProductId` 与分页参数合法性,异常时返回统一错误码。
|
||
|
||
##### 5.3.4.2 规则发布记录列表(GET /channel/sales-product/publish/rules/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/publish/rules/list**(GET,Query 参数)
|
||
|
||
```
|
||
?salesProductId=1&page=1&pageSize=20
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 2,
|
||
"salesProductId": 1,
|
||
"version": "20231228103000",
|
||
"operatorType": "rules",
|
||
"status": "生效",
|
||
"publishUser": "admin",
|
||
"publishTime": "2023-12-28 10:30:00",
|
||
"description": "规则调整",
|
||
"content": {},
|
||
"createTime": "2023-12-28 10:30:00"
|
||
}
|
||
],
|
||
"total": 3
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按产品ID分页查询规则类发布历史,`operatorType` 固定为 `rules`。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `salesProductId` 与分页参数合法性,异常时返回统一错误码。
|
||
|
||
##### 5.3.4.3 发布(POST /channel/sales-product/publish)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/publish**(POST)
|
||
|
||
```json
|
||
{
|
||
"salesProductId": 1,
|
||
"operatorType": "price",
|
||
"description": "价格调整"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 将当前配置发布为新版本并写入发布记录。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `salesProductId`、`operatorType` 必填且可发布。
|
||
|
||
##### 5.3.4.4 回滚到版本(POST /channel/sales-product/publish/rollback)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/sales-product/publish/rollback**(POST)
|
||
|
||
```json
|
||
{
|
||
"salesProductId": 1,
|
||
"recordId": 1,
|
||
"operatorType": "price"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 将配置回滚到指定发布版本并生成回滚记录。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `recordId` 版本存在且与产品、类型一致,不一致时返回业务错误。
|
||
|
||
#### 5.3.5 服务渠道
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 渠道列表 | GET | /channel/service-channel/list | 渠道列表(含渠道组层级) |
|
||
| 渠道详情 | GET | /channel/service-channel/detail | 渠道详情 |
|
||
| 新增渠道 | POST | /channel/service-channel/create | 新增渠道 |
|
||
| 更新渠道 | POST | /channel/service-channel/update | 更新渠道主表 |
|
||
| 保存渠道配置 | POST | /channel/service-channel/config/save | 同 update(Swagger 隐藏) |
|
||
| 删除渠道 | POST | /channel/service-channel/delete | 删除渠道 |
|
||
| 服务商分组列表 | GET | /channel/service-channel/group/list | 获取服务商分组列表(下拉/筛选) |
|
||
| 新增服务商 | POST | /channel/service-channel/group/create | 新增服务商分组(写入 `t_service_channel_group`,保存应用ID与API服务商ID) |
|
||
| 编辑服务商 | POST | /channel/service-channel/group/update | 编辑服务商分组(更新名称/应用ID/API服务商ID) |
|
||
| 删除服务商 | POST | /channel/service-channel/group/delete | 删除服务商分组(若分组下存在渠道需禁止删除) |
|
||
|
||
##### 5.3.5.1 渠道列表(GET /channel/service-channel/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20&groupId=&keyword=&isActive=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"groupId": 1,
|
||
"groupName": "DHL渠道组",
|
||
"channelCode": "DHL-US",
|
||
"channelName": "DHL美国专线",
|
||
"minTime": 3,
|
||
"maxTime": 7,
|
||
"forecastType": "waybill",
|
||
"forecastCode": "",
|
||
"description": "DHL美国专线渠道",
|
||
"isActive": 1,
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
],
|
||
"total": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按分页与筛选条件查询渠道列表,返回渠道及所属分组信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验分页参数合法性,异常时返回统一错误码。
|
||
|
||
##### 5.3.5.2 渠道详情(GET /channel/service-channel/detail)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/detail**(GET,Query 参数)
|
||
|
||
```
|
||
?id=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"groupId": 1,
|
||
"groupName": "DHL渠道组",
|
||
"channelCode": "DHL-US",
|
||
"channelName": "DHL美国专线",
|
||
"minTime": 3,
|
||
"maxTime": 7,
|
||
"forecastType": "waybill",
|
||
"forecastCode": "",
|
||
"description": "DHL美国专线渠道",
|
||
"preScript": "",
|
||
"isActive": 1,
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"createBy": "admin",
|
||
"updateTime": "2024-01-01 12:00:00",
|
||
"updateBy": "admin"
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据渠道 `id` 查询渠道详情与配置信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 必填且存在,不存在时返回业务错误。
|
||
|
||
##### 5.3.5.3 新增渠道(POST /channel/service-channel/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"groupId": 1,
|
||
"channelCode": "DHL-US",
|
||
"channelName": "DHL美国专线",
|
||
"minTime": 3,
|
||
"maxTime": 7,
|
||
"forecastType": "waybill",
|
||
"forecastCode": "",
|
||
"description": "DHL美国专线渠道",
|
||
"preScript": "",
|
||
"isActive": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 新增渠道基础配置并写入审计字段。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `groupId`、`channelCode`、`channelName` 等必填字段,编码冲突时返回业务错误。
|
||
|
||
##### 5.3.5.4 服务商分组列表(GET /channel/service-channel/group/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/group/list**(GET,Query 参数)
|
||
|
||
```
|
||
?keyword=&appId=&apiServiceProviderId=
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"name": "壹号",
|
||
"appId": 8,
|
||
"apiServiceProviderId": 41,
|
||
"createTime": "2026-05-04 14:15:45",
|
||
"updateTime": "1900-01-01 00:00:00"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 查询 `t_service_channel_group` 未删除分组,可按 `keyword`、`appId`、`apiServiceProviderId` 过滤,按 `id` 倒序返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 过滤参数格式非法时返回业务错误;无数据时返回空数组。
|
||
|
||
##### 5.3.5.5 新增服务商(POST /channel/service-channel/group/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/group/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"name": "DHL服务商",
|
||
"appId": 1,
|
||
"apiServiceProviderId": 12
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 在 `t_service_channel_group` 新增服务商分组并写入应用ID与API服务商ID。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `name`、`appId`、`apiServiceProviderId` 必填且有效。
|
||
|
||
##### 5.3.5.6 编辑服务商(POST /channel/service-channel/group/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/group/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"name": "DHL服务商(更新)",
|
||
"appId": 1,
|
||
"apiServiceProviderId": 12
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据分组 `id` 更新服务商名称、应用ID与API服务商ID。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且可编辑,不存在时返回业务错误。
|
||
|
||
##### 5.3.5.7 删除服务商(POST /channel/service-channel/group/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/group/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据分组 `id` 执行逻辑删除。
|
||
|
||
###### 校验与异常
|
||
|
||
- 若分组下存在渠道则禁止删除并返回业务错误。
|
||
|
||
##### 5.3.5.8 更新渠道(POST /channel/service-channel/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"groupId": 1,
|
||
"channelCode": "DHL-US",
|
||
"channelName": "DHL美国专线",
|
||
"minTime": 3,
|
||
"maxTime": 7,
|
||
"forecastType": "waybill",
|
||
"forecastCode": "",
|
||
"description": "DHL美国专线渠道",
|
||
"preScript": "",
|
||
"isActive": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `id` 更新渠道基础信息与渠道配置 tab 数据。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在及字段合法性,更新失败返回业务错误。
|
||
|
||
##### 5.3.5.9 删除渠道(POST /channel/service-channel/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 校验渠道是否可删除后执行逻辑删除并保留历史审计信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 存在关联配置或不可删除状态时返回业务错误。
|
||
#### 5.3.6 渠道买价(子表接口)
|
||
|
||
> **与 §5.3.2 产品卖价对称**:主从表结构、弹窗交互(基础信息 + 计费因素 + 价格表)、CRUD、`price/save` 整树替换与发布快照机制一致;差异为关联 **`serviceChannelId`**(非 `salesProductId`),运行时应付算价读取渠道买价快照(§4.3.4.2)。
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 买价项列表 | GET | /channel/service-channel/price/list | 按渠道ID获取买价项列表 |
|
||
| 新增买价项 | POST | /channel/service-channel/price/create | 新增买价项(含基础信息+计费因素+价格表) |
|
||
| 更新买价项 | POST | /channel/service-channel/price/update | 更新买价项 |
|
||
| 删除买价项 | POST | /channel/service-channel/price/delete | 删除买价项 |
|
||
| 切换买价项状态 | POST | /channel/service-channel/price/toggleStatus | 启用/禁用买价项 |
|
||
| 批量保存买价项 | POST | /channel/service-channel/price/save | 整树替换买价;可选 `publish=true` 同步发布 |
|
||
|
||
##### 5.3.6.1 买价项列表(GET /channel/service-channel/price/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/price/list**(GET,Query 参数)
|
||
|
||
```
|
||
?serviceChannelId=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"serviceChannelId": 1,
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [
|
||
{
|
||
"id": 1,
|
||
"priceId": 1,
|
||
"serialNumber": 1,
|
||
"factor": "货物类型",
|
||
"factorValues": ["普货", "敏感货"]
|
||
},
|
||
{
|
||
"id": 2,
|
||
"priceId": 1,
|
||
"serialNumber": 2,
|
||
"factor": "分区",
|
||
"factorValues": ["欧洲", "美国"]
|
||
}
|
||
],
|
||
"details": [
|
||
{
|
||
"id": 1,
|
||
"priceId": 1,
|
||
"factorValuesJson": {"分区": "欧洲", "货物类型": "普货", "重量": "0-0.5"},
|
||
"calculationFormula": "单价",
|
||
"calculatedValue": 45,
|
||
"customerGroupValues": {"客户组1": 43, "客户组2": 47}
|
||
}
|
||
],
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `serviceChannelId` 查询买价项主表,并**一次聚合**返回 `factors`、`details` 子数据(避免 N+1)。
|
||
- 结果按 `serialNumber` 升序组织,便于前端按配置顺序渲染。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `serviceChannelId` 必填且渠道存在;不存在时返回业务错误。
|
||
- 仅返回未删除记录,查询异常返回统一错误码。
|
||
|
||
##### 5.3.6.2 新增买价项(POST /channel/service-channel/price/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/price/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"serviceChannelId": 1,
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [
|
||
{
|
||
"serialNumber": 1,
|
||
"factor": "货物类型",
|
||
"factorValues": ["普货", "敏感货"]
|
||
}
|
||
],
|
||
"details": [
|
||
{
|
||
"factorValuesJson": {"分区": "欧洲", "货物类型": "普货", "重量": "0-0.5"},
|
||
"calculationFormula": "单价",
|
||
"calculatedValue": 45,
|
||
"customerGroupValues": {"客户组1": 43, "客户组2": 47}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 新增买价主记录,并写入对应 `factors` 与 `details` 子记录(与 §5.3.2.2 卖价新增一致)。
|
||
- 同步写入审计字段,保持主从数据一致性。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `serviceChannelId`、`priceName`、`costItem`、`costUnit` 等必填字段。
|
||
- 校验同渠道下 `serialNumber` 不冲突;冲突时返回业务错误。
|
||
|
||
##### 5.3.6.3 更新买价项(POST /channel/service-channel/price/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/price/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"serviceChannelId": 1,
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [],
|
||
"details": []
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `id` 更新买价主数据,并按最新请求**重建/同步**因素与明细子数据。
|
||
- 保留版本追溯能力,更新后可参与后续发布流程(§5.3.7)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且归属 `serviceChannelId`。
|
||
- 校验更新后配置合法(计算公式、阶梯区间、客户组取值等),不合法时返回业务错误。
|
||
|
||
##### 5.3.6.4 删除买价项(POST /channel/service-channel/price/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/price/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按买价项 `id` 执行逻辑删除,并处理关联子表数据的可见性。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且允许删除。
|
||
- 删除失败返回统一业务错误码。
|
||
|
||
##### 5.3.6.5 切换买价项状态(POST /channel/service-channel/price/toggleStatus)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/price/toggleStatus**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `id` 更新买价项启用状态(`status`)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `status` 仅允许合法枚举值(0/1)。
|
||
- 校验 `id` 存在,不存在时返回业务错误。
|
||
|
||
##### 5.3.6.6 批量保存买价项(POST /channel/service-channel/price/save)
|
||
|
||
> 与 **§5.3.2** 卖价 `price/save` 一致:前端也可逐条 `create/update`;本接口用于**整树替换**当前渠道下全部买价项(含 factors、details),可选连带发布。
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/price/save**(POST)
|
||
|
||
```json
|
||
{
|
||
"serviceChannelId": 1,
|
||
"publish": false,
|
||
"prices": [
|
||
{
|
||
"serialNumber": 1,
|
||
"status": 1,
|
||
"priceName": "运费",
|
||
"costItem": "freight",
|
||
"costUnit": "kg",
|
||
"currency": "RMB",
|
||
"chargingCondition": "",
|
||
"factors": [],
|
||
"details": []
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. 校验 `serviceChannelId` 存在。
|
||
2. 使用临时负数 `Id` 关联子表,调用 **`ReplacePricesAsync`** 整树替换当前渠道买价。
|
||
3. 若 `publish=true`,写入 **`t_service_channel_publish_record`**(`operatorType=price`),快照内容为瘦身后的全部买价项(见 **§5.3.0.3**)。
|
||
|
||
###### 校验与异常
|
||
|
||
- `prices` 可为空数组(表示清空买价);单条校验同 create/update。
|
||
- 发布失败时买价替换已成功则不回滚(与卖价策略一致),返回明确错误信息。
|
||
|
||
#### 5.3.7 服务渠道发布记录(子表接口)
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 价格发布记录列表 | GET | /channel/service-channel/publish/price/list | 按渠道ID获取买价发布历史 |
|
||
| 规则发布记录列表 | GET | /channel/service-channel/publish/rules/list | 按渠道ID获取规则发布历史 |
|
||
| 发布预览 | GET | /channel/service-channel/publish/preview | 预览即将发布的快照 JSON(不落库) |
|
||
| 发布 | POST | /channel/service-channel/publish | 发布当前配置(买价或规则) |
|
||
| 回滚到版本 | POST | /channel/service-channel/publish/rollback | 回滚到指定版本 |
|
||
|
||
##### 5.3.7.1 价格发布记录列表(GET /channel/service-channel/publish/price/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/publish/price/list**(GET,Query 参数)
|
||
|
||
```
|
||
?serviceChannelId=1&page=1&pageSize=20
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"serviceChannelId": 1,
|
||
"version": "20231227112339",
|
||
"operatorType": "price",
|
||
"status": "生效",
|
||
"publishUser": "admin",
|
||
"publishTime": "2023-12-27 11:23:39",
|
||
"description": "买价调整",
|
||
"content": {},
|
||
"createTime": "2023-12-27 11:23:39"
|
||
}
|
||
],
|
||
"total": 5
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按渠道ID分页查询买价发布记录并返回版本信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `serviceChannelId` 与分页参数合法性。
|
||
|
||
##### 5.3.7.2 规则发布记录列表(GET /channel/service-channel/publish/rules/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/publish/rules/list**(GET,Query 参数)
|
||
|
||
```
|
||
?serviceChannelId=1&page=1&pageSize=20
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 2,
|
||
"serviceChannelId": 1,
|
||
"version": "20231228103000",
|
||
"operatorType": "rules",
|
||
"status": "生效",
|
||
"publishUser": "admin",
|
||
"publishTime": "2023-12-28 10:30:00",
|
||
"description": "规则调整",
|
||
"content": {},
|
||
"createTime": "2023-12-28 10:30:00"
|
||
}
|
||
],
|
||
"total": 3
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按渠道ID分页查询规则发布记录,`operatorType` 固定为 `rules`。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `serviceChannelId` 与分页参数合法性。
|
||
|
||
##### 5.3.7.3 发布(POST /channel/service-channel/publish)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/publish**(POST)
|
||
|
||
```json
|
||
{
|
||
"serviceChannelId": 1,
|
||
"operatorType": "price",
|
||
"description": "买价调整"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 发布当前渠道配置并生成新的发布版本记录。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `serviceChannelId`、`operatorType` 必填且可发布。
|
||
|
||
##### 5.3.7.4 回滚到版本(POST /channel/service-channel/publish/rollback)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/service-channel/publish/rollback**(POST)
|
||
|
||
```json
|
||
{
|
||
"serviceChannelId": 1,
|
||
"recordId": 1,
|
||
"operatorType": "price"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 回滚到指定版本并生成回滚操作记录。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `recordId` 与渠道、类型匹配关系,异常时返回业务错误。
|
||
#### 5.3.8 黑名单区 / 偏远分区 / 价格分区(共用 KpiArea)
|
||
|
||
> 共用 `/channel/kpi-area/*` 接口,仅通过请求/响应中的 `areaType`(对应表字段 `AreaType`)区分分区类型。
|
||
|
||
| 类型 | areaType |
|
||
| --- | --- |
|
||
| 黑名单区 | blacklist |
|
||
| 偏远分区 | remote |
|
||
| 价格分区 | price |
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 创建分区 | POST | /channel/kpi-area/create | 创建分区 |
|
||
| 修改分区 | POST | /channel/kpi-area/update | 修改分区 |
|
||
| 删除分区 | POST | /channel/kpi-area/delete | 删除分区 |
|
||
| 分区列表 | POST | /channel/kpi-area/list | 分区列表 |
|
||
| 新增分区明细 | POST | /channel/kpi-area/info/add | 新增分区明细 |
|
||
| 修改分区明细 | POST | /channel/kpi-area/info/update | 修改分区明细 |
|
||
| 删除分区明细 | POST | /channel/kpi-area/info/delete | 删除分区明细 |
|
||
| 分区明细列表 | POST | /channel/kpi-area/info/list | 分区明细列表 |
|
||
| 下载明细导入模板 | GET | /channel/kpi-area/info/import-template | Excel 导入模板 |
|
||
| 导入分区明细 | POST | /channel/kpi-area/info/import | multipart 批量导入明细 |
|
||
|
||
##### 5.3.8.1 创建分区(POST /channel/kpi-area/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"areaType": "blacklist",
|
||
"name": "黑名单区1",
|
||
"systemName": "系统黑名单"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `areaType` 创建分区主记录。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `areaType`、`name` 必填且合法。
|
||
|
||
##### 5.3.8.2 修改分区(POST /channel/kpi-area/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"areaType": "blacklist",
|
||
"name": "黑名单区1",
|
||
"systemName": "系统黑名单"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `id` 更新分区名称与系统名称。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且与 `areaType` 匹配。
|
||
|
||
##### 5.3.8.3 删除分区(POST /channel/kpi-area/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `id` 执行分区逻辑删除。
|
||
|
||
###### 校验与异常
|
||
|
||
- 分区存在明细时按业务规则拦截或级联处理。
|
||
|
||
##### 5.3.8.4 分区列表(POST /channel/kpi-area/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/list**(POST)
|
||
|
||
```json
|
||
{
|
||
"areaType": "blacklist",
|
||
"page": 1,
|
||
"pageSize": 20
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"areaType": "blacklist",
|
||
"name": "黑名单区1",
|
||
"systemName": "系统黑名单",
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
],
|
||
"total": 5
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `areaType` 分页查询分区列表。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验分页参数与 `areaType` 合法性。
|
||
|
||
##### 5.3.8.5 新增分区明细(POST /channel/kpi-area/info/add)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/info/add**(POST)
|
||
|
||
```json
|
||
{
|
||
"areaId": 1,
|
||
"countryCode": "US",
|
||
"countryName": "美国",
|
||
"province": "加州",
|
||
"city": "洛杉矶",
|
||
"leftPostcode": "90001",
|
||
"rightPostcode": "90099"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 向指定分区新增明细记录(国家/省市/邮编区间)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `areaId` 存在且区间数据合法。
|
||
|
||
##### 5.3.8.6 修改分区明细(POST /channel/kpi-area/info/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/info/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"areaId": 1,
|
||
"countryCode": "US",
|
||
"countryName": "美国",
|
||
"province": "加州",
|
||
"city": "洛杉矶",
|
||
"leftPostcode": "90001",
|
||
"rightPostcode": "90099"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据明细 `id` 更新分区明细字段。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在,更新内容合法。
|
||
|
||
##### 5.3.8.7 删除分区明细(POST /channel/kpi-area/info/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/info/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据明细 `id` 执行逻辑删除。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在,不存在时返回业务错误。
|
||
|
||
##### 5.3.8.8 分区明细列表(POST /channel/kpi-area/info/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/kpi-area/info/list**(POST)
|
||
|
||
```json
|
||
{
|
||
"areaId": 1,
|
||
"page": 1,
|
||
"pageSize": 20
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"areaId": 1,
|
||
"countryCode": "US",
|
||
"countryName": "美国",
|
||
"province": "加州",
|
||
"city": "洛杉矶",
|
||
"leftPostcode": "90001",
|
||
"rightPostcode": "90099",
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
],
|
||
"total": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `areaId` 分页查询分区明细列表。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `areaId` 与分页参数合法性。
|
||
#### 5.3.9 计重规则 / 计重进位
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 规则列表 | POST | /channel/weight-rule/list | 规则列表 |
|
||
| 创建规则 | POST | /channel/weight-rule/create | 创建规则 |
|
||
| 更新规则 | POST | /channel/weight-rule/update | 更新规则 |
|
||
| 删除规则 | POST | /channel/weight-rule/delete | 删除规则 |
|
||
| 规则明细列表 | POST | /channel/weight-rule/info/list | 规则明细列表 |
|
||
| 新增明细 | POST | /channel/weight-rule/info/add | 新增明细 |
|
||
| 更新明细 | POST | /channel/weight-rule/info/update | 更新明细 |
|
||
| 删除明细 | POST | /channel/weight-rule/info/delete | 删除明细 |
|
||
| 进位列表 | POST | /channel/weight-rounding/list | 进位列表 |
|
||
| 创建进位 | POST | /channel/weight-rounding/create | 创建进位 |
|
||
| 更新进位 | POST | /channel/weight-rounding/update | 更新进位 |
|
||
| 删除进位 | POST | /channel/weight-rounding/delete | 删除进位 |
|
||
| 进位明细列表 | POST | /channel/weight-rounding/info/list | 进位明细列表 |
|
||
| 新增进位明细 | POST | /channel/weight-rounding/info/add | 新增进位明细 |
|
||
| 更新进位明细 | POST | /channel/weight-rounding/info/update | 更新进位明细 |
|
||
| 删除进位明细 | POST | /channel/weight-rounding/info/delete | 删除进位明细 |
|
||
|
||
##### 5.3.9.1 规则列表(POST /channel/weight-rule/list)
|
||
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/list**(POST)
|
||
|
||
```json
|
||
{
|
||
"page": 1,
|
||
"pageSize": 20
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": { "items": [{ "id": 1, "name": "标准计重规则" }], "total": 5 }
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 分页查询计重规则主表。
|
||
###### 校验与异常
|
||
- 校验分页参数合法性。
|
||
|
||
##### 5.3.9.2 创建规则(POST /channel/weight-rule/create)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/create**(POST)
|
||
|
||
```json
|
||
{ "name": "标准计重规则" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 新增计重规则并写入审计信息。
|
||
###### 校验与异常
|
||
- 校验 `name` 必填且不重复。
|
||
|
||
##### 5.3.9.3 更新规则(POST /channel/weight-rule/update)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/update**(POST)
|
||
|
||
```json
|
||
{ "id": 1, "name": "标准计重规则" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 根据 `id` 更新规则信息。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在。
|
||
|
||
##### 5.3.9.4 删除规则(POST /channel/weight-rule/delete)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/delete**(POST)
|
||
|
||
```json
|
||
{ "id": 1 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 对规则执行逻辑删除。
|
||
###### 校验与异常
|
||
- 校验是否存在关联明细。
|
||
|
||
##### 5.3.9.5 规则明细列表(POST /channel/weight-rule/info/list)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/info/list**(POST)
|
||
|
||
```json
|
||
{ "ruleId": 1, "page": 1, "pageSize": 20 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": { "items": [{ "id": 1, "ruleId": 1, "condition": "gw>vw", "billableWeight": "gw", "volumetricFactor": "6000" }], "total": 3 }
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 按 `ruleId` 分页查询规则明细。
|
||
###### 校验与异常
|
||
- 校验 `ruleId` 必填且有效。
|
||
|
||
##### 5.3.9.6 新增明细(POST /channel/weight-rule/info/add)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/info/add**(POST)
|
||
|
||
```json
|
||
{ "ruleId": 1, "condition": "gw>vw", "billableWeight": "vw", "volumetricFactor": "6000" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 新增规则明细。
|
||
###### 校验与异常
|
||
- 校验 `ruleId` 与字段合法性。
|
||
|
||
##### 5.3.9.7 更新明细(POST /channel/weight-rule/info/update)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/info/update**(POST)
|
||
|
||
```json
|
||
{ "id": 1, "ruleId": 1, "condition": "gw>vw", "billableWeight": "vw", "volumetricFactor": "6000" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 根据 `id` 更新规则明细。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在且归属正确。
|
||
|
||
##### 5.3.9.8 删除明细(POST /channel/weight-rule/info/delete)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rule/info/delete**(POST)
|
||
|
||
```json
|
||
{ "id": 1 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 删除规则明细(逻辑删除)。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在。
|
||
|
||
##### 5.3.9.9 进位列表(POST /channel/weight-rounding/list)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/list**(POST)
|
||
|
||
```json
|
||
{ "page": 1, "pageSize": 20 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": { "items": [{ "id": 1, "name": "标准进位" }], "total": 5 }
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 分页查询计重进位主表。
|
||
###### 校验与异常
|
||
- 校验分页参数。
|
||
|
||
##### 5.3.9.10 创建进位(POST /channel/weight-rounding/create)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/create**(POST)
|
||
|
||
```json
|
||
{ "name": "标准进位" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 新增进位规则。
|
||
###### 校验与异常
|
||
- 校验 `name` 必填。
|
||
|
||
##### 5.3.9.11 更新进位(POST /channel/weight-rounding/update)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/update**(POST)
|
||
|
||
```json
|
||
{ "id": 1, "name": "标准进位" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 根据 `id` 更新进位规则。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在。
|
||
|
||
##### 5.3.9.12 删除进位(POST /channel/weight-rounding/delete)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/delete**(POST)
|
||
|
||
```json
|
||
{ "id": 1 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 逻辑删除进位规则。
|
||
###### 校验与异常
|
||
- 校验是否可删除。
|
||
|
||
##### 5.3.9.13 进位明细列表(POST /channel/weight-rounding/info/list)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/info/list**(POST)
|
||
|
||
```json
|
||
{ "roundingId": 1, "page": 1, "pageSize": 20 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": { "items": [{ "id": 1, "roundingId": 1, "startValue": 0, "endValue": 1, "public": "0.5" }], "total": 5 }
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 按 `roundingId` 分页查询进位明细。
|
||
###### 校验与异常
|
||
- 校验 `roundingId` 存在。
|
||
|
||
##### 5.3.9.14 新增进位明细(POST /channel/weight-rounding/info/add)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/info/add**(POST)
|
||
|
||
```json
|
||
{ "roundingId": 1, "startValue": 0, "endValue": 1, "public": "0.5" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 新增进位明细。
|
||
###### 校验与异常
|
||
- 校验区间与 `public` 进位值合法性。
|
||
|
||
##### 5.3.9.15 更新进位明细(POST /channel/weight-rounding/info/update)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/info/update**(POST)
|
||
|
||
```json
|
||
{ "id": 1, "roundingId": 1, "startValue": 0, "endValue": 1, "public": "0.5" }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 根据 `id` 更新进位明细。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在且归属正确。
|
||
|
||
##### 5.3.9.16 删除进位明细(POST /channel/weight-rounding/info/delete)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/weight-rounding/info/delete**(POST)
|
||
|
||
```json
|
||
{ "id": 1 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 删除进位明细(逻辑删除)。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在。
|
||
|
||
#### 5.3.10 自动触发
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 列表 | GET | /channel/auto-trigger/list | 列表 |
|
||
| 新增 | POST | /channel/auto-trigger/create | 新增 |
|
||
| 更新 | POST | /channel/auto-trigger/update | 更新 |
|
||
| 删除 | POST | /channel/auto-trigger/delete | 删除 |
|
||
| 切换启用状态 | POST | /channel/auto-trigger/toggleStatus | 切换启用状态 |
|
||
|
||
##### 5.3.10.1 列表(GET /channel/auto-trigger/list)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/auto-trigger/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20&operation=&triggerTiming=
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": { "items": [{ "id": 1, "operation": "拦截", "triggerTiming": "下单" }], "total": 5 }
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 分页查询自动触发规则列表。
|
||
###### 校验与异常
|
||
- 校验分页与筛选参数合法。
|
||
|
||
##### 5.3.10.2 新增(POST /channel/auto-trigger/create)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/auto-trigger/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"status": 1,
|
||
"operation": "拦截",
|
||
"triggerTiming": "下单",
|
||
"triggerCondition": "weight>30",
|
||
"promptMessage": "重量超过30kg,请确认",
|
||
"notifier": "admin@example.com"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 新增自动触发规则。
|
||
###### 校验与异常
|
||
- 校验关键字段必填。
|
||
|
||
##### 5.3.10.3 更新(POST /channel/auto-trigger/update)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/auto-trigger/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"status": 1,
|
||
"operation": "拦截",
|
||
"triggerTiming": "下单",
|
||
"triggerCondition": "weight>30",
|
||
"promptMessage": "重量超过30kg,请确认",
|
||
"notifier": "admin@example.com"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 根据 `id` 更新自动触发规则。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在且可编辑。
|
||
|
||
##### 5.3.10.4 删除(POST /channel/auto-trigger/delete)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/auto-trigger/delete**(POST)
|
||
|
||
```json
|
||
{ "id": 1 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 逻辑删除自动触发规则。
|
||
###### 校验与异常
|
||
- 校验 `id` 存在且可删除。
|
||
|
||
##### 5.3.10.5 切换启用状态(POST /channel/auto-trigger/toggleStatus)
|
||
###### 1 接口请求示例
|
||
**请求 /channel/auto-trigger/toggleStatus**(POST)
|
||
|
||
```json
|
||
{ "id": 1, "status": 0 }
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{ "code": 0, "msg": "success", "data": null }
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
###### 核心流程
|
||
- 按 `id` 切换规则启用状态。
|
||
###### 校验与异常
|
||
- 校验 `status` 枚举合法性。
|
||
|
||
#### 5.3.11 问题模板
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 列表 | GET | /channel/issue-template/list | 分页列表 |
|
||
| 详情 | GET | /channel/issue-template/detail | 单条详情 |
|
||
| 下拉选项 | GET | /channel/issue-template/options | 启用模板选项 |
|
||
| 新增 | POST | /channel/issue-template/create | 新增 |
|
||
| 更新 | POST | /channel/issue-template/update | 更新 |
|
||
| 删除 | POST | /channel/issue-template/delete | 软删 |
|
||
| 切换启用状态 | POST | /channel/issue-template/toggleStatus | 切换状态 |
|
||
|
||
##### 5.3.11.1 新增(POST /channel/issue-template/create)
|
||
|
||
```json
|
||
{
|
||
"organizationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||
"issueType": "address",
|
||
"title": "收件地址无法派送",
|
||
"content": "运单 {serviceNo} 收件地址不完整,请补充门牌号与邮编。",
|
||
"notifyMethod": "inApp,email",
|
||
"status": 1
|
||
}
|
||
```
|
||
|
||
**校验**:`issueType` 合法;`title`/`content` 非空;`notifyMethod` 至少一种;同组织同类型不重复。
|
||
|
||
#### 5.3.12 标签设计
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 保存标签模板 | POST | /channel/label-template/save | 保存标签模板 |
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /channel/label-template/save**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 0,
|
||
"name": "尾程标签模板1",
|
||
"templateType": "lastMile",
|
||
"templateJson": {}
|
||
}
|
||
```
|
||
|
||
> 参数说明:`id` 模板ID,0为新增;`templateType` 类型:lastMile(尾程)/customer(客户)/inbound(入库)/outbound(出库)/sku;`templateJson` vg-print 导出的模板 JSON。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"name": "尾程标签模板1",
|
||
"templateType": "lastMile",
|
||
"templateJson": {},
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按本节接口定义执行业务处理流程,完成参数解析、数据查询/写入与结果返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、状态流转与数据存在性进行校验;异常场景返回统一错误码与错误信息。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `LabelTemplateService.SaveAsync`:见 **§5.3.0.8**;`id=0` 新增,否则更新;**Name+TemplateType** 唯一。
|
||
|
||
## 6 操作模块
|
||
|
||
### 6.1 核心需求说明
|
||
|
||
- **入库扫描**:扫描运单号完成入库,记录实重、尺寸、销售产品、目的国、仓库、操作人、操作时间;**仅覆盖 `Type=receive` 行并重算计费重**(规则见 §4.4.3.1),再调用 **§4.3.4.1** 重算应收(`receive` 计费重,`ChargingStage=inbound`);**不修改** `forecast` / `ship` / `downstream` 行;支持查看最近入库记录。
|
||
- **出库扫描**:扫描运单号完成出库,**记录实重、尺寸、销售产品、目的国、服务渠道ID**、仓库、操作人、操作时间(**`t_outbound_record` 在入库记录字段基础上另存 `ServiceChannelId`**,见 §6.2.2);**仅覆盖 `Type=ship`(发货)行尺寸与重量并重算计费重**(实测尺重见请求体,见 §6.3.2.1);**出库成功后更新 `t_order.OrderStatus = pendingOnline`(待上网)并写入 `ShipTime`**;再调用 **§4.3.4.1** 重算应收(`ship` 计费重,`ChargingStage=outbound`);**不修改** `forecast` / `receive` / `downstream` 行;支持查看最近出库记录。
|
||
- **标签打印**:按运单号/客户单号/跟踪号/其他单号选择标签模板打印;记录打印结果与操作人。
|
||
|
||
### 6.2 表结构设计
|
||
|
||
#### 6.2.1 入库记录表 `t_inbound_record`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| WaybillNo | VARCHAR | 64 | - | 是 | 运单号 |
|
||
| Weight | DECIMAL | 10,2 | - | 否 | 实重(kg) |
|
||
| Length | DECIMAL | 10,2 | - | 否 | 长(cm) |
|
||
| Width | DECIMAL | 10,2 | - | 否 | 宽(cm) |
|
||
| Height | DECIMAL | 10,2 | - | 否 | 高(cm) |
|
||
| SalesProductId | BIGINT | - | 外键 | 否 | 销售产品ID |
|
||
| DestinationCountry | VARCHAR | 8 | - | 否 | 目的国 |
|
||
| WarehouseId | BIGINT | - | 外键 | 否 | 仓库ID |
|
||
| OperateTime | DATETIME | - | - | 是 | 操作时间 |
|
||
| OperatorId | BIGINT | - | 外键 | 否 | 操作人ID |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 6.2.2 出库记录表 `t_outbound_record`
|
||
|
||
> **与 `t_inbound_record` 对齐**:除业务含义为「出库」外,件重尺与业务维度字段与入库记录表一致;**另存 `ServiceChannelId`**,关联 **`t_service_channel.Id`**,记录本单出库时所选/所绑定的服务渠道,便于对账与列表筛选。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| WaybillNo | VARCHAR | 64 | - | 是 | 运单号 |
|
||
| Weight | DECIMAL | 10,2 | - | 否 | 实重(kg),与本次出库写入 `t_order_weight.Type=ship` 的 `ActualWeight` 一致(发货复量实测) |
|
||
| Length | DECIMAL | 10,2 | - | 否 | 长(cm) |
|
||
| Width | DECIMAL | 10,2 | - | 否 | 宽(cm) |
|
||
| Height | DECIMAL | 10,2 | - | 否 | 高(cm) |
|
||
| SalesProductId | BIGINT | - | 外键 | 否 | 销售产品ID |
|
||
| DestinationCountry | VARCHAR | 8 | - | 否 | 目的国 |
|
||
| ServiceChannelId | BIGINT | - | 外键(**`t_service_channel.Id`**) | 否 | **服务渠道ID**;请求可选传入,未传时建议服务端按订单已绑定服务渠道或排货结果兜底 |
|
||
| WarehouseId | BIGINT | - | 外键 | 否 | 仓库ID |
|
||
| OperateTime | DATETIME | - | - | 是 | 操作时间 |
|
||
| OperatorId | BIGINT | - | 外键 | 否 | 操作人ID |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 6.2.3 标签打印记录表 `t_label_print_record`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| WaybillNo | VARCHAR | 64 | - | 是 | 运单号 |
|
||
| CustomerOrderNo | VARCHAR | 64 | - | 否 | 客户单号 |
|
||
| TrackingNo | VARCHAR | 64 | - | 否 | 跟踪号 |
|
||
| OtherNumber | VARCHAR | 64 | - | 否 | 其他单号 |
|
||
| LabelTemplateId | BIGINT | - | 外键 | 否 | 标签模板ID |
|
||
| PrintSuccess | TINYINT | - | - | 是 | 是否打印成功 |
|
||
| PrintTime | DATETIME | - | - | 是 | 打印时间 |
|
||
| OperatorId | BIGINT | - | 外键 | 否 | 操作人ID |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
### 6.3 核心接口设计
|
||
|
||
#### 6.3.1 入库扫描
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 入库 | POST | /operation/inbound/create | 入库 |
|
||
| 最近入库记录 | GET | /operation/inbound/recent-list | 最近入库记录 |
|
||
|
||
##### 6.3.1.1 入库(POST /operation/inbound/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /operation/inbound/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"waybillNo": "IDE2607221859123456",
|
||
"weight": 2.5,
|
||
"length": 20,
|
||
"width": 15,
|
||
"height": 10,
|
||
"salesProductId": 1,
|
||
"destinationCountry": "US",
|
||
"warehouseId": 1
|
||
}
|
||
```
|
||
|
||
> 参数说明:`waybillNo` 运单号;`weight` 实重(kg);`length/width/height` 长宽高(cm);`salesProductId` 销售产品ID;`destinationCountry` 目的国二字码;`warehouseId` 仓库ID。操作时间、操作人由服务端从当前会话获取。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"waybillNo": "IDE2607221859123456",
|
||
"weight": 2.5,
|
||
"length": 20,
|
||
"width": 15,
|
||
"height": 10,
|
||
"salesProductId": 1,
|
||
"destinationCountry": "US",
|
||
"warehouseId": 1,
|
||
"operateTime": "2024-01-01 12:00:00",
|
||
"operatorId": 1,
|
||
"createTime": "2024-01-01 12:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 解析 `waybillNo` 等参数,定位订单(及绑定销售产品、已落库的 `t_order_weight` 四行)。
|
||
- 写入 **`t_inbound_record`**(实重、尺寸、销售产品、目的国、仓库、操作人、操作时间等)。
|
||
- **仅覆盖 `t_order_weight` 中本单 `Type=receive` 行**:实测件重尺重算计费重(**不修改 forecast/ship/downstream**)。
|
||
- **§4.3.4.1** 重算应收(`receive` 计费重,`ChargingStage=inbound`)。
|
||
- **TryConfirmServiceChannelAndPayableAsync** 尝试排货与应付:
|
||
- 成功 → **`OrderStatus=pendingPayment`**,清空 **`AllocationPendingReason`**,写入 **`ReceiveTime`**。
|
||
- 失败 → **`OrderStatus=pendingAllocation`**,写入 **`AllocationPendingReason`**(仍完成收货与 inbound 应收)。
|
||
- **允许入库的源状态**:`predeclared`、`pendingAllocation`;已入库状态拒绝重复收货。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、订单状态、单号与订单关联、仓库权限等进行校验;件重尺非法、计重规则未命中、进位无匹配等按业务错误返回。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `InboundRecordService.CreateAsync` → `ApplyReceiveBillingByWaybillAsync`:联动 **`receive` 行**计重、inbound 应收、**可选排货**;状态 **`pendingPayment` / `pendingAllocation`**;`ParcelBillingValidators.EnsureInboundOrderStatus`。
|
||
|
||
##### 6.3.1.2 最近入库记录(GET /operation/inbound/recent-list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /operation/inbound/recent-list**(GET,Query 参数)
|
||
|
||
```
|
||
?limit=20&warehouseId=
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"waybillNo": "IDE2607221859123456",
|
||
"weight": 2.5,
|
||
"length": 20,
|
||
"width": 15,
|
||
"height": 10,
|
||
"salesProductId": 1,
|
||
"salesProductName": "美国空运专线(普货)",
|
||
"destinationCountry": "US",
|
||
"warehouseId": 1,
|
||
"warehouseName": "深圳仓",
|
||
"operateTime": "2024-01-01 12:00:00",
|
||
"operatorId": 1,
|
||
"operatorName": "张三",
|
||
"createTime": "2024-01-01 12:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按接口定义执行业务流程,完成参数解析、数据处理与结果返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、状态与数据存在性进行校验,异常场景返回统一错误码与错误信息。
|
||
|
||
#### 6.3.2 出库扫描
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 出库 | POST | /operation/outbound/create | 出库 |
|
||
| 最近出库记录 | GET | /operation/outbound/recent-list | 最近出库记录 |
|
||
|
||
##### 6.3.2.1 出库(POST /operation/outbound/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /operation/outbound/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"waybillNo": "IDE2607221859123456",
|
||
"weight": 2.5,
|
||
"length": 20,
|
||
"width": 15,
|
||
"height": 10,
|
||
"salesProductId": 1,
|
||
"destinationCountry": "US",
|
||
"serviceChannelId": 100,
|
||
"warehouseId": 1
|
||
}
|
||
```
|
||
|
||
> 参数说明:`waybillNo` 运单号;**`weight` 实重(kg)、`length`/`width`/`height` 长宽高(cm) 必填**(发货复量实测,写入 **`Type=ship` 行**);`salesProductId` 销售产品ID;`destinationCountry` 目的国二字码;**`serviceChannelId` 服务渠道ID**(对应 **`t_service_channel.Id`**,可选,未传时服务端按订单绑定/排货兜底并写入 `t_outbound_record`);`warehouseId` 仓库ID(可选,不传则从订单或默认仓库获取)。**`salesProductId`、`destinationCountry` 可选**,未传时建议服务端用订单绑定/收件国兜底。操作时间、操作人由服务端从当前会话获取。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"waybillNo": "IDE2607221859123456",
|
||
"weight": 2.5,
|
||
"length": 20,
|
||
"width": 15,
|
||
"height": 10,
|
||
"salesProductId": 1,
|
||
"destinationCountry": "US",
|
||
"serviceChannelId": 100,
|
||
"warehouseId": 1,
|
||
"operateTime": "2024-01-01 12:00:00",
|
||
"operatorId": 1,
|
||
"createTime": "2024-01-01 12:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. **定位订单**:解析 `waybillNo`,按运单号关联 **`t_order`** 及本单 **`t_order_weight` 四行**(`forecast` / `receive` / `ship` / `downstream`)。
|
||
2. **前置校验**:
|
||
- 订单须存在且 **`OrderStatus = pendingShip`(待发货)**;**`pendingPayment` / `received`** → **「订单未付款,禁止出库」**;其它状态 → **「当前订单状态不允许出库」**。
|
||
- 须已存在 **`Type=ship`(发货)** 行(预报成功时随主单创建)。
|
||
3. **写入出库记录**:落库 **`t_outbound_record`**(实重、尺寸、销售产品、目的国、**`ServiceChannelId`**、仓库、操作人、操作时间;件重尺与业务维度与 **`t_inbound_record`** 对齐,**并含 `ServiceChannelId`**;`Weight/Length/Width/Height` 取值以请求、订单绑定及下文 **`ship` 行最终件重尺** 为准)。
|
||
4. **更新发货件重尺(`t_order_weight.Type = ship`)**:以请求 **`weight` / `length` / `width` / `height`** 写入 **`ship` 行**的 **`Length`、`Width`、`Height`、`ActualWeight`**(`weight` → `ActualWeight`),并重算 **`Cubic = Length × Width × Height / 1_000_000`**(**不读取、不覆盖其它 `Type` 行**)。
|
||
5. **计重**:对 **`ship` 行**按订单绑定 **`WeightRuleId` / `WeightRoundingId`**(及 rules 快照或子表明细)执行 **§4.4.3.1 步骤 7、8 相同算法**,重算 **`VolumetricWeight`、`ChargeableWeight`**(**不修改 `forecast` / `receive` / `downstream` 行**)。
|
||
6. **更新订单主表**:**`OrderStatus = pendingOnline`(待上网)**,**`ShipTime = 操作时间`**,刷新 **`UpdateTime` / `UpdateUserId`**;持久化 **`ship` 行**与主表。
|
||
7. **阶段计费**(待实现):按 **§4.3.4.1** 以 **`ship` 行** `ChargeableWeight` 重算应收(`ChargingStage=outbound`)。
|
||
|
||
> **与入库对称**:入库 **仅**更新 **`receive` 行** → **`OrderStatus=pendingPayment`** + **`ReceiveTime`**;出库 **仅**更新 **`ship` 行** → **`OrderStatus=pendingOnline`** + **`ShipTime`**。四行件重尺各自独立维护。
|
||
|
||
###### 校验与异常
|
||
|
||
- **`waybillNo`** 必填;**`weight` / `length` / `width` / `height` 必填且均 > 0**。
|
||
- 订单须 **`pendingShip`**;**`pendingPayment` / `received`** 禁止出库;**`pendingOnline` 及以后**(已出库)→ **「订单已出库」**。
|
||
- 缺少 **`Type=ship`** 行 → 业务错误。
|
||
- 计重规则未命中、进位无匹配、价格分区未匹配(若触发 §4.3.4 算价)→ 明确错误信息;**事务回滚**,不写入出库记录与状态变更。
|
||
|
||
###### 3 当前实现(oms0414-back)
|
||
|
||
- `OutboundRecordService.CreateAsync` → **`ApplyShipBillingByWaybillAsync`** + 插入 **`t_outbound_record`**;已覆盖 **`ship` 行**、更新 **`OrderStatus=pendingOnline`** / **`ShipTime`**、写入 outbound 应收。
|
||
|
||
##### 6.3.2.2 最近出库记录(GET /operation/outbound/recent-list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /operation/outbound/recent-list**(GET,Query 参数)
|
||
|
||
```
|
||
?limit=20&warehouseId=
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"waybillNo": "IDE2607221859123456",
|
||
"weight": 2.5,
|
||
"length": 20,
|
||
"width": 15,
|
||
"height": 10,
|
||
"salesProductId": 1,
|
||
"salesProductName": "美国空运专线(普货)",
|
||
"destinationCountry": "US",
|
||
"serviceChannelId": 100,
|
||
"serviceChannelName": "XX组-USPS",
|
||
"warehouseId": 1,
|
||
"warehouseName": "深圳仓",
|
||
"operateTime": "2024-01-01 12:00:00",
|
||
"operatorId": 1,
|
||
"operatorName": "张三",
|
||
"createTime": "2024-01-01 12:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
> 响应字段在 **§6.3.1.2 最近入库记录** 列表基础上增加 **`serviceChannelId` / `serviceChannelName`**(联查 **`t_service_channel`** 及展示名,命名可与实现一致),仅业务来源为出库。
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 分页/条数限制与 **`GET /operation/inbound/recent-list`** 一致;返回 **`t_outbound_record`** 行及联查名称字段。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对 `limit`、`warehouseId` 等查询参数及数据权限校验;异常返回统一错误码与错误信息。
|
||
|
||
#### 6.3.3 标签打印
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 打印标签 | POST | /operation/print-label/print | 打印标签 |
|
||
| 最近打印记录 | GET | /operation/print-label/recent-list | 最近打印记录 |
|
||
| 标签模板列表 | GET | /operation/print-label/template-list | 标签模板列表 |
|
||
|
||
##### 6.3.3.1 打印标签(POST /operation/print-label/print)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /operation/print-label/print**(POST)
|
||
|
||
```json
|
||
{
|
||
"number": "IDE2607221859123456",
|
||
"numberType": "waybill",
|
||
"labelTemplateId": 1
|
||
}
|
||
```
|
||
|
||
> 参数说明:`number` 运单号/客户单号/跟踪号/其他单号;`numberType` 单号类型:waybill(运单号)/customerOrder(客户单号)/tracking(跟踪号)/other(其他单号);`labelTemplateId` 标签模板ID,可选,不传则按 templateType 取默认模板。也可传 `templateType`:lastMile(尾程)/customer(客户)/inbound(入库)/outbound(出库)/sku。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"waybillNo": "IDE2607221859123456",
|
||
"customerOrderNo": "",
|
||
"trackingNo": "",
|
||
"otherNumber": "",
|
||
"labelTemplateId": 1,
|
||
"labelTemplateName": "尾程标签模板1",
|
||
"printSuccess": 1,
|
||
"printTime": "2024-01-01 12:00:00",
|
||
"operatorId": 1,
|
||
"createTime": "2024-01-01 12:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按接口定义执行业务流程,完成参数解析、数据处理与结果返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、状态与数据存在性进行校验,异常场景返回统一错误码与错误信息。
|
||
|
||
##### 6.3.3.2 最近打印记录(GET /operation/print-label/recent-list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /operation/print-label/recent-list**(GET,Query 参数)
|
||
|
||
```
|
||
?limit=20&labelTemplateId=
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"waybillNo": "IDE2607221859123456",
|
||
"customerOrderNo": "CUST001",
|
||
"trackingNo": "TRK001",
|
||
"otherNumber": "",
|
||
"labelTemplateId": 1,
|
||
"labelTemplateName": "尾程标签模板1",
|
||
"printSuccess": 1,
|
||
"printTime": "2024-01-01 12:00:00",
|
||
"operatorId": 1,
|
||
"operatorName": "张三",
|
||
"createTime": "2024-01-01 12:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按接口定义执行业务流程,完成参数解析、数据处理与结果返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、状态与数据存在性进行校验,异常场景返回统一错误码与错误信息。
|
||
|
||
##### 6.3.3.3 标签模板列表(GET /operation/print-label/template-list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /operation/print-label/template-list**(GET,Query 参数)
|
||
|
||
```
|
||
?templateType=lastMile
|
||
```
|
||
|
||
> 参数说明:`templateType` 可选,筛选模板类型:lastMile/customer/inbound/outbound/sku。不传则返回全部可用模板。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"name": "尾程标签模板1",
|
||
"templateType": "lastMile",
|
||
"templateJson": {},
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按接口定义执行业务流程,完成参数解析、数据处理与结果返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 对必填参数、状态与数据存在性进行校验,异常场景返回统一错误码与错误信息。
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
----------------------------------------------------------------------------------------
|
||
## 7 充值管理
|
||
|
||
### 7.1 核心需求说明
|
||
|
||
- **充值流程**:客户提交充值金额 → 选择支付方式(微信/支付宝)→ 上传充值截图 → 提交审核 → 管理员审核(通过/驳回)→ 审核通过后更新客户余额并 **写入账户资金流水**。
|
||
- **充值记录**:完整留存充值全流程信息(`t_recharge_record`),支持查询、导出;充值状态包含:待审核、审核通过、审核驳回。
|
||
- **资金流水**:所有 **余额变动**(充值入账、订单支付等)统一记入 **`t_customer_account_flow`**,前端「资金流水」页查询 **`GET /customer/account-flow/list`**。
|
||
|
||
### 7.2 表结构设计
|
||
|
||
#### 7.2.1 充值记录表 `t_recharge_record`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| CustomerId | BIGINT | - | 外键 → `t_customer.Id` | 是 | 充值归属客户业务主键;子客户登录时是否归并主客户 Id 见 **§1.4** |
|
||
| SerialNumber | VARCHAR | 64 | - | 是 | 流水号,规则:RECHARGE+日期+随机6位数字 |
|
||
| RechargeTime | DATETIME | - | - | 是 | 充值提交时间,默认当前时间 |
|
||
| PayType | TINYINT | - | - | 是 | 支付方式:1-微信,2-支付宝 |
|
||
| RechargeAmount | DECIMAL | 18,2 | - | 是 | 充值金额,≥0.01 |
|
||
| RechargeScreenshot | VARCHAR | 255 | - | 否 | 充值截图URL(存储文件服务器地址) |
|
||
| RechargeStatus | TINYINT | - | - | 是 | 充值状态:0-待审核,1-审核通过,2-审核驳回 |
|
||
| Remark | VARCHAR | 512 | - | 否 | 备注(客户/审核人填写) |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
| AuditTime | DATETIME | - | - | 否 | 审核时间 |
|
||
| AuditBy | VARCHAR | 32 | - | 否 | 审核人(管理员账号) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
|
||
#### 7.2.2 客户账户资金流水表 `t_customer_account_flow`
|
||
|
||
> **定位**:客户 **`t_customer.Balance` 的唯一变动账本**;与 **`t_recharge_record`**(充值申请/审核)分离——后者管「充值业务单据」,本表管「账户余额每一次增减」。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | INT | - | 主键 | 是 | 自增唯一标识 |
|
||
| CustomerId | INT | - | 外键 → `t_customer.Id` | 是 | 流水归属客户 |
|
||
| SerialNumber | VARCHAR | 64 | 唯一索引 | 是 | 流水号,规则:**`FLOW` + yyyyMMddHHmmss + 6 位随机数** |
|
||
| BizType | VARCHAR | 32 | - | 是 | 业务类型:`recharge` 充值入账;`orderPay` 订单支付(可扩展) |
|
||
| BizId | INT | - | - | 否 | 关联业务主键(如 `t_recharge_record.Id`、`t_order.Id`) |
|
||
| BizNo | VARCHAR | 64 | - | 否 | 关联业务单号(如充值流水号、运单号 `ServiceNo`) |
|
||
| Amount | DECIMAL | 18,2 | - | 是 | **变动金额(带符号)**:正数=入账,负数=出账 |
|
||
| BalanceBefore | DECIMAL | 18,2 | - | 是 | 变动前账户余额 |
|
||
| BalanceAfter | DECIMAL | 18,2 | - | 是 | 变动后账户余额(= BalanceBefore + Amount) |
|
||
| Currency | VARCHAR | 8 | - | 是 | 币种,默认 **CNY** |
|
||
| Remark | VARCHAR | 512 | - | 否 | 备注 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 软删,默认 0 |
|
||
| ConcurrencyStamp | CHAR | 36 | - | 是 | 并发戳(AggregateRoot 约定);新建表可用 DEFAULT(UUID()),已有表补列见 `migrate_t_customer_account_flow_concurrency.sql`(ALTER 勿用 UUID 默认值,避免复制 Error 1674) |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间(流水发生时间) |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**索引**:
|
||
|
||
| 索引名 | 字段 | 说明 |
|
||
| --- | --- | --- |
|
||
| `uk_customer_account_flow_serial` | SerialNumber | 流水号唯一 |
|
||
| `idx_customer_account_flow_customer_time` | CustomerId, CreateTime | 客户流水列表 |
|
||
| `idx_customer_account_flow_biz` | BizType, BizId | 按业务反查 |
|
||
|
||
**迁移脚本**:`sql/migrate_t_customer_account_flow.sql`
|
||
|
||
### 7.3 核心接口设计
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 请求参数 | 响应参数 | 接口说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| 提交充值申请 | POST | /recharge/recharge/submit | customerId, payType, rechargeAmount, screenshotUrl, remark | code, msg, data.serialNumber | 生成流水号,保存充值记录,状态为待审核 |
|
||
| 审核充值申请 | POST | /recharge/recharge-record/approve 或 reject | id, auditStatus, auditBy, remark | code, msg | 审核通过则 **`CustomerBalanceMutator` 增余额 + 写 `t_customer_account_flow`**,更新审核时间、状态、审核人 |
|
||
| 查询充值记录 | GET | /recharge/recharge-record/list | customerId, serialNumber, startTime, endTime, status | list, total | 多条件分页查询充值记录 |
|
||
| 导出充值记录 | GET | /recharge/export | customerId, startTime, endTime | 文件流 | 导出充值记录为Excel文件 |
|
||
| 查询账户资金流水 | GET | /customer/account-flow/list | page, pageSize, keyword, bizType, customerId, startTime, endTime | items, total | 分页查询余额变动流水,见 **§7.4** |
|
||
|
||
##### 6.3.1.1 提交充值申请(POST /recharge/recharge/submit)
|
||
|
||
###### 1 接口请求示例
|
||
**请求 /recharge/recharge/submit**(POST)
|
||
|
||
```json
|
||
{
|
||
"customerId": 1,
|
||
"rechargeAmount": 500,
|
||
"payType": 1,
|
||
"rechargeScreenshot": "https://example.com/upload/screenshot.jpg",
|
||
"remark": "充值500元"
|
||
}
|
||
```
|
||
|
||
> 参数说明:`customerId` 为 **`t_customer.Id`**(客户端可不传:用 JWT **`mapId`** 查 `t_customer.UserId` 得到对应 `Id`);`rechargeAmount` 充值金额,≥0.01;`payType` 支付方式:1-微信,2-支付宝;`rechargeScreenshot` 充值截图URL;`remark` 备注。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"serialNumber": "RECHARGE2024010100123456",
|
||
"customerId": 1,
|
||
"rechargeAmount": 500.00,
|
||
"payType": 1,
|
||
"rechargeScreenshot": "https://example.com/upload/screenshot.jpg",
|
||
"rechargeStatus": 0,
|
||
"remark": "充值500元",
|
||
"rechargeTime": "2024-01-01 12:00:00",
|
||
"createTime": "2024-01-01 12:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 创建充值记录并生成流水号,初始状态为待审核。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验金额、支付方式与截图信息合法性,不合法时返回业务错误。
|
||
|
||
##### 6.3.1.2 审核充值申请-通过(POST /recharge/recharge-record/approve)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /recharge/recharge-record/approve**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"remark": "审核通过"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
1. 校验记录 **`RechargeStatus = Pending`**,否则 **「只有待审核记录才能审核通过」**。
|
||
2. 更新 **`t_recharge_record`**:状态=审核通过,写入审核人、审核时间、备注。
|
||
3. 调用 **`CustomerBalanceMutator.CreditRechargeAsync`**:
|
||
- **`t_customer.Balance += RechargeAmount`**;
|
||
- 插入 **`t_customer_account_flow`**:`BizType=recharge`,`BizId=record.Id`,`BizNo=record.SerialNumber`,**`Amount` 为正数**,记录变动前后余额。
|
||
|
||
> 步骤 2~3 在同一工作单元内提交;**不再**单独 `Balance +=` 而不写流水。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验记录状态必须为待审核;重复审核或状态冲突时返回业务错误。
|
||
- 客户不存在 → **「客户不存在」**。
|
||
|
||
##### 6.3.1.3 审核充值申请-驳回(POST /recharge/recharge-record/reject)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /recharge/recharge-record/reject**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"remark": "截图不清晰,请重新上传"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 审核驳回后更新充值状态与审核备注,不变更客户余额。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验记录状态与审核权限,非法操作时返回业务错误。
|
||
|
||
##### 6.3.1.4 查询充值记录(GET /recharge/recharge-record/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /recharge/recharge-record/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20&customerId=1&serialNumber=&rechargeStatus=0&startTime=2024-01-01&endTime=2024-01-31
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"customerId": 1,
|
||
"customerCode": "W2501",
|
||
"customerPhone": "13800138000",
|
||
"serialNumber": "RECHARGE2024010100123456",
|
||
"rechargeTime": "2024-01-01 12:00:00",
|
||
"payType": 1,
|
||
"payTypeName": "微信",
|
||
"rechargeAmount": 500.00,
|
||
"rechargeScreenshot": "https://example.com/upload/screenshot.jpg",
|
||
"rechargeStatus": 0,
|
||
"rechargeStatusName": "待审核",
|
||
"remark": "充值500元",
|
||
"auditTime": null,
|
||
"auditBy": null,
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
],
|
||
"total": 20
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按客户、流水号、状态和时间区间进行分页查询,返回记录列表与总数。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验时间区间与分页参数合法性,查询异常时返回统一错误码。
|
||
|
||
##### 6.3.1.5 导出充值记录(GET /recharge/export)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /recharge/export**(GET,Query 参数)
|
||
|
||
```
|
||
?customerId=1&startTime=2024-01-01&endTime=2024-01-31
|
||
```
|
||
|
||
**响应**
|
||
|
||
> 返回 Excel 文件流,Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据筛选条件导出充值记录为 Excel 文件流。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验导出条件与数据权限,导出失败时返回统一错误信息。
|
||
|
||
### 7.4 客户账户资金流水
|
||
|
||
#### 7.4.1 设计说明
|
||
|
||
| 概念 | 表 / 组件 | 说明 |
|
||
| --- | --- | --- |
|
||
| 账户余额(权威) | `t_customer.Balance` | 客户当前可用余额;**仅通过 `CustomerBalanceMutator` 变更** |
|
||
| 充值业务单据 | `t_recharge_record` | 充值申请、截图、审核状态;**不等同于**资金流水 |
|
||
| 资金流水(账本) | `t_customer_account_flow` | 每一次余额增减的 **不可变记录**,含变动前后余额 |
|
||
| 余额变动服务 | `CustomerBalanceMutator` | 统一 **改 Balance + 插流水**;充值审核、订单支付均调用 |
|
||
|
||
**BizType 枚举(当前)**:
|
||
|
||
| BizType | 中文 | 触发场景 | Amount 符号 |
|
||
| --- | --- | --- | --- |
|
||
| `recharge` | 充值入账 | `POST /recharge/recharge-record/approve` 审核通过 | **正** |
|
||
| `orderPay` | 订单支付 | `POST /order/pay` 待支付订单扣款 | **负** |
|
||
|
||
#### 7.4.2 余额变动业务逻辑(`CustomerBalanceMutator`)
|
||
|
||
**公共步骤 `ApplyAsync`**:
|
||
|
||
1. 读取 **`balanceBefore = customer.Balance`**,计算 **`balanceAfter = balanceBefore + signedAmount`**。
|
||
2. 若 **`signedAmount == 0`** → **「流水金额不能为 0」**;若 **`balanceAfter < 0`** → **「账户余额不足」**。
|
||
3. 更新 **`customer.Balance = balanceAfter`** 并 **`UpdateAsync`**。
|
||
4. 生成唯一 **`SerialNumber`**(`FLOW{yyyyMMddHHmmss}{random6}`,冲突重试最多 5 次)。
|
||
5. 插入 **`t_customer_account_flow`**,写入 `BizType`、`BizId`、`BizNo`、`Amount`(带符号)、`BalanceBefore`、`BalanceAfter`、`Currency=CNY`、`Remark`。
|
||
|
||
**封装方法**:
|
||
|
||
| 方法 | 调用方 | 说明 |
|
||
| --- | --- | --- |
|
||
| `CreditRechargeAsync` | `RechargeService.ApproveAsync` | 入账金额为正 |
|
||
| `DebitOrderPayAsync` | `OrderService.PayAsync` | 出账金额为负;**payAmount 须 > 0** |
|
||
|
||
> 实现类:`Oms.Application.Shared.CustomerBalanceMutator`;仓储:`ICustomerAccountFlowRepository` / `CustomerAccountFlowRepository`。
|
||
|
||
#### 7.4.3 查询账户流水(GET /customer/account-flow/list)
|
||
|
||
**服务**:`CustomerAccountFlowService.GetPaginatedListAsync`
|
||
**分组**:OpenAPI **`OMS-Customer`**
|
||
|
||
**Query 参数**:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| page | int | 否 | 页码,默认 1 |
|
||
| pageSize | int | 否 | 每页条数,默认 20 |
|
||
| keyword | string | 否 | 模糊匹配 **流水号 SerialNumber** 或 **关联单号 BizNo** |
|
||
| bizType | string | 否 | `recharge` / `orderPay`;空或 `all` 表示全部 |
|
||
| customerId | int | 否 | 管理员可按客户筛选;**客户登录时由会话强制过滤为本客户 `t_customer.Id`** |
|
||
| startTime | datetime | 否 | 创建时间起(与 endTime 成对使用) |
|
||
| endTime | datetime | 否 | 创建时间止 |
|
||
|
||
**响应 `items[]` 字段**:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| id | 流水主键 |
|
||
| customerId | 客户 Id |
|
||
| customerCode | 客户编码(列表补齐) |
|
||
| serialNumber | 流水号 |
|
||
| bizType | 业务类型码 |
|
||
| bizTypeName | 业务类型中文(充值入账 / 订单支付) |
|
||
| bizId | 关联业务 Id |
|
||
| bizNo | 关联单号 |
|
||
| amount | 变动金额(正/负) |
|
||
| balanceBefore | 变动前余额 |
|
||
| balanceAfter | 变动后余额 |
|
||
| currency | 币种 |
|
||
| remark | 备注 |
|
||
| createTime | 流水时间 |
|
||
|
||
**请求示例**
|
||
|
||
```
|
||
GET /customer/account-flow/list?page=1&pageSize=50&bizType=orderPay&startTime=2026-06-01%2000:00:00&endTime=2026-06-30%2023:59:59
|
||
```
|
||
|
||
**响应示例**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"customerId": 10,
|
||
"customerCode": "C001",
|
||
"serialNumber": "FLOW20260626164200123456",
|
||
"bizType": "orderPay",
|
||
"bizTypeName": "订单支付",
|
||
"bizId": 24,
|
||
"bizNo": "LP202606261642156945",
|
||
"amount": -128.50,
|
||
"balanceBefore": 500.00,
|
||
"balanceAfter": 371.50,
|
||
"currency": "CNY",
|
||
"remark": "订单支付 LP202606261642156945",
|
||
"createTime": "2026-06-26 16:42:15"
|
||
}
|
||
],
|
||
"total": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 校验与隔离
|
||
|
||
- 客户门户:**`ResolveListCustomerIdAsync`** 强制 `CustomerId` 过滤,**不可**查看他人流水。
|
||
- 管理员:`customerId` 为空时查全部客户流水;传 `customerId` 时按指定客户过滤。
|
||
|
||
#### 7.4.4 前端(itfx-front)
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 路由 | `/user/accountFlow`,菜单 **客户 → 资金流水** |
|
||
| 页面 | `src/views/user/accountFlow/index.vue` |
|
||
| 表格 | **HandsontablePro** + **PaginationPro** |
|
||
| 入口 | 顶部导航余额下拉「资金流水」;支付成功后触发 **`oms:customer-balance-changed`** 刷新余额展示 |
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
----------------------------------------------------------------------------------------
|
||
## 8 系统主数据
|
||
|
||
### 8.1 核心需求说明
|
||
|
||
- **仓库地址**:管理仓库收货地址信息,包括主仓/中转仓/退货仓/其他类型;支持收件人、联系电话、收货地址、客服电话、客服微信及二维码等配置。
|
||
- **国家管理**:维护国家基础资料(中文名称、英文名称、二字代码),供渠道分区、下单地址等模块复用。
|
||
- **字典管理**:维护 **`t_dict`**,供全平台下拉与状态映射复用。**与客户、内部员工相关的 `DictType` 及编码含义**在 **`docs/rbac系统设计-060801.md`** 定义与迭代;OMS 仅保证业务侧按 **`DictType` + `DictCode`** 引用时与实现一致。本篇表结构中 **`DictType` 示例**以 **仓库、通知等纯业务枚举** 为主。
|
||
- **其他配置**:维护 **`t_system_other_config`**,以 **卡片式** 页面(路由 **`/system/otherConfig`**)管理第三方集成参数;当前首条业务为 **小包轨迹服务**(`ConfigCode = track_service`),供订单预报成功后异步调用外部 **`POST {BaseUrl}/OrderV2/UpsertSmallBag`**。运行时优先读库,缺省字段回退 **`appsettings` → `Oms:TrackService`**。
|
||
|
||
### 8.2 表结构设计
|
||
|
||
#### 8.2.1 与 RBAC 文档的职责划分
|
||
|
||
**权限系统**(认证、内部用户、客户主数据、角色权限、菜单、组织等)**全部由 `docs/rbac系统设计-060801.md` 承担**;OMS **「8 系统主数据」** 只描述 **业务侧主数据**:**国家、字典、通知、仓库地址、其他配置** 等(见下)。
|
||
|
||
#### 8.2.2 国家表 `t_country`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| ChineseName | VARCHAR | 128 | - | 是 | 中文名称 |
|
||
| EnglishName | VARCHAR | 128 | - | 是 | 英文名称 |
|
||
| CountryCode | VARCHAR | 2 | - | 是 | 国家二字代码(ISO 3166-1 alpha-2) |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 8.2.3 字典表 `t_dict`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| DictType | VARCHAR | 64 | - | 是 | 字典类型(如 **`notice_type`**、**`warehouse_type`** 等 OMS 业务枚举;与客户/员工相关的 `DictType` 由 **RBAC 文档** 统一约定) |
|
||
| DictSort | INT | - | - | 是 | 字典排序,值越小越靠前 |
|
||
| DictCode | VARCHAR | 64 | - | 是 | 字典编码(同类型下唯一) |
|
||
| DictName | VARCHAR | 128 | - | 是 | 字典名称 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 8.2.4 公告通知表 `t_notice`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| NoticeType | VARCHAR | 16 | - | 是 | 通知类型:notice-公告,message-站内信 |
|
||
| Title | VARCHAR | 200 | - | 是 | 标题 |
|
||
| Content | TEXT | - | - | 是 | 内容 |
|
||
| IsPopup | TINYINT | - | - | 是 | 是否弹窗:0-否,1-是 |
|
||
| ReceiverType | VARCHAR | 16 | - | 是 | 接收对象;**当前实现仅支持 `user`**(见 **§10.3.5**) |
|
||
| ReceiverIds | JSON | - | - | 是 | 接收人 ID 列表;存 **`user.userid`**(INT)数组,非 `t_customer.Id` |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 8.2.5 通知已读明细表 `t_notice_read`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| NoticeId | BIGINT | - | 外键(关联 `t_notice.Id`) | 是 | 通知 ID |
|
||
| ReceiverType | VARCHAR | 16 | - | 是 | 接收对象;**当前实现固定为 `user`** |
|
||
| ReceiverId | BIGINT | - | - | 是 | 已读主体 ID = **`user.userid`**(JWT `mapId`) |
|
||
| ReadTime | DATETIME | - | - | 是 | 已读时间 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
|
||
#### 8.2.6 仓库地址表 `t_warehouse_address`
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
|
||
| WarehouseName | VARCHAR | 128 | - | 是 | 仓库名称 |
|
||
| WarehouseType | VARCHAR | 32 | - | 是 | main/transit/return/other |
|
||
| Recipient | VARCHAR | 64 | - | 否 | 收件人 |
|
||
| ContactPhone | VARCHAR | 32 | - | 否 | 联系电话 |
|
||
| ShippingAddress | TEXT | - | - | 否 | 收货地址 |
|
||
| ServicePhone | VARCHAR | 32 | - | 否 | 客服电话 |
|
||
| ServiceWechat | VARCHAR | 64 | - | 否 | 客服微信 |
|
||
| ServiceWechatFiles | JSON | - | - | 否 | 微信二维码图片 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
#### 8.2.7 系统其他配置表 `t_system_other_config`
|
||
|
||
> **迁移脚本**:`sql/migrate_t_system_other_config.sql`(增量);全量建表见根目录 **`db.sql`**。
|
||
|
||
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Id | INT | - | 主键 | 是 | 自增唯一标识 |
|
||
| ConfigCode | VARCHAR | 64 | - | 是 | 配置编码,**全局唯一**(如 **`track_service`**) |
|
||
| ConfigName | VARCHAR | 128 | - | 是 | 配置名称(展示用,如「小包轨迹服务」) |
|
||
| Enabled | TINYINT | - | - | 是 | 是否启用:0-否,1-是,默认 0 |
|
||
| BaseUrl | VARCHAR | 512 | - | 否 | 第三方服务根地址(轨迹示例:`http://api.nanafa.com/track`) |
|
||
| AppKey | VARCHAR | 128 | - | 否 | 接入 AppKey |
|
||
| AppSecret | VARCHAR | 256 | - | 否 | 接入 AppSecret(**敏感**,列表接口脱敏) |
|
||
| TimeoutSeconds | INT | - | - | 是 | HTTP 超时秒数,默认 30 |
|
||
| Remark | VARCHAR | 512 | - | 否 | 备注 |
|
||
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认 0 |
|
||
| CreateUserId | INT | - | - | 是 | 创建人 |
|
||
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
|
||
| UpdateUserId | INT | - | - | 否 | 更新人 |
|
||
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
|
||
|
||
**索引**
|
||
|
||
| 索引名 | 类型 | 字段 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| uk_system_other_config_code | UNIQUE | ConfigCode | 配置编码唯一 |
|
||
| idx_system_other_config_enabled | KEY | Enabled, Id | 按启用状态筛选 |
|
||
|
||
**预置配置编码(`ConfigCode`)**
|
||
|
||
| ConfigCode | ConfigName(建议) | 消费方 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `track_service` | 小包轨迹服务 | 订单预报成功后异步轨迹同步 | 调用 **`POST {BaseUrl}/OrderV2/UpsertSmallBag`**;请求公共参数含 `appKey`、`timestamp`、`sign`;**签名**:`MD5("appKey={appKey}×tamp={timestamp}&appSecret={appSecret}")` 转 **大写十六进制** |
|
||
|
||
**运行时读取优先级(以 `track_service` 为例)**
|
||
|
||
1. 查询 **`t_system_other_config`**:`ConfigCode = track_service` 且未删除;各字段非空则覆盖对应运行时参数。
|
||
2. 若库中无记录或某字段为空,回退 **`Oms:TrackService`**(`appsettings` / Consul):`Enabled`、`BaseUrl`、`AppKey`、`AppSecret`、`TimeoutSeconds`。
|
||
3. `Enabled = false` 或 `BaseUrl` / `AppKey` / `AppSecret` 不完整时,**跳过** UpsertSmallBag(仅记 Debug 日志,不影响订单主流程)。
|
||
|
||
### 8.3 核心接口设计
|
||
|
||
> **登录、刷新、当前用户**:`POST /rbac/auth/login`、`POST /rbac/auth/refresh-token`、`GET /rbac/auth/get-current-user` 等路径与契约见 **`docs/rbac系统设计-060801.md`** 与 OpenAPI;**不在本篇逐条列出**。
|
||
|
||
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 国家列表 | GET | /system/country/list | 查询国家列表 |
|
||
| 字典列表 | GET | /system/dict/list | 按类型分页查询字典 |
|
||
| 新增字典 | POST | /system/dict/create | 新增字典项 |
|
||
| 更新字典 | POST | /system/dict/update | 更新字典项 |
|
||
| 删除字典 | POST | /system/dict/delete | 删除字典项 |
|
||
| 仓库列表 | GET | /system/warehouse/list | 仓库列表 |
|
||
| 仓库详情 | GET | /system/warehouse/detail | 仓库详情 |
|
||
| 新增仓库 | POST | /system/warehouse/create | 新增 |
|
||
| 更新仓库 | POST | /system/warehouse/update | 更新 |
|
||
| 删除仓库 | POST | /system/warehouse/delete | 删除 |
|
||
| 其他配置列表 | GET | /system/other-config/list | 分页查询其他配置(AppSecret 脱敏) |
|
||
| 其他配置详情 | GET | /system/other-config/detail | 按 Id 查询详情(含完整 AppSecret,供编辑) |
|
||
| 新增其他配置 | POST | /system/other-config/create | 新增 |
|
||
| 更新其他配置 | POST | /system/other-config/update | 更新(AppSecret 留空或 `******` 表示不修改) |
|
||
| 删除其他配置 | POST | /system/other-config/delete | 逻辑删除 |
|
||
| 通知列表 | GET | /system/notice/list | 分页查询通知 |
|
||
| 新增通知 | POST | /system/notice/create | 新增公告/站内信 |
|
||
| 编辑通知 | POST | /system/notice/update | 编辑公告/站内信 |
|
||
| 删除通知 | POST | /system/notice/delete | 删除公告/站内信 |
|
||
| 确认已读 | POST | /system/notice/read-confirm | 当前用户/客户确认已读 |
|
||
|
||
### 8.4 系统主数据接口详细设计(`/system/*` 等)
|
||
|
||
> **范围**:本章 **`/system/*`** 及 **`t_country` / `t_dict` / `t_notice` / `t_warehouse_address` / `t_system_other_config`**;**认证、令牌、权限码、RBAC/客户 HTTP** 见 **`docs/rbac系统设计-060801.md`** 与 **OpenAPI/Swagger**(`/rbac/*`、`/customer/*`)。
|
||
|
||
##### 8.4.1.1 国家列表(GET /system/country/list)
|
||
|
||
###### 1 接口请求示例
|
||
**请求 /system/country/list**(GET)
|
||
|
||
> 无请求参数,返回全部国家字段。
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"chineseName": "中国",
|
||
"englishName": "China",
|
||
"countryCode": "CN"
|
||
},
|
||
{
|
||
"id": 2,
|
||
"chineseName": "美国",
|
||
"englishName": "United States",
|
||
"countryCode": "US"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 查询 `t_country` 全量有效数据,按 `countryCode` 或 `englishName` 排序返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 仅返回未删除记录;无数据时返回空数组。
|
||
|
||
##### 8.4.1.2 字典列表(GET /system/dict/list)
|
||
|
||
###### 1 接口请求示例
|
||
**请求 /system/dict/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20&dictType=warehouse_type&keyword=主仓
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"dictType": "warehouse_type",
|
||
"dictSort": 1,
|
||
"dictCode": "main",
|
||
"dictName": "主仓",
|
||
"createTime": "2026-04-27 10:00:00",
|
||
"updateTime": "2026-04-27 10:00:00"
|
||
}
|
||
],
|
||
"total": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按 `dictType`、关键词分页查询 `t_dict`,按 `dictSort` 升序、`id` 降序返回。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验分页参数合法性;仅返回未删除记录,查询异常时返回统一错误码。
|
||
|
||
##### 8.4.1.3 新增字典(POST /system/dict/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/dict/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"dictType": "warehouse_type",
|
||
"dictSort": 1,
|
||
"dictCode": "main",
|
||
"dictName": "主仓"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 新增字典项并写入创建审计信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `dictType`、`dictSort`、`dictCode`、`dictName` 必填及格式合法性。
|
||
- 校验同一 `dictType` 下 `dictCode` 唯一,重复时返回业务错误。
|
||
|
||
##### 8.4.1.4 更新字典(POST /system/dict/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/dict/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"dictType": "warehouse_type",
|
||
"dictSort": 2,
|
||
"dictCode": "transit",
|
||
"dictName": "中转仓"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据字典 ID 更新字典类型、排序、编码、名称及审计信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且未删除,更新字段合法。
|
||
- 若更新后 `dictType + dictCode` 与其他记录冲突,返回业务错误。
|
||
|
||
##### 8.4.1.5 删除字典(POST /system/dict/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/dict/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据字典 ID 执行逻辑删除并保留历史审计信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在与删除前置条件,不存在或已删除时返回业务错误。
|
||
|
||
##### 8.4.1.6 仓库列表(GET /system/warehouse/list)
|
||
|
||
###### 1 接口请求示例
|
||
**请求 /system/warehouse/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20&warehouseType=main&keyword=
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"warehouseName": "深圳主仓",
|
||
"warehouseType": "main",
|
||
"warehouseTypeName": "主仓",
|
||
"recipient": "张三",
|
||
"contactPhone": "0755-12345678",
|
||
"shippingAddress": "广东省深圳市南山区xxx路xxx号",
|
||
"servicePhone": "400-xxx-xxxx",
|
||
"serviceWechat": "itfx_service",
|
||
"serviceWechatFiles": ["https://example.com/qrcode1.jpg"],
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"updateTime": "2024-01-01 12:00:00"
|
||
}
|
||
],
|
||
"total": 5
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按仓库类型与关键词分页查询仓库地址信息列表。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验分页参数与筛选条件合法性,异常时返回统一错误码。
|
||
|
||
##### 8.4.1.7 仓库详情(GET /system/warehouse/detail)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/warehouse/detail**(GET,Query 参数)
|
||
|
||
```
|
||
?id=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"warehouseName": "深圳主仓",
|
||
"warehouseType": "main",
|
||
"recipient": "张三",
|
||
"contactPhone": "0755-12345678",
|
||
"shippingAddress": "广东省深圳市南山区xxx路xxx号",
|
||
"servicePhone": "400-xxx-xxxx",
|
||
"serviceWechat": "itfx_service",
|
||
"serviceWechatFiles": ["https://example.com/qrcode1.jpg"],
|
||
"createTime": "2024-01-01 12:00:00",
|
||
"createBy": "admin",
|
||
"updateTime": "2024-01-01 12:00:00",
|
||
"updateBy": "admin"
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据仓库 ID 查询仓库详情,返回完整地址与客服信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 必填且存在,不存在时返回业务错误。
|
||
|
||
##### 8.4.1.8 新增仓库(POST /system/warehouse/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/warehouse/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"warehouseName": "深圳主仓",
|
||
"warehouseType": "main",
|
||
"recipient": "张三",
|
||
"contactPhone": "0755-12345678",
|
||
"shippingAddress": "广东省深圳市南山区xxx路xxx号",
|
||
"servicePhone": "400-xxx-xxxx",
|
||
"serviceWechat": "itfx_service",
|
||
"serviceWechatFiles": ["https://example.com/qrcode1.jpg"]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 新增仓库地址配置并写入创建审计信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验仓库名称、类型及地址字段完整性,不合法时返回业务错误。
|
||
|
||
##### 8.4.1.9 更新仓库(POST /system/warehouse/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/warehouse/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"warehouseName": "深圳主仓",
|
||
"warehouseType": "main",
|
||
"recipient": "张三",
|
||
"contactPhone": "0755-12345678",
|
||
"shippingAddress": "广东省深圳市南山区xxx路xxx号",
|
||
"servicePhone": "400-xxx-xxxx",
|
||
"serviceWechat": "itfx_service",
|
||
"serviceWechatFiles": ["https://example.com/qrcode1.jpg"]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据仓库 ID 更新仓库基础信息与联系方式。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在与更新字段合法性,更新失败时返回业务错误。
|
||
|
||
##### 8.4.1.10 删除仓库(POST /system/warehouse/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/warehouse/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据仓库 ID 执行逻辑删除并保留历史记录。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在与删除前置条件,冲突时返回业务错误。
|
||
|
||
##### 8.4.1.11 通知列表(GET /system/notice/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/notice/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=20¬iceType=notice&receiverType=user&keyword=活动
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 101,
|
||
"noticeType": "notice",
|
||
"noticeTypeName": "公告",
|
||
"title": "五一放假通知",
|
||
"content": "仓库 5 月 1 日-5 月 3 日暂停收货。",
|
||
"isPopup": 1,
|
||
"receiverType": "user",
|
||
"receiverTypeName": "用户",
|
||
"receiverIds": [1001, 1002, 1003],
|
||
"readCount": 2,
|
||
"receiverCount": 3,
|
||
"createTime": "2026-04-25 12:00:00",
|
||
"updateTime": "2026-04-25 12:00:00"
|
||
}
|
||
],
|
||
"total": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按通知类型、接收对象、关键词分页查询 `t_notice`。
|
||
- 已读数通过聚合 `t_notice_read`(按 `NoticeId` + `ReceiverId` 去重)计算,接收人总数按 `receiverIds` 统计。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验分页参数合法性,仅返回未删除记录;异常时返回统一错误码。
|
||
|
||
##### 8.4.1.12 新增通知(POST /system/notice/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/notice/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"noticeType": "notice",
|
||
"title": "五一放假通知",
|
||
"content": "仓库 5 月 1 日-5 月 3 日暂停收货。",
|
||
"isPopup": 1,
|
||
"receiverType": "user",
|
||
"receiverIds": [1001, 1002, 1003]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 新增公告/站内信并写入接收对象与接收人列表。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `noticeType`、`title`、`content`、`isPopup`、`receiverType`、`receiverIds` 必填及枚举合法性。
|
||
- `receiverIds` 为空或包含无效用户/客户时返回业务错误。
|
||
|
||
##### 8.4.1.13 编辑通知(POST /system/notice/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/notice/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 101,
|
||
"noticeType": "message",
|
||
"title": "账单补充说明",
|
||
"content": "请于本周内完成 4 月账单核对。",
|
||
"isPopup": 0,
|
||
"receiverType": "customer",
|
||
"receiverIds": [2001, 2002]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据通知 ID 更新通知主体内容与接收范围。
|
||
- 如接收人发生变化,清理 `t_notice_read` 中不在新接收范围内的已读明细。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在且未删除,更新字段合法。
|
||
- 已发布通知可编辑但必须保留至少 1 个接收人。
|
||
|
||
##### 8.4.1.14 删除通知(POST /system/notice/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/notice/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 101
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据通知 ID 执行逻辑删除,保留历史审计信息。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 必填且存在,不存在或已删除时返回业务错误。
|
||
|
||
##### 8.4.1.15 确认已读(POST /system/notice/read-confirm)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/notice/read-confirm**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 101
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 当前登录主体调用后,在 `t_notice_read` 中按 `NoticeId + ReceiverType + ReceiverId` 幂等写入已读记录(**`ReceiverId` = 当前 JWT `mapId`**,即 `user.userid`)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验通知存在且未删除。
|
||
- 校验当前主体在通知接收范围内,不在范围内时返回无权限错误。
|
||
|
||
##### 8.4.1.16 其他配置列表(GET /system/other-config/list)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/other-config/list**(GET,Query 参数)
|
||
|
||
```
|
||
?page=1&pageSize=30&keyword=track
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"configCode": "track_service",
|
||
"configName": "小包轨迹服务",
|
||
"enabled": true,
|
||
"baseUrl": "http://api.nanafa.com/track",
|
||
"appKey": "YOUR_APP_KEY",
|
||
"appSecret": "******",
|
||
"timeoutSeconds": 30,
|
||
"remark": "",
|
||
"createTime": "2026-06-27 10:00:00",
|
||
"updateTime": "2026-06-27 10:00:00"
|
||
}
|
||
],
|
||
"total": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 按关键词(匹配 `ConfigCode`、`ConfigName`)分页查询 `t_system_other_config`,按 `Id` 降序返回。
|
||
- 列表项 **`appSecret`** 固定脱敏为 **`******`**(未配置时返回空)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验分页参数合法性;仅返回未删除记录。
|
||
|
||
##### 8.4.1.17 其他配置详情(GET /system/other-config/detail)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/other-config/detail**(GET,Query 参数)
|
||
|
||
```
|
||
?id=1
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"configCode": "track_service",
|
||
"configName": "小包轨迹服务",
|
||
"enabled": true,
|
||
"baseUrl": "http://api.nanafa.com/track",
|
||
"appKey": "YOUR_APP_KEY",
|
||
"appSecret": "YOUR_APP_SECRET",
|
||
"timeoutSeconds": 30,
|
||
"remark": "",
|
||
"createTime": "2026-06-27 10:00:00",
|
||
"updateTime": "2026-06-27 10:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据主键查询单条配置,返回完整字段(含 **未脱敏** `appSecret`),供管理端编辑表单回填。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 必填且存在,不存在时返回业务错误 **「配置不存在」**。
|
||
|
||
##### 8.4.1.18 新增其他配置(POST /system/other-config/create)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/other-config/create**(POST)
|
||
|
||
```json
|
||
{
|
||
"configCode": "track_service",
|
||
"configName": "小包轨迹服务",
|
||
"enabled": true,
|
||
"baseUrl": "http://api.nanafa.com/track",
|
||
"appKey": "YOUR_APP_KEY",
|
||
"appSecret": "YOUR_APP_SECRET",
|
||
"timeoutSeconds": 30,
|
||
"remark": ""
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 校验入参后写入 `t_system_other_config` 并记录创建审计信息。
|
||
- 当 `ConfigCode = track_service` 时,后续订单 **预报成功** 将按本配置异步调用轨迹 UpsertSmallBag。
|
||
|
||
###### 校验与异常
|
||
|
||
- `configCode`、`configName` 必填;`configCode` **全局唯一**,重复时返回 **「配置编码已存在」**。
|
||
- 当 `configCode = track_service` 时:`baseUrl`、`appKey`、`appSecret` 均必填,否则分别返回 **「轨迹服务地址不能为空」**、**「轨迹服务 AppKey 不能为空」**、**「轨迹服务 AppSecret 不能为空」**。
|
||
- `timeoutSeconds` 小于等于 0 时落库按 **30** 处理。
|
||
|
||
##### 8.4.1.19 更新其他配置(POST /system/other-config/update)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/other-config/update**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"configCode": "track_service",
|
||
"configName": "小包轨迹服务",
|
||
"enabled": true,
|
||
"baseUrl": "http://api.nanafa.com/track",
|
||
"appKey": "YOUR_APP_KEY",
|
||
"appSecret": "",
|
||
"timeoutSeconds": 30,
|
||
"remark": "生产环境"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据 `id` 更新配置;**`appSecret` 为空或为 `******` 时不修改库中已有密钥**(其余字段正常更新)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 存在;`configCode` 唯一性(排除自身)。
|
||
- `configCode = track_service` 时:`baseUrl`、`appKey` 仍必填;`appSecret` 仅在传入新值时更新。
|
||
|
||
##### 8.4.1.20 删除其他配置(POST /system/other-config/delete)
|
||
|
||
###### 1 接口请求示例
|
||
|
||
**请求 /system/other-config/delete**(POST)
|
||
|
||
```json
|
||
{
|
||
"id": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "success",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
###### 2 接口业务逻辑
|
||
|
||
###### 核心流程
|
||
|
||
- 根据主键执行逻辑删除;删除后轨迹同步回退 **`Oms:TrackService`** 或跳过(视 appsettings 与 `Enabled` 而定)。
|
||
|
||
###### 校验与异常
|
||
|
||
- 校验 `id` 必填且存在,不存在时返回 **「配置不存在」**。
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
----------------------------------------------------------------------------------------
|
||
|
||
## 9 需求迭代与变更记录
|
||
|
||
### 9.1 需求迭代目录(按日期管理)
|
||
|
||
> 迭代编号建议使用 `YYYYMMDD-序号`,按日期倒序维护;不同功能迭代可使用 `-01`、`-02` 递增。
|
||
|
||
| 迭代日期 | 迭代编号 | 迭代功能 | 变更模块 | 备注 |
|
||
| --- | --- | --- | --- | --- |
|
||
| 2026-07-22 | 20260722-01 | 查价/下单/入库/出库链路加固;待排货;运单号 IDE | 订单 §2.3、§4.3.5、§4.4.2.1、§4.4.3.1、§6.3;附录 §10.3.1;新手指引 `/order/guide` | 查价可选尺寸+参考价;创建仅 forecast 应收;入库可选排货→`pendingAllocation`;运单号 `IDE{yyMMddHHmmss}{4位随机}` |
|
||
| 2026-07-19 | 20260719-01 | 店铺订单创建物流 / 物流&代发 | 订单 §4.1、§2.3、§4.2.11、**§4.4.7**;附录权限 | 校对弹窗;`batch-create-logistics` / `batch-create-fulfill`;与匹配库存分流 |
|
||
| 2026-07-15 | 20260715-01 | 客户发件地址 + 揽收订单 | 订单 §4.1、§4.2.1、§4.2.10、§4.4.1.1、§4.4.4、§4.4.5;附录 §10.3.9 | 地址簿 `t_customer_sender_address`;`OrderSource=pickup` 复用小包创建;菜单客户/订单 |
|
||
| 2026-07-04 | 20260704-01 | 创建问题件 / 创建理赔件 | 订单 §4.2.1、§4.2.9、§4.4.1.13~§4.4.1.16;渠道 §5.3.0.7;附录 §10.3.6~§10.3.8 | 问题模板消费;`t_order_exception_event`;权限 `Order.Problematic.Create` 等 |
|
||
| 2026-06-27 | 20260627-02 | 规则快照下游元数据 + 订单绑定 | 订单 §4.2.1、§4.3.4.2、§4.4.1.11~§4.4.1.12;渠道 §5.3.0.2 | `Providers` enrich;`t_order` 四字段;轨迹/下游预报优先读订单绑定 |
|
||
| 2026-06-27 | 20260627-01 | 系统其他配置(小包轨迹) | 系统主数据 §8.1、§8.2.7、§8.3、§8.4.1.16~§8.4.1.20 | `t_system_other_config` + `/system/other-config/*`;卡片页 `/system/otherConfig` |
|
||
| 2026-06-26 | 20260626-02 | 渠道模块反向对齐 oms0414-back | 渠道模块 §5.3.0、§5.2.6.2、§10.6.2 | 发布/价格 save/导入等 |
|
||
| 2026-06-26 | 20260626-01 | 反向对齐 oms0414-back 接口实现 | §1.5、§4.3.5、§4.4、§10.6、§9.9 | 快照 Live 降级、同步 batch-create、简化 upload 等 |
|
||
| 2026-06-24 | 20260624-03 | 查价规则与失败原因 | 订单模块 §4.4.2.1 | unit 公式、重量左开右闭、failures |
|
||
| 2026-06-26 | 20260626-01 | 移除收费时机 | 订单 §4.3.4、渠道卖价/买价 | 删除 ChargingTime;应收按 forecast/receive 计重触发 |
|
||
| 2026-06-24 | 20260624-02 | 小包创建与会话/字段约定落地 | 订单模块 §4.2.1、§4.4.3.1~§4.4.3.7 | 客户 Id 会话补全、国家二字码、报关方式枚举、可选运单号等 |
|
||
| 2026-06-24 | 20260624-01 | 订单接口客户数据隔离 | 订单模块 §4.4.0、§3.3.5 | `/admin/order/*` 增删改查强制 `CustomerId` 隔离 |
|
||
| 2026-04-30 | 20260430-03 | 获取渠道分组 | 渠道模块 | 之前漏写该接口 |
|
||
| — | — | **与客户、内部用户、认证、RBAC 相关的迭代** | — | **一律在 `docs/rbac系统设计-060801.md` 维护**;OMS 本篇仅记录业务域迭代 |
|
||
|
||
### 9.2 20260719-01 差异明细(店铺订单创建物流 / 物流&代发)
|
||
|
||
#### 9.2.1 能力
|
||
|
||
| 能力 | 说明 |
|
||
| --- | --- |
|
||
| 创建物流订单 | 勾选店铺单 → 校对收件人/明细 → 选运输方式 → 批量创建 `t_order`(`OrderSource=platform`)并回写 `LinkedOrderId`;**不**扣库存、**不**要求 SKU 映射 |
|
||
| 创建物流&代发订单 | 同上 + 按单绑定库存/包裹(§4.4.6 冻结语义);建单失败或绑定失败按条回滚 |
|
||
| 与匹配库存 | 匹配库存仍走映射扣减建单;三入口前置条件一致但库存语义不同 |
|
||
|
||
#### 9.2.2 接口(待实现)
|
||
|
||
| 方法 | 路径 |
|
||
| --- | --- |
|
||
| POST | `/order/platform-store-order/batch-create-logistics` |
|
||
| POST | `/order/platform-store-order/batch-create-fulfill` |
|
||
|
||
#### 9.2.3 前端(待实现)
|
||
|
||
- `platformStoreOrder` 工具栏两按钮 + `CreateLogisticsModal` / `CreateLogisticsFulfillModal`
|
||
- 代发绑定交互复用 `StockBindModal` 语义;权限 `CreateLogistics` / `CreateFulfill`
|
||
- 菜单三级按钮目录:`src/constants/pageButtonCatalog.ts` → `Order.PlatformStoreOrder.CreateLogistics` / `.CreateFulfill`(RBAC「同步三级按钮」拉取)
|
||
|
||
#### 9.2.4 正文锚点
|
||
|
||
- **§4.4.7**(完整交互与接口设计)
|
||
|
||
---
|
||
|
||
### 9.3 20260715-01 差异明细(客户发件地址 + 揽收订单)
|
||
|
||
#### 9.3.1 表结构(迁移脚本,待编写)
|
||
|
||
| 脚本 | 说明 |
|
||
| --- | --- |
|
||
| `sql/migrate_t_customer_sender_address.sql` | 新建 **`t_customer_sender_address`**(§4.2.10) |
|
||
| `sql/migrate_t_order_pickup_fields.sql` | **`t_order`** 增加 **`OrderSource`**、**`PickupTime`**、**`CustomerSenderAddressId`**;存量行 `OrderSource` 置 `manual` |
|
||
|
||
#### 9.3.2 新增 / 扩展接口
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| POST | `/order/sender-address/list` | §4.4.5 |
|
||
| GET | `/order/sender-address/detail` | §4.4.5 |
|
||
| POST | `/order/sender-address/create` | §4.4.5 |
|
||
| POST | `/order/sender-address/update` | §4.4.5 |
|
||
| POST | `/order/sender-address/delete` | §4.4.5 |
|
||
| POST | `/order/sender-address/set-default` | §4.4.5 |
|
||
| POST | `/order/pickup/create` | §4.4.4;内部调用小包创建核心 |
|
||
| POST | `/order/list` | **扩展**筛选 `orderSource`;响应回显来源/揽收时间 |
|
||
|
||
#### 9.3.3 权限码(RBAC 菜单待配置)
|
||
|
||
| 权限码 | 说明 |
|
||
| --- | --- |
|
||
| `Order.SenderAddress.Query` | 发件地址查询 / 菜单 **客户 → 发件地址** |
|
||
| `Order.SenderAddress.Create` | 新增发件地址 |
|
||
| `Order.SenderAddress.Update` | 更新 / 设默认 |
|
||
| `Order.SenderAddress.Delete` | 删除发件地址 |
|
||
| `Order.Pickup.Query` | 揽收订单菜单 / 列表 |
|
||
| `Order.Pickup.Create` | 创建揽收订单 |
|
||
|
||
#### 9.3.4 前端路由
|
||
|
||
| 菜单 | path | 视图 |
|
||
| --- | --- | --- |
|
||
| 客户 → 发件地址 | `/user/senderAddress` | `views/user/senderAddress/index.vue` |
|
||
| 订单 → 揽收订单 | `/order/pickupOrder` | `views/order/pickupOrder/index.vue` |
|
||
|
||
#### 9.3.5 实现分期
|
||
|
||
| 期次 | 范围 | 状态 |
|
||
| --- | --- | --- |
|
||
| **一期** | 地址簿 CRUD + 揽收创建(仓库→收件映射 + 缺省件重尺/申报)+ 列表 `orderSource` 筛选 + 两菜单页 | **已落地**(待执行 SQL 迁移与 RBAC 权限码配置) |
|
||
| **二期** | 仓库地址结构化国家/省市区邮编;揽收预约与承运商对接;我的订单来源角标/筛选体验 | 待定 |
|
||
|
||
#### 9.3.6 与仓储小包边界(实现时勿混表)
|
||
|
||
| 域 | 主表 | 入口 |
|
||
| --- | --- | --- |
|
||
| 揽收订单 | `t_order`(`OrderSource=pickup`) | `/order/pickupOrder` |
|
||
| 仓储小包/到货 | `C_parcel` 等 | `/warehouse/parcel` |
|
||
|
||
### 9.4 20260704-01 差异明细(创建问题件 / 创建理赔件)
|
||
|
||
#### 9.4.1 表结构(迁移脚本,待执行)
|
||
|
||
| 脚本 | 说明 |
|
||
| --- | --- |
|
||
| `sql/migrate_t_order_handle_status.sql` | **`HandleStatus`** 列(**已编写**;列表子 Tab 已依赖) |
|
||
| `sql/migrate_t_order_exception_fields.sql` | **`t_order`** 扩展:问题件/理赔件快照字段 + **`PreviousOrderStatus`** |
|
||
| `sql/migrate_t_order_exception_event.sql` | 新建 **`t_order_exception_event`**(§4.2.9) |
|
||
|
||
#### 9.4.2 新增接口
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| POST | `/order/mark-problematic` | §4.4.1.13 |
|
||
| POST | `/order/mark-claimed` | §4.4.1.14 |
|
||
| POST | `/order/handle-status/update` | §4.4.1.15 |
|
||
|
||
#### 9.4.3 权限码(RBAC 菜单待配置)
|
||
|
||
| 权限码 | 说明 |
|
||
| --- | --- |
|
||
| `Order.Problematic.Create` | 创建问题件 |
|
||
| `Order.Claim.Create` | 创建理赔件 |
|
||
| `Order.HandleStatus.Update` | 标记已处理 |
|
||
|
||
#### 9.4.4 实现分期
|
||
|
||
| 期次 | 范围 | 状态 |
|
||
| --- | --- | --- |
|
||
| **一期** | 表结构 + 三个 HTTP 接口 + `myOrders` 弹窗/按钮 + 列表字段回显 | **已落地** |
|
||
| **二期** | 占位符渲染、`NotifyMethod` 投递、自动触发消费问题模板 | **部分已落地**(见 §9.4.5) |
|
||
|
||
#### 9.4.5 二期实现说明(2026-07-06)
|
||
|
||
**占位符渲染**(`IssuePlaceholderRenderer`)
|
||
|
||
- 支持 `{serviceNo}`、`{clientNo}`、`{trackNo}`、`{productCode}`、`{channelCode}`、`{issueType}`、`{issueTypeName}`、`{issueTitle}` 及中文别名(如 `{运单号}`、`{客户单号}`)。
|
||
- 标记问题件时按 **单订单** 渲染标题/内容后写入 `t_order` 与 `t_order_exception_event`。
|
||
|
||
**通知投递**(`OrderIssueNotificationService`)
|
||
|
||
| `NotifyMethod` | 行为 |
|
||
| --- | --- |
|
||
| `inApp` | 写入 `t_notice`(`noticeType=message`),接收人为订单客户 `t_customer.UserId` + 自动触发 `notifier` 中的用户 Id |
|
||
| `email` | 使用 **当前操作人** 的 `t_system_batch_email_account` SMTP 配置,发送至客户 `Email` |
|
||
| `sms` / `feishu` / `wechat` | 记录日志并跳过(通道待对接) |
|
||
|
||
**自动触发运行时**(`AutoTriggerRuntimeService`)
|
||
|
||
| 挂钩点 | `TriggerTiming` 关键字示例 |
|
||
| --- | --- |
|
||
| 小包预报成功 | 下单、order、create、parcel、预报 |
|
||
| `/operation/inbound/create` | 入库、inbound、receive、收货 |
|
||
| `/operation/outbound/create` | 出库、outbound、ship、发货 |
|
||
|
||
- **问题件**:按 `triggerCondition` 解析 `issueType`(支持 JSON `{"issueType":"address",...}` 或纯 code),匹配 **问题模板** 后自动标记并通知。
|
||
- **拦截**:`IsIntercept=1`(**事后标记**;**不能阻止预报**,预报同步拦截见 **§5.3.0.6.1**)。
|
||
- **警告**:仅发站内信(不改编订单状态)。
|
||
|
||
### 9.5 20260627-02 差异明细(规则快照下游元数据 + 订单绑定)
|
||
|
||
#### 9.2.1 表结构差异
|
||
|
||
**`t_order`**(迁移 **`sql/migrate_t_order_downstream_integration.sql`**)
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `DownstreamSupplierCode` | itfx **`supplierCode`** |
|
||
| `DownstreamAppKey` | itfx AppKey |
|
||
| `DownstreamAppId` | itfx 应用 Id(Secret 不落库) |
|
||
| `ChannelForecastCode` | 渠道 **ForecastCode** / 轨迹 ApiCode |
|
||
|
||
#### 9.2.2 接口与契约差异
|
||
|
||
**规则发布(`POST /channel/sales-product/publish`,`operatorType=rules`)**
|
||
|
||
- `Content.Providers[]` 增加 **`channelCode`、`forecastCode`、`groupId`、`appId`、`apiServiceProviderId`、`appKey`、`supplierCode`**(**不含 `appSecret`**)。
|
||
|
||
**排货确认(`ConfirmServiceChannelAndPayableAsync`)**
|
||
|
||
- 除 **`ChannelCode`** 外,写入上述四字段至 **`t_order`**。
|
||
|
||
**`POST /order/track-upsert-small-bag`**
|
||
|
||
- 新增;手动同步轨迹 UpsertSmallBag;**ApiCode** 优先 **`ChannelForecastCode`**。
|
||
|
||
**「预报下游订单」(前端 itfx)**
|
||
|
||
- 非 OMS 后端路由;凭证 **`resolveOrderDownstreamCredentials`** 优先订单 **`downstream*`** 字段。
|
||
|
||
**列表/详情 `OrderOutput`**
|
||
|
||
- 响应增加 **`downstreamSupplierCode`、`downstreamAppKey`、`downstreamAppId`、`channelForecastCode`**。
|
||
|
||
---
|
||
|
||
### 9.6 20260624-03 差异明细(查价对接卖价最新版本算价)
|
||
|
||
#### 9.2.1 表结构差异
|
||
|
||
- 无
|
||
|
||
#### 9.2.2 接口与契约差异
|
||
|
||
**`POST /order/price-check`**
|
||
|
||
| 项 | 说明 |
|
||
| --- | --- |
|
||
| 匹配 | 仅按国家+邮编、运输方式、重量尺寸、货物属性匹配快照 |
|
||
| 重量区间 | 左开右闭 `(start,end]`;单值 `0.5` → `(0,0.5]` |
|
||
| 公式 | 字典编码 `unit` / `total` / `continueUnit` 等 |
|
||
| 响应 | `items` + `failures`(`step` + `reason`) |
|
||
|
||
---
|
||
|
||
### 9.7 20260624-02 差异明细(小包创建与会话/字段约定)
|
||
|
||
#### 9.3.1 表结构差异
|
||
|
||
- 无
|
||
|
||
#### 9.3.2 接口与契约差异
|
||
|
||
**`POST /order/parcel/create`、`POST /order/parcel/batch-create`**
|
||
|
||
| 项 | 原约定/易错点 | 现约定(与实现对齐) |
|
||
| --- | --- | --- |
|
||
| `customerId` | 请求体必填 | **可选**;客户登录由 `mapId → t_customer.Id` **会话补全**;不一致 → **「客户ID非法」** |
|
||
| `trackNo` / `serviceNo` | 文档未明确可选 | **可选**;`serviceNo` 空则服务端自动生成 |
|
||
| `receiver.countryCode` | 传 `t_country.Id`(数字) | 传 **ISO 二字码 string**(如 `CN`、`US`);兼容数字字符串 Id |
|
||
| `sender.countryCode` | 数字 Id | 同收件人,**可选** |
|
||
| `exportCustomsMode` | 易传 `9610`、字典 `1` | 须为 §4.2.1 六个 **code**;非法 → **「出口报关方式不合法」** |
|
||
| 收件人校验失败 | — | 缺 `recipientsName` / `countryCode` / `zipCode` / `streetAddress1` → **「收件人信息不完整」** |
|
||
|
||
**`POST /order/upload`**
|
||
|
||
- 创建 **`t_order_batch_job`**(`import-row`),`CustomerId` 与导入行 `CustomerId` **均取会话** `effectiveCustomerId`。
|
||
|
||
**`POST /order/list`、`POST /order/export`、`GET /order/batch/jobs`、`GET /order/batch/jobs/{id}`**
|
||
|
||
- 客户门户 **按 `t_order.CustomerId` / `t_order_batch_job.CustomerId` 隔离**(非 `CreateUserId`);跨客户单条访问返回 **「订单不存在」** / **「批量任务不存在」**。
|
||
|
||
**`GET /order/template/list`、`GET /order/template/download`**
|
||
|
||
- 客户仅见 `standard` + 自有 `custom` 模板;他客模板返回 **「模板不存在」**。
|
||
|
||
**响应字段说明**
|
||
|
||
- 列表/详情中 `sender.countryCode` / `receiver.countryCode` 仍为 **`t_country.Id`(int)**;与 **创建请求体 ISO 二字码** 区分使用。
|
||
|
||
---
|
||
|
||
### 9.8 20260624-01 差异明细(订单客户数据隔离)
|
||
|
||
#### 9.4.1 表结构差异
|
||
|
||
- 无(沿用 `t_order.CustomerId`、`t_order_batch_job.CustomerId`、`t_order_template.CustomerId`;**已删除** `t_order_task`)
|
||
|
||
#### 9.4.2 接口差异
|
||
|
||
**更新接口(订单模块全部 `/order/*`)**
|
||
|
||
- 统一增加 **§4.4.0** 约束:会话解析 `effectiveCustomerId`,列表强制过滤、单条/批量写操作归属校验;跨客户访问返回「订单不存在」或 403。
|
||
|
||
**新增文档章节**
|
||
|
||
- **§3.3.5** 订单模块客户数据隔离(公共规范)
|
||
- **§4.4.0** 客户数据隔离(强制,含实现检查清单)
|
||
|
||
---
|
||
|
||
### 9.9 20260427-03 差异明细(按表结构与接口)
|
||
|
||
> 对比范围:`oms表结构与接口设计20260427.md` vs `oms表结构与接口设计20260430.md`。**与认证、客户、内部用户、RBAC 相关的历史差异**不再在此归档,见 **`docs/rbac系统设计-060801.md`**。
|
||
|
||
#### 9.5.1 表结构差异
|
||
|
||
**新增表(0)**
|
||
|
||
- 无
|
||
|
||
**更新表(0)**
|
||
|
||
- 无
|
||
|
||
**删除表(0)**
|
||
|
||
- 无
|
||
|
||
#### 9.5.2 接口差异
|
||
|
||
**新增接口(1)**
|
||
|
||
- `GET | /channel/service-channel/group/list`(获取渠道分组列表)
|
||
|
||
**更新接口(0)**
|
||
|
||
- 无
|
||
|
||
**删除接口(0)**
|
||
|
||
- 无
|
||
|
||
---
|
||
|
||
### 9.10 20260626-01 差异明细(反向对齐 oms0414-back)
|
||
|
||
> 对照仓库:`C:\Users\wilso\Desktop\oms0414-back`。本文在保留原设计目标的前提下,用 **「当前实现」** 小节标注代码真实行为。
|
||
|
||
#### 9.6.1 新增文档章节
|
||
|
||
- **§1.5** 后端实现对照(技术栈、Service 映射、会话隔离、设计 vs 实现速查)
|
||
- **§4.3.5** 快照解析与算价(`ResolveSnapshotBindingAsync`、`ParcelBillingCalculator`)
|
||
- **§5.3.0** 渠道模块当前实现总览(销售产品、服务渠道、KPI、计重、自动触发、标签)
|
||
- **§10.6** 接口实现状态总表
|
||
|
||
#### 9.6.2 接口与行为差异(设计 → 代码)
|
||
|
||
| 接口 / 能力 | 原设计 / 文档描述 | 当前实现 |
|
||
| --- | --- | --- |
|
||
| 快照读取 | Redis + 内存为主 | **DB 发布记录** + **Live 实时组装**,版本 `LIVE-{id}` |
|
||
| `POST /order/parcel/create` | 应收 + 应付预报 | **仅应收**(`CalculateForecastReceivableFees`) |
|
||
| `POST /order/parcel/batch-create` | 异步 `taskId` | **同步** `batchNo` + `items[]`,逐条 `CreateParcelCoreAsync` |
|
||
| `POST /order/upload` | 等同批量创建全链路 | **批量任务** `import-row`,MQ 异步或同步处理,返回 **`jobId`** |
|
||
| `POST /order/pay` | 扣余额 + 写流水 | **已落地**:`pendingPayment` → 扣应收合计 → `orderPay` 流水 → `pendingShip` |
|
||
| `POST /order/fee/batch-recalculate` | 批量重算 | **无 HTTP 接口** |
|
||
| 入库 / 出库 | 触发 §4.3.4 分阶段算价 | **仅**作业记录表 |
|
||
| 管理员订单列表 | §4.4.0 客户门户 | **管理员不过滤** `CustomerId` |
|
||
| 渠道发布后 Redis 同步 | 写 Redis + 刷新计费内存 | **仅写 DB** 发布记录 |
|
||
| 自动触发规则 | 订单/仓储事件消费 | **部分落地**(关键字匹配 + 事后拦截);**V2 结构化条件 + 预报同步拦截见 §5.3.0.6.1** |
|
||
| 服务渠道 publish/preview | 同销售产品 | **已落地** |
|
||
|
||
#### 9.6.4 渠道模块已落地要点(详见 §5.3.0)
|
||
|
||
- 销售产品 **`price/save`**、排货 **`allocation/save`** 支持 **`publish=true`** 连带发布
|
||
- 销售产品 **`publish/preview`** 预览快照;**`publish/rollback`** 从 `Content` 恢复价格树或规则+排货
|
||
- 服务渠道 **`group/delete`** 有关联渠道时拒绝删除
|
||
- KPI **`info/import`** 逐行导入,失败行记入 **`failures`**
|
||
- 发布版本号 **`yyyyMMddHHmmss`**,`operatorType` 仅 **`price` \| `rules`**
|
||
|
||
#### 9.6.3 已确认与实现对齐(补充到正文)
|
||
|
||
- **`requestId`** 幂等(`t_order.RequestId`)
|
||
- 查价 **`failures[].step`**:`product` / `snapshot` / `blacklist` / `weight` / `zone` / `price`
|
||
- 充值审核 **`CustomerBalanceMutator.CreditRechargeAsync`**(`Pending` 可审,**Balance 与 `t_customer_account_flow` 同事务**)
|
||
- 订单支付 **`CustomerBalanceMutator.DebitOrderPayAsync`**(见 **§4.4.1.3、§7.4**)
|
||
- 面单 **`labelUrl` / `printUrl`**:HTML 文件 `/uploads/prints/orders/`
|
||
|
||
---
|
||
|
||
## 10 附录
|
||
|
||
### 10.1 接口路径规范(kebab-case 汇总)
|
||
|
||
> **网关前缀**:订单模块生产 URL 为 **`https://api.nanafa.com/admin/order/...`**;下表「服务内路径」为 **`/order/...`**,与网关拼接规则:`{VITE_API_ADMIN_URL}` + `/order/{action}`。
|
||
|
||
| 模块 | 网关前缀(生产) | 服务内路径 | 示例 |
|
||
| --- | --- | --- | --- |
|
||
| 订单 | `/admin/order/` | `/order/` | list, detail, pay, pre-declare, cancel-intercept, delete, price-check, parcel/create, parcel/batch-create;**设计中**:fee/batch-recalculate, mark-problematic, mark-claimed, handle-status/update |
|
||
| 渠道-销售产品 | `/admin/channel/`(同订单网关) | `/channel/sales-product/` | list, detail, create, update, config/save, delete, price/list, price/save, allocation/get, publish/preview |
|
||
| 渠道-服务渠道 | `/admin/channel/` | `/channel/service-channel/` | list, detail, create, update, config/save, delete, group/list, price/save, publish/preview, publish/rollback |
|
||
| 渠道-计重 | `/admin/channel/` | `/channel/weight-rule/`、`/channel/weight-rounding/` | list, create, update, delete, info/list, info/add |
|
||
| 渠道-自动触发 | `/admin/channel/` | `/channel/auto-trigger/` | list, create, update, delete, toggleStatus |
|
||
| 渠道-问题模板 | `/admin/channel/` | `/channel/issue-template/` | list, detail, options, create, update, delete, toggleStatus |
|
||
| 渠道-分区(KpiArea) | `/admin/channel/` | `/channel/kpi-area/` | create, update, delete, list, info/*, info/import, info/import-template |
|
||
| 渠道-标签 | `/admin/channel/` | `/channel/label-template/save` | save |
|
||
| 操作 | `/admin/operation/` | `/operation/` | inbound/create, inbound/recent-list, outbound/create, print-label/print |
|
||
| 权限与客户域(非 OMS) | — | — | **`docs/rbac系统设计-060801.md`**(`/customer/*`、`/rbac/*` 等) |
|
||
| 充值 | `/admin/recharge/` | `/recharge/` | recharge/submit, recharge-record/list, approve, reject |
|
||
| 客户账户流水 | `/admin/customer/`(同 admin 网关) | `/customer/account-flow/` | **list**(分页查询资金流水,见 **§7.4**) |
|
||
| 系统 | `/admin/system/` | `/system/` | country/list, warehouse/list, warehouse/detail, warehouse/create, warehouse/update, warehouse/delete, notice/list, notice/create, notice/update, notice/delete, notice/read-confirm |
|
||
|
||
### 10.2 分区类型说明(黑名单/偏远/价格)
|
||
|
||
| 页面 | areaType | 说明 |
|
||
| --- | --- | --- |
|
||
| 黑名单区 | blacklist | 黑名单分区 |
|
||
| 偏远分区 | remote | 偏远分区 |
|
||
| 价格分区 | price | 用于计价/价格表关联的分区 |
|
||
|
||
调用 KpiArea 接口时,通过 `areaType` 区分不同业务分区,无需新建表或接口。
|
||
|
||
### 10.3 状态码与枚举
|
||
|
||
#### 10.3.1 订单状态
|
||
|
||
| status | 中文 |
|
||
| --- | --- |
|
||
| draft | 草稿 |
|
||
| predeclared | 已预报 |
|
||
| pendingAllocation | 待排货 |
|
||
| received | 已收货 |
|
||
| pendingPayment | 待支付 |
|
||
| pendingShip | 待发货 |
|
||
| pendingOnline | 待上网 |
|
||
| shipping | 运输中 |
|
||
| deliverySuccess | 派送成功 |
|
||
| deliveryFailed | 派送失败 |
|
||
| problematic | 问题件 |
|
||
| deleted | 已删除 |
|
||
| claimed | 理赔件 |
|
||
|
||
#### 10.3.2 充值状态
|
||
|
||
| payStatus | 中文 |
|
||
| --- | --- |
|
||
| 0 | 待审核 |
|
||
| 1 | 成功 |
|
||
| 2 | 失败 |
|
||
|
||
#### 10.3.3 仓库类型
|
||
|
||
| warehouseType | 中文 |
|
||
| --- | --- |
|
||
| main | 主仓 |
|
||
| transit | 中转仓 |
|
||
| return | 退货仓 |
|
||
| other | 其他 |
|
||
|
||
#### 10.3.4 通知类型
|
||
|
||
| noticeType | 中文 |
|
||
| --- | --- |
|
||
| notice | 公告 |
|
||
| message | 站内信 |
|
||
|
||
#### 10.3.5 接收对象类型(通知)
|
||
|
||
| receiverType | 中文 | 说明 |
|
||
| --- | --- | --- |
|
||
| user | RBAC 用户(按 `user.userid`) | **`ReceiverIds` / `ReceiverId` 均存 `user.userid`(INT)**,与 JWT Claim **`mapId`** 一致 |
|
||
|
||
**与 RBAC 登录主体的关系**(见 **`docs/rbac系统设计-060801.md`**):
|
||
|
||
- 内部用户、客户主账号、子客户登录后 JWT 均带 **`mapId` = 对应行的 `user.userid`**。
|
||
- 通知模块**当前实现**(`NoticeService`)**仅接受 `receiverType=user`**,创建/更新时校验 `ReceiverIds` 均在 RBAC `user` 表存在;**不支持** `receiverType=customer`。
|
||
- 若需按 `t_customer.Id` 广播给客户,须在实现层扩展 `receiverType` 与校验逻辑,并同步改 OpenAPI 与本文 **§8.2.4 / §8.2.5**。
|
||
|
||
**说明**:**客户分组、客户状态** 等枚举见 RBAC 文档 **`CustomerStatus` / `CustomerGroup`**(`docs/rbac系统设计-060801.md` §6)。
|
||
|
||
#### 10.3.6 问题类型(`IssueTypeCodes`)
|
||
|
||
| code | 中文 |
|
||
| --- | --- |
|
||
| address | 地址异常 |
|
||
| customs | 清关问题 |
|
||
| weight | 重量异常 |
|
||
| document | 单证不全 |
|
||
| cargo | 货物异常 |
|
||
| channel | 渠道异常 |
|
||
| payment | 费用异常 |
|
||
| other | 其他 |
|
||
|
||
#### 10.3.7 理赔类型(`ClaimTypeCodes`,待实现)
|
||
|
||
| code | 中文 |
|
||
| --- | --- |
|
||
| lost | 丢件 |
|
||
| damaged | 破损 |
|
||
| delay | 延误 |
|
||
| customs | 清关理赔 |
|
||
| fee | 费用争议 |
|
||
| other | 其他 |
|
||
|
||
#### 10.3.8 订单处理子状态(`OrderHandleStatusCodes`)
|
||
|
||
| code | 中文 | 适用 `OrderStatus` |
|
||
| --- | --- | --- |
|
||
| pending | 待处理 | `problematic`、`claimed` |
|
||
| processed | 已处理 | `problematic`、`claimed` |
|
||
|
||
**说明**:非问题件/理赔件订单 `HandleStatus` 应为 **NULL**;列表筛选 `handleStatus` 仅在 Tab 为 `problematic` / `claimed` 时传入。
|
||
|
||
#### 10.3.9 订单来源(`OrderSourceCodes`)
|
||
|
||
| Code | 说明 |
|
||
| --- | --- |
|
||
| `manual` | 普通下单(我要下单 / 单笔小包创建);存量订单迁移默认值 |
|
||
| `pickup` | 揽收订单(§4.4.4) |
|
||
| `upload` | Excel 上传下单 |
|
||
| `batch` | JSON 批量创建 |
|
||
| `platform` | 平台店铺订单转 OMS(预留) |
|
||
|
||
### 10.6 接口实现状态总表(oms0414-back,2026-07-04)
|
||
|
||
> 状态:**已落地** = 有 `ApplicationService` 方法且与下表逻辑一致;**部分** = 已暴露 HTTP 但业务未达设计;**设计中** = 本文有契约、代码无接口。
|
||
|
||
#### 10.6.1 订单 `/order/*`
|
||
|
||
| 方法 | 路径 | 状态 | 实现类 / 备注 |
|
||
| --- | --- | --- | --- |
|
||
| POST | `/order/list` | 已落地 | `OrderService.GetPaginatedListAsync` — 客户过滤 `CustomerId`;支持 `orderSource` 筛选 |
|
||
| POST | `/order/pickup/create` | 已落地 | §4.4.4;`PickupOrderParcelBuilder` + `CreateParcelCoreAsync` |
|
||
| POST | `/order/sender-address/list` | 已落地 | §4.4.5 |
|
||
| GET | `/order/sender-address/detail` | 已落地 | §4.4.5 |
|
||
| POST | `/order/sender-address/create` | 已落地 | §4.4.5 |
|
||
| POST | `/order/sender-address/update` | 已落地 | §4.4.5 |
|
||
| POST | `/order/sender-address/delete` | 已落地 | §4.4.5 |
|
||
| POST | `/order/sender-address/set-default` | 已落地 | §4.4.5 |
|
||
| GET | `/order/detail` | 已落地 | 含收发件人、申报、四行重量 |
|
||
| POST | `/order/pay` | 已落地 | 扣余额 + `t_customer_account_flow`;见 **§4.4.1.3** |
|
||
| POST | `/order/pre-declare` | 部分 | 纯状态置 `predeclared` |
|
||
| POST | `/order/intercept` | 已落地 | `IsIntercept=1` |
|
||
| POST | `/order/cancel-intercept` | 已落地 | `IsIntercept=0` |
|
||
| POST | `/order/delete` | 已落地 | 软删 + `deleted` |
|
||
| POST | `/order/print` | 已落地 | HTML 面单 URL |
|
||
| POST | `/order/export` | 已落地 | Excel,24h 过期 |
|
||
| POST | `/order/price-check` | 已落地 | 见 §4.3.5、§4.4.2.1 |
|
||
| GET | `/order/template/list` | 已落地 | 客户见 standard + 自有 custom |
|
||
| GET | `/order/template/download` | 已落地 | 动态生成 Excel 表头 |
|
||
| POST | `/order/upload` | 已落地 | Excel 导入 → 批量任务 `import-row`,见 §4.4.3.5 |
|
||
| GET | `/order/batch/jobs` | 已落地 | 批量任务列表,客户隔离 |
|
||
| GET | `/order/batch/jobs/{id}` | 已落地 | 任务进度与汇总 |
|
||
| GET | `/order/batch/jobs/{id}/items` | 已落地 | 子项明细(含失败行、客户单号) |
|
||
| POST | `/order/batch/jobs/{id}/retry-failed` | 已落地 | 重试失败子项 |
|
||
| POST | `/order/batch/jobs/{id}/cancel` | 已落地 | 取消待处理子项 |
|
||
| POST | `/order/parcel/create` | 部分 | 卖价预报应收;无应付;Live 快照 |
|
||
| POST | `/order/parcel/batch-create` | 部分 | 同步批处理,见 §4.4.3.2 |
|
||
| POST | `/order/fee/batch-recalculate` | 设计中 | §4.4.1.10 |
|
||
| POST | `/order/track-upsert-small-bag` | 已落地 | 手动轨迹 UpsertSmallBag;见 **§4.4.1.11** |
|
||
| POST | `/order/mark-problematic` | 设计中 | §4.4.1.13;批量标记问题件 + 问题模板 |
|
||
| POST | `/order/mark-claimed` | 设计中 | §4.4.1.14;批量创建理赔件 |
|
||
| POST | `/order/handle-status/update` | 设计中 | §4.4.1.15;待处理→已处理 |
|
||
| POST | `/order/debug/track-upsert-small-bag` | 已落地 | Swagger 联调轨迹 |
|
||
|
||
#### 10.6.2 渠道 `/channel/*`
|
||
|
||
> 明细见 **§5.3.0**。
|
||
|
||
| 前缀 | 状态 | Service / 备注 |
|
||
| --- | --- | --- |
|
||
| `/channel/sales-product/*` | 已落地 | `SalesProductService` — 18 个端点:CRUD、`config/save`、价格 CRUD + **`price/save`**、排货、发布列表/预览/发布/回滚 |
|
||
| `/channel/service-channel/*` | 已落地 | `ServiceChannelService` — CRUD、分组 CRUD、买价 CRUD + **`price/save`**、**`publish/preview`**、发布/回滚 |
|
||
| `/channel/kpi-area/*` | 已落地 | 10 个端点:主表 CRUD + 明细分页 CRUD + **Excel 导入/模板** |
|
||
| `/channel/weight-rule/*` | 已落地 | 8 个端点:主表 + info 子表 CRUD(POST 路由) |
|
||
| `/channel/weight-rounding/*` | 已落地 | 8 个端点:同上 |
|
||
| `/channel/auto-trigger/*` | 部分 | CRUD + toggleStatus + **部分运行时**(事后拦截/警告/问题件);**V2 规则引擎 §5.3.0.6.1 未实现** |
|
||
| `/channel/label-template/save` | 已落地 | upsert,`Name+TemplateType` 唯一 |
|
||
|
||
**渠道发布与订单算价衔接**:发布快照写入 DB 后,订单 **`ResolveSnapshotBindingAsync`** 按 `PublishTime <= bindTime` 读取;**Redis 同步未实现**(§5.2.6.2)。
|
||
|
||
#### 10.6.3 操作 `/operation/*`
|
||
|
||
| 方法 | 路径 | 状态 | 备注 |
|
||
| --- | --- | --- | --- |
|
||
| POST | `/operation/inbound/create` | 部分 | 仅写 `t_inbound_record`,不联动订单算价 |
|
||
| GET | `/operation/inbound/recent-list` | 已落地 | |
|
||
| POST | `/operation/outbound/create` | 部分 | 仅写 `t_outbound_record` + 状态校验;不更新 `ship` 件重尺 / `pendingOnline` |
|
||
| GET | `/operation/outbound/recent-list` | 已落地 | |
|
||
| POST | `/operation/print-label/print` | 已落地 | 标签打印记录 |
|
||
| GET | `/operation/print-label/recent-list` | 已落地 | |
|
||
| GET | `/operation/print-label/template-list` | 已落地 | |
|
||
|
||
#### 10.6.4 充值 `/recharge/*`
|
||
|
||
| 方法 | 路径 | 状态 | 备注 |
|
||
| --- | --- | --- | --- |
|
||
| POST | `/recharge/recharge/submit` | 已落地 | multipart,最小 ¥0.01,流水 `RECHARGE{ts}{random}` |
|
||
| POST | `/recharge/recharge-record/approve` | 已落地 | 待审核 → 通过;**`CustomerBalanceMutator` + 流水 `recharge`** |
|
||
| POST | `/recharge/recharge-record/reject` | 已落地 | |
|
||
| GET | `/recharge/recharge-record/list` | 已落地 | 客户过滤;补齐 customerCode/phone |
|
||
| GET | `/recharge/export` | 已落地 | MiniExcel |
|
||
|
||
#### 10.6.4.1 客户账户流水 `/customer/account-flow/*`
|
||
|
||
| 方法 | 路径 | 状态 | 备注 |
|
||
| --- | --- | --- | --- |
|
||
| GET | `/customer/account-flow/list` | 已落地 | `CustomerAccountFlowService` — keyword/bizType/时间/customerId 分页;客户门户强制隔离 |
|
||
|
||
#### 10.6.5 系统 `/system/*`
|
||
|
||
| 方法 | 路径 | 状态 |
|
||
| --- | --- | --- |
|
||
| GET | `/system/country/list` | 已落地 |
|
||
| GET/POST | `/system/dict/*` | 已落地 — list/create/update/delete |
|
||
| GET/POST | `/system/warehouse/*` | 已落地 |
|
||
| GET/POST | `/system/notice/*` | 已落地 — 含 read-confirm |
|
||
|
||
#### 10.6.6 宿主 SimpleAdmin.Api(非 OMS.Application)
|
||
|
||
| 前缀 | 说明 |
|
||
| --- | --- |
|
||
| `/rbac/auth/*` | `AuthService` — 管理员/客户登录、微信登录、刷新、改密等 |
|
||
| `/wechat/*` | 公众号验签、扫码登录、OAuth 注册/绑定 |
|
||
|
||
客户 **`/customer/*`**、RBAC 管理 **`/rbac/user|role|menu|...`** 见 **`docs/rbac系统设计-060801.md`**。
|
||
|
||
---
|
||
|
||
*文档版本:v1.9 | 最后更新:2026-07-19 | 已补充 **§4.4.7** 店铺订单创建物流 / 物流&代发(校对弹窗设计);迭代 **20260719-01***
|
||
|
||
|