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 联调延期
73 lines
4.8 KiB
Markdown
73 lines
4.8 KiB
Markdown
# UR#43 代理系列套餐批量授权功能总结
|
||
|
||
## 完成范围
|
||
|
||
本次复用既有系列授权、套餐授权模型和四个现有接口,不新增候选套餐 API、不新增迁移、不改整店批量调价逻辑:
|
||
|
||
- `GET /api/admin/packages`:按 `series_id` 获取当前操作者可见套餐。
|
||
- `POST /api/admin/shop-series-grants`:首次创建系列授权,并原子创建 1~100 条套餐授权。
|
||
- `GET /api/admin/shop-series-grants/{id}`:读取目标代理已授权套餐及其当前成本价。
|
||
- `PUT /api/admin/shop-series-grants/{id}/packages`:通过既有套餐项字段批量新增、修改成本价或软删除授权。
|
||
|
||
当前仓库不包含前端源码。本次交付框架无关的页面组合与交互契约,前端实现和真实人工验收仍需在对应仓库完成。
|
||
|
||
## 首次批量授权
|
||
|
||
创建系列授权时,`packages` 必填且只能包含 1~100 项;每项必须提供唯一 `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 | 按现有规则更新成本价 |
|
||
|
||
请求要求 1~100 个套餐项,并在一个事务内完成权限校验、价格边界和写入。`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 生成、构建和静态部署检查。
|