## 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，不需迁移 |
