Files
junhong_cmp_fiber/.scratch/ur57-block-exchange-active-refund/PRD.md
2026-07-21 15:26:07 +09:00

5.8 KiB
Raw Blame History

PRDUR#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=2processing_status=2(业务处理成功)时放行。
  • 退款 status=3(已拒绝)或 status=5(已撤销/审批已删除)时放行。
  • 已软删除退款不参与判断。
  • 同一资产只要存在任意一条活跃退款即拒绝;不因另有一条已结束退款而放行。
  • 卡使用退款记录的 iot_card_id,设备使用 device_id;不得依赖可变化的资产展示标识符做关联。

换货创建与一致性

  • 复用 POST /api/admin/exchanges,不新增预检查 API。
  • 校验对象是请求中的旧资产;新资产是否可用继续由换货现有规则判断。
  • 查询能力接受一组卡 ID 和设备 ID一次返回存在活跃退款的资产集合禁止逐资产 N+1。
  • 活跃退款校验必须在换货创建事务内完成,并位于任何换货单、资产占用、状态修改、客户绑定或钱包迁移之前。
  • 校验失败时整个创建用例不产生数据库副作用,也不调用外部系统。
  • 拒绝返回 HTTP 403、code=1005msg=该资产存在退款申请data=null;服务端日志和审计记录具体资产与命中原因,客户端不暴露其他人的退款详情。
  • 查询失败转换为统一内部错误;不得把数据库错误当作“没有活跃退款”继续创建。
  • 这是换货创建的业务不变量。按渐进 DDD 规则,至少将完整“创建换货”用例收口到 Application/Domain旧 Exchange Service 只能作为过渡门面,不能在事务外追加一次易被绕过的检查。
  • 后台、未来批量入口或其他调用创建换货的内部路径必须复用同一用例,不得只在 Handler 校验。

与 UR#35 的依赖

  • processing_statusstatus=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 活跃退款策略 → 换货创建联调。
  • “审批通过”不等于“退款完成”是本需求最重要的验收边界。