docs: 归档 refactor-agent-series-grant 变更文档

将已完成的变更(proposal、design、tasks、delta specs)归档至 openspec/changes/archive/2026-03-04-refactor-agent-series-grant/。变更内容:合并系列分配和套餐分配为系列授权(Grant)、新增梯度佣金模式、新增代理层强充层级规则。50/50 任务全部完成。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-04 11:37:33 +08:00
parent c7b8ecfebf
commit e0cb4498e6
7 changed files with 695 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
## 1. 数据库迁移文件准备
- [x] 1.1 使用 db-migration 规范创建迁移文件:
- **删除** `tb_shop_series_allocation` 的 3 列:`enable_one_time_commission``one_time_commission_trigger``one_time_commission_threshold`
- **新增** `commission_tiers_json JSONB NOT NULL DEFAULT '[]'`(梯度模式专属阶梯金额)
- DOWN 脚本添加说明注释(不恢复数据)
## 2. 删除旧接口Handler / routes / DTO / Service
- [x] 2.1 删除文件:
- `internal/handler/admin/shop_series_allocation.go`
- `internal/handler/admin/shop_package_allocation.go`
- `internal/routes/shop_series_allocation.go`
- `internal/routes/shop_package_allocation.go`
- `internal/model/dto/shop_series_allocation.go`
- `internal/model/dto/shop_package_allocation.go`
- `internal/service/shop_series_allocation/`(整个目录)
- `internal/service/shop_package_allocation/`(整个目录)
- [x] 2.2 `internal/bootstrap/types.go`:删除 `ShopSeriesAllocation``ShopPackageAllocation` 两个 Handler 字段
- [x] 2.3 `internal/bootstrap/handlers.go`:删除对应 Handler 初始化行;删除对应 service 引用
- [x] 2.4 `internal/bootstrap/services.go`:删除 `ShopSeriesAllocation``ShopPackageAllocation` 两个 Service 字段及初始化;移除对应 import
- [x] 2.5 `pkg/openapi/handlers.go`:删除 `ShopSeriesAllocation``ShopPackageAllocation` 两行
- [x] 2.6 `internal/routes/admin.go`:删除 `registerShopSeriesAllocationRoutes``registerShopPackageAllocationRoutes` 两处调用
- [x] 2.7 运行 `go build ./...` 确认无编译错误
## 3. Model 更新
- [x] 3.1 `internal/model/shop_series_allocation.go`
- 删除 `EnableOneTimeCommission``OneTimeCommissionTrigger``OneTimeCommissionThreshold` 三个字段
- 新增 `CommissionTiersJSON string` 字段JSONB默认 `'[]'`
- 新增辅助类型 `AllocationCommissionTier struct { Threshold int64; Amount int64 }`
- 新增 `GetCommissionTiers() ([]AllocationCommissionTier, error)` 方法
- 新增 `SetCommissionTiers(tiers []AllocationCommissionTier) error` 方法
- [x] 3.2 `internal/model/package.go`
- `OneTimeCommissionTier` 新增 `Operator string` 字段json:"operator"
- 新增运算符常量:`TierOperatorGT = ">"` / `TierOperatorGTE = ">="` / `TierOperatorLT = "<"` / `TierOperatorLTE = "<="`
- [x] 3.3 运行 `go build ./...` 确认无编译错误
## 4. 修复梯度模式计算引擎
- [x] 4.1 `internal/service/commission_calculation/service.go`
修改 `calculateChainOneTimeCommission` 中梯度模式分支:
- 原来:直接把 `config.Tiers`(全局)传给 `matchOneTimeCommissionTier`
- 新的:从 `currentSeriesAllocation.GetCommissionTiers()` 取专属金额列表,结合 `config.Tiers`(取 operator/dimension/stat_scope/threshold做匹配
- 匹配逻辑:根据代理销售统计和 tier.Operator 判断是否命中 threshold → 查专属列表同 threshold 的 amount → 即 myAmount未命中任何阶梯时 myAmount = 0
- commission_tiers_json 为空时历史数据fallback 到 `currentSeriesAllocation.OneTimeCommissionAmount`
- [x] 4.2 修改 `matchOneTimeCommissionTier`:接受 agentTiers `[]AllocationCommissionTier` 参数作为金额来源;根据 `tier.Operator` 选择对应比较逻辑(>、>=、<、<=Operator 为空时默认 `>=`
- [x] 4.3 运行 `go build ./...` 确认无编译错误
## 5. 修复强充层级
- [x] 5.1 `internal/service/order/service.go` `checkForceRechargeRequirement()`
- `config.TriggerType == "first_recharge"` 时:直接返回需要强充(不变),不查代理配置
- `config.TriggerType == "accumulated_recharge"``config.EnableForceRecharge == false` 时:
`result.Card.ShopID`(或 `result.Device.ShopID`)查询该代理的 `ShopSeriesAllocation`
若该分配 `EnableForceRecharge=true` 则返回代理强充配置,查询不到时降级返回 `need_force_recharge=false`
- [x] 5.2 验证 `GetPurchaseCheck` 调用路径已覆盖新逻辑(复用同一函数,无需额外修改)
- [x] 5.3 运行 `go build ./...` 确认无编译错误
## 6. 新系列授权 DTO
- [x] 6.1 创建 `internal/model/dto/shop_series_grant_dto.go`,定义:
- `GrantPackageItem`package_id、cost_price、remove *bool
- `ShopSeriesGrantPackageItem`package_id、package_name、package_code、cost_price、shelf_status、status
- `GrantCommissionTierItem`operator string、threshold int64、amount int64
—— operator 仅出现在响应中(从 PackageSeries 合并),请求中不传 operator
- [x] 6.2 定义 `ShopSeriesGrantResponse`
- id、shop_id/name、series_id/name/code、commission_type
- one_time_commission_amount固定模式有效梯度模式返回 0
- commission_tiers []GrantCommissionTierItem梯度模式有值固定模式为空
- force_recharge_locked、force_recharge_enabled、force_recharge_amount
- allocator_shop_id/name、status、packages、created_at、updated_at
- [x] 6.3 定义 `CreateShopSeriesGrantRequest`
- shop_id、series_id
- one_time_commission_amount *int64固定模式必填
- commission_tiers []GrantCommissionTierItem梯度模式必填
- enable_force_recharge *bool、force_recharge_amount *int64
- packages []GrantPackageItem
- [x] 6.4 定义 `UpdateShopSeriesGrantRequest`
- one_time_commission_amount *int64
- commission_tiers []GrantCommissionTierItem
- enable_force_recharge *bool、force_recharge_amount *int64
- [x] 6.5 定义 `ManageGrantPackagesRequest`packages []GrantPackageItem
- [x] 6.6 定义 `ShopSeriesGrantListRequest`page、page_size、shop_id *uint、series_id *uint、allocator_shop_id *uint、status *int及列表 DTO`ShopSeriesGrantListItem` 含 package_count、`ShopSeriesGrantPageResult`
## 7. 新系列授权 Service
- [x] 7.1 创建 `internal/service/shop_series_grant/service.go`,定义 Service 结构及 New() 构造函数
依赖db、shopSeriesAllocationStore、shopPackageAllocationStore、shopPackageAllocationPriceHistoryStore、shopStore、packageStore、packageSeriesStore
- [x] 7.2 实现私有方法 `getParentCeilingFixed()`:固定模式天花板查询
- allocatorShopID=0 → 读 PackageSeries.commission_amount
- allocatorShopID>0 → 读分配者自身的 ShopSeriesAllocation.one_time_commission_amount
- [x] 7.3 实现私有方法 `getParentCeilingTiered()`:梯度模式天花板查询
- allocatorShopID=0 → 读 PackageSeries.config.Tiers 中各 threshold 的 amount
- allocatorShopID>0 → 读分配者自身 ShopSeriesAllocation.commission_tiers_json
- [x] 7.4 实现 `Create()`
- 查询 PackageSeries 确认 commission_type
- 检查重复授权shop_id + series_id 已有 active 记录 → 错误"该代理已存在此系列授权"
- allocator 是代理时:查分配者自身的 ShopSeriesAllocation无记录 → 错误"当前账号无此系列授权,无法向下分配"
- 固定模式one_time_commission_amount 必填 + 天花板校验
- 梯度模式commission_tiers 必填 + 阶梯数量和 threshold 必须与 PackageSeries 完全一致不多不少amount 可为 0+ 每档位天花板校验
- 强充层级判断TriggerType=first_recharge 或平台已设强充 → locked=true 忽略代理传入;仅 accumulated_recharge 且平台未设时接受代理强充配置)
- 事务中创建 ShopSeriesAllocation + N 条 ShopPackageAllocation
- 返回聚合响应
- [x] 7.5 实现 `Get()`:查询 ShopSeriesAllocation → 查 PackageSeries 取全局 tiers含 operator→ 关联套餐分配 → 拼装 ShopSeriesGrantResponse
梯度模式下commission_tiers 响应需将 agent 的 amount 与 PackageSeries tiers 的 operator 按 threshold 合并)
- [x] 7.6 实现 `List()`:分页查询 → 统计 package_count → 返回 ShopSeriesGrantPageResult
- [x] 7.7 实现 `Update()`
- 固定模式:含 one_time_commission_amount 时做天花板校验
- 梯度模式:含 commission_tiers 时做每档位天花板校验
- 平台已设强充时忽略强充变更
- 保存更新
- [x] 7.8 实现 `ManagePackages()`:事务中处理 packages 列表:
- remove=true查找 active 的 ShopPackageAllocation找到则软删除找不到则静默忽略
- 无 remove 标志:校验套餐归属和分配权限,查现有 active 记录(有则更新 cost_price+写历史,无则新建)
- [x] 7.9 实现 `Delete()`:检查子级依赖 → 事务软删除 ShopSeriesAllocation + 所有关联 ShopPackageAllocation
- [x] 7.10 运行 `go build ./...` 确认无编译错误
## 8. Handler、路由及文档生成器
- [x] 8.1 创建 `internal/handler/admin/shop_series_grant.go`,实现 Create、Get、List、Update、ManagePackages、Delete 六个 Handler 方法
- [x] 8.2 创建 `internal/routes/shop_series_grant.go`注册路由Tag: "代理系列授权"
- `GET /shop-series-grants`
- `POST /shop-series-grants`
- `GET /shop-series-grants/:id`
- `PUT /shop-series-grants/:id`
- `DELETE /shop-series-grants/:id`
- `PUT /shop-series-grants/:id/packages`
- [x] 8.3 运行 `go build ./...` 确认无编译错误
## 9. 依赖注入 & Bootstrap
- [x] 9.1 `internal/bootstrap/types.go`:添加 `ShopSeriesGrant *admin.ShopSeriesGrantHandler` 字段
- [x] 9.2 `internal/bootstrap/services.go`import shop_series_grant service 包,添加字段并在 `initServices()` 中初始化
- [x] 9.3 `internal/bootstrap/handlers.go`:添加 ShopSeriesGrant Handler 初始化
- [x] 9.4 `pkg/openapi/handlers.go`:添加 `ShopSeriesGrant: admin.NewShopSeriesGrantHandler(nil)`
- [x] 9.5 `internal/routes/admin.go`:添加 `registerShopSeriesGrantRoutes()` 调用
- [x] 9.6 运行 `go build ./...` 确认完整构建通过
## 10. 执行迁移 & 数据验证
- [x] 10.1 执行迁移(`make migrate-up`),用 db-migration 规范验证3 列已删除commission_tiers_json 列已添加
- [x] 10.2 db-validation创建固定模式授权正常路径确认 ShopSeriesAllocation 和 ShopPackageAllocation 均创建成功
- [x] 10.3 db-validation固定模式金额超过父级天花板时接口返回错误
- [x] 10.4 db-validation梯度模式创建授权commission_tiers_json 正确写入
- [x] 10.5 db-validation梯度模式某档位金额超过父级同档位时接口返回错误
- [x] 10.6 db-validation代理自设强充后购买预检接口返回 need_force_recharge=true金额与代理设置一致
- [x] 10.7 db-validation平台系列已设强充时代理自设强充被锁定购买预检使用平台强充金额
- [x] 10.8 db-validation梯度模式阶梯含 `operator="<"` 时,销售统计低于阈值的代理命中该档位,高于阈值的代理不命中