5.8 KiB
5.8 KiB
PRD:UR#57 活跃退款资产禁止换货
Status: ready-for-agent
Problem Statement
换货创建目前没有检查旧资产是否存在未结束的退款。换货可能在退款审批或资金处理期间改变资产关系,造成资产状态、套餐、钱包和退款处理对象不一致。
七月退款方案又将企微审批状态与实际退款处理状态分离,因此不能只用“审批是否通过”判断退款是否已经结束。
Solution
创建换货前,根据旧卡或旧设备主键批量查询活跃退款。企微审批中、历史已退回、或企微已通过但退款业务处理尚未成功时拒绝换货;已拒绝、已撤销/删除、已软删除、或退款处理已经成功时放行。
校验进入换货创建 Application 用例并与换货单创建保持事务一致。失败返回统一中文业务错误,前端只展示后端结论,不复制退款状态机。
User Stories
- 作为运营人员,我希望退款仍可能影响资产或资金时不能创建换货。
- 作为运营人员,我希望退款被拒绝、撤销或完成后可以正常换货。
- 作为运营人员,我希望拦截后已填写的换货资料仍保留,方便更换资产重试。
- 作为维护人员,我希望审批状态和退款处理状态共同决定活跃性,避免把“审批通过但处理失败”误认为完成。
- 作为维护人员,我希望校验失败不产生换货单、资产占用或迁移半成品。
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 活跃退款策略 → 换货创建联调。
- “审批通过”不等于“退款完成”是本需求最重要的验收边界。