90 lines
5.8 KiB
Markdown
90 lines
5.8 KiB
Markdown
# PRD:UR#57 活跃退款资产禁止换货
|
||
|
||
Status: ready-for-agent
|
||
|
||
---
|
||
|
||
## Problem Statement
|
||
|
||
换货创建目前没有检查旧资产是否存在未结束的退款。换货可能在退款审批或资金处理期间改变资产关系,造成资产状态、套餐、钱包和退款处理对象不一致。
|
||
|
||
七月退款方案又将企微审批状态与实际退款处理状态分离,因此不能只用“审批是否通过”判断退款是否已经结束。
|
||
|
||
## Solution
|
||
|
||
创建换货前,根据旧卡或旧设备主键批量查询活跃退款。企微审批中、历史已退回、或企微已通过但退款业务处理尚未成功时拒绝换货;已拒绝、已撤销/删除、已软删除、或退款处理已经成功时放行。
|
||
|
||
校验进入换货创建 Application 用例并与换货单创建保持事务一致。失败返回统一中文业务错误,前端只展示后端结论,不复制退款状态机。
|
||
|
||
## User Stories
|
||
|
||
1. 作为运营人员,我希望退款仍可能影响资产或资金时不能创建换货。
|
||
2. 作为运营人员,我希望退款被拒绝、撤销或完成后可以正常换货。
|
||
3. 作为运营人员,我希望拦截后已填写的换货资料仍保留,方便更换资产重试。
|
||
4. 作为维护人员,我希望审批状态和退款处理状态共同决定活跃性,避免把“审批通过但处理失败”误认为完成。
|
||
5. 作为维护人员,我希望校验失败不产生换货单、资产占用或迁移半成品。
|
||
|
||
## Implementation Decisions
|
||
|
||
### 活跃退款定义
|
||
|
||
- 退款 `status=1`(待审批/企微审批中)时拦截。
|
||
- 历史退款 `status=4`(已退回)时拦截;新企微流程不再产生该状态,但必须兼容存量数据。
|
||
- 退款 `status=2`(审批已通过)且 `processing_status!=2` 时拦截,包括待处理、处理中和处理失败。
|
||
- 退款 `status=2` 且 `processing_status=2`(业务处理成功)时放行。
|
||
- 退款 `status=3`(已拒绝)或 `status=5`(已撤销/审批已删除)时放行。
|
||
- 已软删除退款不参与判断。
|
||
- 同一资产只要存在任意一条活跃退款即拒绝;不因另有一条已结束退款而放行。
|
||
- 卡使用退款记录的 `iot_card_id`,设备使用 `device_id`;不得依赖可变化的资产展示标识符做关联。
|
||
|
||
### 换货创建与一致性
|
||
|
||
- 复用 `POST /api/admin/exchanges`,不新增预检查 API。
|
||
- 校验对象是请求中的旧资产;新资产是否可用继续由换货现有规则判断。
|
||
- 查询能力接受一组卡 ID 和设备 ID,一次返回存在活跃退款的资产集合;禁止逐资产 N+1。
|
||
- 活跃退款校验必须在换货创建事务内完成,并位于任何换货单、资产占用、状态修改、客户绑定或钱包迁移之前。
|
||
- 校验失败时整个创建用例不产生数据库副作用,也不调用外部系统。
|
||
- 拒绝返回 HTTP 403、`code=1005`、`msg=该资产存在退款申请`、`data=null`;服务端日志和审计记录具体资产与命中原因,客户端不暴露其他人的退款详情。
|
||
- 查询失败转换为统一内部错误;不得把数据库错误当作“没有活跃退款”继续创建。
|
||
- 这是换货创建的业务不变量。按渐进 DDD 规则,至少将完整“创建换货”用例收口到 Application/Domain;旧 Exchange Service 只能作为过渡门面,不能在事务外追加一次易被绕过的检查。
|
||
- 后台、未来批量入口或其他调用创建换货的内部路径必须复用同一用例,不得只在 Handler 校验。
|
||
|
||
### 与 UR#35 的依赖
|
||
|
||
- `processing_status` 和 `status=5` 由 UR#35 退款企微终态模型提供;UR#57 不创建另一套退款状态字段。
|
||
- UR#57 的活跃退款策略必须引用退款领域/Query 中的正式常量和语义,不硬编码一份与 UR#35 分叉的状态表。
|
||
- 推荐先完成 UR#35 的迁移和状态模型,再接入 UR#57;若并行开发,双方先共享同一数据库契约后再编码。
|
||
- 企微审批明细、审批人和意见不参与本次判断;本地退款业务状态与处理状态是判定依据。
|
||
|
||
### 前端
|
||
|
||
- 前端不增加退款预检查请求,也不自行维护审批/处理状态映射。
|
||
- 创建失败时在表单顶部或对应资产处展示“该资产存在退款申请”,并保留所有已填内容。
|
||
- 用户替换旧资产后可重新提交;提交期间保持现有防重复交互。
|
||
- 成功响应和换货后续流程不变。
|
||
|
||
## Testing Decisions
|
||
|
||
- 策略单元测试覆盖状态 1、2、3、4、5,且状态 2 覆盖处理状态 0、1、2、3。
|
||
- PostgreSQL 集成测试分别创建卡退款和设备退款,验证软删除、多个历史退款和任一活跃记录命中的组合。
|
||
- HTTP 集成测试穿过认证、权限、Handler、Application、Store 和统一错误处理,验证拒绝响应及成功路径。
|
||
- 验证拒绝后换货单数量、旧/新资产状态、客户绑定、钱包和套餐均未变化。
|
||
- 验证退款查询按一组资产一次完成;批量能力不得随资产数线性增加查询次数。
|
||
- 验证数据库查询异常返回脱敏 500,不能降级放行。
|
||
- 与 UR#35 联调验证:企微审批中拦截、驳回放行、撤销放行、审批通过但处理失败仍拦截、处理成功放行。
|
||
- 前端人工验收验证错误展示、表单保留和更换资产重试。
|
||
|
||
## Out of Scope
|
||
|
||
- 不改变退款创建、审批、退款金额或资金处理规则。
|
||
- 不新增本地审批动作或前端退款状态预判。
|
||
- 不阻止退款完成后的换货。
|
||
- 不清理或回填历史退款数据;仅兼容历史已退回状态。
|
||
- 不改变新资产选择、换货物流、迁移或完成规则。
|
||
- 不通过本需求新增批量换货 API。
|
||
|
||
## Further Notes
|
||
|
||
- 推荐实施顺序:UR#35 退款状态模型 → UR#57 活跃退款策略 → 换货创建联调。
|
||
- “审批通过”不等于“退款完成”是本需求最重要的验收边界。
|