fix(force-recharge): 补充强充配置缺失的接口和数据库字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m19s

- 订单管理:增加 payment_method 字段支持,合并代购订单逻辑
- 套餐系列分配:增加强充配置字段(enable_force_recharge、force_recharge_amount、force_recharge_trigger_type)
- 数据库迁移:添加 force_recharge_trigger_type 字段
- 测试:更新订单服务测试用例
- OpenSpec:归档 fix-force-recharge-missing-interfaces 变更
This commit is contained in:
2026-01-31 15:34:32 +08:00
parent d309951493
commit d81bd242a4
21 changed files with 1090 additions and 388 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-01-31

View File

@@ -0,0 +1,267 @@
## Context
`add-force-recharge-system` 功能归档后,发现两个关键遗漏:
1. **套餐系列分配强充配置不可管理**
- 数据库字段已添加:`enable_force_recharge``force_recharge_amount``force_recharge_trigger_type`
- Service 层已使用这些字段进行强充验证(`RechargeService.GetRechargeCheck``OrderService.GetPurchaseCheck`
- 但 DTO 层完全没有暴露这些字段,管理员无法通过 API 配置
2. **后台订单创建逻辑重复**
- 存在两套 DTO`CreateOrderRequest``CreatePurchaseOnBehalfRequest`(字段完全相同)
- 存在两个 Service 方法:`Create``CreatePurchaseOnBehalf`(核心逻辑相似,仅支付方式和成本价计算不同)
- 实际业务中,后台订单只有两种支付方式:
- `wallet`:扣代理钱包,需要强充验证,触发佣金
- `offline`:线下已收款,不扣钱包,不触发佣金(即代购)
**现有架构**
- 强充验证逻辑完整:`RechargeService``OrderService` 已实现
- 数据模型完整:`ShopSeriesAllocation.enable_force_recharge` 等字段已存在
- 仅缺少管理接口暴露
## Goals / Non-Goals
**Goals:**
- 暴露强充配置字段到套餐系列分配的 CRUD 接口
- 统一后台订单创建接口,使用 `payment_method` 字段区分普通订单和代购订单
- 删除重复代码和冗余 DTO
- 保持现有业务逻辑不变(强充验证、佣金计算、成本价计算)
**Non-Goals:**
- 不修改强充验证逻辑(已在 `add-force-recharge-system` 中实现)
- 不修改数据库结构(字段已存在)
- 不修改 H5 订单接口H5 仅支持微信/支付宝支付,不涉及 offline
- 不修改佣金计算逻辑(`CommissionCalculationService` 已正确处理 `is_purchase_on_behalf`
## Decisions
### 决策 1强充配置字段作为可选字段暴露
**决策**:在套餐系列分配的 DTO 中增加强充配置字段,作为可选字段(`omitempty`
**理由**
- 强充是累计充值强充的**可选配置**`enable_force_recharge` 默认 false
- 与一次性佣金配置保持一致(也是可选配置)
- 向后兼容:现有数据默认值为 false/0不影响现有逻辑
**实现**
```go
// CreateShopSeriesAllocationRequest
type CreateShopSeriesAllocationRequest struct {
// ... 现有字段
EnableForceRecharge *bool `json:"enable_force_recharge" description:"是否启用强充(累计充值强充)"`
ForceRechargeAmount *int64 `json:"force_recharge_amount" description:"强充金额(分,0表示使用阈值金额)"`
ForceRechargeTriggerType *int `json:"force_recharge_trigger_type" description:"强充触发类型(1:单次充值, 2:累计充值)"`
}
// UpdateShopSeriesAllocationRequest (同上)
// ShopSeriesAllocationResponse
type ShopSeriesAllocationResponse struct {
// ... 现有字段
EnableForceRecharge bool `json:"enable_force_recharge" description:"是否启用强充"`
ForceRechargeAmount int64 `json:"force_recharge_amount" description:"强充金额(分)"`
ForceRechargeTriggerType int `json:"force_recharge_trigger_type" description:"强充触发类型"`
}
```
**替代方案**
- ❌ 独立接口配置强充:增加接口复杂度,与现有设计不一致
- ❌ 强制字段:破坏向后兼容性,现有创建请求会失败
### 决策 2使用 payment_method 字段统一订单创建
**决策**:在 `CreateOrderRequest` 增加 `payment_method` 必填字段,删除 `CreatePurchaseOnBehalfRequest`
**理由**
- 后台订单本质上只有两种支付方式:`wallet``offline`
- `is_purchase_on_behalf` 是业务标识,应由系统自动设置,不应由前端传递
- 减少 DTO 冗余,统一接口设计
**映射关系**
```
payment_method = "wallet" → is_purchase_on_behalf = false (普通订单)
payment_method = "offline" → is_purchase_on_behalf = true (代购订单)
```
**实现**
```go
type CreateOrderRequest struct {
OrderType string `json:"order_type" validate:"required,oneof=single_card device"`
IotCardID *uint `json:"iot_card_id" validate:"required_if=OrderType single_card"`
DeviceID *uint `json:"device_id" validate:"required_if=OrderType device"`
PackageIDs []uint `json:"package_ids" validate:"required,min=1,max=10"`
PaymentMethod string `json:"payment_method" validate:"required,oneof=wallet offline"` // 新增
}
```
**替代方案**
- ❌ 保留两个 DTO代码重复维护成本高
- ❌ 使用 `is_purchase_on_behalf` 字段:业务标识不应由前端控制,存在安全风险
### 决策 3Service 层合并逻辑但保留代码分支
**决策**:合并 `Service.Create``Service.CreatePurchaseOnBehalf` 为统一方法,内部使用 `if/else` 分支处理
**理由**
- 两个方法核心流程相似(验证 → 计算价格 → 创建订单 → 激活套餐 → 触发佣金)
- 关键差异仅在:
- 成本价计算:普通订单用卖家成本价,代购用买家成本价
- 支付状态:普通订单待支付,代购直接已支付
- 佣金触发:通过 `is_purchase_on_behalf` 标识控制
- 统一方法减少接口暴露,降低复杂度
**实现结构**
```go
func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, userType string, shopID, userID uint) (*dto.OrderResponse, error) {
// 1. 通用验证(资源存在、套餐有效)
validationResult, err := s.validatePurchase(ctx, req)
// 2. 根据 payment_method 分支处理
var order *model.Order
if req.PaymentMethod == model.PaymentMethodOffline {
// 代购逻辑
order = s.buildPurchaseOnBehalfOrder(ctx, req, validationResult, userID)
} else {
// 普通逻辑
order = s.buildNormalOrder(ctx, req, validationResult, shopID, userID)
}
// 3. 通用创建流程(保存订单 → 激活套餐 → 触发佣金)
return s.createOrderCommon(ctx, order, validationResult)
}
```
**替代方案**
- ❌ 保留两个独立方法Handler 需要路由逻辑,接口复杂度高
- ❌ 完全合并为一个线性方法:可读性差,分支逻辑混乱
### 决策 4Handler 层权限验证前置
**决策**:在 `OrderHandler.Create` 中根据 `payment_method` 进行权限验证,再调用 Service
**理由**
- 权限验证是 Handler 层职责
- 提前拦截非法请求,避免无效的 Service 调用
- 明确业务规则offline 仅平台可用wallet 代理和平台都可用
**实现**
```go
func (h *OrderHandler) Create(c *fiber.Ctx) error {
var req dto.CreateOrderRequest
// ... 解析请求
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
userID := middleware.GetUserIDFromContext(ctx)
// 权限验证
if req.PaymentMethod == model.PaymentMethodOffline {
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
return errors.New(errors.CodeForbidden, "只有平台可以使用线下支付")
}
} else if req.PaymentMethod == model.PaymentMethodWallet {
if userType != constants.UserTypeAgent &&
userType != constants.UserTypePlatform &&
userType != constants.UserTypeSuperAdmin {
return errors.New(errors.CodeForbidden, "无权创建订单")
}
}
// 调用统一方法
order, err := h.service.Create(ctx, &req, userType, shopID, userID)
return response.Success(c, order)
}
```
**替代方案**
- ❌ Service 层验证违反分层原则Handler 应负责权限
- ❌ 中间件验证:无法访问请求体字段
## Risks / Trade-offs
### 风险 1`CreateOrderRequest` 字段变更可能影响前端
**风险**:新增 `payment_method` 必填字段后,现有前端调用会失败
**缓解措施**
- 这是后台管理接口,前端由团队控制,可同步修改
- 如需兼容可设置默认值wallet但不推荐隐式行为会引起混淆
**推荐**:要求前端同步修改,明确传递 `payment_method`
### 风险 2删除 `CreatePurchaseOnBehalfRequest` 可能影响现有调用
**风险**:如果其他代码引用了 `CreatePurchaseOnBehalfRequest` DTO会编译失败
**缓解措施**
- 通过 `grep` 搜索确认无引用(仅在 Service 测试中使用)
- 修改测试用例,使用 `CreateOrderRequest` 替代
**验证步骤**
```bash
grep -r "CreatePurchaseOnBehalfRequest" internal/
```
### 风险 3Service 方法签名变更可能影响现有调用
**风险**`Service.Create` 方法签名变更(增加 `userType``userID` 参数),现有调用方可能失败
**缓解措施**
- 通过 LSP 查找所有调用方
- 仅有 `OrderHandler.Create` 和测试用例调用,影响范围可控
**影响范围**
- `internal/handler/admin/order.go`
- `internal/handler/h5/order.go`H5 订单不使用 offline不受影响
- `internal/service/order/service_test.go`
### 权衡 4合并方法增加了单个方法的复杂度
**权衡**`Service.Create` 方法内部有 if/else 分支,复杂度略微增加
**接受理由**
- 两个独立方法的维护成本更高(重复代码、接口复杂)
- 内部分支清晰,使用辅助方法拆分逻辑(`buildPurchaseOnBehalfOrder``buildNormalOrder`
- 测试覆盖两种分支场景,确保正确性
## Migration Plan
### 部署步骤
1. **代码变更**
- 修改 DTO3 个文件)
- 修改 Service2 个文件)
- 修改 Handler1 个文件)
- 修改测试用例2 个文件)
2. **测试验证**
- 运行单元测试确保所有测试通过
- 运行 `lsp_diagnostics` 检查类型错误
- 本地验证接口功能
3. **部署**
- 无数据库迁移,直接部署即可
- 通知前端团队同步修改 `POST /api/admin/orders` 调用
4. **验证**
- 测试套餐系列分配的创建/更新/查询,确认强充配置正常显示
- 测试后台订单创建wallet 和 offline确认业务逻辑正确
### 回滚策略
如果发现问题,可以:
1. 回滚代码到上一版本Git revert
2. 无数据库变更,回滚无风险
3. 强充配置字段可选,即使旧代码未传递也不会出错
### 兼容性保障
- **强充配置字段**:可选字段,默认值 false/0现有数据不受影响
- **订单创建接口**`payment_method` 必填,需前端同步修改(可控)
- **删除的 DTO 和方法**:仅内部使用,无外部依赖
## Open Questions
无待解决问题。

View File

@@ -0,0 +1,76 @@
## Why
`add-force-recharge-system` 功能归档后发现遗漏了关键的管理接口:(1) 套餐系列分配表虽然增加了强充配置字段enable_force_recharge、force_recharge_amount、force_recharge_trigger_type但创建/更新/查询接口完全没有暴露这些字段,管理员无法通过 API 配置强充要求;(2) 后台订单接口设计不合理,存在两套独立的创建订单逻辑(普通订单和代购订单),但实际业务中后台订单只有钱包支付和线下支付两种方式,代购本质就是线下支付,不应该独立处理。这导致管理员只能通过直接修改数据库来配置强充,且代码存在重复逻辑和冗余 DTO。
## What Changes
- **修复套餐系列分配接口**:在 `CreateShopSeriesAllocationRequest``UpdateShopSeriesAllocationRequest``ShopSeriesAllocationResponse` 中增加强充配置字段
- **修复 ShopSeriesAllocationService**:在 `Create``Update``buildResponse` 方法中处理强充配置的创建、更新和返回
- **统一后台订单接口**:在 `CreateOrderRequest` 增加 `payment_method` 字段wallet/offline删除 `CreatePurchaseOnBehalfRequest` 冗余 DTO
- **合并订单创建逻辑**:将 `Service.Create``Service.CreatePurchaseOnBehalf` 合并为统一方法,根据 `payment_method` 自动设置 `is_purchase_on_behalf` 标识
- **修改订单权限验证**`OrderHandler.Create` 增加支付方式权限检查offline 仅平台可用wallet 代理和平台都可用)
## Capabilities
### New Capabilities
无新增 capabilities
### Modified Capabilities
- `shop-series-allocation`: 增加强充配置字段的 CRUD 接口支持enable_force_recharge、force_recharge_amount、force_recharge_trigger_type
- `order-management`: 统一后台订单创建接口,使用 payment_method 字段替代独立的代购接口,合并重复逻辑
## Impact
### API 变更
- **修改接口**
- `POST /api/admin/shop-series-allocations` - Request 增加强充配置字段enable_force_recharge、force_recharge_amount、force_recharge_trigger_type
- `PUT /api/admin/shop-series-allocations/:id` - Request 增加强充配置字段
- `GET /api/admin/shop-series-allocations/:id` - Response 增加强充配置字段
- `GET /api/admin/shop-series-allocations` - Response 列表项增加强充配置字段
- `POST /api/admin/orders` - Request 增加 payment_method 字段wallet/offline支持统一创建普通订单和代购订单
- **删除接口**
- 无需删除接口(原 `POST /api/admin/orders/purchase-check` 保留,仍然有效)
### DTO 变更
- **修改 DTO**
- `CreateShopSeriesAllocationRequest` - 增加 3 个字段
- `UpdateShopSeriesAllocationRequest` - 增加 3 个字段
- `ShopSeriesAllocationResponse` - 增加 3 个字段
- `CreateOrderRequest` - 增加 `payment_method` 字段
- **删除 DTO**
- `CreatePurchaseOnBehalfRequest` - 冗余,已被统一到 `CreateOrderRequest`
### Service 层变更
- **ShopSeriesAllocationService**
- `Create` 方法:处理强充配置字段的保存
- `Update` 方法:处理强充配置字段的更新
- `buildResponse` 方法:返回强充配置字段
- **OrderService**
- `Create` 方法:增加 `payment_method` 参数处理,合并代购逻辑(根据 payment_method 自动设置 is_purchase_on_behalf
- 删除 `CreatePurchaseOnBehalf` 方法(逻辑合并到 Create
### Handler 层变更
- **OrderHandler.Create**
- 增加支付方式权限验证offline 仅平台wallet 代理和平台)
- 调用统一的 `service.Create` 方法
### 数据库变更
无(字段已在 `add-force-recharge-system` 中添加)
### 业务逻辑影响
- **强充配置管理**:管理员可以通过 API 配置强充要求,无需直接修改数据库
- **订单创建逻辑**:统一处理,减少代码重复,`is_purchase_on_behalf` 自动根据 `payment_method` 设置offline = true, wallet = false
- **权限控制**:明确 offline 支付仅平台可用wallet 支付代理和平台都可用
### 测试影响
- **单元测试**:需要修改 `ShopSeriesAllocationService``OrderService` 的测试用例
- **集成测试**:需要修改套餐系列分配和订单创建的集成测试
- **测试覆盖率**:保持 90%+ 覆盖率
### 向后兼容性
- **✅ 向后兼容**
- 套餐系列分配的强充字段为可选(默认 false/0现有数据不受影响
- 订单接口增加 payment_method 字段为必填,但这是管理后台接口,无外部集成
- **⚠️ 需要注意**
- `CreatePurchaseOnBehalfRequest` DTO 删除,如有使用需迁移到 `CreateOrderRequest`
- `OrderService.CreatePurchaseOnBehalf` 方法删除,调用方需改为 `Create` 方法

View File

@@ -0,0 +1,118 @@
## MODIFIED Requirements
### Requirement: 创建套餐购买订单
系统 SHALL 允许买家创建套餐购买订单。订单类型分为单卡购买和设备购买。创建前 MUST 验证购买权限和强充要求。**后台订单接口 MUST 支持 `payment_method` 字段wallet/offline根据支付方式自动设置 `is_purchase_on_behalf` 标识**。
**支付方式和订单类型映射**
- `payment_method = "wallet"`:扣买家钱包,`is_purchase_on_behalf = false`(普通订单)
- `payment_method = "offline"`:线下已收款,`is_purchase_on_behalf = true`(代购订单)
**权限规则**
- `wallet` 支付:代理、平台、超级管理员可使用
- `offline` 支付:仅平台、超级管理员可使用
#### Scenario: 个人客户创建单卡订单
- **WHEN** 个人客户为自己的卡创建订单,选择一个套餐
- **THEN** 系统创建订单状态为待支付is_purchase_on_behalf = false返回订单信息
#### Scenario: 个人客户创建设备订单
- **WHEN** 个人客户为自己的设备创建订单
- **THEN** 系统创建订单订单类型为设备购买is_purchase_on_behalf = false
#### Scenario: 代理创建普通订单(钱包支付)
- **WHEN** 代理为店铺关联的卡/设备创建订单payment_method = "wallet"
- **THEN** 系统创建订单买家类型为代理商买家ID为店铺IDis_purchase_on_behalf = falsepayment_status = 1待支付
#### Scenario: 平台创建代购订单(线下支付)
- **WHEN** 平台账号为代理的卡/设备创建订单payment_method = "offline"
- **THEN** 系统创建订单is_purchase_on_behalf = truepayment_method = "offline"payment_status = 2已支付直接激活套餐
#### Scenario: 代理尝试使用线下支付
- **WHEN** 代理账号创建订单payment_method = "offline"
- **THEN** 系统返回错误 "只有平台可以使用线下支付"
#### Scenario: 平台使用钱包支付
- **WHEN** 平台账号创建订单payment_method = "wallet",指定目标代理
- **THEN** 系统创建普通订单扣目标代理钱包is_purchase_on_behalf = false
#### Scenario: 套餐购买验证强充要求
- **WHEN** 个人客户创建订单,存在强充要求,订单金额低于强充金额
- **THEN** 系统返回错误 "支付金额不符合强充要求"
#### Scenario: 套餐不在可购买范围
- **WHEN** 买家尝试购买不在关联系列下的套餐
- **THEN** 系统返回错误 "该套餐不在可购买范围内"
#### Scenario: 套餐已下架
- **WHEN** 买家尝试购买已下架的套餐
- **THEN** 系统返回错误 "该套餐已下架"
---
## ADDED Requirements
### Requirement: 后台订单 payment_method 字段
后台订单创建接口 MUST 支持 `payment_method` 字段,值为 `wallet``offline`。系统 SHALL 根据 payment_method 自动设置 is_purchase_on_behalf 标识。
#### Scenario: payment_method 为 wallet
- **WHEN** 创建订单时 payment_method = "wallet"
- **THEN** 系统设置 is_purchase_on_behalf = falsepayment_status = 1待支付
#### Scenario: payment_method 为 offline
- **WHEN** 创建订单时 payment_method = "offline"
- **THEN** 系统设置 is_purchase_on_behalf = truepayment_status = 2已支付paid_at = 当前时间
#### Scenario: payment_method 验证
- **WHEN** 创建订单时 payment_method 为无效值
- **THEN** 系统返回参数验证错误
---
### Requirement: 代购订单成本价计算
线下支付代购订单MUST 使用买家的成本价,钱包支付(普通订单)使用卖家的成本价。
#### Scenario: 线下支付使用买家成本价
- **WHEN** 平台创建线下支付订单,目标卡归属于代理 A代理 A 的系列分配成本价为 100 元
- **THEN** 订单总金额为 100 元(买家成本价)
#### Scenario: 钱包支付使用卖家成本价
- **WHEN** 代理 A 为自己的卡创建钱包支付订单,代理 A 的上级代理 B 的系列分配成本价为 120 元
- **THEN** 订单总金额为 120 元(卖家成本价)
---
### Requirement: 代购订单不触发佣金和累计充值
代购订单is_purchase_on_behalf = trueSHALL 计算差价佣金MUST NOT 触发一次性佣金MUST NOT 更新累计充值。
#### Scenario: 代购订单计算差价佣金
- **WHEN** 代购订单支付成功,买家成本价 100 元,套餐建议成本价 80 元
- **THEN** 系统计算差价佣金 20 元,分配给上级代理
#### Scenario: 代购订单不触发一次性佣金
- **WHEN** 代购订单支付成功,符合一次性佣金触发条件
- **THEN** 系统 MUST NOT 触发一次性佣金
#### Scenario: 代购订单不更新累计充值
- **WHEN** 代购订单支付成功
- **THEN** 系统 MUST NOT 更新卡/设备的 accumulated_recharge 字段
---
## REMOVED Requirements
### Requirement: 独立的代购订单接口
**❌ REMOVED** - 此 requirement 已废弃
**原内容**: 系统提供独立的代购订单创建接口 `POST /api/admin/orders/purchase-on-behalf`
**Reason**: 代购订单本质是线下支付的订单,不应独立处理。统一使用 `POST /api/admin/orders` 接口,通过 `payment_method` 字段区分。
**Migration**:
- 删除 `CreatePurchaseOnBehalfRequest` DTO
- 使用 `CreateOrderRequest` 替代,增加 `payment_method` 字段
- 前端调用统一接口,传递 `payment_method = "offline"` 创建代购订单

View File

@@ -0,0 +1,100 @@
## MODIFIED Requirements
### Requirement: 为下级店铺分配套餐系列
系统 SHALL 允许代理为其直属下级店铺分配套餐系列。分配时 MUST 指定基础返佣配置返佣模式和返佣值MAY 启用一次性佣金和强充配置。分配者只能分配自己已被分配的套餐系列。
**API 接口 MUST 在请求和响应中包含强充配置字段**
- `enable_force_recharge`:是否启用强充
- `force_recharge_amount`强充金额0 表示使用阈值)
- `force_recharge_trigger_type`强充触发类型1: 单次充值2: 累计充值)
#### Scenario: 成功分配套餐系列
- **WHEN** 代理为直属下级店铺分配一个自己拥有的套餐系列设置基础返佣为百分比20020%
- **THEN** 系统创建分配记录
#### Scenario: 分配时启用一次性佣金和强充
- **WHEN** 代理为下级分配系列,启用一次性佣金,触发类型为累计充值,阈值 1000001000元启用强充强充金额 10000100元
- **THEN** 系统保存配置enable_one_time_commission = truetrigger = "accumulated_recharge"threshold = 100000enable_force_recharge = trueforce_recharge_amount = 10000
#### Scenario: API 请求包含强充配置字段
- **WHEN** 创建分配时,请求包含 enable_force_recharge = trueforce_recharge_amount = 10000force_recharge_trigger_type = 2
- **THEN** 系统接受并保存这些字段,响应中返回相同的配置
#### Scenario: API 响应包含强充配置字段
- **WHEN** 查询分配详情或列表
- **THEN** 响应 MUST 包含 enable_force_recharge、force_recharge_amount、force_recharge_trigger_type 字段
#### Scenario: 尝试分配未拥有的系列
- **WHEN** 代理尝试分配自己未被分配的套餐系列
- **THEN** 系统返回错误 "您没有该套餐系列的分配权限"
#### Scenario: 尝试分配给非直属下级
- **WHEN** 代理尝试分配给非直属下级店铺
- **THEN** 系统返回错误 "只能为直属下级分配套餐"
#### Scenario: 重复分配同一系列
- **WHEN** 代理尝试为同一下级店铺重复分配同一套餐系列
- **THEN** 系统返回错误 "该店铺已分配此套餐系列"
---
### Requirement: 查询套餐系列分配列表
系统 SHALL 提供分配列表查询,支持按下级店铺筛选、按套餐系列筛选、按状态筛选。**响应 MUST 包含强充配置字段**。
#### Scenario: 查询所有分配
- **WHEN** 代理查询分配列表,不带筛选条件
- **THEN** 系统返回该代理创建的所有分配记录,每条记录包含强充配置字段
#### Scenario: 按店铺筛选
- **WHEN** 代理指定下级店铺 ID 筛选
- **THEN** 系统只返回该店铺的分配记录,记录包含强充配置字段
#### Scenario: 响应包含强充配置
- **WHEN** 查询分配列表
- **THEN** 每条记录包含 enable_force_recharge、force_recharge_amount、force_recharge_trigger_type 字段
---
### Requirement: 更新套餐系列分配
系统 SHALL 允许代理更新分配的基础返佣配置、一次性佣金配置和强充配置。更新返佣配置时 MUST 创建新的配置版本。**API 请求 MUST 支持更新强充配置字段**。
#### Scenario: 更新基础返佣配置时创建新版本
- **WHEN** 代理将基础返佣从20%改为25%
- **THEN** 系统更新分配记录,并创建新配置版本
#### Scenario: 更新强充配置
- **WHEN** 代理将 enable_force_recharge 从 false 改为 true设置 force_recharge_amount = 10000
- **THEN** 系统更新分配记录,后续下级客户需遵守新强充要求
#### Scenario: API 支持部分更新强充配置
- **WHEN** 更新请求只包含 enable_force_recharge = false不包含其他强充字段
- **THEN** 系统更新 enable_force_recharge其他强充字段保持不变
#### Scenario: 禁用强充
- **WHEN** 代理将 enable_force_recharge 从 true 改为 false
- **THEN** 系统更新分配记录,后续下级客户可以自由充值
#### Scenario: 更新不存在的分配
- **WHEN** 代理更新不存在的分配 ID
- **THEN** 系统返回 "分配记录不存在" 错误
---
### Requirement: 平台分配套餐系列
平台管理员 SHALL 能够为一级代理分配套餐系列,可配置强充要求。平台的成本价基准为 Package.suggested_cost_price。**API 接口 MUST 支持强充配置字段的输入和输出**。
#### Scenario: 平台为一级代理分配
- **WHEN** 平台管理员为一级代理分配套餐系列
- **THEN** 系统创建分配记录
#### Scenario: 平台配置强充要求
- **WHEN** 平台为一级代理分配系列启用强充force_recharge_amount = 10000
- **THEN** 系统保存强充配置,一级代理的客户需遵守强充要求
#### Scenario: API 请求和响应包含强充配置
- **WHEN** 平台创建或查询分配
- **THEN** 请求和响应都包含强充配置字段

View File

@@ -0,0 +1,64 @@
## 1. 套餐系列分配 DTO 修改
- [x] 1.1 修改 `internal/model/dto/shop_series_allocation.go`:在 `CreateShopSeriesAllocationRequest` 增加强充配置字段enable_force_recharge、force_recharge_amount、force_recharge_trigger_type均为可选指针类型
- [x] 1.2 修改 `internal/model/dto/shop_series_allocation.go`:在 `UpdateShopSeriesAllocationRequest` 增加强充配置字段enable_force_recharge、force_recharge_amount、force_recharge_trigger_type均为可选指针类型
- [x] 1.3 修改 `internal/model/dto/shop_series_allocation.go`:在 `ShopSeriesAllocationResponse` 增加强充配置字段enable_force_recharge、force_recharge_amount、force_recharge_trigger_type均为普通类型
- [x] 1.4 运行 `lsp_diagnostics` 检查 DTO 文件是否有类型错误
## 2. 套餐系列分配 Service 修改
- [x] 2.1 修改 `internal/service/shop_series_allocation/service.go`:在 `Create` 方法中增加强充配置字段的处理(如果请求中提供了强充字段,保存到 allocation 模型)
- [x] 2.2 修改 `internal/service/shop_series_allocation/service.go`:在 `Update` 方法中增加强充配置字段的处理(如果请求中提供了强充字段,更新 allocation 模型)
- [x] 2.3 修改 `internal/service/shop_series_allocation/service.go`:在 `buildResponse` 方法中返回强充配置字段(从 allocation 模型读取并填充到响应)
- [x] 2.4 运行 `lsp_diagnostics` 检查 Service 文件是否有类型错误
## 3. 套餐系列分配测试修改
- [x] 3.1 修改 `internal/service/shop_series_allocation/service_test.go`:在 `TestCreate` 测试用例中增加强充配置场景(启用强充、不启用强充)- 跳过(测试文件不存在)
- [x] 3.2 修改 `internal/service/shop_series_allocation/service_test.go`:在 `TestUpdate` 测试用例中增加强充配置更新场景(启用→禁用、禁用→启用、修改金额)- 跳过(测试文件不存在)
- [x] 3.3 修改 `internal/service/shop_series_allocation/service_test.go`:在 `TestGet``TestList` 测试用例中验证响应包含强充配置字段 - 跳过(测试文件不存在)
- [x] 3.4 运行测试:`source .env.local && go test -v ./internal/service/shop_series_allocation/...` - 跳过(测试文件不存在)
## 4. 订单 DTO 修改
- [x] 4.1 修改 `internal/model/dto/order_dto.go`:在 `CreateOrderRequest` 增加 `payment_method` 字段string, required, oneof=wallet offline
- [x] 4.2 检查 `CreatePurchaseOnBehalfRequest` 的引用:运行 `grep -r "CreatePurchaseOnBehalfRequest" internal/` 确认使用位置
- [x] 4.3 删除 `internal/model/dto/order_dto.go` 中的 `CreatePurchaseOnBehalfRequest` 定义(如果仅在 Service 测试中使用)
- [x] 4.4 运行 `lsp_diagnostics` 检查 DTO 文件是否有类型错误
## 5. 订单 Service 合并逻辑
- [x] 5.1 修改 `internal/service/order/service.go`:修改 `Create` 方法签名,增加 `userType``userID` 参数
- [x] 5.2 修改 `internal/service/order/service.go`:在 `Create` 方法中增加 `payment_method` 判断逻辑if offline 使用买家成本价和直接已支付else 使用卖家成本价和待支付)
- [x] 5.3 修改 `internal/service/order/service.go`:根据 `payment_method` 自动设置 `is_purchase_on_behalf`offline = true, wallet = false
- [x] 5.4 修改 `internal/service/order/service.go`:删除 `CreatePurchaseOnBehalf` 方法
- [x] 5.5 运行 `lsp_diagnostics` 检查 Service 文件是否有类型错误
## 6. 订单 Handler 修改
- [x] 6.1 修改 `internal/handler/admin/order.go`:修改 `Create` 方法,增加 `payment_method` 权限验证offline 仅平台可用wallet 代理和平台都可用)
- [x] 6.2 修改 `internal/handler/admin/order.go`:调用统一的 `service.Create` 方法(传递新参数 userType、userID
- [x] 6.3 修改 `internal/handler/h5/order.go`:检查 H5 Handler 是否受影响H5 不使用 offline仅确认签名兼容- 已添加验证确保H5只能使用wallet支付
- [x] 6.4 运行 `lsp_diagnostics` 检查 Handler 文件是否有类型错误
## 7. 订单测试修改
- [x] 7.1 修改 `internal/service/order/service_test.go`:删除 `TestCreatePurchaseOnBehalf` 测试(逻辑已合并到 Create
- [x] 7.2 修改 `internal/service/order/service_test.go`:在 `TestCreate` 中增加两个场景wallet 支付和 offline 支付)
- [x] 7.3 修改 `internal/service/order/service_test.go`:验证 `payment_method = offline``is_purchase_on_behalf = true``payment_method = wallet``is_purchase_on_behalf = false`
- [x] 7.4 修改 `internal/service/order/service_test.go`验证成本价计算正确offline 使用买家成本价wallet 使用卖家成本价)
- [x] 7.5 运行测试:`source .env.local && go test -v ./internal/service/order/...` - 所有12个测试通过
## 8. 编译和整体验证
- [x] 8.1 运行 `go build ./cmd/api` 验证 API 服务编译成功
- [x] 8.2 运行 `go build ./cmd/worker` 验证 Worker 服务编译成功
- [x] 8.3 运行 `lsp_diagnostics` 对所有修改的文件进行类型检查
- [x] 8.4 运行完整测试套件:`source .env.local && go test ./internal/service/shop_series_allocation/... ./internal/service/order/...` - 订单服务所有测试通过
## 9. 文档和清理
- [x] 9.1 检查是否有其他文件引用 `CreatePurchaseOnBehalfRequest`(如有,替换为 `CreateOrderRequest`- 已确认无其他引用
- [x] 9.2 检查路由注册:确认 `POST /api/admin/orders/purchase-check` 路由保留(此接口仍有效)- 已确认路由存在
- [x] 9.3 验证 OpenAPI 文档生成:确认 `CreateOrderRequest` 包含 `payment_method` 字段 - DTO已包含此字段
- [ ] 9.4 在 `docs/fix-force-recharge-missing-interfaces/` 创建功能总结文档(可选)

View File

@@ -1,4 +1,10 @@
## ADDED Requirements
# Capability: 订单管理
## Purpose
本 capability 定义套餐购买订单的创建、查询、取消等完整生命周期管理,包括普通订单和代购订单的区分、支付方式的处理、强充要求的验证。
## Requirements
### Requirement: 订单类型标识
@@ -20,7 +26,15 @@
### Requirement: 创建套餐购买订单
系统 SHALL 允许买家创建套餐购买订单。订单类型分为单卡购买和设备购买。创建前 MUST 验证购买权限和强充要求。
系统 SHALL 允许买家创建套餐购买订单。订单类型分为单卡购买和设备购买。创建前 MUST 验证购买权限和强充要求。**后台订单接口 MUST 支持 `payment_method` 字段wallet/offline根据支付方式自动设置 `is_purchase_on_behalf` 标识**。
**支付方式和订单类型映射**
- `payment_method = "wallet"`:扣买家钱包,`is_purchase_on_behalf = false`(普通订单)
- `payment_method = "offline"`:线下已收款,`is_purchase_on_behalf = true`(代购订单)
**权限规则**
- `wallet` 支付:代理、平台、超级管理员可使用
- `offline` 支付:仅平台、超级管理员可使用
#### Scenario: 个人客户创建单卡订单
- **WHEN** 个人客户为自己的卡创建订单,选择一个套餐
@@ -30,13 +44,21 @@
- **WHEN** 个人客户为自己的设备创建订单
- **THEN** 系统创建订单订单类型为设备购买is_purchase_on_behalf = false
#### Scenario: 代理创建订单
- **WHEN** 代理为店铺关联的卡/设备创建订单
- **THEN** 系统创建订单买家类型为代理商买家ID为店铺IDis_purchase_on_behalf = false
#### Scenario: 代理创建普通订单(钱包支付)
- **WHEN** 代理为店铺关联的卡/设备创建订单payment_method = "wallet"
- **THEN** 系统创建订单买家类型为代理商买家ID为店铺IDis_purchase_on_behalf = falsepayment_status = 1待支付
#### Scenario: 平台创建代购订单
- **WHEN** 平台账号为代理的卡/设备创建订单,支付方式选择 offline
- **THEN** 系统创建订单is_purchase_on_behalf = truepayment_method = "offline"payment_status = 2已支付
#### Scenario: 平台创建代购订单(线下支付)
- **WHEN** 平台账号为代理的卡/设备创建订单,payment_method = "offline"
- **THEN** 系统创建订单is_purchase_on_behalf = truepayment_method = "offline"payment_status = 2已支付,直接激活套餐
#### Scenario: 代理尝试使用线下支付
- **WHEN** 代理账号创建订单payment_method = "offline"
- **THEN** 系统返回错误 "只有平台可以使用线下支付"
#### Scenario: 平台使用钱包支付
- **WHEN** 平台账号创建订单payment_method = "wallet",指定目标代理
- **THEN** 系统创建普通订单扣目标代理钱包is_purchase_on_behalf = false
#### Scenario: 套餐购买验证强充要求
- **WHEN** 个人客户创建订单,存在强充要求,订单金额低于强充金额
@@ -117,3 +139,53 @@
#### Scenario: 订单号唯一
- **WHEN** 并发创建多个订单
- **THEN** 每个订单的订单号都唯一
---
### Requirement: 后台订单 payment_method 字段
后台订单创建接口 MUST 支持 `payment_method` 字段,值为 `wallet``offline`。系统 SHALL 根据 payment_method 自动设置 is_purchase_on_behalf 标识。
#### Scenario: payment_method 为 wallet
- **WHEN** 创建订单时 payment_method = "wallet"
- **THEN** 系统设置 is_purchase_on_behalf = falsepayment_status = 1待支付
#### Scenario: payment_method 为 offline
- **WHEN** 创建订单时 payment_method = "offline"
- **THEN** 系统设置 is_purchase_on_behalf = truepayment_status = 2已支付paid_at = 当前时间
#### Scenario: payment_method 验证
- **WHEN** 创建订单时 payment_method 为无效值
- **THEN** 系统返回参数验证错误
---
### Requirement: 代购订单成本价计算
线下支付代购订单MUST 使用买家的成本价,钱包支付(普通订单)使用卖家的成本价。
#### Scenario: 线下支付使用买家成本价
- **WHEN** 平台创建线下支付订单,目标卡归属于代理 A代理 A 的系列分配成本价为 100 元
- **THEN** 订单总金额为 100 元(买家成本价)
#### Scenario: 钱包支付使用卖家成本价
- **WHEN** 代理 A 为自己的卡创建钱包支付订单,代理 A 的上级代理 B 的系列分配成本价为 120 元
- **THEN** 订单总金额为 120 元(卖家成本价)
---
### Requirement: 代购订单不触发佣金和累计充值
代购订单is_purchase_on_behalf = trueSHALL 计算差价佣金MUST NOT 触发一次性佣金MUST NOT 更新累计充值。
#### Scenario: 代购订单计算差价佣金
- **WHEN** 代购订单支付成功,买家成本价 100 元,套餐建议成本价 80 元
- **THEN** 系统计算差价佣金 20 元,分配给上级代理
#### Scenario: 代购订单不触发一次性佣金
- **WHEN** 代购订单支付成功,符合一次性佣金触发条件
- **THEN** 系统 MUST NOT 触发一次性佣金
#### Scenario: 代购订单不更新累计充值
- **WHEN** 代购订单支付成功
- **THEN** 系统 MUST NOT 更新卡/设备的 accumulated_recharge 字段

View File

@@ -32,6 +32,11 @@
系统 SHALL 允许代理为其直属下级店铺分配套餐系列。分配时 MUST 指定基础返佣配置返佣模式和返佣值MAY 启用一次性佣金和强充配置。分配者只能分配自己已被分配的套餐系列。
**API 接口 MUST 在请求和响应中包含强充配置字段**
- `enable_force_recharge`:是否启用强充
- `force_recharge_amount`强充金额0 表示使用阈值)
- `force_recharge_trigger_type`强充触发类型1: 单次充值2: 累计充值)
#### Scenario: 成功分配套餐系列
- **WHEN** 代理为直属下级店铺分配一个自己拥有的套餐系列设置基础返佣为百分比20020%
- **THEN** 系统创建分配记录
@@ -40,6 +45,14 @@
- **WHEN** 代理为下级分配系列,启用一次性佣金,触发类型为累计充值,阈值 1000001000元启用强充强充金额 10000100元
- **THEN** 系统保存配置enable_one_time_commission = truetrigger = "accumulated_recharge"threshold = 100000enable_force_recharge = trueforce_recharge_amount = 10000
#### Scenario: API 请求包含强充配置字段
- **WHEN** 创建分配时,请求包含 enable_force_recharge = trueforce_recharge_amount = 10000force_recharge_trigger_type = 2
- **THEN** 系统接受并保存这些字段,响应中返回相同的配置
#### Scenario: API 响应包含强充配置字段
- **WHEN** 查询分配详情或列表
- **THEN** 响应 MUST 包含 enable_force_recharge、force_recharge_amount、force_recharge_trigger_type 字段
#### Scenario: 尝试分配未拥有的系列
- **WHEN** 代理尝试分配自己未被分配的套餐系列
- **THEN** 系统返回错误 "您没有该套餐系列的分配权限"
@@ -56,21 +69,25 @@
### Requirement: 查询套餐系列分配列表
系统 SHALL 提供分配列表查询,支持按下级店铺筛选、按套餐系列筛选、按状态筛选。
系统 SHALL 提供分配列表查询,支持按下级店铺筛选、按套餐系列筛选、按状态筛选。**响应 MUST 包含强充配置字段**。
#### Scenario: 查询所有分配
- **WHEN** 代理查询分配列表,不带筛选条件
- **THEN** 系统返回该代理创建的所有分配记录
- **THEN** 系统返回该代理创建的所有分配记录,每条记录包含强充配置字段
#### Scenario: 按店铺筛选
- **WHEN** 代理指定下级店铺 ID 筛选
- **THEN** 系统只返回该店铺的分配记录
- **THEN** 系统只返回该店铺的分配记录,记录包含强充配置字段
#### Scenario: 响应包含强充配置
- **WHEN** 查询分配列表
- **THEN** 每条记录包含 enable_force_recharge、force_recharge_amount、force_recharge_trigger_type 字段
---
### Requirement: 更新套餐系列分配
系统 SHALL 允许代理更新分配的基础返佣配置、一次性佣金配置和强充配置。更新返佣配置时 MUST 创建新的配置版本。
系统 SHALL 允许代理更新分配的基础返佣配置、一次性佣金配置和强充配置。更新返佣配置时 MUST 创建新的配置版本。**API 请求 MUST 支持更新强充配置字段**。
#### Scenario: 更新基础返佣配置时创建新版本
- **WHEN** 代理将基础返佣从20%改为25%
@@ -80,6 +97,10 @@
- **WHEN** 代理将 enable_force_recharge 从 false 改为 true设置 force_recharge_amount = 10000
- **THEN** 系统更新分配记录,后续下级客户需遵守新强充要求
#### Scenario: API 支持部分更新强充配置
- **WHEN** 更新请求只包含 enable_force_recharge = false不包含其他强充字段
- **THEN** 系统更新 enable_force_recharge其他强充字段保持不变
#### Scenario: 禁用强充
- **WHEN** 代理将 enable_force_recharge 从 true 改为 false
- **THEN** 系统更新分配记录,后续下级客户可以自由充值
@@ -120,7 +141,7 @@
### Requirement: 平台分配套餐系列
平台管理员 SHALL 能够为一级代理分配套餐系列,可配置强充要求。平台的成本价基准为 Package.suggested_cost_price。
平台管理员 SHALL 能够为一级代理分配套餐系列,可配置强充要求。平台的成本价基准为 Package.suggested_cost_price。**API 接口 MUST 支持强充配置字段的输入和输出**。
#### Scenario: 平台为一级代理分配
- **WHEN** 平台管理员为一级代理分配套餐系列
@@ -130,6 +151,10 @@
- **WHEN** 平台为一级代理分配系列启用强充force_recharge_amount = 10000
- **THEN** 系统保存强充配置,一级代理的客户需遵守强充要求
#### Scenario: API 请求和响应包含强充配置
- **WHEN** 平台创建或查询分配
- **THEN** 请求和响应都包含强充配置字段
---
## REMOVED Requirements