触发条件接入 AI 助手(选模型、发送 skill、一键填入);预报时机并入下单并支持公式验证;下单失败可继续修改或存草稿;补充订单附件、H5 下单与渠道买价相关优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
363 KiB
OMS 业务系统 — 表结构与接口设计
1 导读
1.1 这份文档解决什么问题?
- 订单:创建、预报、支付、状态机、费用与版本对齐、模板/批量任务、客户发件地址簿、揽收订单等。
- 渠道:销售产品、服务渠道、计价与发布、计重、分区、自动触发、标签等。
- 操作:入库、出库、面单打印等仓储作业。
- 充值:充值申请、审核及入账(客户余额权威在
t_customer.Balance;余额变动统一写入t_customer_account_flow资金流水,审核通过后由 OMS 客户服务更新余额)。 - 系统主数据:国家、字典、仓库地址、其他配置(含小包轨迹服务)、通知等
/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 |
| 系统面单 / 订单附件 | 独立表;系统面单仅 1 份;客户只读系统面单 | 已实现:t_order_file;创单成功后异步/同步生成 PDF 写入 systemLabel;打印优先读文件表,缺失则补生成;已废弃 t_order.LabelUrl / LabelGenerateStatus / LabelGenerateError |
| 规则快照下游元数据 | 排货 Provider 含渠道/itfx 对接字段 | 已实现:规则发布时 AllocationProviderSnapshotEnricher enrich Providers;排货确认写入 t_order 四字段;AppSecret 不落快照/订单 |
| 轨迹 / 下游预报 | 预报后异步轨迹;下游 createOrderOne | 部分:预报成功异步 UpsertSmallBag;「我的订单」可手动 POST /order/track-upsert-small-bag;下游预报 POST /order/downstream-forward-create-one(及批量任务);成功回写 DownstreamServiceNo / TrackNo,下游面单写入 t_order_file.downstreamLabel |
| 创建问题件 / 理赔件 | §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 发布表为审计与降级。
- 运营维护销售产品、卖价/买价、分区(KpiArea)、计重、排货、服务渠道等。
- 发布 产生 不可变版本;结构化快照写入 Redis,计费服务加载 本地内存。
- 查价与正式下单共用:按绑定时刻(如
bindTime)选取应对生效的 rules/price 版本 → 规则匹配(分区、计重、黑名单等)→ 出费用明细;查价响应宜带rulesVersion/priceVersion便于与订单对齐。
当前实现(oms0414-back):步骤 2~3 的 Redis/内存路径 尚未接入;运行时直接读
t_sales_product_publish_record,无发布时 Live 组装(见 §4.3.5)。
2.3 主流程 B:订单(客户侧业务链)
设计意图:先锁「计费契约版本」、再按履约阶段分批落费;历史订单不因后台新发版被静默改价。
- 查价(可选)→ 创建 / 预报 → 校验 → 草稿与包裹 → 预报计重完成后将全部启用卖价项写入
t_order_fee(ChargingStage=forecast)。 - 契约固化:预报等关键节点写入
SalesProductRulesVersion、SalesProductPriceVersion(及渠道侧应付相关版本,若有);非显式「改单重算」流程 不得 因后台新发布而改写已存版本号。 - 系统面单:创单进入
predeclared后触发生成(队列开启则异步generate-label-pdf,否则同步);PDF 落盘并写入t_order_file(FileType=systemLabel,仅 1 份可替换);失败不阻断创单,打印时补生成。模板解析顺序:订单LabelTemplateId→ 销售产品绑定模板 → 全局默认模板。 - 状态机(概念顺序;枚举见
## 10 附录→### 10.3):如 草稿 → 已预报 → 待排货(可选) → 待支付 → 待发货 → 待上网 → 运输中 → 派送成功/失败;另有拦截、问题件(problematic)、理赔件(claimed)、删除等分支。入库排货失败时进入pendingAllocation,写入AllocationPendingReason,仍保留收货与 inbound 应收。 - 支付:OMS 侧订单状态与支付动作对接;扣减
t_customer.Balance并 写入t_customer_account_flow(BizType=orderPay,金额为负);支付金额取订单t_order_fee应收合计。 - 分阶段计费(按件重尺类型):卖价/买价 不再配置「收费时机」;应收在
t_order_weight.Type=forecast或receive计重完成 后触发,全部启用价格项参与;t_order_fee.ChargingStage标记本次算价所用重量阶段(forecast/inbound)。出库阶段(ship行)待 §6.3.2.1 接入。 - 批量下单:Excel/模板 或 JSON 批量接口 → 异步任务;单次最多受理 2000 条。
- 揽收订单(§4.4.4):客户维护 发件地址簿(§4.2.10 / §4.4.5)→ 创建时选 发件地址 + 仓库 + 运输方式(销售产品)+ 揽收时间 + 货物名称/重量 → 服务端组装为小包创建入参 → 复用
CreateParcelCoreAsync/ §4.4.3.1 落t_order(OrderSource=pickup)。不是仓储模块C_parcel/ 到货登记另开一套订单主表。 - 店铺订单创建物流(§4.4.7):平台店铺单勾选 → 校对收件人/明细 → 选运输方式 → 批量创建
t_order(OrderSource=platform)并回写LinkedOrderId;若「物流&代发」则同弹窗内按单绑定包裹/库存(复用 §4.4.6 语义)。与「匹配库存」分流:后者依赖平台 SKU 映射并创建时扣减库存。
2.4 主流程 C:操作(仓库)
- 入库扫描:实重、尺寸等落库;仅更新
Type=receive件重尺并重算;可触发inbound阶段应收计费。 - 出库扫描:仅更新
Type=ship发货件重尺并重算计费重;订单 待发货 → 待上网,写入ShipTime;可触发outbound阶段应收计费。 - 标签打印:按单号与模板输出,记录打印结果。
2.5 主流程 D:充值
- 客户提交 →
t_recharge_record待审核。 - 审核通过/驳回;通过时 经
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/ FakeAggregateRoot的 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 | 更新时间(可空) |
建表 / 迁移检查清单
- 新建表:
CREATE TABLE必须含ConcurrencyStamp CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'(建议放在IsDeleted之后)。 - 增量脚本:若实体已有该属性而库表没有,须
ALTER TABLE ... ADD COLUMN ConcurrencyStamp ...(可参考sql/fix_concurrency_stamp_missing.sql)。 - 复制安全(Error/Note 1674):
DEFAULT (UUID())/UPDATE ... SET x = UUID()在 STATEMENT/MIXED binlog 下会被判为 unsafe(主从可能得到不同值)。迁移 DDL 一律用常量默认值;真实 UUID 由应用插入/更新时写入。若必须用UUID()回填历史行,会话临时SET SESSION binlog_format='ROW'(需权限)或接受仅在 ROW 复制拓扑执行。 - 评审:新迁移 PR 对照本节必备字段,禁止再出现「实体有列、库表无列」。
- 典型报错定位:接口返回
Unknown column 'ConcurrencyStamp'→ 查 SQL 中的表名 → 对该表补列后重试。
DDL 示例片段
`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、t_order_file 等通过 OrderId 间接隔离,禁止仅凭子表 Id 绕过主表 CustomerId 校验;t_customer_sender_address 以 CustomerId 直接隔离(§4.2.10) |
| 内部用户 | loginType=0 默认禁止调用 /admin/order/* 客户订单接口;若运营后台需跨客户查单,须 另设 内部管理路径与 PermissionCode,不得复用客户侧 /admin/order 且不带客户隔离 |
安全原则:
CustomerId不得由前端单独作为可信事实;不得仅按订单Id操作而不校验归属。
4 订单模块
4.1 核心需求说明
- 我的订单:支持订单全流程管理,包括分页查询、详情查看、支付、预报、拦截/取消拦截、删除、打印、导出、订单附件(§4.2.12 / §4.4.1.17);创建问题件、创建理赔件、处理子状态筛选;可按
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 | - | 否 | 跟踪号 |
| DownstreamServiceNo | VARCHAR | 64 | - | 否 | 服务商单号(下游 createOrderOne 成功响应 Data.serviceNo);OMS 自有运单号仍为 ServiceNo |
| 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) |
| LabelTemplateId | INT | - | 外键 → t_label_template.Id |
否 | 出单实际使用的标签模板 Id(模板配置,非面单文件地址;文件见 t_order_file.systemLabel) |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
已废弃字段(勿再写入):原
LabelUrl/LabelGenerateStatus/LabelGenerateError已迁至t_order_file(见 §4.2.12),迁移脚本删除订单侧列。
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 | - | 否 | 电话 |
| 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 |
| 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 | - | 否 | 电话 |
| 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(应用层维护) |
不变量
AvailableQty = Qty(或 Quantity) - FrozenQty,且三者均 ≥ 0。- 绑定保存:校验
BindQty ≤ AvailableQty(对本单原占用先释放再算)。 - 包裹 Tab 允许全部生命周期(
inTransit/arrived/stored)。 - 已上架包裹行(有
SkuId)绑定包裹时同时冻结对应库存,避免重复售卖。 - 保存采用全量覆盖:提交
items[]为最终态。
4.2.12 订单附件表 t_order_file
用途:订单级附件(交易证明、系统面单、下游面单、末端面单、红外图片)统一落库;不再在
t_order上存面单 URL / 生成状态字段。
系统面单:FileType=systemLabel仅允许 1 份;生成或上传时先软删同类型旧记录再插入(替换)。其它类型每类最多 20 份。
客户 ACL:客户登录仅可 查询systemLabel;禁止上传/删除任意附件类型。内部账号可管理全部类型。
迁移脚本:sql/20260728_order_file.sql(建表 + 迁移旧LabelUrl→systemLabel+ 删除订单旧列);注释补丁sql/20260728_order_file_comments.sql。
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | INT | - | 主键 | 是 | 自增 |
| OrderId | INT | - | 外键 → t_order.Id |
是 | 订单 Id |
| FileType | VARCHAR | 32 | - | 是 | 附件类型:tradeProof 交易证明 / systemLabel 系统面单 / downstreamLabel 下游面单 / lastMileLabel 末端面单 / infraredImage 红外图片 |
| FileName | VARCHAR | 255 | - | 否 | 原始文件名 |
| FileUrl | VARCHAR | 512 | - | 是 | 文件地址(相对路径如 /uploads/... 或外链) |
| ContentType | VARCHAR | 128 | - | 否 | 文件 MIME 类型 |
| FileSize | BIGINT | - | - | 否 | 文件大小(字节) |
| Sort | INT | - | - | 是 | 同类型内排序,默认 0 |
| Source | VARCHAR | 32 | - | 是 | 来源:upload 上传 / systemGenerate 系统生成 / downstream 下游回传 / migrate 迁移;默认 upload |
| Remark | VARCHAR | 512 | - | 否 | 备注 |
| IsDeleted | TINYINT | - | - | 是 | 软删除:0-否,1-是,默认 0 |
| ConcurrencyStamp | CHAR | 36 | - | 是 | 并发戳;见 §3.1.2,默认 UUID |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
索引
| 索引名 | 类型 | 字段 | 说明 |
|---|---|---|---|
| idx_ord_file_order_type | KEY | OrderId, FileType, IsDeleted | 按订单+类型查询 |
写入约定
| 场景 | FileType | Source | 说明 |
|---|---|---|---|
创单成功 / 打印补生成 / 批量 generate-label-pdf |
systemLabel |
systemGenerate |
LabelPdfGenerationService;替换旧系统面单 |
| 内部上传 | 任一类型 | upload |
POST /order/files/upload;systemLabel 仍仅 1 份 |
下游 createOrderOne 成功返回 labelUrl |
downstreamLabel |
downstream |
不写 t_order;与 DownstreamServiceNo/TrackNo 一并回写 |
历史 t_order.LabelUrl 迁移 |
systemLabel |
migrate |
一次性 SQL |
4.3 下单计费与版本策略(配置 → 运行时 → 下单)
本节约定销售产品维度的规则/卖价如何进入线上算价链路,以及与
t_order版本字段的对应关系;与渠道模块中「运营发布 → 生成版本」接口衔接。
4.3.1 总体流程
运营新建版本 → 录入/维护规则与卖价 → 发布并设为生效
↓
系统组装结构化 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)
- 筛选:
status=1的全部卖价项。 - 重量:按触发节点取
forecast/receive/ship行计费重。 - 输出:upsert
t_order_fee,幂等键OrderId + CostItem + ChargingStage。 - 已实现:
/order/parcel/create、ApplyReceiveBillingByWaybillAsync。
4.3.4.2 应付费用重算(Type=payable)
- 排货选渠:读取订单绑定 规则快照 内
Providers与Scheme;按 客户范围(CustomerIds空=全部)过滤候选服务商;对每个候选 服务渠道 试算买价总额,再按方案选型:costLowest:应付总额最低;timeFastest:MinTime最小;overallBest:Weight最大。 选中后回写t_order.ChannelCode,并自选中Providers项固化下游对接字段:DownstreamSupplierCode、DownstreamAppKey、DownstreamAppId、ChannelForecastCode(实现:ConfirmServiceChannelAndPayableAsync→AllocationProviderSnapshotEnricher.ApplyDownstreamFieldsToOrder)。
- 筛选:
status=1的全部买价项(优先读渠道买价发布快照,无则 Live 组装)。 - 重量:预报阶段取
downstream行计费重;入库阶段取receive行计费重(收货复量后重新排货)。 - 输出:upsert
t_order_fee,幂等键OrderId + CostItem + ChargingStage;PriceNo写渠道买价版本。 - 调用方:
/order/parcel/create(预报)、ApplyReceiveBillingByWaybillAsync(入库)、/order/fee/recalculate-payable。 - 下游运行时读取:轨迹 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)
bindTime = DateTime.Now。- 分别查询
t_sales_product_publish_record:OperatorType = 'rules' | 'price',PublishTime <= bindTime,按发布时间降序取生效记录。 - 有发布记录:反序列化
Content为规则/价格快照 DTO;价格快照Prices为空 → 失败 「未命中价格快照版本(内存/Redis/DB)」。 - 无发布记录(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。
- 规则:从
- 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 我要下单 中列出的全部接口,以及同模块后续增补的订单相关接口。
会话客户上下文
- 从 JWT 解析
mapId、loginType(见 §3.3.2)。 - 仅
loginType=1(客户登录) 允许访问本组接口;否则返回 401/403。 - 查询
t_customer:UserId = mapId AND IsDeleted = 0,得到effectiveCustomerId = t_customer.Id。 - 若
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、GET/POST /order/files* |
打印/导出/附件前校验归属;导出筛选范围 限定当前客户;客户附件仅可读 systemLabel |
批量与关联资源
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 已落地)
- 封装
ResolveSessionCustomerAsync/ResolveListCustomerIdAsync(OmsSessionHelper),创建类接口 先解析effectiveCustomerId再校验其它字段;列表/导出/任务列表 强制WHERE CustomerId = effectiveCustomerId(禁止仅用CreateUserId过滤)。 - 列表类 Repository 查询 统一注入
CustomerId条件,禁止仅靠前端传参过滤。 GetOrderOrThrowAsync(id)在存在性校验后增加order.CustomerId == effectiveCustomerId,不匹配返回 「订单不存在」。- 创建类接口(
parcel/create、batch-create、upload):未传customerId时由会话补全;已传则须与会话一致;落库 仅写入effectiveCustomerId。 - 单测/集成测覆盖:客户 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 | 打印订单;优先返回 t_order_file.systemLabel,缺失则补生成 PDF;须校验订单归属 |
| 订单附件列表 | GET | /order/files | 按类型分组;客户仅返回 systemLabel;须校验订单归属 |
| 上传订单附件 | POST | /order/files/upload | multipart;客户账号 禁止;须校验订单归属 |
| 删除订单附件 | POST | /order/files/delete | 客户账号 禁止;须校验订单归属 |
| 导出 | 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 | 问题件/理赔件 待处理→已处理;须校验订单归属 |
| 预报下游订单 | POST | /order/downstream-forward-create-one | OMS 组装 createOrderOne 并转发 itfx;成功回写服务商单号/跟踪号,下游面单入 t_order_file(§4.4.1.12) |
4.4.1.1 分页查询订单列表(POST /order/list)
1 接口请求示例
请求 /order/list(POST,分页参数放 Body)
{
"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)
{
"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 接口业务逻辑
核心流程
- 解析会话
effectiveCustomerId(§4.4.0)。 - 构造分页查询条件时 固定追加
t_order.CustomerId = effectiveCustomerId(及IsDeleted = 0、状态/关键词/时间等前端筛选)。 - 忽略请求体中任意用于切换客户的
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
响应
{
"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 接口业务逻辑
核心流程
- 按
id查询订单;不存在或IsDeleted=1时返回「订单不存在」。 - 校验
t_order.CustomerId == effectiveCustomerId(§4.4.0);不匹配时 同样返回「订单不存在」(禁止泄露他客订单)。 - 映射详情并返回。
校验与异常
- 禁止仅凭
id跨客户读取。
4.4.1.3 支付(POST /order/pay)
1 接口请求示例
请求 /order/pay(POST)
{
"id": 1,
"requestId": ""
}
requestId 幂等,可选。
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
GetOrderOrThrowAsync(id):订单存在且客户隔离通过。- 状态校验:若已
pendingShip且已有PayTime→ 「订单已支付」;若OrderStatus非pendingPayment且非received(received为入库后历史数据兼容)→ 「当前订单状态不可支付」。 - 应付金额:汇总该订单
t_order_fee中Type=receivable的Amount;≤ 0 → 「订单应收金额无效,无法支付」。 - 扣款:加载
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位随机}。
- 校验
- 更新订单:
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)
{
"id": 1,
"requestId": ""
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
GetOrderOrThrowAsync(id)。- 直接将
OrderStatus置为predeclared并保存。 - 不重跑计重/算价/黑名单;不校验是否已有预报费用。
3 当前实现(oms0414-back)
- 实现:
OrderService.PreDeclareAsync— 纯状态更新,供运营手工补状态。
4.4.1.5 拦截(POST /order/intercept)
1 接口请求示例
请求 /order/intercept(POST)
{
"id": 1,
"requestId": ""
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
GetOrderOrThrowAsync(id)→ 设置IsIntercept = 1并保存。
3 当前实现(oms0414-back)
OrderService.InterceptAsync。
4.4.1.6 取消拦截(POST /order/cancel-intercept)
1 接口请求示例
请求 /order/cancel-intercept(POST)
{
"id": 1,
"requestId": ""
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
GetOrderOrThrowAsync(id)→ 设置IsIntercept = 0并保存。
3 当前实现(oms0414-back)
OrderService.CancelInterceptAsync。
4.4.1.7 删除(POST /order/delete)
1 接口请求示例
请求 /order/delete(POST)
{
"id": 1,
"requestId": ""
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
GetOrderOrThrowAsync(id)。OrderStatus = deleted,调用仓储 软删除(IsDeleted=1)。
3 当前实现(oms0414-back)
OrderService.DeleteAsync— 先更新状态再DeleteAsync软删。
4.4.1.8 打印(POST /order/print)
1 接口请求示例
请求 /order/print(POST)
{
"id": 1,
"requestId": ""
}
响应
{
"code": 0,
"msg": "success",
"data": {
"printUrl": "/uploads/prints/orders/....pdf",
"labelGenerateStatus": "Ready",
"labelGenerateError": null
}
}
labelGenerateStatus/labelGenerateError为 本次响应瞬时字段(Ready / Failed),不落库。
2 接口业务逻辑
核心流程
GetOrderOrThrowAsync(id)→ 映射订单详情。- 若已存在
t_order_file(FileType=systemLabel) 有效记录,直接返回其FileUrl作为printUrl。 - 否则调用
LabelPdfGenerationService.GenerateAndPersistAsync:按模板渲染 PDF → 落盘/uploads/prints/orders/→ 替换写入systemLabel;成功返回 PDF 相对路径。 - PDF 仍失败时:回落
CreateOrderPrintFileAsync生成简易 HTML stub,避免彻底不可打;响应带失败原因。
3 当前实现(oms0414-back)
OrderService.PrintAsync+OrderFileManager+LabelPdfGenerationService;批量打印走PrintForBatchAsync/ 批量任务action=print。
4.4.1.9 导出(POST /order/export)
1 接口请求示例
请求 /order/export(POST)
{
"status": "all|draft|...",
"keyword": "",
"createTimeStart": "",
"createTimeEnd": ""
}
响应
{
"code": 0,
"msg": "success",
"data": {
"downloadUrl": "",
"expireAt": ""
}
}
2 接口业务逻辑
核心流程
- 客户登录时
ResolveListCustomerIdAsync限定导出范围。 - 按
status、keyword、createTimeStart/End查询订单(与 list 同 predicate)。 - MiniExcel 写入
wwwroot/uploads/exports/orders/orders-{timestamp}.xlsx。 - 清理 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)
{
"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行已存在且有效)。
响应
{
"code": 0,
"msg": "success",
"data": {
"total": 3,
"successCount": 2,
"failCount": 1,
"failures": [{ "orderId": 1003, "reason": "缺少 t_order_weight.Type=downstream" }]
}
}
2 接口业务逻辑
核心流程
- 校验:
orderIds去重、长度上限(如 ≤500,可配置)、订单存在且IsDeleted=0;逐单校验t_order.CustomerId = effectiveCustomerId(§4.4.0),越权单记入failures或整批拒绝;调用方须已登录且为loginType=1客户。 - 当
scopes含receivable(重算应收)- 逐单加载:绑定
SalesProductPriceVersion卖价快照、t_order_weight、t_order_fee既有应收行(若策略为全量重算可先逻辑清理再写)。 - 按 §4.3.4.1:全部启用卖价项;重量取对应
t_order_weight行ChargeableWeight,upsert 应收。
- 逐单加载:绑定
- 当
scopes含payable(重算应付)- 逐单校验
t_order_weight存在Type=downstream且可用于买价匹配;加载绑定 买价 快照。 - 按 §4.3.4.2:仅
downstream行参与重量/件重尺因子,重算并 upsertType=payable。
- 逐单校验
- 事务:建议 按订单 单事务提交,避免一批中一单失败影响其他单;
failures逐单记录原因。 - 与履约链路的关系:预报成功、入库完成、出库完成 应在各自流程内调用 §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)
{ "id": 1001 }
响应(SmallBagTrackUpsertResult)
{
"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 接口业务逻辑
- 校验订单存在、未删除、归属当前客户(§4.4.0);
ServiceNo非空。 - 组装
SmallBagTrackUpsertRequest:客户编码、运单号、跟踪号、渠道、产品、收件人国家/省/邮编等。 ApiCode:优先t_order.ChannelForecastCode;为空且ChannelCode有值时,回退查t_service_channel.ForecastCode。- 轨迹凭证读
t_system_other_config(ConfigCode=track_service),缺省回退Oms:TrackService(§8.2.7);未启用或参数不全时返回skipped=true及原因。 - 联调入口:
POST /order/debug/track-upsert-small-bag(Swagger,可自定义测试字段)。
4.4.1.12 预报下游订单(POST /order/downstream-forward-create-one)
实现状态:已落地(OMS 后端转发 itfx;亦可批量任务
downstream-forward-create-one)。
运营在「我的订单」勾选订单后,由 OMS 组装 createOrderOne Body 并调用 itfx 下游;凭证优先读订单绑定字段(§4.3.4.2)。
1 凭证解析优先级
| 顺序 | 来源 | 字段 |
|---|---|---|
| 1(优先) | t_order 绑定字段(排货确认时写入) |
downstreamSupplierCode、downstreamAppKey、downstreamAppId |
| 2(回退) | 渠道树 live 查询 | ChannelCode → t_service_channel → 分组 → itfx App / ServiceProvider |
AppSecret 不落订单、不落规则快照;无论哪条路径,均按 downstreamAppId(或分组 AppId) 调 itfx GET /App/GetInfo 读取。
2 成功回写
| 下游 Data 字段 | 落库位置 |
|---|---|
serviceNo |
t_order.DownstreamServiceNo(OMS 自有运单号 ServiceNo 不变) |
trackNo |
t_order.TrackNo(空值不覆盖已有) |
labelUrl |
t_order_file,FileType=downstreamLabel,Source=downstream(不写订单主表) |
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 请求示例
{
"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 响应示例(部分失败)
{
"code": 0,
"msg": "success",
"data": {
"successCount": 1,
"failures": [
{ "orderId": 1002, "serviceNo": "LP002", "reason": "仅草稿或已删除订单不可标记问题件" }
]
}
}
5 落库与副作用
- 更新
t_order:OrderStatus=problematic,HandleStatus=pending,问题字段、IssueTime=now,PreviousOrderStatus。 - 插入
t_order_exception_event:EventType=mark_problematic,快照标题/内容/模板/通知方式。 - 通知(二期):引用问题模板时读取
NotifyMethod并投递(inApp站内信、email操作人 SMTP 已接入;sms/feishu/wechat待通道对接);通知失败 不回滚 问题件标记。 - 客户隔离:逐单
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 请求示例
{
"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 落库
t_order:OrderStatus=claimed,HandleStatus=pending,理赔字段、ClaimTime=now,PreviousOrderStatus;清空问题件专用字段(若曾有问题字段残留)。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 请求示例
{
"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 附件(§4.4.1.17):内部可上传各类型;客户门户仅展示系统面单只读 |
| 非问题件/理赔件 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.1.17 订单附件(GET/POST /order/files*)
实现状态:已落地(
OrderService.OrderFiles+OrderFileManager;前端订单详情抽屉「附件」Tab)。
1 查询(GET /order/files?orderId=)
- 校验订单归属。
- 返回按
FileType分组的附件列表;客户登录仅含systemLabel,其它类型键可不返回或为空数组。
2 上传(POST /order/files/upload,multipart)
- 表单:
orderId、fileType、files。 - 客户账号禁止;内部账号校验订单归属后写入。
systemLabel仅允许 1 个文件(替换旧记录);其它类型合计不超过 20。
3 删除(POST /order/files/delete)
{ "id": 1 }
- 客户账号禁止;软删
t_order_file行。
4.4.2 我要查价
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 查价 | POST | /order/price-check | 按销售产品最新生效卖价版本试算运费并返回;须客户登录(§4.4.0) |
4.4.2.1 查价(POST /order/price-check)
1 接口请求示例
请求 /order/price-check(POST)
{
"destinationCountry": "US", // 目的国二字码
"shippingMethod": "", // 运输方式
"cargoWeight": "2", // 货物重量(kg)
"length": "20", // 长(cm)
"width": "15", // 宽(cm)
"height": "10", // 高(cm)
"cargoType": "普货", // 货物类型
"destinationProvince": "", // 目的省州
"postalCode": "" // 邮编
}
响应
{
"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 接口业务逻辑
核心流程
- 校验:
destinationCountry必填(ISO 二字码);cargoWeight须 > 0;尺寸可选 — 全空则按实重估算,或长/宽/高三者齐全(部分填写 → 报错)。 - 绑定时刻:
bindTime = DateTime.Now;解析卖价最新生效快照(ResolveSnapshotBindingAsync)。 - 产品范围:未传
shippingMethod时遍历 全部已启用 销售产品;有值则按代码/名称过滤;未找到 →failures[0].step=product。 - 逐产品试算(按快照明细匹配;无邮编时跳过黑名单;分区无匹配时可 zone 兜底 取最低价分区,标记
quoteLevel=estimated):- 分区:用 目的国 + 邮编(及省州/城市若有)匹配;未命中且无兜底 →
step=zone。 - 计重:无尺寸时按实重;有尺寸则体积重参与;失败 →
step=weight。 - 货物/仓库:未筛选时展开 general/electronics/sensitive × 全部仓库 组合试算。
- 算价:
CalculateQuoteReceivableFees;无明细命中 →step=price。
- 分区:用 目的国 + 邮编(及省州/城市若有)匹配;未命中且无兜底 →
- 结果:成功写入
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。
{
"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" // 备注
}
]
}
响应
{
"code": 0,
"msg": "success",
"data": {
"clientNo": "",
"serviceNo": "",
"trackNo": "",
"labelUrl": ""
}
}
幂等时可选传 requestId。
2 接口业务逻辑
核心流程
- 本接口用于创建小包订单预报数据,执行顺序固定为:参数校验 -> 查询销售产品与规则/价格快照绑定 -> 黑名单校验(命中则中止,不落库) -> 创建订单草稿 -> 偏远校验 -> 计费重计算 -> 仅「预报」时机费用计算与落库 -> 更新订单状态 -> 返回结果。(入库/出库时机费用见 §4.3.4,由仓储收货、出库流程触发。)
- 参数校验失败,或销售产品/快照绑定失败、黑名单校验命中时,均不创建任何订单数据,直接返回业务错误。
- 进入草稿创建后,先将订单主数据、发件人、收件人、申报明细、预报件重尺数据落库,订单状态固定写入
draft。 - 后续偏远、计费重、费用等流程全部基于已创建的草稿订单继续处理;如任一业务环节失败,订单状态不扩展新枚举,统一保留
draft,并在t_order.PredeclareFailReason记录失败原因。
详细业务逻辑
-
参数解析与基础数据校验
- 会话客户(优先):调用
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。
- 会话客户(优先):调用
-
查询销售产品配置(用于定位发布记录)
- 查询
t_sales_product,条件:ProductCode = productCode and IsDeleted = 0 and IsActive = 1 - 返回字段:
Id、ProductCode、ProductName - 如不存在,不创建订单,直接返回失败(错误信息需明确「销售产品不存在或未启用」)。
- 查询
-
绑定销售产品计费快照(强关联:以创建订单当刻绑定的价格与规则为准;运行时优先 Redis + 本地内存)
- 绑定时点:以本次请求处理时间作为
bindTime(草稿落库后,t_order.CreateTime应与该时点一致或可由该时点推导)。 - 主路径(推荐):按
salesProductId = t_sales_product.Id与bindTime,从 Redis / 计费服务本地内存 解析「当前应对外生效」的 规则快照 与 价格快照(结构化 JSON,与运营发布组装的版本一致),得到:rulesSnapshot:含version、effectiveStartAt(或等价字段)、contentpriceSnapshot:含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。
- 绑定时点:以本次请求处理时间作为
-
黑名单校验(拦截型,来自已绑定的 rules 快照
content)- 读取
rulesSnapshot.content.areas.blacklist.rules(国家/省市/邮编区间规则集合)。 - 匹配规则:
- 国家必须匹配参数校验阶段已解析的收件国家编码(与后续写入
t_order.ConsigneeCountry的值一致,来源于t_country.CountryCode) - 若规则配置了省/市,则请求体收件人
receiver的省/市也必须匹配 - 邮编落在任一
postcodeRanges.left ~ postcodeRanges.right区间内即视为命中
- 国家必须匹配参数校验阶段已解析的收件国家编码(与后续写入
- 命中处理:不创建订单,直接返回失败(错误信息需明确「收件地址命中黑名单区域」),不进入草稿落库及后续流程。
- 读取
-
创建订单草稿
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固定写draftConsigneeCountry取收件国家对应的t_country.CountryCodeIsIntercept初始写0IsRemote初始写0PredeclareFailReason初始写空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.7Type枚举一致) - 写入字段:
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更新差异化测量)
- 一次性写入 4 条记录,
-
偏远区域校验(标记型,来自已绑定的 rules 快照)
- 读取
rulesSnapshot.content.areas.remote.rules(国家/省市/邮编区间规则集合)。 - 匹配规则与黑名单一致。
- 命中则更新
t_order.IsRemote = 1;未命中则更新t_order.IsRemote = 0 - 偏远校验不拦截订单,只影响后续费用匹配。
- 读取
-
计费重计算(有效规则 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):gw = ActualWeight(实重,kg)。- 依次遍历
rules中每一行(已按Id升序):对该行解析volumetricFactor为正数,计算vw = (Length × Width × Height) / volumetricFactor(与产品历史口径一致:体积 cm³ ÷ 系数 → 体积重 kg;若实现使用 m³,须在服务内统一换算)。 - 将该行的
gw、vw代入condition(支持>、>=、<、<=、==等,运算对象可为gw、vw及常量;不支持跨行交叉引用)。 - 第一条使
condition为真的行:命中行;若无一命中,失败(PredeclareFailReason建议「计重规则条件均未命中」)。 - 命中行的
billableWeight取值仅为gw或vw(大小写不敏感):rawW = (billableWeight == 'gw') ? gw : vw。 - 写回
t_order_weight.VolumetricWeight:建议取命中行对应的vw。预报草稿将同一vw写入四行(创建时初始拷贝策略)。入库仅写receive行;出库仅写ship行;均不向其它Type同步。
- 输入:仅使用
-
计费重进位(有效进位 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) × uceil为数学向上取整;实现须固定小数位数(如 3 位)避免浮点误差。
- 更新
t_order_weight.ChargeableWeight:预报阶段四行同步为同一计费重。入库仅写receive行**;**出库**仅写 **ship行;均不向其它Type同步。若public为空或非法,失败。
- 解析
-
价格分区匹配(来自已绑定的 rules 快照)
- 读取
rulesSnapshot.content.areas.price.rules,按收件国家/省市/邮编区间匹配得到zoneName(如“美国西部”)。 - 若未匹配到价格分区,更新
t_order.PredeclareFailReason = '价格分区未匹配(规则快照)',订单状态保持draft,返回失败。
- 读取
-
费用计算与费用落库(预报节点仅应收)
- 重算应收(§4.3.4.1):全部启用卖价项;重量取
Type=forecast;upsertChargingStage=forecast。 - 不在创建阶段排货/写应付;渠道与应付在 入库
ApplyReceiveBillingByWaybillAsync中TryConfirmServiceChannelAndPayableAsync处理(见 §6.3.1.1)。 - 强校验:存在启用卖价项但未匹配到任何明细时,预报失败。
- 固定报价示例规则(某产品,来自 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,总费用 = 18ChargeableWeight = 1.2kg,超出0.2kg,按 0.5kg 进 1 个续重单位,总费用 = 18 + 6 = 24ChargeableWeight = 1.6kg,超出0.6kg,按 0.5kg 进 2 个续重单位,总费用 = 18 + 12 = 30
- 该示例说明:计费重来源仅为
t_order_weight,不参与t_order_declaration的商品重量汇总逻辑。
- 成功完成预报
- 当偏远标记完成、计费重计算完成、本阶段(预报)应计费用写入完成后(黑名单已在草稿落库前完成校验):
- 更新
t_order.OrderStatus = 'predeclared' - 更新
t_order.PredeclareFailReason = '' - 更新
t_order.UpdateUserId、t_order.UpdateTime
- 更新
- 系统面单:调用
EnqueueGenerateLabelPdfAsync(MQ 开启则入队generate-label-pdf,否则同步生成);写入t_order_file.systemLabel;失败不阻断创单。 - 返回字段:
clientNo:取t_order.ClientNoserviceNo:取t_order.ServiceNotrackNo:取t_order.TrackNolabelUrl:取已生成的 系统面单FileUrl;若尚未就绪则为空字符串(异步队列场景常见)
状态与失败处理
- 参数校验失败:不创建订单,直接返回错误。
- 销售产品不存在或未启用、规则/价格快照未命中、黑名单命中:均不创建订单,直接返回错误(与参数校验一致,不落库,不写
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;触发系统面单生成写入 t_order_file.systemLabel;响应 labelUrl 读文件表(未就绪可为空) |
| 失败 | 草稿已建则 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幂等。
[
{
"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" // 备注
}
]
}
]
响应
{
"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 接口业务逻辑
核心流程
- 校验数组非空、
Count ≤ 2000(常量ParcelBatchMaxCount)。 - 生成
batchNo,按序for循环 调用CreateParcelCoreAsync。 - 每条
try/catch:成功累加successCount并填充items(含labelUrl);失败写入items且success=false。 - 返回
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=
响应
{
"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 行;超出则拒绝本次上传并返回错误,不创建任务。
响应
{
"code": 0,
"msg": "success",
"data": {
"jobId": 1001,
"async": true
}
}
jobId:批量任务 ID,前端跳转 批量任务详情页(/order/batchJobs?jobId=)。async:true表示 RabbitMQ 异步入队处理;false表示 MQ 未启用、已在当前请求内同步处理完毕。
2 接口业务逻辑
核心流程
ResolveEffectiveCustomerAsync→effectiveCustomerId;校验模板可见性(standard或归属客户的custom)。- 解析 Excel 有效数据行(最多 2000 行),每行映射为
OrderParcelCreateInput字段集。 - 创建
t_order_batch_job(Action=import-row,PayloadJson含taskName、templateId)及t_order_batch_job_item(每行一条,PayloadJson含rowNumber、rowDataJson)。 - MQ 开启:逐子项发布 RabbitMQ,异步调用
CreateParcelCoreAsync(§4.4.3.1 全链路)。 - MQ 关闭:同步逐条
ProcessAsync,当场更新子项状态与任务汇总。 - 返回
{ 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
响应(任务详情摘要)
{
"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"
}
}
响应(子项摘要)
{
"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 与现有能力结合(总览)
客户维护发件地址簿(§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 请求示例
{
"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 业务处理
- 解析
effectiveCustomerId(§4.4.0)。 - 校验必填:
productCode、warehouseId、senderAddressId、pickupTime、cargoName、actualWeight。 - 查询
t_customer_sender_address:Id = senderAddressId AND CustomerId = effectiveCustomerId AND IsDeleted = 0;否则 「发件地址不存在」。 - 查询
t_warehouse_address:同 §4.4.3.1;不存在则失败;按 §4.4.4.1 组装receiver。 - 组装
OrderParcelCreateInput(§4.4.4.2)→ 调用CreateParcelCoreAsync。 - 在写入
t_order时(扩展点):OrderSource = pickup,PickupTime = pickupTime,CustomerSenderAddressId = senderAddressId。- 实现建议:给
CreateParcelCoreAsync/ 草稿插入增加可选「来源上下文」参数,避免复制整段创建代码。
- 实现建议:给
- 后续偏远/黑名单/计费/预报与 §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)
{
"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)
{
"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-backOrderService.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(必填)。
响应
{
"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)
{
"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)
{
"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)
{
"orderId": 1001,
"items": [
{ "bindType": "inventory", "skuId": 88, "bindQty": 2, "inventoryId": null },
{ "bindType": "parcelItem", "parcelId": 50, "parcelItemId": 501, "bindQty": 1 }
]
}
服务端步骤
- 校验订单归属;
OrderStatus为已删除/已取消则拒绝;若已有deducted明细则拒绝改绑(须先业务回滚,本期不做回滚接口)。 - 解析仓库;无仓库且候选依赖仓库时拒绝。
- 读取现有
Status=bound明细 → 全部释放冻结 → 软删/标released。 - 按
items[]重新冻结并插入bound明细(空数组等价清空)。 inventory:按inventoryId或 SKU 多库位分摊冻结C_inventory。parcelItem:冻结C_parcel_item;若行上已有SkuId,同步按 SKU 冻结库存。- 不校验申报明细数量。
- 事务提交;响应同 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 用户故事与校对目标
作为客户,我勾选若干待履约店铺单后:
- 创建物流订单:只关心「运输方式是否选对、收件人/国家/地址/电话是否与平台单一致、申报品名数量是否对」。确认后批量生成 OMS 物流单。
- 创建物流&代发订单:在上述校对之外,还要确认「每一张店铺单各自绑了哪些库存 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 单 │
│ 主按钮「确认创建」/「确认创建并绑定」 │
└─────────────────────────────────────────────────────────────┘
校对优先的交互细则
- 打开即选中第一单;右侧默认展示该单收件人 + 明细,强迫用户「看见」再提交。
- 收件人校对卡(只读为主,允许本弹窗内临时改写收件人字段,仅影响本次创建入参,不写回店铺主数据除非另开编辑):
- 姓名、电话、国家二字码、省/市、邮编、详细地址分行展示;缺关键字段标红「缺项」。
- 与列表列
recipientName/orderCountry同源(店铺单结构化收件字段,见拉单落库)。
- 商品明细表:平台 SKU、品名、数量、单价;排除
IsShipExcluded行;空行则该单不可提交。 - 共享运输方式:顶部单一
a-select(销售产品,同匹配库存下拉数据源);所有勾选单共用;变更后底部摘要同步刷新。 - 仅物流:仓库可选(有则写入订单
WarehouseId;无则创建链路按小包规则处理)。右侧无绑定区,仅校对。 - 物流&代发:
- 仓库 必填;变更仓库时清空各单已选绑定草稿并提示。
- 右侧绑定区复用「我的订单」
StockBindModal交互语义(左候选库存/包裹 Tab + 搜索;右已选 + 数量步进);数据先落在 前端草稿(按storeOrderId分桶),提交时一并传给后端。 - 左清单每行徽标:
未绑定(灰)/已绑 n 件(主色)。 - 提交门槛:每一单至少 1 条绑定且数量 >0;未就绪单在清单上抖动/标红,点击跳转到该单。
- 二次确认(
Modal.confirm):展示一行摘要「将创建 N 张物流单,运输方式 XXX;(代发)仓库 YYY,共绑定 Z 件」。确认后才调接口。 - 结果反馈:成功/失败按单展示(对齐
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 |
建单 + 按单保存绑定(冻结) |
共同请求字段
{
"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 |
忽略 | 必填且非空 |
共同响应(对齐批量匹配)
{
"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(单条)
- 客户隔离 +
EnsureCreatableAsync(同匹配:未搁置、pending、无有效 LinkedOrder)。 - 加载店铺单行(排除
IsShipExcluded);用平台行组装申报(PlatformSku/ProductName/ 数量 / 单价;重量缺省最小值)。不要求MappedSkuId。 - 收件人:
receiver覆盖 > 店铺结构化收件字段 >PlatformExtraJson解析(与现网ParseReceiver一致)。 RequestId = platform-store-order:{storeOrderId}(与匹配库存共用幂等键,避免重复建单)。OrderSource=platform,ClientNo=PlatformOrderNo,ProductCode、可选WarehouseId。CreateParcelForBatchAsync→ 回写LinkedOrderId、FulfillStatus=toShip、LastSyncTime;有 TrackNo 可写ShippingLabelNo。- 不扣减库存。
B. batch-create-fulfill(单条)
- 执行 A;成功后立刻按
bindItems调用与 §4.4.6.5 相同的冻结保存逻辑(orderId= 新建物流单 Id,warehouseId= 请求仓库)。 - 若绑定失败:回滚本条(软删/取消新建物流单或标记删除 + 清空
LinkedOrderId,并释放已冻冻结),该条记入failCount;不影响批次内其他已成功单。 - 绑定数量与申报件数 不强制对齐(同 §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 验收要点(客户校对场景)
- 缺收件人国家/地址时,右侧标红且无法提交该单(或整批拦截并定位到问题单)。
- 运输方式未选,主按钮禁用。
- 代发:未绑任何资源时主按钮禁用;换仓后绑定草稿清空。
- 提交后列表出现
linkedServiceNo,履约状态变为待发货;代发单在「我的订单」打开绑定详情可见冻结行。 - 对已关联物流单再次点击两入口,前置校验拒绝。
- 与「匹配库存」并行存在,文案不混淆(工具栏分组或次要按钮区分)。
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快照)结构规范(用于限制校验与计费)
设计目标:
- 强绑定有效期:订单创建时按当时的生效版本(PublishTime)绑定发布记录;后续计算以绑定版本为准。
- 计算快:下单/计费/校验不再联查
t_kpi_area*、t_weight_rule*、t_weight_rounding*、t_sales_product_price*等多表;只读取一次发布记录Content(可缓存)。- 可回溯:任何时候都能根据订单绑定的发布记录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 加载或启动预热)。
发布同步流程(必须):
- 业务侧完成一次规则或价格发布,写入
t_sales_product_publish_record(含Version、Status、PublishTime、Content)。 - 将
Content标准化后写入 Redis(对应OperatorType的 key);更新current指针指向新版本(与「生效」语义一致)。 - 通知各计费实例刷新内存;实例冷启动可从 Redis 全量加载,必要时可从发布表补齐缺失版本。
- 若 Redis 写入失败:应阻断或重试发布,直至线上可见一致快照,避免「库里有版本但线上算价仍用旧缓存」。
1) 通用字段(rules / price 共用)
schemaVersion:快照结构版本(从 1 开始)operatorType:rules或price(与发布记录OperatorType保持一致)salesProduct:发布时的产品基础信息快照(用于展示与校验,不参与联查)effective:startAt:生效时间(一般等于PublishTime)endAt:失效时间(可为空,表示至今有效;当有新版本发布时可由系统补齐旧版本endAt)
checksum:可选,用于校验内容是否被篡改(如 sha256)
2) rules 快照内容(限制 + 计重 + 进位)
Content 示例(OperatorType=rules):
{
"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):
{
"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 匹配问题模板并标记 |
同左 |
两类执行阶段:
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):
{
"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)条件表达式模型
叶子节点(比较):
{ "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) |
分组节点:
{ "logic": "and|or", "children": [ /* 叶子或分组 */ ] }
否定(可选):
{ "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)验收标准(本例)
- 在
/channel/autoTrigger配置 §3 示例规则并启用。 - 预报一单:GB、客户名含「阿里国际站/深圳一达通」、尺寸 61×40×40、产品非
EX_ASP_HKDHL_YD→ 接口失败,文案「超尺寸,禁止下单」,不产生predeclared订单。 - 将产品改为
EX_ASP_HKDHL_YD→ 放行。 - 尺寸改为 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 |
与卖价对称 |
| 自动触发执行 | 部分落地 |
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
响应
{
"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
响应
{
"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)
{
"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"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 校验产品编码唯一性与必填字段后新增产品记录。
校验与异常
- 初始化产品配置与默认状态,写入审计字段。
5.3.1.4 更新产品(POST /channel/sales-product/update)
1 接口请求示例
请求 /channel/sales-product/update(POST)
{
"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"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 根据
id校验产品存在性后更新可变字段(包含产品配置 tab 的保存内容)。
校验与异常
- 记录操作人和更新时间,并触发配置缓存刷新。
5.3.1.5 删除产品(POST /channel/sales-product/delete)
1 接口请求示例
请求 /channel/sales-product/delete(POST)
{
"id": 1
}
响应
{
"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
响应
{
"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)
{
"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}
}
]
}
响应
{
"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)
{
"id": 1,
"salesProductId": 1,
"serialNumber": 1,
"status": 1,
"priceName": "运费",
"costItem": "freight",
"costUnit": "kg",
"currency": "RMB",
"chargingCondition": "",
"factors": [],
"details": []
}
响应
{
"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)
{
"id": 1
}
响应
{
"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)
{
"id": 1,
"status": 0
}
响应
{
"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
响应
{
"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)
{
"salesProductId": 1,
"publish": false,
"scheme": "costLowest",
"providers": [
{
"id": 0,
"serialNumber": 1,
"providerName": "一代物流",
"serviceChannelId": 10,
"weight": 100,
"customerIds": [1, 2]
}
]
}
响应
{
"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
响应
{
"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
响应
{
"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)
{
"salesProductId": 1,
"operatorType": "price",
"description": "价格调整"
}
响应
{
"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)
{
"salesProductId": 1,
"recordId": 1,
"operatorType": "price"
}
响应
{
"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
响应
{
"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
响应
{
"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)
{
"groupId": 1,
"channelCode": "DHL-US",
"channelName": "DHL美国专线",
"minTime": 3,
"maxTime": 7,
"forecastType": "waybill",
"forecastCode": "",
"description": "DHL美国专线渠道",
"preScript": "",
"isActive": 1
}
响应
{
"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=
响应
{
"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)
{
"name": "DHL服务商",
"appId": 1,
"apiServiceProviderId": 12
}
响应
{
"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)
{
"id": 1,
"name": "DHL服务商(更新)",
"appId": 1,
"apiServiceProviderId": 12
}
响应
{
"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)
{
"id": 1
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 根据分组
id执行逻辑删除。
校验与异常
- 若分组下存在渠道则禁止删除并返回业务错误。
5.3.5.8 更新渠道(POST /channel/service-channel/update)
1 接口请求示例
请求 /channel/service-channel/update(POST)
{
"id": 1,
"groupId": 1,
"channelCode": "DHL-US",
"channelName": "DHL美国专线",
"minTime": 3,
"maxTime": 7,
"forecastType": "waybill",
"forecastCode": "",
"description": "DHL美国专线渠道",
"preScript": "",
"isActive": 1
}
响应
{
"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)
{
"id": 1
}
响应
{
"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
响应
{
"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)
{
"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}
}
]
}
响应
{
"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)
{
"id": 1,
"serviceChannelId": 1,
"serialNumber": 1,
"status": 1,
"priceName": "运费",
"costItem": "freight",
"costUnit": "kg",
"currency": "RMB",
"chargingCondition": "",
"factors": [],
"details": []
}
响应
{
"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)
{
"id": 1
}
响应
{
"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)
{
"id": 1,
"status": 0
}
响应
{
"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)
{
"serviceChannelId": 1,
"publish": false,
"prices": [
{
"serialNumber": 1,
"status": 1,
"priceName": "运费",
"costItem": "freight",
"costUnit": "kg",
"currency": "RMB",
"chargingCondition": "",
"factors": [],
"details": []
}
]
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 校验
serviceChannelId存在。 - 使用临时负数
Id关联子表,调用ReplacePricesAsync整树替换当前渠道买价。 - 若
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
响应
{
"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
响应
{
"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)
{
"serviceChannelId": 1,
"operatorType": "price",
"description": "买价调整"
}
响应
{
"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)
{
"serviceChannelId": 1,
"recordId": 1,
"operatorType": "price"
}
响应
{
"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)
{
"areaType": "blacklist",
"name": "黑名单区1",
"systemName": "系统黑名单"
}
响应
{
"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)
{
"id": 1,
"areaType": "blacklist",
"name": "黑名单区1",
"systemName": "系统黑名单"
}
响应
{
"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)
{
"id": 1
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 根据
id执行分区逻辑删除。
校验与异常
- 分区存在明细时按业务规则拦截或级联处理。
5.3.8.4 分区列表(POST /channel/kpi-area/list)
1 接口请求示例
请求 /channel/kpi-area/list(POST)
{
"areaType": "blacklist",
"page": 1,
"pageSize": 20
}
响应
{
"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)
{
"areaId": 1,
"countryCode": "US",
"countryName": "美国",
"province": "加州",
"city": "洛杉矶",
"leftPostcode": "90001",
"rightPostcode": "90099"
}
响应
{
"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)
{
"id": 1,
"areaId": 1,
"countryCode": "US",
"countryName": "美国",
"province": "加州",
"city": "洛杉矶",
"leftPostcode": "90001",
"rightPostcode": "90099"
}
响应
{
"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)
{
"id": 1
}
响应
{
"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)
{
"areaId": 1,
"page": 1,
"pageSize": 20
}
响应
{
"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)
{
"page": 1,
"pageSize": 20
}
响应
{
"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)
{ "name": "标准计重规则" }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 新增计重规则并写入审计信息。
校验与异常
- 校验
name必填且不重复。
5.3.9.3 更新规则(POST /channel/weight-rule/update)
1 接口请求示例
请求 /channel/weight-rule/update(POST)
{ "id": 1, "name": "标准计重规则" }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 根据
id更新规则信息。
校验与异常
- 校验
id存在。
5.3.9.4 删除规则(POST /channel/weight-rule/delete)
1 接口请求示例
请求 /channel/weight-rule/delete(POST)
{ "id": 1 }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 对规则执行逻辑删除。
校验与异常
- 校验是否存在关联明细。
5.3.9.5 规则明细列表(POST /channel/weight-rule/info/list)
1 接口请求示例
请求 /channel/weight-rule/info/list(POST)
{ "ruleId": 1, "page": 1, "pageSize": 20 }
响应
{
"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)
{ "ruleId": 1, "condition": "gw>vw", "billableWeight": "vw", "volumetricFactor": "6000" }
响应
{ "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)
{ "id": 1, "ruleId": 1, "condition": "gw>vw", "billableWeight": "vw", "volumetricFactor": "6000" }
响应
{ "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)
{ "id": 1 }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 删除规则明细(逻辑删除)。
校验与异常
- 校验
id存在。
5.3.9.9 进位列表(POST /channel/weight-rounding/list)
1 接口请求示例
请求 /channel/weight-rounding/list(POST)
{ "page": 1, "pageSize": 20 }
响应
{
"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)
{ "name": "标准进位" }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 新增进位规则。
校验与异常
- 校验
name必填。
5.3.9.11 更新进位(POST /channel/weight-rounding/update)
1 接口请求示例
请求 /channel/weight-rounding/update(POST)
{ "id": 1, "name": "标准进位" }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 根据
id更新进位规则。
校验与异常
- 校验
id存在。
5.3.9.12 删除进位(POST /channel/weight-rounding/delete)
1 接口请求示例
请求 /channel/weight-rounding/delete(POST)
{ "id": 1 }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 逻辑删除进位规则。
校验与异常
- 校验是否可删除。
5.3.9.13 进位明细列表(POST /channel/weight-rounding/info/list)
1 接口请求示例
请求 /channel/weight-rounding/info/list(POST)
{ "roundingId": 1, "page": 1, "pageSize": 20 }
响应
{
"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)
{ "roundingId": 1, "startValue": 0, "endValue": 1, "public": "0.5" }
响应
{ "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)
{ "id": 1, "roundingId": 1, "startValue": 0, "endValue": 1, "public": "0.5" }
响应
{ "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)
{ "id": 1 }
响应
{ "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=
响应
{
"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)
{
"status": 1,
"operation": "拦截",
"triggerTiming": "下单",
"triggerCondition": "weight>30",
"promptMessage": "重量超过30kg,请确认",
"notifier": "admin@example.com"
}
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 新增自动触发规则。
校验与异常
- 校验关键字段必填。
5.3.10.3 更新(POST /channel/auto-trigger/update)
1 接口请求示例
请求 /channel/auto-trigger/update(POST)
{
"id": 1,
"status": 1,
"operation": "拦截",
"triggerTiming": "下单",
"triggerCondition": "weight>30",
"promptMessage": "重量超过30kg,请确认",
"notifier": "admin@example.com"
}
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 根据
id更新自动触发规则。
校验与异常
- 校验
id存在且可编辑。
5.3.10.4 删除(POST /channel/auto-trigger/delete)
1 接口请求示例
请求 /channel/auto-trigger/delete(POST)
{ "id": 1 }
响应
{ "code": 0, "msg": "success", "data": null }
2 接口业务逻辑
核心流程
- 逻辑删除自动触发规则。
校验与异常
- 校验
id存在且可删除。
5.3.10.5 切换启用状态(POST /channel/auto-trigger/toggleStatus)
1 接口请求示例
请求 /channel/auto-trigger/toggleStatus(POST)
{ "id": 1, "status": 0 }
响应
{ "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)
{
"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)
{
"id": 0,
"name": "尾程标签模板1",
"templateType": "lastMile",
"templateJson": {}
}
参数说明:
id模板ID,0为新增;templateType类型:lastMile(尾程)/customer(客户)/inbound(入库)/outbound(出库)/sku;templateJsonvg-print 导出的模板 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)
{
"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。操作时间、操作人由服务端从当前会话获取。
响应
{
"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=
响应
{
"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)
{
"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可选,未传时建议服务端用订单绑定/收件国兜底。操作时间、操作人由服务端从当前会话获取。
响应
{
"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 接口业务逻辑
核心流程
- 定位订单:解析
waybillNo,按运单号关联t_order及本单t_order_weight四行(forecast/receive/ship/downstream)。 - 前置校验:
- 订单须存在且
OrderStatus = pendingShip(待发货);pendingPayment/received→ 「订单未付款,禁止出库」;其它状态 → 「当前订单状态不允许出库」。 - 须已存在
Type=ship(发货) 行(预报成功时随主单创建)。
- 订单须存在且
- 写入出库记录:落库
t_outbound_record(实重、尺寸、销售产品、目的国、ServiceChannelId、仓库、操作人、操作时间;件重尺与业务维度与t_inbound_record对齐,并含ServiceChannelId;Weight/Length/Width/Height取值以请求、订单绑定及下文ship行最终件重尺 为准)。 - 更新发货件重尺(
t_order_weight.Type = ship):以请求weight/length/width/height写入ship行的Length、Width、Height、ActualWeight(weight→ActualWeight),并重算Cubic = Length × Width × Height / 1_000_000(不读取、不覆盖其它Type行)。 - 计重:对
ship行按订单绑定WeightRuleId/WeightRoundingId(及 rules 快照或子表明细)执行 §4.4.3.1 步骤 7、8 相同算法,重算VolumetricWeight、ChargeableWeight(不修改forecast/receive/downstream行)。 - 更新订单主表:
OrderStatus = pendingOnline(待上网),ShipTime = 操作时间,刷新UpdateTime/UpdateUserId;持久化ship行与主表。 - 阶段计费(待实现):按 §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=
响应
{
"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)
{
"number": "IDE2607221859123456",
"numberType": "waybill",
"labelTemplateId": 1
}
参数说明:
number运单号/客户单号/跟踪号/其他单号;numberType单号类型:waybill(运单号)/customerOrder(客户单号)/tracking(跟踪号)/other(其他单号);labelTemplateId标签模板ID,可选,不传则按 templateType 取默认模板。也可传templateType:lastMile(尾程)/customer(客户)/inbound(入库)/outbound(出库)/sku。
响应
{
"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=
响应
{
"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。不传则返回全部可用模板。
响应
{
"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)
{
"customerId": 1,
"rechargeAmount": 500,
"payType": 1,
"rechargeScreenshot": "https://example.com/upload/screenshot.jpg",
"remark": "充值500元"
}
参数说明:
customerId为t_customer.Id(客户端可不传:用 JWTmapId查t_customer.UserId得到对应Id);rechargeAmount充值金额,≥0.01;payType支付方式:1-微信,2-支付宝;rechargeScreenshot充值截图URL;remark备注。
响应
{
"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)
{
"id": 1,
"remark": "审核通过"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 校验记录
RechargeStatus = Pending,否则 「只有待审核记录才能审核通过」。 - 更新
t_recharge_record:状态=审核通过,写入审核人、审核时间、备注。 - 调用
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)
{
"id": 1,
"remark": "截图不清晰,请重新上传"
}
响应
{
"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
响应
{
"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:
- 读取
balanceBefore = customer.Balance,计算balanceAfter = balanceBefore + signedAmount。 - 若
signedAmount == 0→ 「流水金额不能为 0」;若balanceAfter < 0→ 「账户余额不足」。 - 更新
customer.Balance = balanceAfter并UpdateAsync。 - 生成唯一
SerialNumber(FLOW{yyyyMMddHHmmss}{random6},冲突重试最多 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
响应示例
{
"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 为例)
- 查询
t_system_other_config:ConfigCode = track_service且未删除;各字段非空则覆盖对应运行时参数。 - 若库中无记录或某字段为空,回退
Oms:TrackService(appsettings/ Consul):Enabled、BaseUrl、AppKey、AppSecret、TimeoutSeconds。 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)
无请求参数,返回全部国家字段。
响应
{
"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=主仓
响应
{
"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)
{
"dictType": "warehouse_type",
"dictSort": 1,
"dictCode": "main",
"dictName": "主仓"
}
响应
{
"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)
{
"id": 1,
"dictType": "warehouse_type",
"dictSort": 2,
"dictCode": "transit",
"dictName": "中转仓"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 根据字典 ID 更新字典类型、排序、编码、名称及审计信息。
校验与异常
- 校验
id存在且未删除,更新字段合法。 - 若更新后
dictType + dictCode与其他记录冲突,返回业务错误。
8.4.1.5 删除字典(POST /system/dict/delete)
1 接口请求示例
请求 /system/dict/delete(POST)
{
"id": 1
}
响应
{
"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=
响应
{
"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
响应
{
"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)
{
"warehouseName": "深圳主仓",
"warehouseType": "main",
"recipient": "张三",
"contactPhone": "0755-12345678",
"shippingAddress": "广东省深圳市南山区xxx路xxx号",
"servicePhone": "400-xxx-xxxx",
"serviceWechat": "itfx_service",
"serviceWechatFiles": ["https://example.com/qrcode1.jpg"]
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 新增仓库地址配置并写入创建审计信息。
校验与异常
- 校验仓库名称、类型及地址字段完整性,不合法时返回业务错误。
8.4.1.9 更新仓库(POST /system/warehouse/update)
1 接口请求示例
请求 /system/warehouse/update(POST)
{
"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"]
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 根据仓库 ID 更新仓库基础信息与联系方式。
校验与异常
- 校验
id存在与更新字段合法性,更新失败时返回业务错误。
8.4.1.10 删除仓库(POST /system/warehouse/delete)
1 接口请求示例
请求 /system/warehouse/delete(POST)
{
"id": 1
}
响应
{
"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=活动
响应
{
"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)
{
"noticeType": "notice",
"title": "五一放假通知",
"content": "仓库 5 月 1 日-5 月 3 日暂停收货。",
"isPopup": 1,
"receiverType": "user",
"receiverIds": [1001, 1002, 1003]
}
响应
{
"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)
{
"id": 101,
"noticeType": "message",
"title": "账单补充说明",
"content": "请于本周内完成 4 月账单核对。",
"isPopup": 0,
"receiverType": "customer",
"receiverIds": [2001, 2002]
}
响应
{
"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)
{
"id": 101
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 根据通知 ID 执行逻辑删除,保留历史审计信息。
校验与异常
- 校验
id必填且存在,不存在或已删除时返回业务错误。
8.4.1.15 确认已读(POST /system/notice/read-confirm)
1 接口请求示例
请求 /system/notice/read-confirm(POST)
{
"id": 101
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 当前登录主体调用后,在
t_notice_read中按NoticeId + ReceiverType + ReceiverId幂等写入已读记录(ReceiverId= 当前 JWTmapId,即user.userid)。
校验与异常
- 校验通知存在且未删除。
- 校验当前主体在通知接收范围内,不在范围内时返回无权限错误。
8.4.1.16 其他配置列表(GET /system/other-config/list)
1 接口请求示例
请求 /system/other-config/list(GET,Query 参数)
?page=1&pageSize=30&keyword=track
响应
{
"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
响应
{
"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)
{
"configCode": "track_service",
"configName": "小包轨迹服务",
"enabled": true,
"baseUrl": "http://api.nanafa.com/track",
"appKey": "YOUR_APP_KEY",
"appSecret": "YOUR_APP_SECRET",
"timeoutSeconds": 30,
"remark": ""
}
响应
{
"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)
{
"id": 1,
"configCode": "track_service",
"configName": "小包轨迹服务",
"enabled": true,
"baseUrl": "http://api.nanafa.com/track",
"appKey": "YOUR_APP_KEY",
"appSecret": "",
"timeoutSeconds": 30,
"remark": "生产环境"
}
响应
{
"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)
{
"id": 1
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
2 接口业务逻辑
核心流程
- 根据主键执行逻辑删除;删除后轨迹同步回退
Oms:TrackService或跳过(视 appsettings 与Enabled而定)。
校验与异常
- 校验
id必填且存在,不存在时返回 「配置不存在」。
9 需求迭代与变更记录
9.1 需求迭代目录(按日期管理)
迭代编号建议使用
YYYYMMDD-序号,按日期倒序维护;不同功能迭代可使用-01、-02递增。
| 迭代日期 | 迭代编号 | 迭代功能 | 变更模块 | 备注 |
|---|---|---|---|---|
| 2026-07-28 | 20260728-01 | 订单附件表 + 系统面单落文件表 | 订单 §1.5.4、§2.3、§4.1、§4.2.1、§4.2.12、§4.4.1、§4.4.1.8、§4.4.1.12、§4.4.1.17、§4.4.3.1、§10.6.1 | t_order_file;废弃订单 LabelUrl/LabelGenerate*;客户只读 systemLabel;下游 labelUrl→downstreamLabel |
| 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.mdvsoms表结构与接口设计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:优先t_order_file.systemLabel(PDF);缺失时打印可补生成或回落 HTML stub/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, print, files, files/upload, files/delete, price-check, parcel/create, parcel/batch-create, downstream-forward-create-one;设计中: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均在 RBACuser表存在;不支持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 |
已落地 | 优先 t_order_file.systemLabel;缺失补生成 PDF |
| GET | /order/files |
已落地 | 按类型分组;客户仅 systemLabel |
| POST | /order/files/upload |
已落地 | 内部上传;客户禁止 |
| POST | /order/files/delete |
已落地 | 软删;客户禁止 |
| 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 快照;成功后生成 systemLabel |
| POST | /order/parcel/batch-create |
部分 | 同步批处理,见 §4.4.3.2 |
| POST | /order/downstream-forward-create-one |
已落地 | itfx createOrderOne;回写 DownstreamServiceNo/TrackNo + downstreamLabel |
| GET | /order/downstream-create-one-body |
已落地 | 预览下游请求体 |
| 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