Files
junhong_cmp_fiber/openspec/changes/archive/2026-03-28-add-refund-system/tasks.md
huang f339fb1987
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m8s
fix: 资产钱包自动创建机制 — 修复C端购买时钱包不存在报错
- client_order: 新增 getOrCreateWallet 兜底,钱包不存在时自动创建
- device_import: 设备导入事务内同步创建设备钱包
- iot_card_import: IoT卡批量导入后批量创建卡钱包
- queue/handler: 传递 AssetWalletStore 给两个导入 handler
- migration 000098: 为存量IoT卡和设备补建资产钱包
2026-03-30 11:37:41 +08:00

64 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务清单add-refund-system
> 退款功能从零建设,严格串行:数据模型 → Store → Service → Handler → 路由注册 → 文档。
> 审批通过后需自动执行资产处理和佣金回扣,涉及跨模块依赖。
## 任务组 1I-1 数据模型
- [x] 1.1 新建迁移文件 `migrations/000XXX_create_refund_request.up.sql`(编号接续现有最大值),创建 `tb_refund_request` 表,包含所有字段(参考 design.md 表设计,含 shop_id、processor_id、processed_at、reject_reason、remark、asset_reset 等)
- [x] 1.2 新建对应 `.down.sql``DROP TABLE IF EXISTS tb_refund_request;`
- [x] 1.3 新建 `internal/model/refund.go`,定义 `RefundRequest` GORM Model使用 `gorm.Model` + `BaseModel` 嵌入),实现 `TableName()` 返回 `tb_refund_request`,所有字段显式指定 `gorm:"column:xxx"` 标签,注释使用中文
- [x] 1.4 在 `pkg/constants/` 中新增以下常量:
- 退款状态:`RefundStatusPending=1, RefundStatusApproved=2, RefundStatusRejected=3, RefundStatusReturned=4`
- 交易类型:`AgentTransactionTypeCommissionDeduct = "commission_deduct"`(在 `pkg/constants/wallet.go` 的代理钱包交易类型分组中新增)
- 关联业务类型:`ReferenceTypeRefund = "refund"`(在 `pkg/constants/wallet.go` 的关联业务类型分组中新增)
- [x] 1.5 新建 `internal/model/dto/refund_dto.go`,定义请求/响应 DTO`CreateRefundRequest``RejectRefundRequest``ResubmitRefundRequest``ApproveRefundRequest``ReturnRefundRequest``RefundListRequest``RefundResponse``RefundListResponse`。所有 DTO 字段需含 `description` 标签
- [x] 1.6 执行迁移,通过 DBHub 确认表已创建
- [x] 1.7 验证:`go build ./...` 编译通过
## 任务组 2I-2 接口实现
- [x] 2.1 新建 `internal/store/postgres/refund_store.go`,实现 `Create``GetByID``List`(分页+筛选,含 `ApplyShopFilter` 数据权限过滤)、`Update` 方法
- [x] 2.2 新建 `internal/service/refund/service.go`,实现 7 个业务方法:`Create``List``GetByID``Approve``Reject``Return``Resubmit`。每个状态变更方法 MUST 使用条件更新 `WHERE status = expected` 保证幂等。`Create` 方法 MUST 检查同一订单不能重复退款(已存在 status IN (1,2,4) 的退款记录时拒绝)
- [x] 2.3 退款单号生成:`generateRefundNo()` 格式 `RF` + 年月日时分秒 + 6位随机数参考 `GenerateOrderNo` 实现
- [x] 2.4 `Create` 方法中从关联订单读取 `seller_shop_id`(或根据 buyer_type/buyer_id 确定)填入退款单的 `shop_id` 字段
- [x] 2.5 新建 `internal/handler/admin/refund.go`,实现 7 个 Handler 方法,遵循 Handler 只做参数解析+响应格式化的原则
- [x] 2.6 新建 `internal/routes/refund.go`,注册 `/api/admin/refunds` 路由组(参考 `routes/commission.go` 的 Register 模式),路由中间件限制仅平台用户可访问
- [x] 2.7 在 `internal/bootstrap/stores.go``services.go``handlers.go` 中注册 refund 模块:
- Store: `RefundStore`
- Service 依赖注入: `db`, `refundStore`, `orderStore`, `commissionRecordStore`, `agentWalletStore`, `agentWalletTransactionStore`, `stopResumeService`, `deviceService`, `packageActivationService`, `iotCardStore`, `deviceStore`, `assetWalletStore`
- Handler: `RefundHandler`
- [x] 2.8 在 `internal/routes/routes.go` 中调用 `registerRefundRoutes`
- [x] 2.9 更新 `cmd/api/docs.go``cmd/gendocs/main.go`,注册退款 Handler
- [x] 2.10 验证:`go build ./...` 编译通过
## 任务组 3I-3 佣金全额回扣
- [x] 3.1 在 `internal/service/refund/service.go` 中新增私有方法 `deductAllCommission(ctx, refundID)`
- [x] 3.2 实现回扣逻辑:查退款单 → 查该订单所有已入账佣金记录(`WHERE order_id=? AND status=1`,使用 `CommissionStatusReleased` 常量)→ 对每条记录全额扣减对应代理的佣金钱包(`wallet_type="commission"`,使用 `GetCommissionWallet`)→ 使用乐观锁更新余额(`WHERE version=?`)→ 创建交易流水(`transaction_type="commission_deduct"`, `reference_type="refund"`, `reference_id=退款单ID`)→ 全部完成后标记 `commission_deducted=true`
- [x] 3.3 在 `Approve()` 方法中,事务提交成功后通过 Goroutine 调用 `deductAllCommission`
- [x] 3.4 回扣失败记录 Error 日志(含 refund_id 和 error不影响审批结果
- [x] 3.5 验证:`go build ./...` 编译通过
## 任务组 4I-4 资产处理
- [x] 4.1 在 `internal/service/package/activation_service.go` 中新增公开方法 `InvalidateAllPackagesByAsset(ctx, assetType string, assetID uint) error`:查询 `status IN (0,1,2)` 的套餐使用记录(按 `iot_card_id``device_id` 查),批量更新为 `status=4`(已失效)
- [x] 4.2 在 `internal/service/refund/service.go` 中新增私有方法 `handleAssetReset(ctx, refundID)`,包含:
- a. 查退款单和关联订单,确定资产类型(单卡/设备和资产ID
- b. 调用 `InvalidateAllPackagesByAsset` 失效所有套餐
- c. 停机:单卡调 `stopResumeService.ManualStopCard(iccid)` / 设备调 `deviceService.StopDevice(deviceID)`
- d. 世代重置(参照 `exchange/service.go` 第 278-305 行):`generation+1``asset_status=1`、清零累计充值字段、清理个人客户绑定、删除旧资产钱包并创建新空钱包
- e. 更新订单 `payment_status=4`(已退款)
- f. 标记退款单 `asset_reset=true`
- [x] 4.3 在 `Approve()` 方法中,事务提交成功后通过 Goroutine 调用 `handleAssetReset`(与佣金回扣独立执行)
- [x] 4.4 资产处理失败记录 Error 日志(含 refund_id 和 error`asset_reset` 保持 `false`,不影响审批结果
- [x] 4.5 验证:`go build ./...` 编译通过
## 收尾验证
- [x] 5.1 执行 `go build ./...`,确认全量编译通过
- [x] 5.2 通过 DBHub 确认 `tb_refund_request` 表结构正确
- [x] 5.3 通过 `rg "refund" internal/bootstrap/` 确认模块已注册
- [x] 5.4 通过 `rg "commission_deduct" pkg/constants/` 确认新常量已添加
- [x] 5.5 通过 `rg "InvalidateAllPackagesByAsset" internal/service/package/` 确认新方法已添加