Files
junhong_cmp_fiber/openspec/changes/archive/2026-03-05-fix-tiered-commission-tier-fields/design.md
huang b52cb9a078
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m27s
fix: 修复梯度佣金档位字段缺失,补全授权接口响应字段及强充有效状态
- OneTimeCommissionTierDTO 补充 operator 字段映射
- GrantCommissionTierItem 补充 dimension/stat_scope 字段(从全局配置合并)
- 系列授权列表/详情补充强充锁定状态和强充金额的有效值计算
- 同步 OpenSpec 主规范并归档变更文档
2026-03-05 11:23:28 +08:00

67 lines
5.5 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.
## Context
`refactor-agent-series-grant` 变更在 Model 层(`model.OneTimeCommissionTier`)已正确新增 `Operator` 字段,并添加了运算符常量(`TierOperatorGT/GTE/LT/LTE`),但遗漏了同步更新 DTO 层和 Service 层的映射逻辑。
具体遗漏:
1. `OneTimeCommissionTierDTO`package_series_dto.go未加 `Operator` 字段 → API 无法读写 operator
2. `GrantCommissionTierItem`shop_series_grant_dto.go未加 `Dimension``StatScope` 字段 → grant 响应中梯度档位条件不透明
3. `package_series/service.go``dtoToModelConfig()` / `modelToDTO()` 未处理 `Operator` 映射
4. `shop_series_grant/service.go``buildGrantResponse()` 合并全局 tiers 时未携带 `Dimension``StatScope`
5. 系列授权列表/详情响应未正确反映 `force_recharge_enabled` 有效状态,且列表缺少 `force_recharge_locked``force_recharge_amount` 字段
无数据库结构变更,纯 DTO + Service 层修复。
**Bug 3 根因详述**`forceRechargeLocked = config.TriggerType == FirstRecharge || config.EnableForceRecharge`。当此条件为 `true`Service 正确跳过写 `allocation.EnableForceRecharge=true`,分配表中该字段保持 `false`。但 List 只返回 `a.EnableForceRecharge`Detail `buildGrantResponse` 也返回 `allocation.EnableForceRecharge`,两处均未计算有效状态,导致前端看到 `force_recharge_enabled=false`
## Goals / Non-Goals
**Goals:**
- `OneTimeCommissionTierDTO` 支持 `operator` 字段的读写(创建/更新套餐系列时可传,查询时返回)
- `GrantCommissionTierItem` 响应中补充展示 `dimension``stat_scope`(只读,从 PackageSeries 全局配置合并)
- 向前兼容:`operator` 字段均使用 `omitempty`,老客户端请求不传时默认 `>=`(沿用 Model 层现有 fallback 逻辑)
- `ShopSeriesGrantListItem` 补充 `force_recharge_locked``force_recharge_amount` 字段
- 列表和详情 `force_recharge_enabled` 反映有效状态(`allocation.EnableForceRecharge || forceRechargeLocked`);锁定时 `force_recharge_amount``config.ForceAmount`
**Non-Goals:**
- 不修改数据库结构、迁移文件
- 不修改 `model.OneTimeCommissionTier`(已正确)
- 不修改佣金计算引擎(`commission_calculation/service.go`
- 不修改 `GrantCommissionTierItem` 请求侧(代理创建授权时仍不传 operator/dimension/stat_scope这三个字段来自全局配置不可修改
- 不修改梯度档位的校验逻辑threshold 匹配等已正确)
## Decisions
### 决策 1`Operator` 在 DTO 中用 `omitempty`
`OneTimeCommissionTierDTO.Operator``GrantCommissionTierItem.Operator` 均使用 `json:"operator,omitempty"`
**理由**:现有未设 operator 的套餐系列JSONB 中该字段为空字符串,`omitempty` 避免返回无意义的 `""` 给前端,且与 Model 层的"Operator 为空时 fallback 到 `>=`"语义一致。
**备选**:永远返回字符串(空或 `>=`)→ 否决,因为"空"在 JSON 中会被序列化为 `""`,语义不清晰,而写入 JSONB 时可能覆盖原本为空的历史数据。
### 决策 2`Dimension`/`StatScope` 仅出现在响应中,请求侧不开放
`GrantCommissionTierItem``Dimension``StatScope` 仅用于 GET/POST/PUT 的响应输出(从 PackageSeries 全局配置按 threshold 合并),请求体中代理仍只传 `threshold``amount`
**理由**:这两个字段是套餐系列级别的全局配置,代理在创建授权时不能修改条件,只能修改金额。与 `Operator` 的处理方式保持一致(已在上次变更中确立该设计原则)。
### 决策 3`buildGrantResponse` 按 threshold 索引合并全部条件字段
现有代码按 threshold 构建 `agentAmountMap`,在遍历 `config.Tiers` 时合并 `Operator`。本次同步合并 `Dimension``StatScope`无需额外查询O(N) 时间复杂度N 为档位数(通常 ≤ 5性能无影响。
### 决策 4`force_recharge_enabled` 返回有效状态而非存储状态
列表和详情响应中,`force_recharge_enabled = allocation.EnableForceRecharge || forceRechargeLocked`
**理由**:前端关心的是「强充是否实际生效」而非「代理是否主动开启」。当系列锁定强充时,即使 allocation 存储 `false`Service 正确设计:锁定时不覆盖),对用户而言强充仍然生效。返回 `false` 会让前端误判,需在响应层修正。
**备选**:返回存储值(`allocation.EnableForceRecharge`)并依靠 `force_recharge_locked` 由前端推导 → 否决,因为历史已有前端对接问题,且两个字段冗余更易出错。统一在后端计算有效状态是更稳健的 API 设计。
## Risks / Trade-offs
| 风险 | 缓解措施 |
|------|----------|
| 历史套餐系列 JSONB 中梯度档位无 `operator`/`dimension`/`stat_scope` | 响应用 `omitempty`,缺失字段不出现在响应中而非返回空值;前端应做好字段缺失的降级处理 |
| `CreateShopSeriesGrantRequest.CommissionTiers` 中含有 `operator`/`dimension`/`stat_scope` 字段(因共用同一 DTO 结构) | `GrantCommissionTierItem` 在请求侧这三个字段会被忽略Service 层不读取),无副作用;可在 description 注释中说明仅响应有效 |
| 锁定时 `force_recharge_amount` 来源变更config 而非 allocation | 列表新增字段详情原有字段调整逻辑前端只需使用响应值无需区分来源allocation 表 `force_recharge_amount` 在锁定时仍存 0不需迁移 |