# Data Model: 平台订单状态受控调整 **Date**: 2026-08-10 **Database change**: None ## 1. Existing Persistent Entities ### 1.1 `PosOrder` / `pos_order` 本功能只使用现有字段,不新增列。 | Java 字段 | 数据库列 | 类型 | 本功能含义 | |---|---|---|---| | `id` | `id` | BIGINT | 内部订单主键,管理端接口路径参数 | | `ddId` | `dd_id` | VARCHAR | 业务订单号,日志和 OMG 流水关联键 | | `type` | `type` | BIGINT | 0 外送、1 自取、2 堂食 | | `state` | `state` | BIGINT | 0 待处理、1 已接单、2 已出餐、3 已完成、4 已取消 | | `deliveryStatus` | `delivery_status` | BIGINT NULL | 外送:0 待接单、1 骑手已接单、2 配送中、3 已送达 | | `payStatus` | `pay_status` | BIGINT | 0 未支付、1 已支付、2 已退款 | | `afterSaleStatus` | `after_sale_status` | BIGINT | 0 无售后、1~6 为售后流程状态 | | `payType` | `pay_type` | VARCHAR | 1 线下/到付,7 OMG;历史类型不在本功能启用 | | `collectPayment` | `collect_payment` | VARCHAR | 到付标识,保留给既有账单逻辑 | | `qsId` | `qs_id` | BIGINT NULL | 骑手 ID;本功能不提供指派能力 | | `sdTime` | `sd_time` | DATETIME NULL | 实际送达时间 | | `points` | `points` | INT NULL | 退款/取消时需要返还的积分 | ### 1.2 `PosOrderLog` / `pos_order_log` | 字段 | 用途 | |---|---| | `ddId` | 关联业务订单号 | | `operatorType` | 管理员固定为 1;系统资金日志为 0 | | `operatorId` | 当前后台管理员 ID | | `operatorName` | 当前后台管理员名称 | | `content` | 状态前后值、动作和原因,最大 512 字 | | `logTime` | 服务端写入时间 | 人工操作使用同步日志,保证状态事务回滚时不会残留“成功”日志。 ### 1.3 `PosOrderOmgPayment` / `pos_order_omg_payment` 订单层 `payStatus` 只有 0/1/2;OMG 流水有更细的内部状态: | `pay_status` | 含义 | 管理端行为 | |---|---|---| | 0 | 未支付 | 允许查询补单 | | 1 | 已支付 | 未完成订单允许退款 | | 2 | 支付失败 | 不得人工改为已支付;需要重新发起支付 | | 3 | 已退款 | 订单层应补偿为 `payStatus=2` | | 4 | 退款中/结果未知 | 禁止重复退款,提示等待对账 | ### 1.4 `PosOrderOmgRefund` / `pos_order_omg_refund` | 字段组合 | 含义 | |---|---| | `action=R, rtnCode=1` | OMG 自动退款成功 | | `action=R, rtnCode!=1` | OMG 明确退款失败 | | `action=R, rtnCode=NULL` | 请求处理中或结果未知,结合 `rtnMsg` 判断 | | `action=NULL, rtnCode=NULL` | 支付方式不支持退款 API,等待人工处理 | | `action=NULL, rtnCode=1` | 管理员确认已在 OMG 后台完成人工退款 | 同一支付流水只保留一个未决人工待办;已存在时重复请求返回原状态。 ## 2. API-only Models ### 2.1 `AdminOrderActionRequest` 所有支付动作与状态动作共享的旧快照和审计字段。 | 字段 | Java 类型 | 校验 | |---|---|---| | `expectedState` | `Long` | 必填,0~4 | | `expectedDeliveryStatus` | `Long` | 可空;非空时 0~3 | | `expectedPayStatus` | `Long` | 必填,0~2 | | `expectedAfterSaleStatus` | `Long` | 必填,0~6 | | `reason` | `String` | 必填,trim 后 1~200 字 | ### 2.2 `AdminOrderStatusUpdateRequest` 继承/组合 `AdminOrderActionRequest`,增加: | 字段 | Java 类型 | 校验 | |---|---|---| | `targetState` | `Long` | 可空,非空时 0~4 | | `targetDeliveryStatus` | `Long` | 可空,非空时 0~3 | 规则:至少一个目标值与快照不同;未提交的目标保持当前值。外送送达可以只提交 `targetDeliveryStatus=3`,服务端自动同步 `state=3`。 ### 2.3 `AdminOrderStatusContext` | 字段 | 类型 | 说明 | |---|---|---| | `id`, `ddId` | Long/String | 订单标识 | | `type`, `payType` | Long/String | 类型判定依据 | | `state`, `deliveryStatus`, `payStatus`, `afterSaleStatus` | Long | 当前快照 | | `sdTime` | Date | 当前送达时间 | | `riderAssigned` | Boolean | `qsId != null` | | `allowedOrderStates` | List | 包含当前值及可选下一值 | | `allowedDeliveryStatuses` | List | 非外送为空 | | `canConfirmOfflinePayment` | Boolean | 可确认线下收款 | | `canConfirmOfflineRefund` | Boolean | 可确认线下退款 | | `canReconcileOmg` | Boolean | 可查询 OMG 补单 | | `canRefundOmg` | Boolean | 可发起 OMG 退款 | | `canConfirmManualOmgRefund` | Boolean | 已有待办且可确认人工退款完成 | | `omgPaymentStatus` | Integer NULL | 最新 OMG 流水状态 0~4 | | `manualRefundPending` | Boolean | 存在未决人工退款记录 | | `refundUnknown` | Boolean | OMG 流水为退款中/结果未知 | `allowed*` 和 `can*` 是 UI 提示,不替代写接口校验。 ### 2.4 `OmgRefundOutcome` | 状态 | 说明 | 订单层处理 | |---|---|---| | `REFUNDED` | OMG 明确退款成功 | 同步为已退款/已取消/售后已退款 | | `MANUAL_PENDING` | 无自动退款 API | 保持已支付,显示待人工处理和确认入口 | | `UNKNOWN` | 请求结果未知 | 保持已支付,OMG 流水保持退款中 | | `FAILED` | 网关明确失败 | 保持已支付,OMG 流水恢复已支付 | | `IDEMPOTENT` | 已退款或正在处理 | 返回当前最终状态,无重复副作用 | ## 3. State Invariants 以下不变量必须在每个写入口成立: 1. `type IN (1,2) → deliveryStatus IS NULL`。 2. `deliveryStatus=3 → type=0 AND state=3 AND sdTime IS NOT NULL`。 3. `state=3 → payStatus=1 AND afterSaleStatus=0`(本期不允许完成后退款)。 4. `payStatus=2 → state=4 AND afterSaleStatus=3`(本功能处理的全额退款)。 5. `payType=7 AND payStatus=1` 必须存在 OMG 已支付流水。 6. `payType=7 AND payStatus=2` 必须存在 OMG 自动退款成功记录或管理员明确确认人工退款完成;人工待办不提前置 2。 7. `state IN (3,4)`、`payStatus=2` 为本功能终态,普通状态接口不能重新打开。 8. `afterSaleStatus>0` 时,普通订单/配送状态接口不得变更数据。 ## 4. Order State Transitions ### 4.1 Normal order state | Current | Target | Additional conditions | Result | |---|---|---|---| | 0 | 1 | OMG 订单必须已支付;无售后 | `state=1` | | 1 | 2 | 无售后 | `state=2`;外送若配送为空则初始化为 0 | | 2 | 3 | 已支付;外送已送达,自取/堂食无配送状态 | `state=3` + 完成副作用 | | 0 | 4 | 未支付、无售后 | `state=4` | | 1 | 4 | 未支付、无售后 | `state=4` | 其他跳转全部拒绝。已支付订单取消必须使用退款动作。 ### 4.2 Delivery state | Current | Target | Conditions | Result | |---|---|---|---| | NULL | 0 | 外送,`state=2` | 等待骑手接单 | | 0 | 1 | 外送,已有 `qsId`,`state=2` | 骑手已接单 | | 1 | 2 | 外送,已有 `qsId`,`state=2` | 配送中 | | 2 | 3 | 外送,已有 `qsId`,已支付,无售后 | 配送已送达 + 订单完成 + 送达时间 + 账单 | 配送状态不得跳级或回退。相同值视为无变化。 ## 5. Payment State Transitions ### 5.1 Offline `payType="1"` ```text 0 --confirm-payment--> 1 1 --confirm-refund--> 2 ``` - 收款不自动完成订单;完成仍要满足订单/配送规则。 - 退款原子同步 `state=4` 和 `afterSaleStatus=3`。 ### 5.2 OMG `payType="7"` ```text order.payStatus 0 --callback/query + ledger CAS--> 1 order.payStatus 1 --real refund success--> 2 ``` 管理员请求体不能携带目标 `payStatus`。OMG 流水状态 4 期间订单仍显示已支付,并额外显示“退款结果待核对”。 对于无退款 API 的支付方式:创建未决人工记录时订单和支付流水保持已支付;管理员完成外部人工退款并二次确认后,支付流水通过既有 `1→4→3` CAS,新增 `action=NULL, rtnCode=1` 的完成记录,再同步订单三状态。 ## 6. Composite Snapshot CAS 逻辑条件: ```sql UPDATE pos_order SET ... WHERE id = :id AND state = :expectedState AND pay_status = :expectedPayStatus AND after_sale_status = :expectedAfterSaleStatus AND delivery_status <=> :expectedDeliveryStatus; ``` 实现使用 MyBatis-Plus 条件构造器参数化生成;上面的 MySQL `<=>` 仅用于说明 NULL-safe 语义,不要求新增 XML SQL。 CAS 失败时: - 不写订单日志; - 不生成账单、不返积分、不推送; - 返回业务冲突并要求重新加载 `status-context`。 ## 7. Audit Content 日志内容保持可读且不超过 512 字: ```text 平台调整[订单/配送]:state 2→3,delivery 2→3,pay 1→1,afterSale 0→0;原因:骑手端漏操作 平台确认线下收款:pay 0→1;原因:门店已收现金 平台发起OMG补单:pay 0→1;原因:用户提供付款凭证 平台OMG退款成功:state 1→4,pay 1→2,afterSale 0→3;原因:商家无法履约 ``` 不得记录 OMG HashKey/HashIV、token、完整回调原文或其他秘密。 ## 8. Database Migration 无数据库迁移,不修改 `updatesql/sql.md`。如测试发现现有生产表 `pos_order_log.content` 不是 512 字,应暂停实现并单独提出迁移评审,不在本功能中自动执行 DDL。