## 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. **梯度佣金是否可叠加?** - 当前设计:达到最高档位只拿最高档佣金 - 待确认:是否需要累加所有达标档位的佣金?