From 09d13ff64aaefee4005f87b1e2c70596e8f06b26 Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Wed, 22 Jul 2026 16:41:57 +0800 Subject: [PATCH] docs: add existing user plan grant task --- docs/06-tasks.md | 2 ++ docs/tasks/T-633.md | 61 +++++++++++++++++++++++++++++++++++++++++++++ progress.md | 7 ++++++ 3 files changed, 70 insertions(+) create mode 100644 docs/tasks/T-633.md diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 064e6f7..b31cf6b 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -111,6 +111,7 @@ | T-630 | 账号订阅授权与默认多设备模式 | T-628, T-629 | **已完成。** 将蝦皮圈产品专属授权从“设备会话 + 设备凭证”调整为“API Key 对应账号 + 软件订阅权益”。同一账号默认允许多台电脑使用,不设置 `LicenseSeat` 设备数量硬限制;`ClientDevice` / `DeviceSession` 仅保留为可选观测和风控数据。新增 `GET /api/v1/cmshopee/subscription/status`,返回账号对应产品的订阅状态、套餐和到期时间;新增 `subscription_required` / `subscription_expired` 错误码。授权服务只查询用户 `SoftwareEntitlement`,不再读取 `DeviceCredential`。`CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=false` 时只写账号订阅影子日志并保持兼容,开启后产品专属生成入口才拒绝无订阅用户;通用 `/api/v1/generate/*`、充值、点数和历史迁移接口不变。保留旧设备/卡密数据只读,不删除历史记录,不再签发新设备凭证;过期设备会话也不会阻断账号订阅授权。已补账号无订阅/有效订阅状态、强制拦截和多设备上下文回归测试;`check`、编译检查和目标 API 测试通过。 | DONE | | T-631 | 隐藏旧设备授权模型的 admin 菜单入口 | T-630 | 已完成:从 django-admin 应用索引隐藏 `LicenseSeat`、`LegacyMigrationGrant`、`MigrationRequest`、`DeviceCredential` 四个旧方案模型;保留数据库表、历史数据和只读模型。当前订阅、订单、授权事件、设备和会话观测模型继续可见,不改变 API、授权和账务行为。定向 admin 测试、`check`、迁移一致性和编译检查通过。详见 [`tasks/T-631.md`](tasks/T-631.md)。 | DONE | | T-632 | 会员订阅三阶段启用与简化运营 | T-630, T-631 | **已完成。** 新增 `CMSHOPEE_SUBSCRIPTION_MODE=open|shadow|enforce`,将真实权益状态与最终访问结果分离;`open` / `shadow` 不写虚假权益,`enforce` 才拦截产品专属 submit。状态接口补齐桌面端账号、套餐、顶层有效期、会员中心、通知及观测字段,并保留 T-630 旧字段。admin 首页仅保留“会员套餐”和“用户会员”,其他模型只隐藏不删除。5 条无数据库单元测试、`check`、迁移一致性和编译通过;数据库集成测试因旧远程 MySQL 拒绝连接未取得结果。详见 [`tasks/T-632.md`](tasks/T-632.md)。 | DONE | +| T-633 | 存量用户批量授予过渡测试套餐 | T-632, T-626 | 新增默认只预演的批量授权命令,通过明确 `plan_id`、非空原因、`--execute` 与预期人数双重确认,为执行时已有的启用非后台账号授予指定套餐。已有同产品有效/宽限期权益用户必须跳过;实际授予复用 `grant_software_entitlement()`,在单事务内生成权益快照、席位和授权事件,任一失败整批回滚。不得修改订阅模式、API Key、点数、充值、软件订单或生成记录。详见 [`tasks/T-633.md`](tasks/T-633.md)。 | DOING | ## 里程碑 @@ -133,6 +134,7 @@ - M17:图片生成任务后台图片缩略预览(T-622)。 - M18:图片生成任务单图 / 多图筛选(T-623)。 - M19:蝦皮圈设备授权与订阅迁移(T-624~T-629)。 +- M20:存量用户过渡测试套餐安全批量授予(T-633)。 ## 待办池(Backlog) diff --git a/docs/tasks/T-633.md b/docs/tasks/T-633.md new file mode 100644 index 0000000..45f32fd --- /dev/null +++ b/docs/tasks/T-633.md @@ -0,0 +1,61 @@ +# T-633 存量用户批量授予过渡测试套餐 + +## 背景 + +线上已创建用于开发和联调阶段的“测试”会员套餐。当前订阅仍运行在 `shadow` 模式,存量用户不会被强制拦截,但在正式切换到 `enforce` 前,需要用真实 `SoftwareEntitlement` 验证桌面端会员状态、到期时间和后续套餐切换流程。 + +直接在数据库批量插入权益会绕过套餐快照、席位和 `LicenseEvent`,逐个在 admin 操作又容易漏发或重复发放。因此需要一个默认只预演、显式确认后才执行的批量命令。 + +## 目标 + +1. 给执行时已有的启用非后台账号批量授予指定测试套餐。 +2. 复用现有授权服务,完整生成权益快照、席位和授权事件。 +3. 提供预演、人数校验、事务回滚和重复执行保护,降低生产批处理风险。 +4. 不影响已有正式会员、API Key、点数、充值、软件订单和生成任务。 + +## 范围 + +### 批量命令 + +- 新增 `grant_existing_users_plan` management command。 +- 套餐必须通过数据库 `plan_id` 精确指定,且执行时仍为启用状态;不按可能重名的套餐名称模糊匹配。 +- 默认只输出预演统计,不写数据库。 +- 实际执行必须同时传入 `--execute` 和预演得到的 `--expected-grant-count`;人数不一致时整批拒绝。 +- `--reason` 必填并写入每一条授权事件,不能生成无原因的批量权益。 + +### 用户范围和幂等 + +- 默认只处理 `is_active=True`、`is_staff=False`、`is_superuser=False` 的账号。 +- 对指定套餐所属产品,用户只要已有仍处于有效期或宽限期的权益,就跳过本次授予;不得用测试套餐覆盖或制造与正式套餐并存的有效权益。 +- 已过期或已撤销权益不阻止重新授予。 +- 执行阶段锁定套餐和目标用户,并在同一数据库事务内重新计算范围;任何一名用户授予失败都必须整批回滚。 + +### 授权与留痕 + +- 每名实际授予用户必须复用 `grant_software_entitlement()`,不得直接创建 `SoftwareEntitlement`。 +- 权益继续复制套餐名称、价格、时长、设备数和宽限期快照,并按既有服务生成席位。 +- 每项授予写 `LicenseEvent(granted)`,记录统一的非空批处理原因。 +- 命令输出只给出符合条件、跳过和实际授予数量,不输出邮箱、API Key 或其他敏感数据。 + +## 实施约束 + +- 不修改 `CMSHOPEE_SUBSCRIPTION_MODE`,本任务执行期间线上继续保持 `shadow`。 +- 不撤销、续期或改写用户已有权益。 +- 不修改 `UserWallet`、`PointsLedger`、`RechargeOrder`、`SoftwareOrder` 或生成记录。 +- 不新增数据迁移,不直接运行原始 SQL。 +- 本任务只完成测试套餐的安全批量授予;测试结束后的正式套餐切换另行落任务,不通过编辑套餐反向修改权益快照。 + +## 验收条件 + +1. 默认预演不创建权益、席位或授权事件,并返回待授予和跳过数量。 +2. 未提供 `--execute` 时不能写数据;执行时缺少或错误的预期授予人数必须拒绝且不产生部分数据。 +3. 启用的普通用户获得指定套餐;后台账号、停用账号和已有同产品有效权益用户不受影响。 +4. 每个新权益具有正确套餐快照、到期时间和宽限截止时间,并存在对应 `granted` 授权事件。 +5. 同一命令重复执行时已获有效权益用户被跳过,不重复授予。 +6. 任一授予失败时整批回滚。 +7. `manage.py check`、目标测试、迁移一致性、编译检查和 `git diff --check` 通过。 +8. 线上执行前完成数据库备份;预演人数与实际执行人数一致;执行后权益数、事件数和订阅状态接口抽样验收通过。 + +## 状态 + +DOING。 diff --git a/progress.md b/progress.md index f7c74e6..9bd00d3 100644 --- a/progress.md +++ b/progress.md @@ -2146,3 +2146,10 @@ - admin:软件授权应用索引只显示“会员套餐”和“用户会员”;软件订单、事件、设备观测及历史授权模型继续注册、保留直接管理地址和历史数据。`licensing.0005` 只修改两个模型的显示名称。 - 验证:5 条不依赖数据库的模式/合约/宽限期/admin 单元测试通过;`manage.py check`、迁移一致性、编译与 `git diff --check` 通过;非法模式按预期阻止启动。数据库 API/admin 定向测试已尝试,但旧远程 MySQL `43.128.3.240` 拒绝连接,本机 MySQL 又不接受项目账号,因此未取得数据库集成测试结果。 - 发布约束:当前部署保持 `open`;切 `shadow` / `enforce` 前必须在可用 MySQL 测试库补跑 T-632 数据库集成回归,并核对桌面端状态页。 + +## 2026-07-22 规划:T-633 存量用户批量授予过渡测试套餐 + +- 线上只读核对:存在且仅存在一个启用的 `cmshopee`“测试”套餐;当前有 30 个启用的非后台账号、1 个启用的后台账号,数据库尚无 `SoftwareEntitlement`。 +- 方案:新增默认只预演的 `grant_existing_users_plan` 命令。实际执行必须指定套餐 ID、非空原因、`--execute` 和与预演一致的预期授予人数;默认排除后台/超级用户,并跳过已有同产品有效或宽限期权益的账号。 +- 安全边界:批量授予必须在单事务中锁定套餐和目标用户,逐项复用 `grant_software_entitlement()` 生成套餐快照、席位和授权事件;任一失败整批回滚。不得直接写权益表,不修改订阅模式、点数、充值、软件订单或生成任务。 +- 发布顺序:任务文档提交后实现命令和测试;线上部署后先备份数据库,再预演、按预演人数执行并抽样验证订阅状态。测试结束后的正式套餐切换另行立项。