Files
junhong_cmp_fiber/openspec/changes/archive/2026-03-02-agent-allocation-shelf-status/design.md
huang 61155952a7
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m56s
feat: 新增代理分配套餐上架状态(shelf_status)功能
- 新增数据库迁移:为 shop_package_allocation 表添加 shelf_status 字段
- 更新模型/DTO:ShopPackageAllocation 增加 ShelfStatus 字段及相关枚举
- 更新套餐分配 Service:支持上架/下架状态管理逻辑
- 更新套餐 Store/Service:根据 shelf_status 过滤可售套餐
- 更新购买验证 Service:引入上架状态校验逻辑
- 归档 OpenSpec 变更:2026-03-02-agent-allocation-shelf-status
- 同步更新主规范文档:allocation-shelf-status、package-management、purchase-validation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 15:38:54 +08:00

86 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
### 当前状态
`tb_package``shelf_status` 字段1-上架, 2-下架),由平台控制套餐的全局可见性。`tb_shop_package_allocation` 只有 `status` 字段1-启用, 2-禁用),没有代理独立的上下架字段。
当代理调用 `PATCH /api/admin/packages/:id/shelf`service 层直接修改 `tb_package.shelf_status`,导致该操作影响全平台——所有代理和平台的该套餐都被下架。
### 核心矛盾
"上下架"在业务上有两个层次:
- **平台层**:控制平台自营店面的客户侧可见性,与代理无关
- **代理层**:每个代理独立控制自己客户侧的可见性,互不影响
目前两个层次合并成一个字段,是 Bug 的根源。
### 约束
- `tb_shop_package_allocation.status`(启用/禁用)语义为"分配者临时暂停该分配",不等同于上下架
- 平台若要停止某套餐全网销售,应回收分配而非修改 shelf_status
- `Package.status`(全局禁用)是唯一影响全平台购买的开关
## Goals / Non-Goals
**Goals:**
-`tb_shop_package_allocation` 新增 `shelf_status` 字段,默认上架
- `PATCH /packages/:id/shelf` 接口按调用者角色路由到不同数据层平台→package代理→allocation
- 代理查看套餐列表/详情时,`shelf_status` 返回自己分配记录的值
- 购买校验按购买场景分流:代理场景检查 `allocation.shelf_status`,平台场景检查 `package.shelf_status`
- 修复 `UpdateAllocationStatus` 接口缺少所有者校验的安全 Bug
**Non-Goals:**
- 不引入级联上下架代理A下架不影响代理B
- 不修改 `Package.status`(全局禁用)的语义和操作权限
- 不在代理层增加"启用/禁用套餐"能力status 仍只有分配者可改)
- 不变更 URL 路径(同一接口服务不同角色)
## Decisions
### 决策 1角色上下文路由在 Service 层实现
**选择**:在 `PackageService.UpdateShelfStatus()` 中通过 `middleware.GetUserTypeFromContext(ctx)` 判断角色,路由到不同的 store 操作。
**理由**Handler 层保持薄,不含业务逻辑。角色判断属于业务规则,放 Service 层符合分层原则。URL 不变对前端友好,无需区分调用地址。
**备选方案**:为代理新增单独的 API 路由(如 `PATCH /shop-package-allocations/:id/shelf`)。问题是代理需要知道 allocation ID 而非 package ID增加前端复杂度且未来其他类似接口都要新增一套路由。
### 决策 2默认上架
**选择**:分配给代理时,`allocation.shelf_status` 默认为 1上架
**理由**:分配行为本身代表分配者希望下级销售此套餐,默认上架减少操作步骤。代理可主动下架。
### 决策 3购买校验按场景分流
**选择**
- 客户直接从平台购买 → 检查 `Package.status == enabled AND Package.shelf_status == on`
- 客户通过代理购买 → 检查 `Package.status == enabled AND allocation(seller).shelf_status == on`
**理由**:平台 shelf_status 仅代表平台自营店面状态,与代理销售解耦。代理链路只检查最终销售代理的 allocation不向上追溯上级若不想让下级卖应回收分配
### 决策 4分配 status 修改加所有者校验
**选择**`UpdateAllocationStatus` 检查调用者是否为该 allocation 的 `allocator_shop_id`(平台用户则通过,代理用户需 allocator_shop_id == 调用者 shop_id
**理由**:当前无校验,任意代理可修改任意分配记录的 status是安全漏洞。
## Risks / Trade-offs
**[风险] 存量数据的 shelf_status 默认值** → 迁移时 `ALTER TABLE``DEFAULT 1 NOT NULL`,存量记录全部视为上架,符合业务现状(原来没有这个字段时代理就是在卖)。
**[风险] 购买校验需要额外查询 allocation** → 校验路径需增加一次 `GetByShopAndPackage` 查询。订单创建场景已有多个查询,增加一次影响可接受;可加索引 `(shop_id, package_id)` 优化(已存在)。
**[风险] 前端展示的 shelf_status 语义变化** → 代理端看到的 shelf_status 从 package 级变为 allocation 级,前端无需改动(字段名相同,值含义不变),但平台管理员在查看"代理管理的套餐"时无法直接看到各代理的 shelf_status这属于 Non-Goal。
## Migration Plan
1. 生成数据库迁移文件:`tb_shop_package_allocation` 增加 `shelf_status INT NOT NULL DEFAULT 1`
2. 执行迁移(无停机,仅 ADD COLUMN
3. 部署新代码(接口行为自动分流)
4. 无回滚风险:新增字段,原有字段语义不变
## Open Questions
无。所有设计决策已在探索阶段与业务确认。