Files
junhong_cmp_fiber/docs/ur43-series-package-bulk-authorization/功能总结.md
break 5c4d17e9fc
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
收口七月卡状态回调与系列授权兼容契约
完成运营商实名回调、业务事件观测序列与受控配置装配,同时恢复 UR43 已交付的 packages[].remove 字段及旧响应兼容,统一更新 OpenSpec、OpenAPI 和交付文档。

Constraint: 七月测试环境里程碑不新增或运行自动化测试

Rejected: 以必填 operation_type 替换 packages[].remove | 会破坏已交付前端契约

Confidence: high

Scope-risk: broad

Directive: 后续修改系列套餐管理接口必须保持 packages[].remove 和 ShopSeriesGrantResponse 兼容

Tested: go run ./cmd/gendocs;go build -buildvcs=false ./...;openspec validate complete-july-iteration-test-release --strict;git diff --check

Not-tested: 按本 Change 约定未运行 go test,真实运营商与 Gateway 联调延期
2026-07-24 19:59:24 +08:00

73 lines
4.8 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.
# UR#43 代理系列套餐批量授权功能总结
## 完成范围
本次复用既有系列授权、套餐授权模型和四个现有接口,不新增候选套餐 API、不新增迁移、不改整店批量调价逻辑
- `GET /api/admin/packages`:按 `series_id` 获取当前操作者可见套餐。
- `POST /api/admin/shop-series-grants`:首次创建系列授权,并原子创建 1100 条套餐授权。
- `GET /api/admin/shop-series-grants/{id}`:读取目标代理已授权套餐及其当前成本价。
- `PUT /api/admin/shop-series-grants/{id}/packages`:通过既有套餐项字段批量新增、修改成本价或软删除授权。
当前仓库不包含前端源码。本次交付框架无关的页面组合与交互契约,前端实现和真实人工验收仍需在对应仓库完成。
## 首次批量授权
创建系列授权时,`packages` 必填且只能包含 1100 项;每项必须提供唯一 `package_id` 和非负 `cost_price`。后端在同一 PostgreSQL 事务内锁定并校验目标店铺、上级有效系列授权、套餐和上级有效套餐授权,全部通过后才写系列授权、套餐授权和价格历史。
- 代理只能授权当前直属下级店铺。
- 套餐必须未删除、属于目标系列且不是赠送套餐;禁用或下架不等于不能预先配置授权。
- 代理必须拥有对应的有效上级套餐授权;目标成本价不得低于当前上级成本价。
- 任一项失败时,系列授权、套餐授权和价格历史全部回滚,不会留下空系列授权。
- `expiry_base_override` 继续遵守 UR#55 契约,必须显式传 `null` 或合法枚举。
## 后续兼容契约
`PUT /api/admin/shop-series-grants/{id}/packages` 继续接受已交付的 `packages[].remove` 字段:
```json
{
"expiry_base_override": null,
"packages": [
{"package_id": 1001, "cost_price": 6500},
{"package_id": 1002, "cost_price": 7000},
{"package_id": 1003, "remove": true}
]
}
```
| 套餐项 | 成本价字段 | 业务语义 |
| --- | --- | --- |
| `remove=true` | 可省略 | 软删除当前授权;已不存在按幂等成功 |
| `remove` 缺失或为 `false`,当前未授权 | 必填,允许 0 | 新增套餐授权 |
| `remove` 缺失或为 `false`,当前已授权 | 必填,允许 0 | 按现有规则更新成本价 |
请求要求 1100 个套餐项,并在一个事务内完成权限校验、价格边界和写入。`remove` 是 2026-03-04 已交付字段,不得删除、改名或替换为必填顶层 `operation_type`
成功响应继续返回刷新后的 `ShopSeriesGrantResponse`,保持旧前端无需适配新的响应结构。
## 前端读取与价格语义
首次授权只读取 `GET /api/admin/packages?series_id={series_id}`;后续管理并行读取套餐列表和授权详情,再按 `package_id` 合并:
1. 套餐列表中的 `cost_price` 表示当前上级成本价。平台视角是公司成本价,代理视角是该代理自己的授权成本价。
2. 授权详情 `packages[].cost_price` 表示目标代理当前授权成本价。
3. 套餐列表 `suggested_retail_price` 表示建议零售价。
4. 详情存在的套餐标记 `is_authorized=true`;只在列表出现的套餐标记 `is_authorized=false`,未授权成本显示“-”,不能用 0 代替。
5. 已授权但因上级权限变化不再出现在普通列表的存量项,仍从详情只读展示,不能静默消失。
页面可以提供授权、调价、移除三种交互模式。新增模式只允许选择未授权项;调价和移除模式只允许选择已授权项;提交移除时设置对应套餐项的 `remove=true`。提交期间禁止重复提交。
## 权限、可靠性与范围裁决
- 超级管理员和平台账号沿用现有管理范围;代理必须仍是目标店铺直属上级,不能仅凭历史 `allocator_shop_id` 操作已转移店铺。
- 资源不存在与越权统一返回安全错误,不暴露其他代理授权或成本价。
- 价格历史与授权写入同事务Audit Event 已由七月总 Change 明确移出本次范围,不接入 Audit Writer也不以 Integration Log 冒充业务审计。
- 本任务不修改订单、客户套餐使用、零售价、佣金、强充或整店批量调价,不自动级联回收下级授权。
## 发布与回滚
后端发布必须兼容旧前端,不要求切换新命令字段。发布前应确认 OpenAPI 仍包含 `packages[].remove`,并核对同一店铺/套餐有效授权重复、孤立下级授权和异常价格;本功能没有新增迁移。
回滚应用时保留新版本已经产生的系列授权、套餐授权和价格历史,不清表、不物理删除业务事实。自动化测试与真实 PostgreSQL 并发验收按七月总 Change 的 6.1/6.3 延期本批只执行格式化、OpenAPI 生成、构建和静态部署检查。