## 1. DTO 层变更 - [x] 1.1 新增 `ShopFundSummaryItem`(含 `main_balance`、`main_frozen_balance` 及原有佣金字段),新增 `ShopFundSummaryListReq`、`ShopFundSummaryPageResult`;删除旧的 `ShopCommissionSummaryItem` / `ShopCommissionSummaryListReq` / `ShopCommissionSummaryPageResult` - [x] 1.2 新增主钱包流水 DTO:`MainWalletTransactionItem`、`MainWalletTransactionListRequest`、`MainWalletTransactionListResponse` - [x] 1.3 删除废弃 DTO:`MyCommissionSummaryResp`、`MyWithdrawalListReq`、`MyCommissionRecordListReq`、`MyCommissionRecordItem`、`MyCommissionRecordPageResult`(确认无其他引用后删除) - **保留**:`CommissionStatsRequest`、`DailyCommissionStatsRequest` / `CommissionStatsResponse` / `DailyCommissionStatsResponse` — 迁移后的 `/shops/:id/commission-stats` 和 `/shops/:id/commission-daily-stats` 继续复用 - **保留**:`CreateMyWithdrawalReq`、`CreateMyWithdrawalResp` — 迁移后的 `POST /shops/:id/withdrawal-requests` 继续复用 ## 2. Store 层变更 - [x] 2.1 在 `AgentWalletStore` 新增 `GetShopMainWalletBatch(ctx context.Context, shopIDs []uint) (map[uint]*model.AgentWallet, error)` 方法:`WHERE shop_id IN (?) AND wallet_type = 'main'` 单次查询,内部调用 `middleware.ApplyShopFilter` 对齐现有 `GetShopCommissionSummaryBatch` 的防御式过滤 - [x] 2.2 在 `AgentWalletTransactionStore` 新增过滤结构体 `AgentWalletTransactionListFilters`(字段:`TransactionType string`、`StartDate string`、`EndDate string`),以及以下两个方法: - `ListByWalletIDWithFilters(ctx, walletID uint, opts *store.QueryOptions, filters *AgentWalletTransactionListFilters) ([]*model.AgentWalletTransaction, error)`:在 `WHERE agent_wallet_id = ?` 基础上叠加 transaction_type / 日期过滤,按 `created_at DESC` 排序 - `CountByWalletID(ctx, walletID uint, filters *AgentWalletTransactionListFilters) (int64, error)`:与列表方法过滤条件一致,用于分页 total 统计 - [x] 2.3 删除 `AgentWalletTransactionStore.ListByShopID` 和 `CountByShopID`:已确认全局无其他调用者(Grep 验证),且按 shopID 过滤会混入佣金钱包流水,留着会被误用。删除前再次 `grep -rn "ListByShopID\|CountByShopID" --include="*.go"` 确认 ## 3. Service 层变更(ShopCommissionService 扩展) - [x] 3.0 更新 `ShopCommissionService.New()` 构造函数,在现有参数基础上新增两个依赖: - `commissionWithdrawalSettingStore *postgres.CommissionWithdrawalSettingStore`(task 3.4 迁移 `CreateWithdrawalRequest` 时校验最低提现金额和每日次数上限) - `agentWalletTransactionStore *postgres.AgentWalletTransactionStore`(task 3.5 新增 `ListMainWalletTransactions` 使用) - [x] 3.1 将 `ListShopCommissionSummary` 改造为 `ListShopFundSummary`:删除原方法,方法签名/返回类型切换至 `ShopFundSummaryListReq` / `ShopFundSummaryPageResult`;调用 `GetShopMainWalletBatch` 批量拉取主钱包余额,合并到 `ShopFundSummaryItem` 中;主钱包不存在时 `main_balance` / `main_frozen_balance` 返回 0 - [x] 3.2 从 `MyCommissionService` 迁移 `GetStats` 到 `ShopCommissionService`,签名改为 `(ctx, shopID uint, req *dto.CommissionStatsRequest)`;**方法入口第一行**调用 `middleware.CanManageShop(ctx, shopID)`,失败直接返回 - [x] 3.3 从 `MyCommissionService` 迁移 `GetDailyStats` 到 `ShopCommissionService`,签名改为 `(ctx, shopID uint, req *dto.DailyCommissionStatsRequest)`;同样入口加 `CanManageShop` - [x] 3.4 从 `MyCommissionService` 迁移 `CreateWithdrawalRequest` 到 `ShopCommissionService`,签名改为 `(ctx, shopID uint, req *dto.CreateMyWithdrawalReq)`;**入口权限校验严于其他方法**: - 必须 `middleware.GetUserTypeFromContext(ctx) == constants.UserTypeAgent`,否则返回 403"仅代理商用户可发起提现" - 必须 `shopID == middleware.GetShopIDFromContext(ctx)`,否则返回 403"仅可为本人店铺发起提现" - **不使用 `CanManageShop`**(默认会放行整个下级层级,业务上不允许顶级代理替下级提现) - 保留原有的条件更新 `Where("id = ? AND balance - frozen_balance >= ?")` 并发保护 - [x] 3.5 在 `ShopCommissionService` 新增 `ListMainWalletTransactions(ctx, shopID uint, req *dto.MainWalletTransactionListRequest)` 方法: - **入口第一行**调用 `middleware.CanManageShop(ctx, shopID)` - 通过 `AgentWalletStore.GetMainWallet(ctx, shopID)` 获取主钱包;不存在则返回空列表(`total=0`)不报错 - 调用 `ListByWalletIDWithFilters` / `CountByWalletID`(task 2.2 新增)查询流水 - **不得**使用 `ListByShopID`(已删除) - [x] 3.6 **补齐已有方法的越权校验**(回归漏洞修复): - `ListShopWithdrawalRequests`:方法入口加 `middleware.CanManageShop(ctx, shopID)` - `ListShopCommissionRecords`:方法入口加 `middleware.CanManageShop(ctx, shopID)` - 理由:`/my/withdrawal-requests` 和 `/my/commission-records` 废弃后代理改用 `/shops/:shop_id/...`,若不补校验,代理只要猜到其他 shopID 就能读取他人佣金明细和提现记录 ## 4. Service 层删除(MyCommissionService) - [x] 4.1 全局 grep 确认 `MyCommissionService` / `myCommissionService` / `my_commission` 的所有引用(Handler、bootstrap、routes、cmd) - [x] 4.2 删除 `internal/service/my_commission/service.go` 及整个 `my_commission` 目录 ## 5. Handler 层变更(ShopCommissionHandler 扩展) - [x] 5.1 将 `ListCommissionSummary` 改造为 `ListFundSummary`(重命名+切换 DTO 类型),调用 `ShopCommissionService.ListShopFundSummary`。方法只在 Handler 层做参数解析,不写权限逻辑 - [x] 5.2 新增 `GetCommissionStats` 方法,从路径参数取 `shop_id`,调用迁移后的 `GetStats`(权限校验在 Service 层) - [x] 5.3 新增 `GetCommissionDailyStats` 方法,从路径参数取 `shop_id`,调用迁移后的 `GetDailyStats` - [x] 5.4 新增 `CreateWithdrawal` 方法,从路径参数取 `shop_id`,调用迁移后的 `CreateWithdrawalRequest` - [x] 5.5 新增 `ListMainWalletTransactions` 方法,从路径参数取 `shop_id`,调用 `ShopCommissionService.ListMainWalletTransactions` - [x] 5.6 Handler 注释同步更新(HTTP 方法和路径),遵循 comment-standards ## 6. Handler 层删除(MyCommissionHandler) - [x] 6.1 删除 `internal/handler/admin/my_commission.go` ## 7. 路由层变更 - [x] 7.1 在 `registerShopCommissionRoutes` 中:将 `GET /commission-summary` 改为 `GET /fund-summary`,更新 Summary 为 "代理商资金概况",更新 Tags、Input(`ShopFundSummaryListReq`)、Output(`ShopFundSummaryPageResult`) - [x] 7.2 在 `registerShopCommissionRoutes` 中新增四条路由: - `GET /:shop_id/main-wallet/transactions` → `ListMainWalletTransactions` - `GET /:shop_id/commission-stats` → `GetCommissionStats` - `GET /:shop_id/commission-daily-stats` → `GetCommissionDailyStats` - `POST /:shop_id/withdrawal-requests` → `CreateWithdrawal` - [x] 7.3 删除 `internal/routes/my_commission.go` - [x] 7.4 在路由注册入口(`internal/routes/admin.go` 或 registry)中移除 `registerMyCommissionRoutes` 的调用 ## 8. Bootstrap 变更 - [x] 8.1 在 `internal/bootstrap/types.go` 的 `Handlers` 结构体中:删除 `MyCommissionHandler` 字段,确认 `ShopCommissionHandler` 字段存在 - [x] 8.2 在 `internal/bootstrap/services.go` 中:删除 `myCommissionService` 的初始化,移除对 `my_commission` 包的引用;更新 `shopCommissionService` 的初始化,补充 task 3.0 新增的两个依赖(`stores.CommissionWithdrawalSetting`、`stores.AgentWalletTransaction`) - [x] 8.3 在 `internal/bootstrap/handlers.go` 中删除 `MyCommissionHandler` 的初始化 ## 9. 文档生成器更新 - [x] 9.1 在 `cmd/api/docs.go` 中:删除 `NewMyCommissionHandler(nil)` 相关行,确认 `ShopCommissionHandler` 已包含新方法 - [x] 9.2 在 `cmd/gendocs/main.go` 中做同样清理 ## 10. 编译与验证 - [x] 10.1 执行 `go build ./...`,确保无编译错误 - [x] 10.2 `grep -rn "MyCommission\|my_commission\|ListByShopID\|CountByShopID\|ShopCommissionSummary" --include="*.go"` 确认无残留引用 - [x] 10.3 使用 PostgreSQL MCP 验证 `GET /shops/fund-summary` 返回的 `main_balance` 与 `tb_agent_wallet` 中对应记录一致(shop_id=1: balance=50000, shop_id=9: balance=20000,主钱包批量查询逻辑正确) - [x] 10.4 使用 PostgreSQL MCP 验证 `GET /shops/:id/main-wallet/transactions` 返回的流水与 `tb_agent_wallet_transaction` 中 `wallet_type=main` 钱包 ID 的记录一致(wallet_id=1 有3条记录,wallet_id=17 有2条记录,通过 agent_wallet_id 关联正确) - [ ] 10.5 手动验证代理越权: - 代理账号 A 请求 `/shops/{B的shop_id}/commission-records` / `/commission-stats` / `/commission-daily-stats` / `/withdrawal-requests` / `/main-wallet/transactions`(B 不是 A 的下级)应全部返回 403 - 顶级代理 P 请求 `POST /shops/{下级shop_id}/withdrawal-requests` 应返回 403"仅可为本人店铺发起提现"(验证比 `CanManageShop` 更严格的限制) - 平台账号请求 `POST /shops/:shop_id/withdrawal-requests` 应返回 403"仅代理商用户可发起提现" - [ ] 10.6 验证原 `/my/commission-stats`、`/my/commission-daily-stats`、`POST /my/withdrawal-requests` 迁移后数据返回与迁移前等价(相同 shopID 相同查询条件,结果一致)