## Context

Phase 1 完成了套餐系列和套餐的基础管理，但代理商还不能分销套餐。本期实现代理套餐分配机制，使上级代理能够：
1. 为下级店铺分配可销售的套餐系列
2. 通过加价模式设置下级的成本价
3. 配置梯度佣金（基于销量/销售额的阶梯奖励）

**当前代理层级结构**：
- 店铺通过 `Shop.parent_id` 维护层级关系
- 最多 7 级代理
- 数据权限通过 `GetSubordinateShopIDs()` 递归查询

## Goals / Non-Goals

**Goals:**
- 实现套餐系列级别的分配机制
- 支持固定金额和百分比两种加价模式
- 支持梯度佣金配置（月度/季度/年度/自定义时间范围）
- 代理能查看自己被分配的套餐及成本价
- 可选的单套餐级别成本价覆盖

**Non-Goals:**
- 不实现卡/设备的套餐系列关联（Phase 3）
- 不实现订单支付流程（Phase 4）
- 不实现佣金计算逻辑（Phase 5）
- 不支持跨级分配（只能分配给直属下级）

## Decisions

### 1. 分配模型设计

**决策**：三个独立模型

```go
// ShopSeriesAllocation 店铺套餐系列分配
type ShopSeriesAllocation struct {
    gorm.Model
    BaseModel
    ShopID                      uint   // 被分配的店铺 ID
    SeriesID                    uint   // 套餐系列 ID
    AllocatorShopID             uint   // 分配者店铺 ID（上级）
    PricingMode                 string // 加价模式: fixed-固定金额 percent-百分比
    PricingValue                int64  // 加价值（分或千分比）
    OneTimeCommissionTrigger    string // 一次性佣金触发类型: one_time_recharge-单次充值 accumulated_recharge-累计充值
    OneTimeCommissionThreshold  int64  // 一次性佣金触发阈值（分）
    OneTimeCommissionAmount     int64  // 一次性佣金金额（分）
    Status                      int    // 状态 1-启用 2-禁用
}

// ShopSeriesCommissionTier 梯度佣金配置
type ShopSeriesCommissionTier struct {
    gorm.Model
    BaseModel
    AllocationID     uint      // 关联的分配 ID
    TierType         string    // 梯度类型: sales_count-销量 sales_amount-销售额
    PeriodType       string    // 周期类型: monthly-月度 quarterly-季度 yearly-年度 custom-自定义
    PeriodStartDate  *time.Time // 自定义周期开始日期
    PeriodEndDate    *time.Time // 自定义周期结束日期
    ThresholdValue   int64     // 阈值（销量或金额）
    CommissionAmount int64     // 佣金金额（分）
}

// ShopPackageAllocation 店铺单套餐分配（可选覆盖）
type ShopPackageAllocation struct {
    gorm.Model
    BaseModel
    ShopID          uint  // 被分配的店铺 ID
    PackageID       uint  // 套餐 ID
    AllocationID    uint  // 关联的系列分配 ID
    CostPrice       int64 // 覆盖的成本价（分）
    Status          int   // 状态 1-启用 2-禁用
}
```

**理由**：
- 系列级别分配是主要方式，减少配置工作量
- 单套餐分配用于特殊场景（如某个套餐给特定代理优惠价）
- 梯度佣金独立模型，支持多档配置

### 2. 加价模式与成本价计算

**决策**：成本价 = 上级成本价 + 加价值

```
# 固定金额加价
下级成本价 = 上级成本价 + pricing_value

# 百分比加价（pricing_value 为千分比，如 100 = 10%）
下级成本价 = 上级成本价 × (1 + pricing_value / 1000)
```

**理由**：
- 基于上级成本价加价，确保每级都有利润空间
- 千分比精度满足业务需求（0.1% 精度）
- 平台作为顶级，其成本价 = Package.suggested_cost_price

**约束**：
- 下级成本价 ≥ 上级成本价（禁止负加价）
- 验证时需递归获取上级成本价

### 3. 成本价获取逻辑

**决策**：递归查询 + 缓存

```go
func GetCostPrice(shopID, packageID uint) int64 {
    // 1. 检查是否有单套餐覆盖
    if override := GetPackageAllocation(shopID, packageID); override != nil {
        return override.CostPrice
    }
    
    // 2. 获取系列分配
    allocation := GetSeriesAllocation(shopID, package.SeriesID)
    if allocation == nil {
        return 0 // 未分配，不可购买
    }
    
    // 3. 获取上级成本价
    parentCostPrice := GetParentCostPrice(allocation.AllocatorShopID, packageID)
    
    // 4. 计算当前成本价
    return CalculatePrice(parentCostPrice, allocation.PricingMode, allocation.PricingValue)
}
```

**理由**：
- 单套餐覆盖优先级最高
- 递归到平台级别时，使用 Package.suggested_cost_price
- 可考虑缓存热点套餐的成本价（后续优化）

### 4. 梯度佣金周期计算

**决策**：支持固定周期和自定义周期

| PeriodType | 计算方式 |
|------------|----------|
| monthly | 当月 1 日 00:00 至月末 23:59:59 |
| quarterly | 当季度第一天至最后一天 |
| yearly | 当年 1 月 1 日至 12 月 31 日 |
| custom | PeriodStartDate 至 PeriodEndDate |

**理由**：
- 固定周期覆盖常见场景
- 自定义周期支持促销活动等特殊需求

### 5. API 设计

**决策**：RESTful + 嵌套资源

```
# 套餐系列分配
POST   /api/admin/shop-series-allocations              为下级分配系列
GET    /api/admin/shop-series-allocations              查询分配列表
GET    /api/admin/shop-series-allocations/:id          分配详情
PUT    /api/admin/shop-series-allocations/:id          更新分配
DELETE /api/admin/shop-series-allocations/:id          删除分配
PATCH  /api/admin/shop-series-allocations/:id/status   启用/禁用

# 梯度佣金（嵌套在分配下）
POST   /api/admin/shop-series-allocations/:id/tiers    添加梯度
GET    /api/admin/shop-series-allocations/:id/tiers    梯度列表
PUT    /api/admin/shop-series-allocations/:id/tiers/:tierId  更新梯度
DELETE /api/admin/shop-series-allocations/:id/tiers/:tierId  删除梯度

# 单套餐分配
POST   /api/admin/shop-package-allocations             分配单套餐
GET    /api/admin/shop-package-allocations             查询列表
PUT    /api/admin/shop-package-allocations/:id         更新
DELETE /api/admin/shop-package-allocations/:id         删除

# 代理可售套餐
GET    /api/admin/my-packages                          查询我的可售套餐
GET    /api/admin/my-packages/:id                      套餐详情（含成本价）
```

## Risks / Trade-offs

### 风险 1：递归成本价计算性能

**风险**：多级代理场景下，递归查询成本价可能较慢

**缓解**：
- 首期不做缓存，观察实际性能
- 如有问题，后续增加 Redis 缓存（按 shop_id + package_id 缓存）
- 缓存失效策略：分配变更时清除相关缓存

### 风险 2：分配一致性

**风险**：上级删除分配后，下级的分配关系如何处理

**缓解**：
- 删除分配时检查是否有下级依赖
- 如有下级依赖，禁止删除或级联禁用
- 本期采用禁止删除策略，要求先清理下级分配

### 风险 3：梯度佣金统计复杂度

**风险**：统计周期内的销量/销售额可能涉及大量数据

**缓解**：
- 佣金计算在 Phase 5 实现
- 可考虑定时任务预计算周期统计数据
- 本期只做配置，不做实际统计

## Open Questions

1. **是否支持批量分配？**
   - 当前设计：单个分配
   - 待确认：是否需要批量为多个下级分配同一系列？

2. **分配删除策略？**
   - 当前设计：有下级依赖时禁止删除
   - 待确认：是否需要级联删除或级联禁用？

3. **梯度佣金是否可叠加？**
   - 当前设计：达到最高档位只拿最高档佣金
   - 待确认：是否需要累加所有达标档位的佣金？
