# 订单中心新手指引 > 本文面向第一次接触 **订单模块(OMS Order)** 的运营、客服、产品与开发同学。 > **第三章** 以 `POST /order/parcel/create` 为核心,用流程图拆解 `CreateParcelCoreAsync` 的**业务校验、分区运算、计重算价、排货选渠**全链路。 --- ## 1. 系统是什么? **订单模块**负责:查价试算、小包预报(创建订单)、订单全生命周期、Excel 导入、批量异步任务。 后端入口:`OrderService`(`Oms.Application.Services.Orders`),Swagger 分组 **OMS-Order**。 **心智模型:** 创建订单 = **预报应收流水线** — 绑快照 → 校验地址 → 计重 → 匹配分区 → **仅写应收(forecast)** → 状态 `predeclared`。**排货与渠道买价在入库环节处理**(失败则 `pendingAllocation` 待排货,不阻断收货与应收)。 **全链路:** 查价(参考)→ 下单(预报应收)→ 入库(实测 + 应收 + 可选排货)→ 付款 → 出库(发货实测 + outbound 应收)。 --- ## 2. 核心概念速查 | 概念 | 说明 | |------|------| | **ProductCode** | 客户选的运输方式,对应销售产品 | | **ChannelCode** | 服务渠道,**系统排货自动写入**,客户不手选 | | **Rules 快照** | 计重/黑名单/偏远/排货/providers,版本 `SalesProductRulesVersion` | | **Price 快照** | 卖价矩阵 + 价格分区明细,版本 `SalesProductPriceVersion` | | **zoneKeys** | 价格分区匹配后生成的「分区键」列表,用于价格矩阵行匹配 | | **forecast 重量** | 预报阶段计重行,应收费用用它 | | **downstream 重量** | 下游计重行,**排货应付费用用它**(可与 forecast 相同) | | **ChargingStage** | `forecast` / `inbound` / `outbound`,费用按阶段区分 | | **AllocationPendingReason** | 入库排货/渠道买价未匹配时的原因(`pendingAllocation` 状态展示) | ### 订单状态(生命周期相关) | 状态 | 含义 | |------|------| | `draft` | 草稿;预报计费失败时保留,并写 `PredeclareFailReason` | | `predeclared` | **创建成功**后的默认状态(仅预报应收,未排货) | | `pendingAllocation` | **待排货**:已入库、应收已算,但排货/渠道买价未成功 | | `pendingPayment` | 待支付(入库排货成功或手动重算应付成功后) | | `pendingShip` | 待发货(已付款) | | `pendingOnline` | 待上网(已出库) | ### 运单号(ServiceNo) | 项 | 规则 | |----|------| | 前缀 | **`IDE`** | | 格式 | `IDE` + **`yyMMddHHmmss`**(年份取后两位,如 2026 → `26`)+ 4 位随机数 | | 示例 | `IDE2607221859123456` | | 自定义 | 请求体可传 `serviceNo`,须唯一;否则服务端自动生成 | --- ## 3. 创建订单接口全链路(重点) ### 3.1 API 与代码入口 | 项目 | 值 | |------|-----| | **HTTP** | `POST /order/parcel/create` | | **方法** | `OrderService.CreateParcelAsync` → `CreateParcelCoreAsync` | | **文件** | `OrderService.ParcelBilling.cs` | | **算价引擎** | `ParcelBillingCalculator.cs` | | **排货选渠** | `AllocationResolver.cs` | 以下入口最终都调用同一核心方法: | 入口 | API / 动作 | |------|------------| | 页面下单 | `POST /order/parcel/create` | | JSON 批量 | `POST /order/parcel/batch-create`(≤2000) | | Excel 导入 | `POST /order/upload` → 行映射后调用 | | MQ Worker | `parcel-create` / `import-row` | --- ### 3.2 总览流程图 ```mermaid flowchart TD A[POST /order/parcel/create] --> B{RequestId 已存在?} B -->|是| Z1[返回已有订单 BuildParcelCreateOutput] B -->|否| C[NormalizeParcelCreateInput
清空 TrackNo/ServiceNo] C --> D[ResolveEffectiveCustomer 解析客户] D --> E[ValidateParcelCreateInputAsync
业务校验] E -->|失败| X1[抛 BusinessException
不落库] E --> F[加载 SalesProduct 须启用] F -->|失败| X1 F --> G[ResolveSnapshotBindingAsync
绑 rules+price 快照] G -->|失败| X1 G --> H[解析目的国 Country] H --> I{IsBlacklistHitAsync?} I -->|命中| X1 I --> J[生成 ServiceNo IDE+yyMMddHHmmss+随机] J --> K[Insert t_order 草稿] K --> L[SaveParcelChildren
收发件/申报/4行重量] L --> M{预报计费 try} M --> N[偏远判定 IsRemote] N --> O[ApplyChargeableWeightAsync 计重] O --> P[ResolveOrderBillingContext 分区] P --> Q[UpsertReceivableFeesAsync 应收 forecast] Q --> S[OrderStatus=predeclared] S --> T[轨迹 Upsert + 自动触发] T --> Z2[返回 OrderParcelCreateOutput] M -->|异常| U[MarkParcelDraftFailedAsync
draft + PredeclareFailReason] U --> X2[抛 BusinessException] ``` > **关键分界:** 黑名单校验在 **Insert 之前**,失败完全不落库;计重/分区/算价/排货在 **Insert 之后** 的 `try` 块内,失败会保留 `draft` 订单并记录失败原因。 --- ### 3.3 阶段一:入参规范化与业务校验(落库前) #### 3.3.0 客户身份解析 `ResolveEffectiveCustomerAsync` 在规范化与校验之前,系统会先解析「这笔单属于哪个客户」: ```mermaid flowchart TD C0[ResolveEffectiveCustomerAsync] --> C1{当前登录身份} C1 -->|客户账号| C2[忽略入参 CustomerId
强制使用会话客户] C1 -->|管理员/运营| C3{入参 CustomerId > 0?} C3 -->|否| CE1[客户ID不能为空] C3 -->|是| C4[按 CustomerId 查库] C4 --> C5{客户存在且 Status=Normal?} C5 -->|否| CE2[客户不存在或状态不可用] C5 --> C6[写回 input.CustomerId] ``` > 客户门户下单时无需手填 `CustomerId`;后台代客下单必须显式传入有效客户 ID。 #### 3.3.1 规范化 `NormalizeParcelCreateInput` | 字段 | 处理 | |------|------| | `TrackNo` | 强制置 `null`(不允许客户端传入) | | `ServiceNo` | 强制置 `null`(系统生成) | | `ClientNo` | Trim;空则 `null` | | `RequestId` | Trim;用于幂等 | #### 3.3.2 业务校验 `ValidateParcelCreateInputAsync` ```mermaid flowchart TD V0[开始校验] --> V1{ClientNo 非空?} V1 -->|否| E1[客户单号不能为空] V1 --> V2{CustomerId > 0?} V2 -->|否| E2[客户ID不能为空] V2 --> V3{同客户 ClientNo 唯一?} V3 -->|重复| E3[客户单号已存在] V3 --> V4{ProductCode 非空?} V4 -->|否| E4[产品代码不能为空] V4 --> V5{WarehouseId > 0?} V5 -->|否| E5[仓库ID不能为空] V5 --> V6{长宽厚实重均 > 0?} V6 -->|否| E6[件重尺必须大于0] V6 --> V7{CargoAttribute 合法?} V7 -->|否| E7[货物属性不合法
general/electronics/sensitive] V7 --> V8{ExportCustomsMode 合法或空?} V8 -->|否| E8[出口报关方式不合法] V8 --> V9{DeclaredCurrency 非空?} V9 -->|否| E9[申报币种不能为空] V9 --> V10{Quantity > 0?} V10 -->|否| E10[包裹数量不能为空] V10 --> V11{收件人完整?} V11 -->|否| E11[收件人信息不完整
姓名/国家/邮编/地址1] V11 --> V12{发件人姓名非空?} V12 -->|否| E12[发件人姓名不能为空] V12 --> V13{申报明细 Items 非空?} V13 -->|否| E13[申报明细不能为空] V13 --> V14[逐行校验申报品
EnName/UnitPrice/UnitWeight/Quantity] V14 --> V15{仓库存在?} V15 -->|否| E15[仓库不存在] V15 --> V16{客户存在且状态正常?} V16 -->|否| E16[客户不存在或状态不可用] V16 --> OK[校验通过] ``` | 校验项 | 规则 | 失败文案 | |--------|------|----------| | 幂等 | `RequestId` 已有订单则直接返回 | — | | 客户单号 | 非空;同 `CustomerId` 下唯一 | 客户单号不能为空 / 已存在 | | 产品 | `ProductCode` 非空 | 产品代码不能为空 | | 仓库 | `WarehouseId > 0` 且记录存在 | 仓库ID不能为空 / 仓库不存在 | | 件重尺 | 长/宽/高/实重均 > 0 | 件重尺必须大于0 | | 货物属性 | `general` / `electronics` / `sensitive` | 货物属性不合法 | | 申报 | 币种、数量、明细行必填完整 | 见上 | | 收件人 | 姓名、国家、邮编、地址1 | 收件人信息不完整 | | 客户 | 存在且 `Status=Normal` | 客户不存在或状态不可用 | #### 3.3.3 产品与快照(仍在落库前) ```mermaid flowchart TD P1[GetByProductCode] --> P2{产品存在且 IsActive?} P2 -->|否| E[销售产品不存在或未启用] P2 --> P3[ResolveSnapshotBindingAsync bindTime=Now] P3 --> P4[取 rules 生效发布记录] P3 --> P5[取 price 生效发布记录] P4 -->|无| P4L[BuildLiveRulesSnapshot 内存拼装] P4 -->|有| P4S[反序列化 rules Content] P5 -->|无| P5L[BuildLivePriceSnapshot] P5 -->|有| P5S[反序列化 price Content] P5L --> P5C{Prices.Count>0?} P5C -->|否| E2[未命中价格快照版本] P4L --> P4R{WeightRuleId 有值?} P4R -->|否| E3[未命中规则快照版本] P3 --> P6[写入绑定版本号
RulesVersion / PriceVersion] P6 --> P7[解析目的国 CountryCode] P7 --> P8{黑名单命中?} P8 -->|是| E4[收件地址命中黑名单区域] ``` **快照选取规则:** `PublishTime <= bindTime` 且 `Status=生效` 的**最新**发布记录;无记录则 `LIVE-{productId}`,价格侧必须有启用价格项,规则侧 LIVE 时必须有 `WeightRuleId`。 --- ### 3.4 阶段二:落库草稿 | 步骤 | 动作 | 写入表/字段 | |------|------|-------------| | 1 | `ResolveServiceNoAsync` | 运单号 **`IDE{yyMMddHHmmss}{4位随机}`** | | 2 | `BuildDraftOrderEntity` | `t_order`:产品、国家、快照版本、初始 `draft`;**创建阶段不写 ChannelCode 排货结果** | | 3 | `BuildInitialWeightRows` | `t_order_weight` 四行:`forecast` / `receive` / `ship` / `downstream` | | 4 | `SaveParcelChildren` | `t_order_sender`、`t_order_recipient`、`t_order_declaration` | **初始重量行:** 四行复制同一套长宽厚实重;`Cubic = L×W×H/1,000,000`;后续计重结果通过 `SyncWeightRows` 同步到全部四行。 --- ### 3.5 阶段三:预报计费(try 块内) #### 3.5.1 子流程总图 ```mermaid flowchart TD T1[偏远 IsRemoteHitAsync] --> T2[ApplyChargeableWeightAsync
仅算 forecast 行] T2 --> T3[SyncWeightRows 同步四行重量] T3 --> T4[ResolvePriceZoneKeysAsync] T4 --> T5{zoneKeys 非空?} T5 -->|否| E1[价格分区未匹配] T5 --> T6[UpsertReceivableFeesAsync
ChargingStage=forecast] T6 --> T8[status=predeclared] ``` > **创建阶段不算应付、不排货。** 渠道与买价在 **入库** `ApplyReceiveBillingByWaybillAsync` 中通过 `TryConfirmServiceChannelAndPayableAsync` 尝试;失败写入 `AllocationPendingReason`,订单进 **`pendingAllocation`**,仍保留入库实测与 inbound 应收。 --- #### 3.5.2 偏远判定 `IsRemoteHitAsync` 与黑名单逻辑对称,但**不阻断创建**,只写 `order.IsRemote`: 1. 解析有效 `RemoteAreaId`(产品 → 规则快照 → 快照内 Product); 2. 优先用快照内 `RemoteAreaInfos` 匹配; 3. 否则查库 `t_kpi_area_info`; 4. 匹配规则:国/省/市相等 + 邮编区间 **左开右闭** `(left, right]`。 --- #### 3.5.3 计重运算 `ApplyChargeableWeightAsync` ```mermaid flowchart TD W1[取 WeightRuleId] --> W2{有计重规则?} W2 -->|否| E1[未配置计重规则] W2 --> W3[加载规则明细 ruleInfos
优先快照 WeightRuleInfos] W3 --> W4[CalculateRawBillableWeight] W4 --> W5[逐条规则按 Id 排序] W5 --> W6[体积重 vw = L×W×H / 泡比因子] W6 --> W7{Condition 命中?
gw/vw 表达式} W7 -->|否| W5 W7 -->|是| W8[raw = BillableWeight 为 gw 或 vw] W8 --> W9[ApplyRounding 进位] W9 --> W10[命中进位区间 Start~End] W10 --> W11[ChargeableWeight = ceil raw/unit × unit] W11 --> W12[写 forecast.VolumetricWeight / ChargeableWeight] ``` | 运算 | 公式/规则 | |------|-----------| | 体积 | `Cubic = L×W×H / 1,000,000`(保留 6 位) | | 体积重 | `vw = L×W×H / VolumetricFactor`(保留 3 位) | | 条件 | `Condition` 表达式,变量 `gw`/`vw`,如 `gw>=vw` | | 原始计费重 | `BillableWeight=gw` 取实重,否则取体积重 | | 进位 | 找 `StartValue ≤ raw ≤ EndValue` 的区间,`ceil(raw/Public)×Public` | **失败文案:** 未配置计重规则 / 计重进位 / 计重规则条件均未命中 / 计重进位区间未匹配。 --- #### 3.5.4 价格分区匹配 `ResolvePriceZoneKeysAsync` ```mermaid flowchart TD Z1{price 快照含 PriceAreaInfos?} Z1 -->|是| Z2[快照内逐条匹配国省市邮编] Z1 -->|否| Z3[ResolveEffectivePriceAreaId] Z3 --> Z4[查 t_kpi_area + info 明细] Z2 --> Z5{命中一条?} Z4 --> Z5 Z5 -->|否| Z6[返回 null → 创建失败] Z5 --> Z7[BuildPriceZoneKeys] Z7 --> Z8[生成 zoneKeys 列表
分区名/Id/省/市等] ``` **地址匹配规则(黑名单/偏远/价格分区通用):** | 维度 | 规则 | |------|------| | 国家 | 配置了则必须相等(忽略大小写) | | 省/市 | 配置了则必须相等 | | 邮编 | 配置了左右端点则必须落在区间内;数字按数值比,否则按字符串比 | | 邮编区间 | **左开右闭** `(left, right]` | **失败文案:** `价格分区未匹配(规则快照)` --- #### 3.5.5 应收费用 `UpsertReceivableFeesAsync` 输入:`price 快照`、`forecast.ChargeableWeight`、`zoneKeys`、`CargoAttribute`、`CustomerGroup`、`chargingStage=forecast`。 ```mermaid flowchart TD R1[遍历卖价快照中 Status=1 的价格项] --> R2[MatchPriceDetail 匹配明细行] R2 --> R3{分区因素匹配 zoneKeys?} R3 --> R4{货物属性匹配?} R4 --> R5{仓库因素匹配?} R5 --> R6{重量区间匹配?
左开右闭} R6 -->|命中| R7[CalculateDetailAmount] R7 --> R8{公式类型} R8 -->|total| R9[客户组价或 CalculatedValue] R8 -->|unit| R10[单价 × 计费重] R8 -->|continue| R11[首重价 + 续重 × 续单价] R9 --> R12[写入 OrderFee type=receivable] R10 --> R12 R11 --> R12 R2 -->|无匹配| R1 R12 --> R13{有费用行 或 无启用项?} R13 -->|有启用项但零匹配| E[产品卖价快照未配置或未匹配到有效价格] ``` | 价格明细匹配 | 说明 | |--------------|------| | 分区因素 | `FactorValuesJson` 中「分区」须与 `zoneKeys` 任一相等 | | 货物属性 | 因素含货物类型时须匹配 `general/electronics/sensitive` | | 重量段 | `(start, end]`,多段命中取**区间最窄**的一条 | | 客户组价 | `CustomerGroupValues` 按客户组取价,否则 `CalculatedValue` | #### 3.5.5.1 价格明细匹配 `MatchPriceDetail`(应收/应付共用) ```mermaid flowchart TD M0[遍历价格项全部 Details] --> M1{分区因素与 zoneKeys 相交?} M1 -->|否| M0 M1 --> M2{货物类型因素匹配?
无因素则跳过} M2 -->|否| M0 M2 --> M3{仓库因素匹配?
创建订单时通常无仓库因素} M3 -->|否| M0 M3 --> M4{明细含重量因素?} M4 -->|否| M5[暂记为候选 bestMatch] M4 -->|是| M6[解析重量区间 左开右闭] M6 --> M7{chargeableWeight 在区间内?} M7 -->|否| M0 M7 --> M8[区间宽度 = end - start] M8 --> M9{比当前 best 更窄?} M9 -->|是| M10[更新 bestMatch] M9 -->|否| M0 M5 --> M0 M10 --> M0 M0 -->|遍历结束| M11[返回 bestMatch 或 null] ``` **重量区间解析规则:** | 配置写法 | 解析结果 | 示例 | |----------|----------|------| | 单值 `0.5` | `(0, 0.5]` | 0.5kg 以内 | | 区间 `0,0.5` 或 `0-0.5` | `(0, 0.5]` | 同上 | | 区间 `0.5,1` | `(0.5, 1]` | 0.5kg 以上至 1kg | #### 3.5.5.2 金额计算公式 `CalculateDetailAmount` 匹配到明细行后,按 `CalculationFormula` 计算金额: ```mermaid flowchart TD F0[命中明细行] --> F1{CalculationFormula} F1 -->|total| F2[客户组价 ?? CalculatedValue] F1 -->|unit| F3[单价 × 计费重
round 4 位小数] F1 -->|continue| F4{能关联上一档 total 价?} F4 -->|是| F5[basePrice + 计费重-区间起点 × 续单价] F4 -->|否| F6[首重价 + ceil溢出/续重单位 × 续单价] F1 -->|其他| F7[CalculatedValue 或 0] ``` | 公式类型 | 计算逻辑 | 展示公式示例 | |----------|----------|--------------| | `total` | 固定总价;优先读 `CustomerGroupValues[客户组]` | `12.5` | | `unit` | `单价 × ChargeableWeight` | `3.2*1.5` | | `continue`(关联上档) | 找同分区上一重量档的 `total` 价为 base,超出部分 × 续单价 | `10+(1.5-0.5)*8` | | `continue`(单行首续重) | `首重价 + ceil((计费重-首重)/续重单位) × 续单价` | `10+2*5` | > **应收**遍历卖价快照全部 `Status=1` 价格项,每项独立匹配明细;**应付**对渠道买价快照做同样逻辑。若存在启用项但全部匹配失败且金额为 0,则抛错。 --- #### 3.5.6 入库排货与应付(创建时不执行) 排货逻辑在 **入库收货** 时调用 `TryConfirmServiceChannelAndPayableAsync`(失败不抛异常): ```mermaid flowchart TD A1[入库实测件重尺 receive 行] --> A2[重算 inbound 应收] A2 --> A3[TryConfirmServiceChannelAndPayableAsync] A3 -->|成功| A4[OrderStatus=pendingPayment
清空 AllocationPendingReason] A3 -->|失败| A5[OrderStatus=pendingAllocation
写 AllocationPendingReason] ``` | Scheme | 选渠规则 | |--------|----------| | `costLowest`(默认) | 应付总额最小,同价按序号 | | `timeFastest` | 渠道 `MinTime` 最小 | | `overallBest` | 服务商 `Weight`(评分)最大 | **注意:** 入库排货应付使用 **`receive` 行**的 `ChargeableWeight`(实测后重算)。手动「重算应付」成功也会清原因并改为 `pendingPayment`。 **写入订单字段(排货成功时):** `ChannelCode`、`DownstreamSupplierCode`、`DownstreamAppKey`、`ChannelForecastCode` 等。 --- ### 3.6 阶段四:成功收尾与失败处理 #### 成功 | 步骤 | 说明 | |------|------| | 状态 | `OrderStatus = predeclared`,清空 `PredeclareFailReason` | | 轨迹 | `EnqueueSmallBagTrackUpsertAsync` 异步小包轨迹 | | 触发 | `AutoTriggerRuntimeService.FireAsync(OrderCreate)`,失败**不影响**主流程 | | 返回 | `OrderParcelCreateOutput`(运单号、费用摘要等) | #### 失败 `MarkParcelDraftFailedAsync` | 场景 | 订单状态 | 字段 | |------|----------|------| | 计重/分区/应收/排货任一抛错 | 保持 `draft` | `PredeclareFailReason = 异常信息` | > 落库前校验失败(黑名单、快照缺失等):**不 Insert**,直接 `BusinessException`。 > 落库后计费失败:订单已存在,状态 `draft`,便于排查与重试。 --- ### 3.7 创建过程数据流一览 ```mermaid flowchart LR subgraph Input I1[OrderParcelCreateInput] end subgraph Snapshot S1[Rules Snapshot] S2[Price Snapshot] S3[Channel Price Snapshot] end subgraph Compute C1[ChargeableWeight] C2[zoneKeys] C3[Receivable Fees] C4[Payable Fees] end subgraph DB D1[t_order] D2[t_order_weight x4] D3[t_order_fee] end I1 --> S1 I1 --> S2 S1 --> C1 C1 --> C2 S2 --> C2 C2 --> C3 S1 --> C4 S3 --> C4 C3 --> D3 C4 --> D3 I1 --> D1 I1 --> D2 C1 --> D2 ``` --- ### 3.8 常见失败原因速查(创建/预报) | 报错关键词 | 排查方向 | |------------|----------| | 销售产品不存在或未启用 | 渠道 → 销售产品状态 | | 未命中价格/规则快照 | 是否发布;LIVE 时是否有价格项/计重规则 | | 收件地址命中黑名单 | 黑名单分区明细 + 产品关联 | | 价格分区未匹配 | 价格分区是否覆盖目的邮编/省市 | | 未配置计重规则/进位 | 产品配置 + 规则发布快照 | | 计重规则条件均未命中 | 件重尺与规则 Condition | | 卖价未匹配到有效价格 | 卖价矩阵分区/重量段/货物属性 | | 卖价未匹配到有效价格 | 卖价矩阵分区/重量段/货物属性 | | 入库排货失败 | 订单为 **待排货**,查 `AllocationPendingReason`;补排货规则/渠道买价后重算应付 | | 客户单号已存在 | 同客户重复下单 | --- ## 4. 查价 vs 创建 vs 入库 vs 出库 ### 4.1 环节对比 | 对比 | 查价 | 下单(预报) | 入库 | 出库 | |------|------|--------------|------|------| | 接口 | `POST /order/price-check` | `POST /order/parcel/create` | `POST /operation/inbound/create` | `POST /operation/outbound/create` | | 落库 | 否 | 是 | 写入库记录 + 订单 | 写出库记录 + 订单 | | 尺寸 | **可选**(全空按实重估算) | **必填** 长/宽/高/实重 > 0 | 实测必填 | 实测必填 | | 邮编/黑名单 | 无邮编则**跳过**黑名单/偏远 | **必填邮编**,黑名单**硬拦截** | 用订单已有地址 | — | | 计费 | 参考价,`queryHint` + `quoteLevel` | 仅 **forecast 应收** | **inbound 应收** + 可选排货应付 | **outbound 应收** | | 排货 | 不执行 | **不执行** | 可选;失败 → `pendingAllocation` | 不执行 | | 状态 | — | `predeclared` | `pendingPayment` 或 `pendingAllocation` | `pendingOnline`(须已付款) | ### 4.2 查价宽松规则(对客) - **必填**:目的国 + 实重 > 0。 - **尺寸**:全空或三者齐全;部分填写会报错。 - **未填邮编**:不校验黑名单;分区无匹配时可 **zone 兜底**(取最低价分区,标记为估算)。 - **未选货物属性/仓库**:按 **全部启用产品 × 全部仓库 × 货物类型展开**(general/electronics/sensitive)试算。 - 响应 **`queryHint.disclaimers`**、每行 **`quoteLevel`**(`exact` / `estimated`)提示「仅供参考,入库实测后可能调整」。 ### 4.3 共用校验 `ParcelBillingValidators` | 方法 | 用途 | |------|------| | `EnsurePriceCheckDimensions` | 查价:尺寸全空或齐全 | | `EnsurePositiveDimensions` | 下单/入库/出库:件重尺均 > 0 | | `EnsureInboundOrderStatus` | 入库:仅 `predeclared` / `pendingAllocation` | --- ## 5. 其他菜单(简表) | 菜单 | 路径 | 功能 | |------|------|------| | 新手指引 | `/order/guide` | 本文档 | | 我要查价 | `/order/priceCheck` | 运费试算 | | 我要下单 | `/order/placeOrder` | 单笔/Excel 导入 | | 我的订单 | `/order/myOrders` | 列表/付款/拦截/费用 | | 模板字段映射 | `/order/templateMapping` | 自定义 Excel 列映射 | | 批量任务 | `/order/batchJobs` | MQ 批处理监控 | --- ## 6. 后端代码索引 | 主题 | 文件 / 方法 | |------|-------------| | 创建主流程 | `OrderService.ParcelBilling.cs` → `CreateParcelCoreAsync` | | 入参校验 | `ValidateParcelCreateInputAsync` + `ParcelBillingValidators` | | 快照绑定 | `ResolveSnapshotBindingAsync` / `ResolveOrderBillingContextAsync` | | 计重 | `ApplyChargeableWeightAsync` + `ParcelBillingCalculator` | | 应收 | `UpsertReceivableFeesAsync` | | 入库排货 | `ApplyReceiveBillingByWaybillAsync` → `TryConfirmServiceChannelAndPayableAsync` | | 出库 | `ApplyShipBillingByWaybillAsync` | | 查价 | `OrderService.ParcelQuote.cs` + `PriceCheckHintBuilder` | | 运单号 | `ResolveServiceNoAsync` → `IDE{yyMMddHHmmss}{随机}` | | API 路由 | `OrderService.cs` → `CreateParcelAsync` | --- ## 7. 推荐阅读顺序 1. 本文 **§3 创建流程** + **§4 全链路对比**; 2. **渠道新手指引**(快照发布、排货配置、渠道买价); 3. 用 **我要查价** 看参考价,再 **我要下单** 下测试单; 4. 运营 **入库/出库** 扫描,在「我的订单」核对状态与费用; 5. 预报失败对照 **§3.8** 与 `PredeclareFailReason`;待排货对照 `AllocationPendingReason`。