归档
This commit is contained in:
68
openspec/specs/agent-fund-summary/spec.md
Normal file
68
openspec/specs/agent-fund-summary/spec.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# agent-fund-summary Specification
|
||||
|
||||
## Purpose
|
||||
代理商资金概况列表,聚合展示代理的预充值钱包(主钱包)和佣金钱包余额信息,供平台和代理多层级查看资金概况。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 代理商资金概况列表
|
||||
|
||||
系统 SHALL 提供 `GET /api/admin/shops/fund-summary` 接口,返回分页的代理商资金概况列表,同时包含预充值钱包(主钱包)和佣金钱包的余额信息。
|
||||
|
||||
**响应字段**(`ShopFundSummaryItem`):
|
||||
- `shop_id`:店铺 ID
|
||||
- `shop_name`:店铺名称
|
||||
- `shop_code`:店铺编码
|
||||
- `username`:主账号用户名
|
||||
- `phone`:主账号手机号
|
||||
- `main_balance`:预充值钱包余额(分)
|
||||
- `main_frozen_balance`:预充值钱包冻结余额(分)
|
||||
- `total_commission`:累计佣金总额(分)
|
||||
- `withdrawn_commission`:已提现佣金(分)
|
||||
- `unwithdraw_commission`:未提现佣金(分)
|
||||
- `frozen_commission`:冻结中佣金(分)
|
||||
- `withdrawing_commission`:提现中佣金(分)
|
||||
- `available_commission`:可提现佣金(分)
|
||||
- `created_at`:店铺创建时间
|
||||
|
||||
**查询参数**(`ShopFundSummaryListReq`):
|
||||
- `page`:页码(默认 1)
|
||||
- `page_size`:每页数量(默认 20,最大 100)
|
||||
- `shop_name`:店铺名称模糊查询
|
||||
- `username`:主账号用户名模糊查询
|
||||
|
||||
**实现要求**:
|
||||
- 主钱包余额通过 `AgentWalletStore.GetShopMainWalletBatch` 批量查询,避免 N+1
|
||||
- 若代理暂无主钱包记录(未充值),`main_balance` 和 `main_frozen_balance` 返回 0
|
||||
- `main_frozen_balance` 字段为未来预留(当前业务无主钱包冻结场景,值恒为 0),前端可暂不展示
|
||||
- 数据权限:列表查询走 `Shop` 表的数据权限过滤(`SubordinateShopIDs`)。平台人员返回全部代理;代理账号返回自己 + 所有下级店铺(而不是只返回自己一条)
|
||||
|
||||
#### Scenario: 平台人员查看所有代理资金概况
|
||||
|
||||
- **WHEN** 平台人员请求 `GET /shops/fund-summary`
|
||||
- **THEN** 系统返回所有代理的分页列表,每条包含 `main_balance` 和佣金钱包字段
|
||||
|
||||
#### Scenario: 无下级代理的账号查看自己的资金概况
|
||||
|
||||
- **WHEN** 无下级代理的代理账号请求 `GET /shops/fund-summary`
|
||||
- **THEN** 系统按数据权限过滤,只返回该代理自己的一条记录
|
||||
|
||||
#### Scenario: 有下级代理的顶级代理查看资金概况
|
||||
|
||||
- **WHEN** 一个拥有多个下级代理的顶级代理账号请求 `GET /shops/fund-summary`
|
||||
- **THEN** 系统返回自己 + 所有下级代理店铺的资金概况列表(按 `SubordinateShopIDs` 过滤)
|
||||
|
||||
#### Scenario: 代理暂无主钱包时返回零值
|
||||
|
||||
- **WHEN** 代理从未充值,`tb_agent_wallet` 中无该店铺的 `wallet_type=main` 记录
|
||||
- **THEN** `main_balance` 和 `main_frozen_balance` 返回 0,其余字段正常返回
|
||||
|
||||
#### Scenario: 按店铺名称过滤
|
||||
|
||||
- **WHEN** 传入 `shop_name=张三`
|
||||
- **THEN** 系统只返回店铺名称包含"张三"的代理记录
|
||||
|
||||
#### Scenario: 企业账号无权访问
|
||||
|
||||
- **WHEN** 企业账号请求此接口
|
||||
- **THEN** 系统返回 403 错误,消息为"企业账号无权访问代理资金功能"
|
||||
@@ -316,3 +316,24 @@ func RedisAgentWalletLockKey(shopID uint, walletType string) string
|
||||
- **THEN** 系统使用 Redis 分布式锁 `agent_wallet:lock:10:main`,第一个请求获得锁,第二个请求等待或失败
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 批量查询店铺主钱包余额
|
||||
|
||||
系统 SHALL 在 `AgentWalletStore` 中提供 `GetShopMainWalletBatch(ctx, shopIDs []uint) map[uint]*AgentWallet` 方法,一次查询多个店铺的主钱包(`wallet_type=main`)记录,返回以 `shop_id` 为 key 的 map。
|
||||
|
||||
**实现要求**:
|
||||
- 使用 `WHERE shop_id IN (?) AND wallet_type = 'main'` 单次查询,不得逐条查询
|
||||
- 不在 map 中的 shop_id 表示该店铺暂无主钱包,调用方按零值处理
|
||||
- 与现有 `GetShopCommissionSummaryBatch` 对称设计
|
||||
|
||||
#### Scenario: 批量查询多个店铺的主钱包
|
||||
|
||||
- **WHEN** 传入 shopIDs `[1, 2, 3]`,其中 shop 3 无主钱包记录
|
||||
- **THEN** 返回 map `{1: &wallet1, 2: &wallet2}`,shop 3 不在 map 中
|
||||
|
||||
#### Scenario: 传入空列表
|
||||
|
||||
- **WHEN** 传入空 `shopIDs`
|
||||
- **THEN** 直接返回空 map,不执行 DB 查询
|
||||
|
||||
---
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 迁移接口的越权校验(通用要求)
|
||||
|
||||
本规范下所有 `/shops/:shop_id/...` 迁移/新增接口,Service 层方法入口 SHALL 调用 `middleware.CanManageShop(ctx, shopID)` 做显式越权校验。Handler 层只做参数解析,不承担权限逻辑。该要求同时追溯适用于已有但缺少校验的 `ListShopWithdrawalRequests`、`ListShopCommissionRecords` 两个方法(回归漏洞修复)。
|
||||
|
||||
统一错误返回:`errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")`。
|
||||
|
||||
#### Scenario: 代理传非自己管辖店铺 ID 被 Service 层拦截
|
||||
|
||||
- **WHEN** 代理 A(shopID=10)请求本规范任意一个 `/shops/:shop_id/...` 接口,传入代理 B(shopID=20)的 shopID
|
||||
- **THEN** Service 层入口的 `middleware.CanManageShop(ctx, 20)` 返回 error,接口返回 403,消息 "无权限操作该资源或资源不存在"
|
||||
|
||||
#### Scenario: 平台人员不受 CanManageShop 限制
|
||||
|
||||
- **WHEN** 平台人员请求任意 `/shops/:shop_id/...` 接口
|
||||
- **THEN** `middleware.CanManageShop` 直接放行,接口正常返回数据
|
||||
|
||||
#### Scenario: 已有方法 ListShopCommissionRecords / ListShopWithdrawalRequests 补齐校验
|
||||
|
||||
- **WHEN** 代理账号请求 `/shops/{非自己管辖shopID}/commission-records` 或 `/withdrawal-requests`
|
||||
- **THEN** 实现后返回 403(回归漏洞修复前这两个方法会返回数据)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 查询佣金记录列表
|
||||
|
||||
系统 SHALL 提供佣金记录列表查询,支持按店铺、佣金来源、时间范围、状态筛选。
|
||||
|
||||
70
openspec/specs/main-wallet-transactions/spec.md
Normal file
70
openspec/specs/main-wallet-transactions/spec.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# main-wallet-transactions Specification
|
||||
|
||||
## Purpose
|
||||
预充值钱包(主钱包)交易流水查询,为管理员和代理提供主钱包的充值、扣款、退款等流水明细查看能力。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 预充值钱包流水查询
|
||||
|
||||
系统 SHALL 提供 `GET /api/admin/shops/:shop_id/main-wallet/transactions` 接口,分页返回指定代理店铺的预充值钱包(主钱包)交易流水记录。
|
||||
|
||||
**响应字段**(`MainWalletTransactionItem`):
|
||||
- `id`:流水记录 ID
|
||||
- `transaction_type`:交易类型。主钱包可能出现的值为 `recharge`-充值入账 / `deduct`-套餐扣款 / `refund`-退款(当前 DB 数据仅见 `recharge`,`deduct` / `refund` 随代购扣款/退款业务上线而出现;接口不对类型做枚举白名单,透传 DB 原值)
|
||||
- `transaction_subtype`:交易子类型(细分场景,如 `order_payment`,可为空)
|
||||
- `amount`:变动金额(分,正数为入账,负数为扣款)
|
||||
- `balance_before`:变动前余额(分)
|
||||
- `balance_after`:变动后余额(分)
|
||||
- `remark`:备注(可为空)
|
||||
- `created_at`:流水时间
|
||||
|
||||
**查询参数**(`MainWalletTransactionListRequest`):
|
||||
- `shop_id`:路径参数,店铺 ID(必填)
|
||||
- `page`:页码(默认 1)
|
||||
- `page_size`:每页数量(默认 20,最大 100)
|
||||
- `transaction_type`:按类型过滤(可选)
|
||||
- `start_date`:开始日期,`YYYY-MM-DD`(可选)
|
||||
- `end_date`:结束日期,`YYYY-MM-DD`(可选)
|
||||
|
||||
**实现要求**:
|
||||
- **Service 层入口必须调用 `middleware.CanManageShop(ctx, shopID)` 做越权校验**,校验失败直接返回 `errors.CodeForbidden`;不得依赖 `GetMainWallet` 的隐式过滤(该方法不做权限校验)
|
||||
- 先通过 `AgentWalletStore.GetMainWallet(shopID)` 获取主钱包;若不存在则返回空列表(`total=0`),不报错
|
||||
- 使用 `AgentWalletTransactionStore.ListByWalletIDWithFilters / CountByWalletID`(task 2.2 新增)查询流水,支持 transaction_type 和日期过滤
|
||||
- 旧方法 `ListByShopID / CountByShopID` 已在 task 2.3 删除(会跨钱包类型返回数据,易被误用)
|
||||
- 结果按 `created_at DESC` 排序
|
||||
|
||||
#### Scenario: 平台人员查看指定代理的预充值流水
|
||||
|
||||
- **WHEN** 平台人员请求 `GET /shops/123/main-wallet/transactions`
|
||||
- **THEN** 系统返回店铺 123 的主钱包流水,按时间倒序,含变动前后余额
|
||||
|
||||
#### Scenario: 代理查看自己的预充值流水
|
||||
|
||||
- **WHEN** 代理账号请求 `GET /shops/自己shop_id/main-wallet/transactions`
|
||||
- **THEN** 系统返回该代理自己的主钱包流水记录
|
||||
|
||||
#### Scenario: 代理尝试查看他人流水被拦截
|
||||
|
||||
- **WHEN** 代理账号请求 `GET /shops/他人shop_id/main-wallet/transactions`
|
||||
- **THEN** 系统返回 403 错误,消息为"无权限操作该资源或资源不存在"
|
||||
|
||||
#### Scenario: 代理暂无主钱包时返回空列表
|
||||
|
||||
- **WHEN** 代理从未充值,主钱包不存在
|
||||
- **THEN** 系统返回空列表,`total` 为 0,不报错
|
||||
|
||||
#### Scenario: 按交易类型过滤
|
||||
|
||||
- **WHEN** 传入 `transaction_type=recharge`
|
||||
- **THEN** 系统只返回充值入账类型的流水
|
||||
|
||||
#### Scenario: 按日期范围过滤
|
||||
|
||||
- **WHEN** 传入 `start_date=2026-01-01&end_date=2026-03-31`
|
||||
- **THEN** 系统只返回该日期范围内的流水记录
|
||||
|
||||
#### Scenario: 企业账号无权访问
|
||||
|
||||
- **WHEN** 企业账号请求此接口
|
||||
- **THEN** 系统返回 403 错误
|
||||
Reference in New Issue
Block a user