# 订单中心新手指引
> 本文面向第一次接触 **订单模块(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`。