All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m8s
- client_order: 新增 getOrCreateWallet 兜底,钱包不存在时自动创建 - device_import: 设备导入事务内同步创建设备钱包 - iot_card_import: IoT卡批量导入后批量创建卡钱包 - queue/handler: 传递 AssetWalletStore 给两个导入 handler - migration 000098: 为存量IoT卡和设备补建资产钱包
64 lines
6.1 KiB
Markdown
64 lines
6.1 KiB
Markdown
# 任务清单:add-refund-system
|
||
|
||
> 退款功能从零建设,严格串行:数据模型 → Store → Service → Handler → 路由注册 → 文档。
|
||
> 审批通过后需自动执行资产处理和佣金回扣,涉及跨模块依赖。
|
||
|
||
## 任务组 1:I-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 ./...` 编译通过
|
||
|
||
## 任务组 2:I-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 ./...` 编译通过
|
||
|
||
## 任务组 3:I-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 ./...` 编译通过
|
||
|
||
## 任务组 4:I-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/` 确认新方法已添加
|