159 lines
4.8 KiB
Markdown
159 lines
4.8 KiB
Markdown
# 需求05:套餐分配生效条件(ExpiryBase 覆盖)
|
||
|
||
---
|
||
|
||
## 背景
|
||
|
||
`Package.ExpiryBase` 已存在(`from_activation` / `from_purchase`),在套餐创建时设定,控制套餐何时开始计时。
|
||
|
||
需求:分配套餐给代理时,可以对单条分配记录二次覆盖这个值。
|
||
|
||
---
|
||
|
||
## 快照链设计
|
||
|
||
```
|
||
ShopPackageAllocation.expiry_base_override(分配时设置覆盖值)
|
||
↓ 订单创建时读取,取有效值(override 优先,否则用套餐默认)
|
||
PackageUsage.expiry_base_snapshot(快照进去)
|
||
↓ 激活时读取
|
||
activation_service 读 usage.ExpiryBaseSnapshot,不再直接读 pkg.ExpiryBase
|
||
```
|
||
|
||
遗留数据兜底:`ExpiryBaseSnapshot` 为空(旧数据)时,回退读 `pkg.ExpiryBase`,行为不变。
|
||
|
||
---
|
||
|
||
## 数据库变更
|
||
|
||
### 1. ShopPackageAllocation 新增覆盖字段
|
||
|
||
```sql
|
||
ALTER TABLE tb_shop_package_allocation
|
||
ADD COLUMN expiry_base_override VARCHAR(30) DEFAULT NULL
|
||
COMMENT '生效条件覆盖(NULL=使用套餐默认值, from_activation=实名即生效, from_purchase=购买即生效)';
|
||
```
|
||
|
||
### 2. PackageUsage 新增快照字段
|
||
|
||
```sql
|
||
ALTER TABLE tb_package_usage
|
||
ADD COLUMN expiry_base_snapshot VARCHAR(30) NOT NULL DEFAULT ''
|
||
COMMENT '生效条件快照(创建时从分配记录取有效值写入,空字符串=旧数据兜底读套餐原值)';
|
||
```
|
||
|
||
旧数据不回填,默认空字符串,激活时自动兜底。
|
||
|
||
---
|
||
|
||
## Model 变更
|
||
|
||
### ShopPackageAllocation(`internal/model/shop_package_allocation.go`)
|
||
|
||
```go
|
||
// ExpiryBaseOverride 生效条件覆盖
|
||
// NULL = 使用宿主套餐的 ExpiryBase;有值 = 分配时指定,不受套餐后续修改影响
|
||
ExpiryBaseOverride *string `gorm:"column:expiry_base_override;type:varchar(30);comment:生效条件覆盖 NULL=使用套餐默认 from_activation=实名即生效 from_purchase=购买即生效" json:"expiry_base_override"`
|
||
```
|
||
|
||
### PackageUsage(`internal/model/package.go`)
|
||
|
||
```go
|
||
// ExpiryBaseSnapshot 生效条件快照(创建订单时写入,空字符串=旧数据兜底读套餐原值)
|
||
ExpiryBaseSnapshot string `gorm:"column:expiry_base_snapshot;type:varchar(30);not null;default:'';comment:生效条件快照 创建时从分配记录取有效值" json:"expiry_base_snapshot"`
|
||
```
|
||
|
||
---
|
||
|
||
## 业务逻辑变更
|
||
|
||
### 1. 订单创建时快照(`internal/service/order/service.go`)
|
||
|
||
订单创建已通过 `GetByShopAndPackage` 查询分配记录(现有逻辑),在此基础上追加:
|
||
|
||
```go
|
||
// 取生效条件有效值:分配覆盖 > 套餐默认
|
||
expiryBase := pkg.ExpiryBase
|
||
if allocation.ExpiryBaseOverride != nil && *allocation.ExpiryBaseOverride != "" {
|
||
expiryBase = *allocation.ExpiryBaseOverride
|
||
}
|
||
// 创建 PackageUsage 时写入快照
|
||
usage.ExpiryBaseSnapshot = expiryBase
|
||
```
|
||
|
||
### 2. 激活时读快照(`internal/service/package/activation_service.go`)
|
||
|
||
```go
|
||
// 修改前:直接读套餐原值
|
||
expiryBase := pkg.ExpiryBase
|
||
|
||
// 修改后:优先读快照,旧数据兜底
|
||
expiryBase := pkg.ExpiryBase // 兜底(旧数据 ExpiryBaseSnapshot 为空)
|
||
if usage.ExpiryBaseSnapshot != "" {
|
||
expiryBase = usage.ExpiryBaseSnapshot
|
||
}
|
||
```
|
||
|
||
同文件另一处(`activation_service.go:556`)同理修改。
|
||
|
||
`internal/service/order/service.go:2290` 中后台囤货路径的 `ExpiryBase` 判断也需同样改为读快照(兜底逻辑一致)。
|
||
|
||
---
|
||
|
||
## API 变更
|
||
|
||
### 1. 分配套餐接口(新增参数)
|
||
|
||
```
|
||
POST /admin/shop-package-allocations
|
||
```
|
||
|
||
请求 DTO 新增字段:
|
||
|
||
```go
|
||
ExpiryBaseOverride *string `json:"expiry_base_override" validate:"omitempty,oneof=from_activation from_purchase" description:"生效条件覆盖(不传=使用套餐默认, from_activation=实名即生效, from_purchase=购买即生效)"`
|
||
```
|
||
|
||
### 2. 修改已分配套餐的生效条件(新接口)
|
||
|
||
```
|
||
PATCH /admin/shop-package-allocations/{id}/expiry-base
|
||
```
|
||
|
||
请求 DTO:
|
||
|
||
```go
|
||
type UpdateAllocationExpiryBaseRequest struct {
|
||
ExpiryBaseOverride *string `json:"expiry_base_override" validate:"omitempty,oneof=from_activation from_purchase" description:"生效条件(null=恢复套餐默认, from_activation=实名即生效, from_purchase=购买即生效)"`
|
||
}
|
||
```
|
||
|
||
> 注意:修改已有分配记录的覆盖值,**不影响**已创建的 PackageUsage(快照已定),只影响后续新建的订单。
|
||
|
||
---
|
||
|
||
## 前端对接
|
||
|
||
### 套餐分配弹框
|
||
|
||
新增"生效条件"选择项:
|
||
|
||
```
|
||
生效条件:
|
||
○ 跟随套餐默认(默认选中,不传 expiry_base_override)
|
||
○ 购买即生效(from_purchase)
|
||
○ 实名即生效(from_activation)
|
||
```
|
||
|
||
### 已分配套餐列表
|
||
|
||
列表新增"生效条件"列:
|
||
|
||
| 值 | 展示 |
|
||
|----|------|
|
||
| NULL | 套餐默认 |
|
||
| `from_activation` | 实名即生效(已覆盖) |
|
||
| `from_purchase` | 购买即生效(已覆盖) |
|
||
|
||
操作列增加"修改生效条件"按钮,调用 `PATCH /admin/shop-package-allocations/{id}/expiry-base`。
|