# Implementation Plan: 平台订单状态受控调整

**Branch**: `test`（沿用当前分支，不创建新分支） | **Date**: 2026-08-10 | **Spec**: [spec.md](./spec.md)

**Input**: Feature specification from `/specs/018-order-status-admin/spec.md`

## Summary

平台订单管理改为使用状态专用接口，不再把完整 `PosOrder` 对象提交给通用编辑接口。修改弹窗先读取服务端计算的状态上下文与允许操作，再通过旧状态快照执行 CAS 更新：订单状态和配送状态只允许合法前向流转；线下支付通过确认收款/确认退款维护；OMG 支付通过现有查询补单和真实退款流水维护。所有成功的人工操作同步记录管理员、前后状态和原因。

本功能同时修复两个已经确认的状态不变量缺口：骑手送达必须同步 `deliveryStatus=3`、`state=3`、`sdTime` 并执行幂等完成结算；OMG 退款成功必须同步 `payStatus=2`、`afterSaleStatus=3`、`state=4`。不新增数据库表或字段。

## Technical Context

**Language/Version**: Java 21；JavaScript（Vue 2.6）
**Primary Dependencies**: Spring Boot 3.3.5、RuoYi-Vue 3.8.5、Spring Security、MyBatis-Plus/MyBatis XML、Redisson、Vue 2.6、Element UI 2.15、Axios 0.24、Vue I18n 8
**Storage**: MySQL；复用 `pos_order`、`pos_order_log`、`pos_order_omg_payment`、`pos_order_omg_refund`，无 DDL 变更
**Testing**: JUnit 5、Mockito、Spring Boot Test；前端 ESLint、生产构建和人工界面验证
**Target Platform**: RuoYi Java Web 服务及桌面浏览器管理后台
**Project Type**: 后端多模块 Web 服务 + 独立 Vue 管理后台
**Performance Goals**: 本地状态操作只读取单笔订单并执行单行条件更新；OMG 查询/退款不增加额外网关调用；订单列表查询不新增关联查询
**Constraints**: 在线支付状态必须以 OMG 事实为准；禁止完整实体批量赋值；外部支付请求不得持有长数据库事务；状态/账单/日志必须幂等；四语言文案齐全；不创建分支
**Scale/Scope**: 1 个订单管理页面、1 个现有订单 Controller、2 条现有履约/支付链路、7 个管理端状态接口；无数据库迁移

## Constitution Check

*GATE: Phase 0 前检查；Phase 1 设计完成后复查。*

项目 `.specify/memory/constitution.md` 仍是未初始化的占位模板，无法提供可执行条款。本功能以根目录 `CLAUDE.md`、管理后台 `CLAUDE.md`、006 订单状态规格和 016 OMG 支付规格作为实际质量门槛。

| Gate | 结论 | 处理 |
|---|---|---|
| 使用当前有效订单/支付代码 | PASS | 只修改 `PosOrderController`、`PosOrderQsOprateController`、`OmgPayController` 及其现有服务；不接入废弃支付通道 |
| 支付与退款真实性 | PASS | OMG 只允许回调/查询核销和真实退款；线下操作必须显式确认并审计 |
| 输入最小化与权限 | PASS | 新接口使用专用 DTO、`@Valid`、`system:order:edit`，不接收金额/用户/商品等字段 |
| 并发与幂等 | PASS | 订单使用四状态旧快照 CAS；OMG 继续使用支付流水 CAS；完成账单只在成功进入完成态后执行 |
| 数据库变更规范 | PASS | 无表结构变更，不修改 `updatesql/sql.md` |
| 前端国际化 | PASS | 新增及本页触及的状态文案同步简中、繁中、英文、越南文 |
| 测试优先 | PASS | 先写策略/服务/OMG 回归测试，再实现接口和页面 |
| 变更范围 | PASS | 不创建分支，不修改与订单状态无关的功能 |

Phase 1 复查：设计未引入新的数据库、支付渠道、权限体系或跨模块反向依赖，所有 Gate 继续通过。

## Project Structure

### Documentation (this feature)

```text
specs/018-order-status-admin/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
└── contracts/
    └── admin-order-status-api.md
```

`tasks.md` 由下一阶段 `/speckit.tasks` 生成，本阶段不创建。

### Source Code (repository root)

```text
foodie_server/
├── ruoyi-admin/src/main/java/com/ruoyi/app/order/
│   ├── PosOrderController.java                 # 管理端状态接口、旧 PUT 接口加固
│   ├── PosOrderQsOprateController.java         # 骑手送达改走统一完成逻辑
│   ├── OrderLifecycleService.java              # 新增：状态策略、CAS、完成/线下支付/退款
│   └── dto/
│       ├── AdminOrderActionRequest.java        # 新增：旧状态快照 + 原因
│       ├── AdminOrderStatusUpdateRequest.java  # 新增：目标订单/配送状态
│       └── AdminOrderStatusContext.java        # 新增：当前状态与允许操作
├── ruoyi-admin/src/main/java/com/ruoyi/app/pay/
│   ├── OmgPayController.java                   # 复用补单/退款核心，补齐退款状态不变量
│   └── dto/
│       └── OmgRefundOutcome.java               # 新增：成功/人工待办/结果未知/失败
├── ruoyi-admin/src/test/java/com/ruoyi/app/order/
│   ├── OrderLifecycleServiceTest.java          # 新增
│   └── PosOrderAdminStatusControllerTest.java  # 新增
└── ruoyi-admin/src/test/java/com/ruoyi/app/pay/
    └── OmgPayControllerTest.java               # 扩展退款与补单回归

foodie-admin-vue/
└── src/
    ├── api/system/order.js                     # 新增状态上下文和动作 API
    ├── views/system/order/index.vue            # 受控修改弹窗与支付动作
    └── api/language/
        ├── language.zh_CN.js
        ├── language.zh_TW.js
        ├── language.en_US.js
        └── language.vi.js
```

**Structure Decision**: 状态不变量和本地事务放在 `OrderLifecycleService`，Controller 仅做权限、参数绑定与响应转换。OMG 不复制网关逻辑，继续复用现有 `OmgPayController.reconcileByQuery` 和退款流水服务；本期用明确结果对象替代管理员侧解析错误字符串，不进行整套 OMG Controller 重构。

## Phase 0: Research Outcome

详细结论见 [research.md](./research.md)。核心决策如下：

1. 增加 `GET /system/order/{id}/status-context`，由后端返回当前快照和允许操作，前端不自行复制完整状态机。
2. 本地状态更新使用 `state + deliveryStatus + payStatus + afterSaleStatus` 旧值组成的无模式变更 CAS；更新行数为 0 时要求刷新。
3. 配送/完成、线下收款和线下退款集中到 `OrderLifecycleService`，同步写 `OrderLogHelper.logSync`。
4. OMG 补单继续复用 `reconcileByQuery` 的金额、交易号和流水 CAS；管理员不能直接提交 `payStatus=1`。
5. OMG 退款继续先将支付流水从已支付 CAS 为退款中，再调用网关；成功后统一完成订单/售后状态，超时则保留退款中等待核对；人工待办经管理员核实后使用同一流水 CAS 完成。
6. `payType="1"` 是本期唯一可人工确认的线下支付方式；`payType="7"` 是 OMG；历史在线类型不得按线下订单处理。
7. 不新增审计表；原因限制 1～200 字，结构化前后值写入现有 512 字订单日志内容。

## Phase 1: Design

### 1. 状态上下文与能力判定

状态弹窗不再调用通用订单详情作为可提交表单。新上下文接口返回：

- 订单标识、类型、支付类型、当前四状态、送达时间、是否已有骑手；
- 允许的下一订单状态和配送状态；
- 是否允许确认线下收款、线下退款、OMG 补单、OMG 退款；
- OMG 流水是否退款中或等待人工退款。

能力字段只是界面提示，所有写接口仍重新读取订单并执行同样校验，不能依赖前端隐藏按钮实现安全控制。

### 2. 专用请求模型

所有写请求携带：

- `expectedState`；
- `expectedDeliveryStatus`（允许为 `null`）；
- `expectedPayStatus`；
- `expectedAfterSaleStatus`；
- `reason`（去首尾空格后 1～200 字）。

订单/配送修改额外携带可空的 `targetState`、`targetDeliveryStatus`，至少一个目标值发生变化。支付操作不接收目标支付状态，目标由服务端动作固定决定。

### 3. 状态策略

- 订单普通流转：`0→1→2→3`；取消仅 `0/1→4`。
- 已支付订单不能通过普通状态接口取消；必须走对应退款动作。
- `state=3/4`、`payStatus=2` 或 `afterSaleStatus>0` 禁止普通状态调整。
- 外送完成要求 `payStatus=1` 且 `deliveryStatus=3`；配送改为 3 时同步完成。
- 自取/堂食完成要求 `payStatus=1`，配送状态始终为 `null`。
- 配送普通流转：`null→0→1→2→3`；`null→0` 仅在外送且 `state=2`；进入 1/2 要求已有骑手，本期不提供指派骑手。
- 相同目标视为幂等无变化，不重复生成日志、账单或推送。

完整矩阵见 [data-model.md](./data-model.md)。

### 4. 原子更新和完成副作用

`OrderLifecycleService` 先根据数据库最新值校验请求快照，再使用 MyBatis-Plus 条件更新：

```text
WHERE id = :id
  AND state = :expectedState
  AND pay_status = :expectedPayStatus
  AND after_sale_status = :expectedAfterSaleStatus
  AND delivery_status <=> :expectedDeliveryStatus
```

Java 实现对可空配送状态分别使用 `isNull` 或 `eq`，不拼接 SQL。只有 CAS 成功进入 `state=3` 的调用才生成商家/骑手账单；账单继续使用现有按订单检查。状态、账单和同步审计日志处于同一 Spring 事务。

骑手送达改调用同一完成方法，从而补齐 `state=3` 和完成账单，同时保留现有到付用户账单处理。原有骑手锁保留，CAS 作为支付回调、定时任务和其他节点并发时的最终保护。

### 5. 支付和退款

#### 线下

- 确认收款：仅 `payType="1"`、`payStatus=0`、非取消/退款订单；CAS 为 `payStatus=1`。
- 确认退款：仅 `payType="1"`、`payStatus=1`、`state!=3`、`afterSaleStatus=0`；二次确认后 CAS 为 `payStatus=2, afterSaleStatus=3, state=4`，并复用现有积分返还幂等能力。

#### OMG

- 补单：管理端先校验快照和 `payType="7"`，再调用 `reconcileByQuery(ddId, "admin")`；支付流水和订单核销仍由现有 CAS 决定。
- 退款：已完成订单先拒绝。信用卡退款成功后同步三个业务状态；不支持 API 的支付方式只记录一次人工待办，订单仍保持已支付；管理员在 OMG 后台完成退款后，可基于该待办二次确认，流水通过 `1→4→3` CAS 后同步三个业务状态；网关超时保留 OMG 流水退款中并禁止重复发起。
- 外部 HTTP 调用不包在长事务中。网关成功后业务状态同步失败时记录高优先级同步日志，后续只允许补偿状态，禁止再次退款。

### 6. 旧接口加固

`PUT /system/order` 删除旧状态值 7、12 和 `kefuState→state=5` 分支。请求只要包含 `state`、`deliveryStatus`、`payStatus`、`afterSaleStatus` 或 `sdTime` 即拒绝，并提示使用状态专用接口，防止旧页面或构造请求绕过规则。其他历史非状态编辑能力保持原样。

### 7. 管理后台

- 修改弹窗只保存状态上下文、目标值、旧快照和原因，不再把完整订单对象回传。
- 订单/配送下拉框只展示服务端返回的允许值；终态只读。
- 支付状态始终只读，根据能力显示“确认线下收款”“查询 OMG 支付”“确认线下退款”“发起 OMG 退款”“确认 OMG 人工退款完成”等按钮。
- 退款使用二次确认；提交期间按钮 loading，阻止重复点击；成功后重新读取详情和列表。
- 将本页订单、配送、支付、售后状态文案统一改为四语言 `$t()`，避免新弹窗出现中文硬编码。

### 8. API Contract

接口、请求和响应示例见 [contracts/admin-order-status-api.md](./contracts/admin-order-status-api.md)。所有写接口复用 `system:order:edit` 权限，并使用 `@RepeatSubmit` 限制短时间重复操作。

## Implementation Sequence

1. 先编写状态策略、CAS、完成副作用和 Controller 合约测试，确认失败。
2. 实现 DTO、状态上下文和 `OrderLifecycleService`。
3. 增加管理端状态/线下支付接口，并加固旧 `PUT /system/order`。
4. 让骑手送达复用统一完成逻辑，补充状态和账单回归测试。
5. 扩展 OMG 管理补单/退款结果处理，补齐退款后三状态一致性和人工待办幂等。
6. 修改管理后台 API、弹窗、操作按钮和四语言文案。
7. 执行后端测试、编译、前端 lint/build 及 [quickstart.md](./quickstart.md) 手工场景。

## Verification Strategy

### Automated

- 状态策略单元测试覆盖全部合法/非法转移、类型限制、售后和终态。
- 服务测试验证 CAS 条件、CAS 失败无副作用、完成只结算一次、原因日志内容。
- Controller 测试验证权限注解、参数校验和额外业务字段无法进入请求 DTO。
- OMG 测试覆盖补单金额不符、回调/补单幂等、退款成功、人工待办、超时未知和重复退款。
- 执行 `mvn -pl ruoyi-admin -am test` 与前端 lint/build。

### Manual

- 外送 `2/2/1/0 → 3/3/1/0`（订单/配送/支付/售后）并检查送达时间、账单、日志。
- 自取/堂食线下收款后完成。
- OMG 漏回调补单；OMG 信用卡退款；延期支付人工退款待办。
- 旧快照并发提交、非法回退、已完成退款、自取修改配送、空原因全部拒绝。
- 四种语言检查无原始 key 和中文泄漏。

## Rollout and Rollback

- 后端状态接口与前端页面同批发布；先部署后端可保证旧前端仍能读取订单，但旧状态提交会收到明确拒绝。
- 无数据库迁移，回滚只需回退后端和前端代码。
- 发布后重点观察：CAS 冲突、OMG 人工待办、退款结果未知、退款成功但业务状态同步失败日志。

## Risks

| 风险 | 缓解措施 |
|---|---|
| OMG 网关成功但本地状态失败 | 流水先保留真实退款状态，记录高优先级日志，仅做本地补偿，不重复退款 |
| 完成路径并发重复结算 | 状态 CAS 成功者才执行完成副作用，现有账单按订单幂等检查兜底 |
| 管理员页面陈旧覆盖回调/骑手结果 | 四状态快照 CAS，冲突时强制刷新 |
| 历史在线支付被误当线下 | 线下动作只允许 `payType="1"`，不是简单判断 `!=7` |
| 无独立审计字段 | 使用同步订单日志，原因上限 200 字，内容包含四状态前后值 |
| Constitution 未初始化 | 在本计划显式列出并执行 `CLAUDE.md` 与既有规格门槛 |

## Complexity Tracking

无 Constitution 违规需要豁免。新增状态上下文接口和生命周期服务是隔离完整实体更新、统一状态不变量所需的最小结构。
