feat: 实现套餐管理模块,包含套餐系列、双状态管理、废弃模型清理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
- 新增套餐系列管理 (CRUD + 状态切换) - 新增套餐管理 (CRUD + 启用/禁用 + 上架/下架双状态) - 清理 8 个废弃分佣模型及对应数据库表 - Package 模型新增建议成本价、建议售价、上架状态字段 - 完整的 Store/Service/Handler 三层实现 - 包含单元测试和集成测试 - 归档 add-package-module change - 新增多个 OpenSpec changes (订单支付、店铺套餐分配、一次性分佣、卡设备系列绑定)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-27
|
||||
217
openspec/changes/add-shop-package-allocation/design.md
Normal file
217
openspec/changes/add-shop-package-allocation/design.md
Normal file
@@ -0,0 +1,217 @@
|
||||
## 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. **梯度佣金是否可叠加?**
|
||||
- 当前设计:达到最高档位只拿最高档佣金
|
||||
- 待确认:是否需要累加所有达标档位的佣金?
|
||||
61
openspec/changes/add-shop-package-allocation/proposal.md
Normal file
61
openspec/changes/add-shop-package-allocation/proposal.md
Normal file
@@ -0,0 +1,61 @@
|
||||
## Why
|
||||
|
||||
Phase 1 完成了套餐基础模块,但代理商还不能分销套餐。需要实现代理套餐分配机制:上级代理为下级分配套餐系列,设置成本价(通过加价模式计算),并支持梯度佣金配置。代理只能看到和销售被分配的套餐。
|
||||
|
||||
## What Changes
|
||||
|
||||
**新增模型:**
|
||||
- `ShopSeriesAllocation`:店铺套餐系列分配,记录哪个店铺被分配了哪个套餐系列、成本价加价模式、**一次性佣金触发配置**
|
||||
- `ShopSeriesCommissionTier`:梯度佣金配置,基于销量/销售额设置不同的阶梯奖励金额
|
||||
- `ShopPackageAllocation`:店铺单套餐分配(可选),用于覆盖系列级别的成本价设置
|
||||
|
||||
**新增 API:**
|
||||
- 为下级店铺分配套餐系列(设置加价模式)
|
||||
- 查询店铺的套餐系列分配列表
|
||||
- 更新/删除套餐系列分配
|
||||
- 配置梯度佣金(按系列)
|
||||
- 为下级店铺分配单个套餐(覆盖成本价)
|
||||
- 代理查看自己可销售的套餐列表(含成本价)
|
||||
|
||||
**业务规则:**
|
||||
- 加价模式:固定金额加价 或 百分比加价(基于上级成本价)
|
||||
- 代理给下级设置的成本价 ≥ 自己的成本价(不可亏本)
|
||||
- 梯度佣金支持时间范围配置(月度/季度/年度/自定义)
|
||||
- 套餐系列分配是主要方式,单套餐分配用于特殊覆盖
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `shop-series-allocation`: 店铺套餐系列分配 - 为下级店铺分配套餐系列,设置加价模式计算成本价
|
||||
- `shop-commission-tier`: 梯度佣金配置 - 基于销量/销售额配置不同档位的一次性佣金
|
||||
- `shop-package-allocation`: 店铺单套餐分配 - 可选的单套餐级别成本价覆盖
|
||||
- `agent-available-packages`: 代理可售套餐查询 - 代理查看自己被分配的套餐及成本价
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
<!-- 无 -->
|
||||
|
||||
## Impact
|
||||
|
||||
**代码影响:**
|
||||
- `internal/model/` - 新增 3 个模型文件
|
||||
- `migrations/` - 创建 3 个新表
|
||||
- `internal/handler/admin/` - 新增分配管理 Handler
|
||||
- `internal/service/` - 新增分配管理 Service
|
||||
- `internal/store/postgres/` - 新增 3 个 Store
|
||||
- `internal/model/dto/` - 新增请求/响应 DTO
|
||||
- `internal/bootstrap/` - 注册新组件
|
||||
- `internal/router/` - 注册新路由
|
||||
|
||||
**API 影响:**
|
||||
- 新增 `/api/admin/shop-series-allocations/*` 路由组
|
||||
- 新增 `/api/admin/shop-package-allocations/*` 路由组
|
||||
- 新增 `/api/admin/my-packages` 代理可售套餐查询
|
||||
|
||||
**数据库影响:**
|
||||
- 新增表:`tb_shop_series_allocation`, `tb_shop_series_commission_tier`, `tb_shop_package_allocation`
|
||||
|
||||
**依赖关系:**
|
||||
- 依赖 Phase 1(add-package-module)完成
|
||||
- Phase 3(卡/设备关联)依赖本期
|
||||
@@ -0,0 +1,65 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 查询代理可售套餐列表
|
||||
|
||||
系统 SHALL 允许代理查询自己被分配的所有套餐。结果 MUST 包含套餐信息和代理的成本价。支持按套餐系列筛选、按套餐类型筛选。
|
||||
|
||||
#### Scenario: 查询所有可售套餐
|
||||
- **WHEN** 代理查询可售套餐列表
|
||||
- **THEN** 系统返回该代理被分配的所有套餐系列下的启用且上架的套餐
|
||||
|
||||
#### Scenario: 响应包含成本价
|
||||
- **WHEN** 代理查询可售套餐
|
||||
- **THEN** 每个套餐包含:套餐信息、建议售价、代理成本价、利润空间
|
||||
|
||||
#### Scenario: 按系列筛选
|
||||
- **WHEN** 代理指定套餐系列 ID 筛选
|
||||
- **THEN** 系统只返回该系列下的套餐
|
||||
|
||||
#### Scenario: 只返回可售套餐
|
||||
- **WHEN** 代理查询可售套餐
|
||||
- **THEN** 系统只返回状态为启用(1)且上架状态为上架(1)的套餐
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 查询代理可售套餐详情
|
||||
|
||||
系统 SHALL 允许代理查询单个套餐的详细信息,包含完整的价格信息。
|
||||
|
||||
#### Scenario: 查询可售套餐详情
|
||||
- **WHEN** 代理查询指定套餐的详情
|
||||
- **THEN** 系统返回套餐完整信息,包含:成本价、建议售价、价格来源(系列加价/单套餐覆盖)
|
||||
|
||||
#### Scenario: 查询未分配的套餐
|
||||
- **WHEN** 代理查询一个未被分配的套餐详情
|
||||
- **THEN** 系统返回 "您没有该套餐的销售权限" 错误
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 成本价计算优先级
|
||||
|
||||
系统计算代理成本价时 MUST 遵循以下优先级:
|
||||
1. 单套餐覆盖价(如果存在且启用)
|
||||
2. 系列级别加价计算
|
||||
|
||||
#### Scenario: 存在单套餐覆盖
|
||||
- **WHEN** 代理查询一个有覆盖价的套餐
|
||||
- **THEN** 成本价使用覆盖价,价格来源标记为 "单套餐覆盖"
|
||||
|
||||
#### Scenario: 使用系列加价
|
||||
- **WHEN** 代理查询一个无覆盖价的套餐
|
||||
- **THEN** 成本价 = 上级成本价 + 加价值,价格来源标记为 "系列加价"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 查询代理被分配的套餐系列
|
||||
|
||||
系统 SHALL 允许代理查询自己被分配的套餐系列列表。
|
||||
|
||||
#### Scenario: 查询被分配的系列
|
||||
- **WHEN** 代理查询自己的套餐系列分配
|
||||
- **THEN** 系统返回所有分配给该代理的套餐系列(启用状态的)
|
||||
|
||||
#### Scenario: 响应包含系列下套餐数量
|
||||
- **WHEN** 代理查询被分配的系列
|
||||
- **THEN** 每个系列包含:系列信息、可售套餐数量、加价模式信息
|
||||
@@ -0,0 +1,77 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 配置梯度佣金
|
||||
|
||||
系统 SHALL 允许代理为套餐系列分配配置梯度佣金。每个梯度包含:梯度类型(销量/销售额)、周期类型、阈值、佣金金额。
|
||||
|
||||
#### Scenario: 添加销量梯度佣金
|
||||
- **WHEN** 代理为分配添加梯度:类型=销量,周期=月度,阈值=100,佣金=5000分
|
||||
- **THEN** 系统创建梯度配置,当下级月销量达到 100 时可获得 50 元佣金
|
||||
|
||||
#### Scenario: 添加销售额梯度佣金
|
||||
- **WHEN** 代理添加梯度:类型=销售额,周期=季度,阈值=100000分,佣金=10000分
|
||||
- **THEN** 系统创建梯度配置,当下级季度销售额达到 1000 元时可获得 100 元佣金
|
||||
|
||||
#### Scenario: 配置自定义周期
|
||||
- **WHEN** 代理添加梯度,周期类型=自定义,指定开始和结束日期
|
||||
- **THEN** 系统创建梯度配置,统计指定日期范围内的数据
|
||||
|
||||
#### Scenario: 添加多个梯度档位
|
||||
- **WHEN** 代理为同一分配添加多个梯度(如:100件=50元,200件=120元,500件=350元)
|
||||
- **THEN** 系统创建多个梯度记录,支持阶梯奖励
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 查询梯度佣金配置
|
||||
|
||||
系统 SHALL 提供梯度佣金配置的查询功能,按分配 ID 查询。
|
||||
|
||||
#### Scenario: 查询分配的梯度配置
|
||||
- **WHEN** 代理查询指定分配的梯度配置
|
||||
- **THEN** 系统返回该分配下的所有梯度配置,按阈值升序排列
|
||||
|
||||
#### Scenario: 分配无梯度配置
|
||||
- **WHEN** 代理查询一个没有配置梯度的分配
|
||||
- **THEN** 系统返回空列表
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 更新梯度佣金配置
|
||||
|
||||
系统 SHALL 允许代理更新梯度配置的阈值和佣金金额。
|
||||
|
||||
#### Scenario: 更新梯度阈值
|
||||
- **WHEN** 代理将梯度阈值从 100 改为 150
|
||||
- **THEN** 系统更新梯度记录
|
||||
|
||||
#### Scenario: 更新梯度佣金金额
|
||||
- **WHEN** 代理将佣金金额从 5000 改为 6000
|
||||
- **THEN** 系统更新梯度记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 删除梯度佣金配置
|
||||
|
||||
系统 SHALL 允许代理删除梯度配置。
|
||||
|
||||
#### Scenario: 删除梯度配置
|
||||
- **WHEN** 代理删除指定的梯度配置
|
||||
- **THEN** 系统软删除该梯度记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 梯度佣金周期类型
|
||||
|
||||
系统 MUST 支持以下周期类型:
|
||||
- monthly:月度(当月 1 日至月末)
|
||||
- quarterly:季度(当季第一天至最后一天)
|
||||
- yearly:年度(1 月 1 日至 12 月 31 日)
|
||||
- custom:自定义(指定开始和结束日期)
|
||||
|
||||
#### Scenario: 月度周期
|
||||
- **WHEN** 配置月度周期的梯度
|
||||
- **THEN** 统计范围为当月 1 日 00:00:00 至月末 23:59:59
|
||||
|
||||
#### Scenario: 自定义周期必填日期
|
||||
- **WHEN** 代理选择自定义周期但未提供开始或结束日期
|
||||
- **THEN** 系统返回参数验证错误
|
||||
@@ -0,0 +1,65 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 为下级店铺分配单个套餐
|
||||
|
||||
系统 SHALL 允许代理为下级店铺的特定套餐设置覆盖成本价。此功能用于对单个套餐给予特殊定价,优先级高于系列级别的加价计算。
|
||||
|
||||
#### Scenario: 成功分配单套餐覆盖价
|
||||
- **WHEN** 代理为下级的某个套餐设置覆盖成本价 8000 分
|
||||
- **THEN** 系统创建单套餐分配记录,该下级购买此套餐时成本价为 8000 分(不再使用系列加价计算)
|
||||
|
||||
#### Scenario: 覆盖价低于上级成本价
|
||||
- **WHEN** 代理尝试设置的覆盖价低于自己的成本价
|
||||
- **THEN** 系统返回错误 "覆盖价不能低于您的成本价"
|
||||
|
||||
#### Scenario: 套餐未在系列分配中
|
||||
- **WHEN** 代理尝试为一个未分配系列下的套餐设置覆盖价
|
||||
- **THEN** 系统返回错误 "该套餐的系列未分配给此店铺"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 查询单套餐分配列表
|
||||
|
||||
系统 SHALL 提供单套餐分配的查询功能,支持按店铺、套餐、状态筛选。
|
||||
|
||||
#### Scenario: 查询店铺的单套餐分配
|
||||
- **WHEN** 代理查询指定店铺的单套餐分配列表
|
||||
- **THEN** 系统返回该店铺的所有单套餐覆盖配置
|
||||
|
||||
#### Scenario: 查询结果包含套餐信息
|
||||
- **WHEN** 代理查询单套餐分配列表
|
||||
- **THEN** 响应包含套餐名称、套餐编码、原计算成本价、覆盖成本价
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 更新单套餐分配
|
||||
|
||||
系统 SHALL 允许代理更新单套餐分配的覆盖成本价。
|
||||
|
||||
#### Scenario: 更新覆盖成本价
|
||||
- **WHEN** 代理将覆盖成本价从 8000 改为 7500
|
||||
- **THEN** 系统更新记录,下级的该套餐成本价变为 7500
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 删除单套餐分配
|
||||
|
||||
系统 SHALL 允许代理删除单套餐分配。删除后恢复使用系列级别的加价计算。
|
||||
|
||||
#### Scenario: 删除单套餐覆盖
|
||||
- **WHEN** 代理删除单套餐分配记录
|
||||
- **THEN** 系统软删除记录,下级的该套餐成本价恢复为系列加价计算值
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 单套餐分配状态管理
|
||||
|
||||
系统 SHALL 允许代理启用/禁用单套餐分配。禁用后恢复使用系列级别价格。
|
||||
|
||||
#### Scenario: 禁用单套餐覆盖
|
||||
- **WHEN** 代理禁用单套餐分配
|
||||
- **THEN** 该套餐暂时使用系列级别的加价计算
|
||||
|
||||
#### Scenario: 启用单套餐覆盖
|
||||
- **WHEN** 代理启用已禁用的单套餐分配
|
||||
- **THEN** 该套餐恢复使用覆盖成本价
|
||||
@@ -0,0 +1,103 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 为下级店铺分配套餐系列
|
||||
|
||||
系统 SHALL 允许代理为其直属下级店铺分配套餐系列。分配时 MUST 指定加价模式(固定金额或百分比)和加价值。可选配置一次性佣金触发条件(触发类型、阈值、金额)。分配者只能分配自己已被分配的套餐系列。
|
||||
|
||||
#### Scenario: 成功分配套餐系列
|
||||
- **WHEN** 代理为直属下级店铺分配一个自己拥有的套餐系列,设置固定金额加价 1000 分
|
||||
- **THEN** 系统创建分配记录,下级成本价 = 上级成本价 + 1000
|
||||
|
||||
#### Scenario: 百分比加价分配
|
||||
- **WHEN** 代理设置百分比加价模式,加价值为 100(10%)
|
||||
- **THEN** 系统创建分配记录,下级成本价 = 上级成本价 × 1.1
|
||||
|
||||
#### Scenario: 尝试分配未拥有的系列
|
||||
- **WHEN** 代理尝试分配自己未被分配的套餐系列
|
||||
- **THEN** 系统返回错误 "您没有该套餐系列的分配权限"
|
||||
|
||||
#### Scenario: 尝试分配给非直属下级
|
||||
- **WHEN** 代理尝试分配给非直属下级店铺
|
||||
- **THEN** 系统返回错误 "只能为直属下级分配套餐"
|
||||
|
||||
#### Scenario: 重复分配同一系列
|
||||
- **WHEN** 代理尝试为同一下级店铺重复分配同一套餐系列
|
||||
- **THEN** 系统返回错误 "该店铺已分配此套餐系列"
|
||||
|
||||
#### Scenario: 配置一次性佣金触发条件
|
||||
- **WHEN** 代理分配时设置一次性佣金触发类型为"单次充值",阈值 30000 分,金额 5000 分
|
||||
- **THEN** 系统创建分配记录,下级的卡/设备在单次充值 ≥ 300 元时可获得 50 元一次性佣金
|
||||
|
||||
#### Scenario: 配置累计充值触发条件
|
||||
- **WHEN** 代理分配时设置一次性佣金触发类型为"累计充值",阈值 50000 分,金额 8000 分
|
||||
- **THEN** 系统创建分配记录,下级的卡/设备在累计充值 ≥ 500 元时可获得 80 元一次性佣金
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 查询套餐系列分配列表
|
||||
|
||||
系统 SHALL 提供分配列表查询,支持按下级店铺筛选、按套餐系列筛选、按状态筛选。结果 MUST 包含计算后的成本价。
|
||||
|
||||
#### Scenario: 查询所有分配
|
||||
- **WHEN** 代理查询分配列表,不带筛选条件
|
||||
- **THEN** 系统返回该代理创建的所有分配记录
|
||||
|
||||
#### Scenario: 按店铺筛选
|
||||
- **WHEN** 代理指定下级店铺 ID 筛选
|
||||
- **THEN** 系统只返回该店铺的分配记录
|
||||
|
||||
#### Scenario: 响应包含成本价
|
||||
- **WHEN** 代理查询分配列表
|
||||
- **THEN** 每条记录包含计算后的下级成本价
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 更新套餐系列分配
|
||||
|
||||
系统 SHALL 允许代理更新分配的加价模式和加价值。更新后下级的成本价 MUST 同步变化。
|
||||
|
||||
#### Scenario: 更新加价值
|
||||
- **WHEN** 代理将加价值从 1000 改为 2000
|
||||
- **THEN** 系统更新分配记录,下级成本价相应增加
|
||||
|
||||
#### Scenario: 更新不存在的分配
|
||||
- **WHEN** 代理更新不存在的分配 ID
|
||||
- **THEN** 系统返回 "分配记录不存在" 错误
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 删除套餐系列分配
|
||||
|
||||
系统 SHALL 允许代理删除分配记录。如果有下级依赖此分配,MUST 禁止删除。
|
||||
|
||||
#### Scenario: 成功删除无依赖的分配
|
||||
- **WHEN** 代理删除一个没有下级依赖的分配记录
|
||||
- **THEN** 系统软删除该记录
|
||||
|
||||
#### Scenario: 尝试删除有下级依赖的分配
|
||||
- **WHEN** 代理尝试删除一个已被下级使用的分配(下级基于此分配又分配给了更下级)
|
||||
- **THEN** 系统返回错误 "存在下级依赖,无法删除"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 启用/禁用套餐系列分配
|
||||
|
||||
系统 SHALL 允许代理切换分配的启用状态。禁用后下级 MUST NOT 能使用该分配购买套餐。
|
||||
|
||||
#### Scenario: 禁用分配
|
||||
- **WHEN** 代理将分配状态设为禁用
|
||||
- **THEN** 系统更新状态,下级无法基于此分配购买套餐
|
||||
|
||||
#### Scenario: 启用分配
|
||||
- **WHEN** 代理将禁用的分配设为启用
|
||||
- **THEN** 系统更新状态,下级可以继续使用
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 平台分配套餐系列
|
||||
|
||||
平台管理员 SHALL 能够为一级代理分配套餐系列。平台的成本价基准为 Package.suggested_cost_price。
|
||||
|
||||
#### Scenario: 平台为一级代理分配
|
||||
- **WHEN** 平台管理员为一级代理分配套餐系列
|
||||
- **THEN** 系统创建分配记录,一级代理成本价 = suggested_cost_price + 加价值
|
||||
167
openspec/changes/add-shop-package-allocation/tasks.md
Normal file
167
openspec/changes/add-shop-package-allocation/tasks.md
Normal file
@@ -0,0 +1,167 @@
|
||||
## 1. 新增模型
|
||||
|
||||
- [ ] 1.1 创建 `internal/model/shop_series_allocation.go`,定义 ShopSeriesAllocation 模型(shop_id, series_id, allocator_shop_id, pricing_mode, pricing_value, one_time_commission_trigger, one_time_commission_threshold, one_time_commission_amount, status)
|
||||
- [ ] 1.2 创建 `internal/model/shop_series_commission_tier.go`,定义 ShopSeriesCommissionTier 模型(allocation_id, tier_type, period_type, period_start_date, period_end_date, threshold_value, commission_amount)
|
||||
- [ ] 1.3 创建 `internal/model/shop_package_allocation.go`,定义 ShopPackageAllocation 模型(shop_id, package_id, allocation_id, cost_price, status)
|
||||
|
||||
## 2. 数据库迁移
|
||||
|
||||
- [ ] 2.1 创建迁移文件,创建 tb_shop_series_allocation 表
|
||||
- [ ] 2.2 创建 tb_shop_series_commission_tier 表
|
||||
- [ ] 2.3 创建 tb_shop_package_allocation 表
|
||||
- [ ] 2.4 添加必要的索引(shop_id, series_id, allocation_id)
|
||||
- [ ] 2.5 本地执行迁移验证
|
||||
|
||||
## 3. 套餐系列分配 DTO
|
||||
|
||||
- [ ] 3.1 创建 `internal/model/dto/shop_series_allocation.go`,定义 CreateShopSeriesAllocationRequest(含 one_time_commission_trigger, one_time_commission_threshold, one_time_commission_amount 可选字段)
|
||||
- [ ] 3.2 定义 UpdateShopSeriesAllocationRequest
|
||||
- [ ] 3.3 定义 ShopSeriesAllocationListRequest(支持 shop_id, series_id, status 筛选)
|
||||
- [ ] 3.4 定义 UpdateStatusRequest
|
||||
- [ ] 3.5 定义 ShopSeriesAllocationResponse(包含计算后的成本价)
|
||||
|
||||
## 4. 梯度佣金 DTO
|
||||
|
||||
- [ ] 4.1 定义 CreateCommissionTierRequest(tier_type, period_type, period_start_date, period_end_date, threshold_value, commission_amount)
|
||||
- [ ] 4.2 定义 UpdateCommissionTierRequest
|
||||
- [ ] 4.3 定义 CommissionTierResponse
|
||||
|
||||
## 5. 单套餐分配 DTO
|
||||
|
||||
- [ ] 5.1 创建 `internal/model/dto/shop_package_allocation.go`,定义 CreateShopPackageAllocationRequest
|
||||
- [ ] 5.2 定义 UpdateShopPackageAllocationRequest
|
||||
- [ ] 5.3 定义 ShopPackageAllocationListRequest
|
||||
- [ ] 5.4 定义 ShopPackageAllocationResponse
|
||||
|
||||
## 6. 代理可售套餐 DTO
|
||||
|
||||
- [ ] 6.1 定义 MyPackageListRequest(series_id, package_type 筛选)
|
||||
- [ ] 6.2 定义 MyPackageResponse(包含成本价、建议售价、价格来源)
|
||||
- [ ] 6.3 定义 MySeriesAllocationResponse
|
||||
|
||||
## 7. 套餐系列分配 Store
|
||||
|
||||
- [ ] 7.1 创建 `internal/store/postgres/shop_series_allocation_store.go`,实现 Create 方法
|
||||
- [ ] 7.2 实现 GetByID 方法
|
||||
- [ ] 7.3 实现 GetByShopAndSeries 方法(检查重复分配)
|
||||
- [ ] 7.4 实现 Update 方法
|
||||
- [ ] 7.5 实现 Delete 方法
|
||||
- [ ] 7.6 实现 List 方法(支持分页和筛选)
|
||||
- [ ] 7.7 实现 UpdateStatus 方法
|
||||
- [ ] 7.8 实现 HasDependentAllocations 方法(检查下级依赖)
|
||||
- [ ] 7.9 实现 GetByShopID 方法(获取店铺的所有分配)
|
||||
|
||||
## 8. 梯度佣金 Store
|
||||
|
||||
- [ ] 8.1 创建 `internal/store/postgres/shop_series_commission_tier_store.go`,实现 Create 方法
|
||||
- [ ] 8.2 实现 GetByID 方法
|
||||
- [ ] 8.3 实现 Update 方法
|
||||
- [ ] 8.4 实现 Delete 方法
|
||||
- [ ] 8.5 实现 ListByAllocationID 方法
|
||||
|
||||
## 9. 单套餐分配 Store
|
||||
|
||||
- [ ] 9.1 创建 `internal/store/postgres/shop_package_allocation_store.go`,实现 Create 方法
|
||||
- [ ] 9.2 实现 GetByID 方法
|
||||
- [ ] 9.3 实现 GetByShopAndPackage 方法
|
||||
- [ ] 9.4 实现 Update 方法
|
||||
- [ ] 9.5 实现 Delete 方法
|
||||
- [ ] 9.6 实现 List 方法
|
||||
- [ ] 9.7 实现 UpdateStatus 方法
|
||||
|
||||
## 10. 套餐系列分配 Service
|
||||
|
||||
- [ ] 10.1 创建 `internal/service/shop_series_allocation/service.go`,实现 Create 方法(验证权限、检查重复、计算成本价)
|
||||
- [ ] 10.2 实现 Get 方法
|
||||
- [ ] 10.3 实现 Update 方法
|
||||
- [ ] 10.4 实现 Delete 方法(检查下级依赖)
|
||||
- [ ] 10.5 实现 List 方法
|
||||
- [ ] 10.6 实现 UpdateStatus 方法
|
||||
- [ ] 10.7 实现 GetParentCostPrice 辅助方法(递归获取上级成本价)
|
||||
- [ ] 10.8 实现 CalculateCostPrice 辅助方法(根据加价模式计算)
|
||||
|
||||
## 11. 梯度佣金 Service
|
||||
|
||||
- [ ] 11.1 在 shop_series_allocation service 中实现 AddTier 方法
|
||||
- [ ] 11.2 实现 UpdateTier 方法
|
||||
- [ ] 11.3 实现 DeleteTier 方法
|
||||
- [ ] 11.4 实现 ListTiers 方法
|
||||
|
||||
## 12. 单套餐分配 Service
|
||||
|
||||
- [ ] 12.1 创建 `internal/service/shop_package_allocation/service.go`,实现 Create 方法(验证系列已分配、验证成本价)
|
||||
- [ ] 12.2 实现 Get 方法
|
||||
- [ ] 12.3 实现 Update 方法
|
||||
- [ ] 12.4 实现 Delete 方法
|
||||
- [ ] 12.5 实现 List 方法
|
||||
- [ ] 12.6 实现 UpdateStatus 方法
|
||||
|
||||
## 13. 代理可售套餐 Service
|
||||
|
||||
- [ ] 13.1 创建 `internal/service/my_package/service.go`,实现 ListMyPackages 方法(获取可售套餐列表)
|
||||
- [ ] 13.2 实现 GetMyPackage 方法(获取单个套餐详情含成本价)
|
||||
- [ ] 13.3 实现 ListMySeriesAllocations 方法(获取被分配的系列)
|
||||
- [ ] 13.4 实现 GetCostPrice 核心方法(成本价计算,考虑优先级)
|
||||
|
||||
## 14. 套餐系列分配 Handler
|
||||
|
||||
- [ ] 14.1 创建 `internal/handler/admin/shop_series_allocation.go`,实现 Create 接口
|
||||
- [ ] 14.2 实现 Get 接口
|
||||
- [ ] 14.3 实现 Update 接口
|
||||
- [ ] 14.4 实现 Delete 接口
|
||||
- [ ] 14.5 实现 List 接口
|
||||
- [ ] 14.6 实现 UpdateStatus 接口
|
||||
- [ ] 14.7 实现 AddTier 接口
|
||||
- [ ] 14.8 实现 UpdateTier 接口
|
||||
- [ ] 14.9 实现 DeleteTier 接口
|
||||
- [ ] 14.10 实现 ListTiers 接口
|
||||
|
||||
## 15. 单套餐分配 Handler
|
||||
|
||||
- [ ] 15.1 创建 `internal/handler/admin/shop_package_allocation.go`,实现 Create 接口
|
||||
- [ ] 15.2 实现 Get 接口
|
||||
- [ ] 15.3 实现 Update 接口
|
||||
- [ ] 15.4 实现 Delete 接口
|
||||
- [ ] 15.5 实现 List 接口
|
||||
- [ ] 15.6 实现 UpdateStatus 接口
|
||||
|
||||
## 16. 代理可售套餐 Handler
|
||||
|
||||
- [ ] 16.1 创建 `internal/handler/admin/my_package.go`,实现 ListMyPackages 接口
|
||||
- [ ] 16.2 实现 GetMyPackage 接口
|
||||
- [ ] 16.3 实现 ListMySeriesAllocations 接口
|
||||
|
||||
## 17. Bootstrap 注册
|
||||
|
||||
- [ ] 17.1 在 stores.go 中注册 ShopSeriesAllocationStore, ShopSeriesCommissionTierStore, ShopPackageAllocationStore
|
||||
- [ ] 17.2 在 services.go 中注册 ShopSeriesAllocationService, ShopPackageAllocationService, MyPackageService
|
||||
- [ ] 17.3 在 handlers.go 中注册 ShopSeriesAllocationHandler, ShopPackageAllocationHandler, MyPackageHandler
|
||||
|
||||
## 18. 路由注册
|
||||
|
||||
- [ ] 18.1 注册 `/api/admin/shop-series-allocations` 路由组
|
||||
- [ ] 18.2 注册 `/api/admin/shop-series-allocations/:id/tiers` 嵌套路由
|
||||
- [ ] 18.3 注册 `/api/admin/shop-package-allocations` 路由组
|
||||
- [ ] 18.4 注册 `/api/admin/my-packages` 路由
|
||||
- [ ] 18.5 注册 `/api/admin/my-series-allocations` 路由
|
||||
|
||||
## 19. 文档生成器更新
|
||||
|
||||
- [ ] 19.1 在 docs.go 和 gendocs/main.go 中添加新 Handler
|
||||
- [ ] 19.2 执行文档生成验证
|
||||
|
||||
## 20. 测试
|
||||
|
||||
- [ ] 20.1 ShopSeriesAllocationStore 单元测试
|
||||
- [ ] 20.2 ShopPackageAllocationStore 单元测试
|
||||
- [ ] 20.3 ShopSeriesAllocationService 单元测试(覆盖权限验证、成本价计算)
|
||||
- [ ] 20.4 MyPackageService 单元测试(覆盖成本价优先级)
|
||||
- [ ] 20.5 套餐系列分配 API 集成测试
|
||||
- [ ] 20.6 代理可售套餐 API 集成测试
|
||||
- [ ] 20.7 执行 `go test ./...` 确认通过
|
||||
|
||||
## 21. 最终验证
|
||||
|
||||
- [ ] 21.1 执行 `go build ./...` 确认编译通过
|
||||
- [ ] 21.2 启动服务,手动测试分配流程
|
||||
- [ ] 21.3 验证成本价计算逻辑正确
|
||||
Reference in New Issue
Block a user