# 018 - 平台订单状态受控调整

**创建日期**：2026-08-10
**状态**：Implemented（代码与自动化验证完成，预发布手工验收待执行）
**工作方式**：沿用当前工作分支，不创建新分支
**输入**：平台订单管理当前只能修改订单状态，需要兼容配送状态和支付状态，同时避免人工修改破坏配送、支付、退款及结算链路。

## 背景

平台订单列表已经展示 `state`、`deliveryStatus`、`payStatus` 和 `afterSaleStatus`，但修改弹窗目前只提供订单状态。后端通用更新接口直接接收完整订单对象，并残留旧订单状态值的处理逻辑；如果只在页面增加两个下拉框，会产生越权字段更新、并发覆盖、虚假支付、虚假退款及状态组合矛盾等风险。

本功能为平台管理员提供受约束的订单状态调整能力。配送状态可按订单履约规则推进；支付状态通过“确认线下收款、支付补单、发起退款”等业务操作维护，不提供不受约束的任意修改。

## 锁定的业务决策

1. 平台管理员可以调整订单状态和配送状态，但所有变化必须满足订单类型、当前状态和售后状态约束。
2. 支付状态不提供可任意选择“未支付、已支付、已退款”的普通下拉框。
3. 线下支付通过“确认收款”变更为已支付；线下退款通过“确认线下退款”变更为已退款。
4. OMG 在线支付只能由支付回调或向 OMG 查询补单后变更为已支付，只能在真实退款成功后变更为已退款。
5. OMG 不支持自动退款的支付方式进入“等待人工退款”状态，在人工退款得到确认前不得标记为已退款。
6. 配送状态改为“已送达”时，必须同步完成订单并记录送达时间。
7. 已完成、已取消、已退款等终态不得通过普通状态调整重新打开。
8. 已完成订单的退款涉及商家、骑手结算冲正，本期不支持直接人工退款，必须留待完整售后与结算冲正能力处理。
9. 每次人工调整必须填写原因，并记录管理员、调整前后状态和调整时间。
10. 状态更新必须防止覆盖支付回调、骑手操作或其他管理员刚刚写入的新状态。

## 状态定义

### 订单状态 `state`

| 值 | 含义 |
|---|---|
| 0 | 待处理 |
| 1 | 已接单 |
| 2 | 已出餐 |
| 3 | 已完成 |
| 4 | 已取消 |

### 配送状态 `deliveryStatus`

仅适用于 `type=0` 的外送订单。

| 值 | 含义 |
|---|---|
| 0 | 待接单 |
| 1 | 骑手已接单 |
| 2 | 配送中 |
| 3 | 已送达 |

### 支付状态 `payStatus`

| 值 | 含义 |
|---|---|
| 0 | 未支付 |
| 1 | 已支付 |
| 2 | 已退款 |

## 状态操作矩阵

| 操作 | 允许条件 | 结果 | 禁止情况 |
|---|---|---|---|
| 调整订单状态 | 目标值为 0～4，当前订单不是终态，组合状态合法 | 更新订单状态并记录原因 | 完成/取消后回退；已支付在线订单直接取消但未退款 |
| 推进配送状态 | 外送订单、无处理中售后、目标状态不早于当前状态 | 更新配送状态 | 自取/堂食；状态回退；售后处理中 |
| 标记已送达 | 外送订单，履约条件满足 | `deliveryStatus=3`、`state=3`、记录送达时间，并执行幂等完成处理 | 非外送订单；订单已取消/退款 |
| 确认线下收款 | 线下支付订单、`payStatus=0`、订单未取消/退款 | `payStatus=1` | OMG 在线支付；终态订单 |
| 确认线下退款 | 线下支付订单、`payStatus=1`、订单尚未完成结算 | `payStatus=2`、`afterSaleStatus=3`、`state=4` | 已完成订单；OMG 在线支付；重复退款 |
| OMG 查询补单 | `payType=7`、`payStatus=0`、订单未取消 | 查询 OMG 真实交易；确认成功后更新为已支付 | 查询失败、金额不符、交易未支付时不得改单 |
| OMG 发起退款 | `payType=7`、`payStatus=1`、订单尚未完成结算 | 真实退款成功后同步支付、售后和订单状态 | 未支付、已退款、已完成结算订单 |
| OMG 人工退款待办 | OMG 支付方式不支持退款 API | 保留已支付状态并提示等待人工处理 | 人工退款未确认前不得标记已退款 |
| 确认 OMG 人工退款完成 | 已存在人工退款待办，管理员已在 OMG 后台核实退款完成 | 记录人工确认并同步支付、售后和订单状态 | 无待办、退款结果未核实、已完成结算订单 |

## User Scenarios & Testing

### User Story 1 - 平台调整外送配送状态（Priority: P1）

平台管理员可以在订单修改弹窗查看并推进外送订单的配送状态，用于处理骑手端漏操作或运营纠错。

**Why this priority**：这是当前页面明确缺失的履约管理能力，且不涉及外部资金事实。

**Independent Test**：选择一笔外送订单，将配送状态从“配送中”调整为“已送达”，确认订单同时完成并记录送达时间和操作日志。

**Acceptance Scenarios**：

1. **Given** 外送订单处于配送中，**When** 管理员改为已送达并填写原因，**Then** 配送状态为已送达、订单状态为已完成、送达时间有值。
2. **Given** 自取或堂食订单，**When** 管理员打开修改弹窗，**Then** 不提供配送状态修改入口。
3. **Given** 已送达订单，**When** 管理员尝试回退为配送中，**Then** 系统拒绝且数据不变。
4. **Given** 订单存在处理中售后，**When** 管理员尝试推进配送状态，**Then** 系统拒绝并提示先处理售后。

---

### User Story 2 - 平台确认线下收款（Priority: P1）

平台管理员可以为实际已经线下收款但系统仍显示未支付的现金订单确认收款。

**Why this priority**：自取、堂食现金订单在完成前可能保持未支付，需要平台具备异常兜底能力。

**Independent Test**：选择一笔未支付的线下现金订单，执行确认收款，确认支付状态更新且不会生成在线支付流水。

**Acceptance Scenarios**：

1. **Given** 未支付的线下现金订单，**When** 管理员确认收款并填写原因，**Then** 支付状态变为已支付并记录操作日志。
2. **Given** 未支付的 OMG 订单，**When** 管理员尝试直接确认收款，**Then** 系统拒绝并引导使用支付补单。
3. **Given** 已支付或已退款订单，**When** 管理员重复确认收款，**Then** 系统拒绝且不产生重复副作用。

---

### User Story 3 - 平台处理 OMG 漏单（Priority: P1）

平台管理员可以对仍显示未支付的 OMG 订单发起支付状态查询，只有 OMG 返回真实支付成功且金额、订单号等信息校验通过后，系统才补记为已支付。

**Why this priority**：在线支付状态是资金事实，必须以支付平台为准，同时平台需要处理回调丢失场景。

**Independent Test**：构造一笔回调丢失但 OMG 查询为成功的订单，执行平台补单后确认支付流水和订单只核销一次。

**Acceptance Scenarios**：

1. **Given** OMG 实际支付成功但订单仍未支付，**When** 管理员查询补单，**Then** 订单更新为已支付并记录补单日志。
2. **Given** OMG 返回未支付或交易失败，**When** 管理员查询补单，**Then** 订单不得被标记为已支付。
3. **Given** OMG 返回金额或订单信息不一致，**When** 管理员查询补单，**Then** 系统拒绝核销并记录需人工核对的日志。
4. **Given** 回调和平台补单并发到达，**When** 两条链路同时处理，**Then** 只允许一次支付核销和一次业务副作用。

---

### User Story 4 - 平台发起受控退款（Priority: P2）

平台管理员可以对尚未完成结算的已支付订单发起退款。OMG 订单必须调用真实退款能力；线下订单必须由管理员确认已经完成线下退款。

**Why this priority**：退款能力重要但风险高，必须在支付确认和配送纠错能力之后实现。

**Independent Test**：分别验证 OMG 信用卡退款成功、OMG 不支持自动退款、线下退款确认及已完成订单拒绝退款。

**Acceptance Scenarios**：

1. **Given** 尚未完成结算的 OMG 信用卡订单，**When** 管理员发起退款且 OMG 返回成功，**Then** 支付状态为已退款、售后状态为已退款、订单状态为已取消。
2. **Given** OMG 支付方式不支持退款 API，**When** 管理员发起退款，**Then** 系统生成/保留人工退款待办，不提前修改为已退款。
3. **Given** 尚未完成结算的线下已支付订单，**When** 管理员确认已线下退款并二次确认，**Then** 系统同步退款和取消状态并记录原因。
4. **Given** 已完成并产生结算的订单，**When** 管理员发起退款，**Then** 系统拒绝并提示需要完整售后与结算冲正流程。
5. **Given** OMG 延期支付订单已有人工退款待办且管理员已在 OMG 后台完成退款，**When** 管理员二次确认人工退款完成，**Then** 系统记录确认并同步支付、售后和订单状态。

---

### User Story 5 - 可审计且防并发覆盖（Priority: P1）

平台管理员的每次人工调整都可追溯；如果订单在管理员打开弹窗后被支付回调、骑手或其他管理员更新，本次提交不得覆盖新状态。

**Why this priority**：状态调整涉及资金和履约事实，审计及并发保护是上线前提。

**Independent Test**：管理员打开订单后模拟支付回调改变状态，再提交旧页面数据，确认提交失败且回调结果保留。

**Acceptance Scenarios**：

1. **Given** 管理员未填写修改原因，**When** 提交状态调整，**Then** 前后端均拒绝提交。
2. **Given** 页面加载后订单状态已被其他链路改变，**When** 管理员提交旧状态快照，**Then** 系统提示刷新且不覆盖新数据。
3. **Given** 状态调整成功，**When** 查看订单操作日志，**Then** 可以看到管理员、调整前后值、原因和时间。
4. **Given** 请求携带金额、用户、门店或商品等额外字段，**When** 提交状态调整，**Then** 这些字段不得被更新。

## Edge Cases

- 配送状态从 `NULL` 开始时，只能在外送订单进入可配送阶段后初始化为待接单。
- 缺少骑手信息时，不得伪造“骑手已接单”或“配送中”；平台本期不提供指派骑手能力。
- 已取消、已退款订单收到迟到支付成功回调时，继续沿用现有异常资金记录和人工核对机制，不重新履约。
- OMG 查询接口超时或返回未知状态时保持原支付状态，允许稍后重试。
- OMG 退款请求超时且结果未知时保持待核对状态，不允许重复发起可能造成重复退款的请求。
- 退款成功但订单状态同步失败时，必须记录高优先级异常日志，并允许幂等补偿，不能再次退款。
- 相同目标状态的重复提交不得重复生成账单、退款、推送或操作日志。
- `afterSaleStatus>0` 的订单不得通过普通状态调整绕过售后流程。

## Functional Requirements

- **FR-001**：平台订单修改界面 MUST 展示订单状态、配送状态、当前支付状态及人工调整原因。
- **FR-002**：状态调整和支付操作 MUST 仅允许具备订单编辑权限的平台管理员执行。
- **FR-003**：状态调整请求 MUST 使用状态专用输入模型，禁止接收或更新完整订单业务字段。
- **FR-004**：订单状态 MUST 仅接受 0～4，配送状态仅接受 0～3，支付状态仅接受 0～2。
- **FR-005**：配送状态修改 MUST 仅适用于外送订单；自取和堂食订单的配送状态保持 `NULL`。
- **FR-006**：普通配送调整 MUST 仅允许向前推进，禁止从已送达或已完成状态回退。
- **FR-007**：配送状态变为已送达时 MUST 原子同步订单完成状态和送达时间。
- **FR-008**：完成订单所需的账单及关联副作用 MUST 幂等，重复请求不得重复结算。
- **FR-009**：线下未支付订单 MUST 通过“确认收款”操作才能变为已支付。
- **FR-010**：OMG 未支付订单 MUST 通过回调或支付查询核验后才能变为已支付，禁止人工直接写入。
- **FR-011**：OMG 已支付订单 MUST 在真实退款成功后才能变为已退款。
- **FR-012**：不支持自动退款的 OMG 支付方式 MUST 保持待人工处理状态，人工确认前不得标记退款完成。
- **FR-013**：允许的全额退款成功后 MUST 同步 `payStatus=2`、`afterSaleStatus=3`、`state=4`。
- **FR-014**：已完成并产生结算的订单 MUST 拒绝本期退款操作。
- **FR-015**：`afterSaleStatus>0` 时，系统 MUST 拒绝可能绕过售后流程的普通状态修改。
- **FR-016**：每次人工调整 MUST 要求非空原因，并限制合理长度。
- **FR-017**：每次成功调整 MUST 同步记录订单号、管理员、状态前后值、调整原因和时间。
- **FR-018**：状态更新 MUST 使用旧状态条件或等效并发控制；条件不匹配时要求管理员刷新重试。
- **FR-019**：支付补单和退款 MUST 复用现有 OMG 幂等流水，不得另建一套不一致的支付事实。
- **FR-020**：现有通用订单编辑入口 MUST 不再允许更新订单、配送、支付和售后状态，防止绕过专用规则。
- **FR-021**：骑手确认送达时 MUST 同步 `deliveryStatus=3`、`state=3` 和送达时间，保持与平台调整相同的状态不变量。
- **FR-022**：新增平台文案 MUST 同步提供简体中文、繁体中文、英文和越南文。
- **FR-023**：状态调整成功后页面 MUST 刷新订单详情，展示服务端最终状态而不是本地推测值。
- **FR-024**：OMG 人工退款完成 MUST 仅允许在已有未决人工退款记录且管理员二次确认后写入，确认前订单支付状态保持已支付。

## Key Entities

- **PosOrder（已有）**：订单业务主体，使用 `state`、`deliveryStatus`、`payStatus`、`afterSaleStatus`、`type`、`payType` 和 `sdTime` 表达订单状态。
- **PosOrderLog（已有）**：记录人工调整的管理员、状态变化、原因及时间。
- **PosOrderOmgPayment（已有）**：OMG 支付事实和幂等核销依据，在线支付状态不得与其冲突。
- **PosOrderOmgRefund（已有）**：OMG 退款请求、处理结果及人工退款待办记录。

## Success Criteria

- **SC-001**：平台管理员可以在一个订单管理入口完成受控的订单状态、配送状态和支付业务操作。
- **SC-002**：外送订单标记已送达后，订单状态、配送状态和送达时间三项一致率达到 100%。
- **SC-003**：任何在线订单都不能在缺少有效支付回调或查询核验的情况下被标记为已支付。
- **SC-004**：任何 OMG 订单都不能在真实退款成功或人工退款确认前被标记为已退款。
- **SC-005**：非法状态、状态回退、订单类型不匹配和售后冲突请求的数据变更数为 0。
- **SC-006**：并发回调、补单和管理员操作场景下，支付核销、退款及订单完成副作用均最多执行一次。
- **SC-007**：人工状态调整日志覆盖率为 100%，每条日志均包含操作者、前后状态和原因。
- **SC-008**：状态专用接口无法修改金额、用户、门店、商品、优惠等非状态字段。
- **SC-009**：平台新增文案在简中、繁中、英文和越南文环境下均无原始 key 或空白文本。

## Assumptions

- `type=0/1/2` 分别表示外送、自取、堂食。
- `payType=7` 表示 OMG 在线支付；其他历史支付方式不重新启用。
- 继续复用现有 `system:order:edit` 权限，不新增角色体系。
- 继续复用现有订单日志、OMG 支付流水和 OMG 退款记录，不新增数据库字段。
- 订单完成相关账单逻辑具备按订单幂等检查，本功能只在状态成功进入完成态后触发。

## Out of Scope

- 已完成订单的商家/骑手结算冲正及完整售后退款流程。
- 人工将已完成、已取消或已退款订单恢复为进行中。
- 平台直接指派或更换骑手。
- 新增支付渠道或重新启用 VNPay、ZaloPay、NewebPay。
- 修改订单金额、商品、优惠、用户、门店或收货信息。
- 新增数据库表或字段。
- 为本功能创建 Git 分支。

## Spec Kit 执行顺序

1. 以本文件作为 `/speckit.specify` 阶段产物。
2. 下一步执行技术调研与 `/speckit.plan`，明确接口、事务、并发和文件改动。
3. 计划审核后执行 `/speckit.tasks`，拆分可独立验证的测试与实现任务。
4. 按测试先行顺序执行任务并逐项验证。
5. 全程沿用当前工作分支，不执行创建或切换分支操作。
