## MODIFIED Requirements ### Requirement: ExchangeOrder 换货单模型定义 系统 SHALL 定义 `ExchangeOrder` 模型并映射到 `tb_exchange_order`,用于承载客户端换货完整生命周期。 模型字段 MUST 至少包含: - 基础:`id`、`created_at`、`updated_at`、`deleted_at`、`creator`、`updater` - 单号:`exchange_no` - 流程:`flow_type` - 旧资产:`old_asset_type`、`old_asset_id`、`old_asset_identifier` - 新资产:`new_asset_type`、`new_asset_id`、`new_asset_identifier` - 收货:`recipient_name`、`recipient_phone`、`recipient_address` - 物流:`express_company`、`express_no` - 迁移:`migrate_data`、`migration_completed`、`migration_balance` - 时间:`shipped_at`、`completed_at` - 业务:`exchange_reason`、`remark`、`status` - 多租户:`shop_id` `flow_type` MUST 使用字符串枚举,至少支持: - `shipping`:需要客户填写收货地址、后台发货、后台确认完成 - `direct`:创建时直接完成,不经过客户填写地址和后台发货 `flow_type` MUST 在数据库层设置默认值 `shipping`,用于兼容历史记录与旧创建请求。 `ExchangeOrder` SHALL 嵌入 `BaseModel` 并实现 `TableName() string`,返回 `tb_exchange_order`。 #### Scenario: 创建 shipping 换货单模型实例 - **WHEN** 系统创建新的 `shipping` 换货单记录 - **THEN** 记录 MUST 包含旧资产快照、流程类型 `flow_type=shipping`、收货信息占位、迁移状态字段和多租户字段 #### Scenario: 创建 direct 换货单模型实例 - **WHEN** 系统创建新的 `direct` 换货单记录 - **THEN** 记录 MUST 包含旧资产快照、新资产快照、流程类型 `flow_type=direct` - **AND** 记录在创建成功时即可具备 `completed_at` --- ### Requirement: 换货状态常量定义 系统 MUST 使用 int 常量定义换货状态: - `1` 待填写信息 - `2` 待发货 - `3` 已发货待确认 - `4` 已完成 - `5` 已取消 系统 MUST 使用独立的字符串常量定义换货流程类型: - `shipping` - `direct` #### Scenario: 状态与流程常量一致性 - **WHEN** Service、Store、Handler 读取或更新换货状态与流程类型 - **THEN** 各层 MUST 使用统一常量值,禁止硬编码散落魔法数字和字符串 --- ### Requirement: 换货状态机流转规则 系统 SHALL 执行以下状态机: - `shipping`: - 创建换货单后:`status=1` - 客户填写收货信息后:`1 -> 2` - 后台发货后:`2 -> 3` - 后台确认完成后:`3 -> 4` - 取消:仅允许 `1/2 -> 5` - `direct`: - 创建换货单并完成后:`status=4` - 不经过 `1/2/3` - 不走客户端填写收货地址 - 不走后台发货 - 不进入取消状态机 - 任一步失败时不保留半成品 `direct` 单据 系统 MUST 禁止非法流转(如 `3 -> 5`、`4 -> 2`、`direct 进入 2`)。 #### Scenario: shipping 已发货不可取消 - **WHEN** `shipping` 换货单状态为 `3` 且请求取消 - **THEN** 系统 MUST 拒绝并返回状态流转非法错误 #### Scenario: direct 创建即完成 - **WHEN** 后台以 `flow_type=direct` 创建换货单且所有校验通过 - **THEN** 系统 MUST 直接创建 `status=4` 的换货单 - **AND** MUST 写入 `completed_at` #### Scenario: direct 失败不落半成品状态 - **WHEN** 后台以 `flow_type=direct` 创建换货单但完成事务失败 - **THEN** 系统 MUST 回滚创建 - **AND** MUST NOT 产生 `flow_type=direct AND status IN (1,2,3)` 的换货单 #### Scenario: direct 不允许进入发货阶段 - **WHEN** `direct` 换货单请求执行发货或填写收货地址 - **THEN** 系统 MUST 拒绝并返回流程类型不支持该操作的错误 --- ### Requirement: 换货单号生成规则 系统 MUST 为每个换货单生成全局可追踪单号,格式为:`EXC + 时间戳片段 + 随机数片段`。 生成规则 SHALL 满足: - 前缀固定为 `EXC` - 包含日期/时间信息用于人工排查 - 包含随机片段降低并发冲突概率 #### Scenario: 生成换货单号 - **WHEN** 后台发起换货并创建新单 - **THEN** 系统 MUST 生成形如 `EXC20260319XXXXXX` 的单号并写入 `exchange_no` --- ### Requirement: 换货关键业务时间字段语义 系统 SHALL 使用独立的业务时间字段表达发货完成和换货完成,而不是复用 `updated_at`。 字段语义 MUST 满足: - `shipped_at`:仅在 `shipping` 流程发货成功后写入 - `completed_at`:仅在换货完成成功后写入,`shipping` 与 `direct` 共用 - `direct` 流程 MUST 保持 `shipped_at` 为空 - 历史已完成单据若 `completed_at` 为空,查询与追溯层 MUST 回退使用 `updated_at` #### Scenario: shipping 写入发货时间 - **WHEN** `shipping` 换货单执行后台发货成功 - **THEN** 系统 MUST 记录 `shipped_at=当前时间` #### Scenario: direct 不写发货时间 - **WHEN** `direct` 换货单创建并完成成功 - **THEN** 系统 MUST 保持 `shipped_at` 为空 - **AND** MUST 写入 `completed_at=当前时间` #### Scenario: 历史单据完成时间兼容 - **WHEN** 查询历史已完成换货单且 `completed_at` 为空 - **THEN** 系统 MUST 在追溯展示中回退使用 `updated_at` 作为兼容完成时间