fix: 店铺分配覆盖为实名即生效的套餐纳入沉默期配置口径
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m27s

存量中套餐本身购买即生效、店铺分配覆盖为实名即生效的正式套餐,购买后同样等实名,
但原口径只看套餐本身:无法配置默认沉默期、不进未配置清单与打开前检查,
待实名记录无法回填导致总开关打不开,新购买也绕过沉默期。

- 未配置清单与打开前检查纳入存在实名即生效分配覆盖的套餐
- 购买即生效正式套餐允许选填默认沉默期,加油包仍禁止
- 分配覆盖购买即生效 → 实名即生效在套餐已配置默认沉默期时放行
- 同步 OpenSpec design D11、spec、tasks 3.4/3.5 与前端说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
邹进
2026-10-27 17:56:29 +09:00
parent 69ec98aa5f
commit af57de0d4d
7 changed files with 59 additions and 21 deletions

View File

@@ -82,6 +82,12 @@
回填(沉默期、首次购买套餐时间)放在 `cmd/` 下的一次性工具,支持"只出清单"与"执行"两种模式,分批、只处理空值、可重复执行。生产执行前 MUST 由业务确认清单。生产只读拉数遵守项目约束(dbhub 只读)。
### D11 分配覆盖为实名即生效的套餐纳入配置口径
最终生效条件 = 店铺分配覆盖值 ?? 套餐值。存量数据中有套餐本身 `from_purchase`、但店铺分配覆盖为 `from_activation` 的正式套餐,购买后同样等实名;若只按套餐本身判断,这类套餐既不能配置默认沉默期,也不进打开前检查,待实名记录永远无法回填,总开关打不开,新购买也绕过沉默期(10-27 测试环境验证发现)。
口径:正式套餐本身 `from_activation`,或存在未删除的分配覆盖为 `from_activation`,即视为"需要默认沉默期"。后者计入未配置清单与打开前检查。购买即生效正式套餐允许选填默认沉默期(不强制,只在分配覆盖为实名即生效时使用;否则第一次覆盖前无法配置,形成死锁),加油包仍禁止。分配覆盖 `from_purchase → from_activation` 由一律阻断改为"套餐已配置默认沉默期时放行",与"套餐 `from_purchase → from_activation` 须同时提供默认沉默期"保持同一原则。购买时的沉默期设置仍按记录的 `expiry_base_snapshot` 与套餐默认值判断,不变(替代技术方案 §3.3 第 4 行"一律阻断")。
## Risks / Trade-offs
- [打开总开关后一批历史套餐立即失效,过期满 180 天的无法付费延期] → 回填清单单独列出"将过期""将过期且无法付费延期"数量,业务确认后再打开;特殊延期可兜底;可应急关闭。

View File

@@ -25,13 +25,20 @@
### Requirement: 套餐默认沉默期与是否支持延期配置
实名即生效套餐 MUST 配置默认沉默期 `silence_period_days`(1~90 天,新建时预填 30)与 `silence_extension_enabled`(默认支持);普通套餐 MUST NOT 写入沉默期。修改默认值 MUST 只影响之后的新购买,并写审计。系统 SHALL 提供"未配置沉默期的实名即生效套餐"清单(筛选与导出)。
实名即生效套餐 MUST 配置默认沉默期 `silence_period_days`(1~90 天,新建时预填 30)与 `silence_extension_enabled`(默认支持);加油包 MUST NOT 写入沉默期;购买即生效正式套餐 MAY 选填默认沉默期(不强制,仅在店铺分配覆盖为 `from_activation` 时使用)。修改默认值 MUST 只影响之后的新购买,并写审计。系统 SHALL 提供"未配置沉默期的实名即生效套餐"清单(筛选与导出)。
配置口径中的"实名即生效套餐"指正式套餐本身为 `from_activation`,**或**存在未删除的店铺分配把它覆盖为 `from_activation`(存量数据)。后者 MUST 计入"未配置沉默期的实名即生效套餐"清单与总开关打开前检查"未配置默认沉默期的套餐"。
#### Scenario: 实名即生效套餐未填默认沉默期
- **WHEN** 新建或编辑 `from_activation` 套餐时未提供 `silence_period_days`,或值不在 1~90
- **THEN** 系统拒绝保存,返回 `silence_period_out_of_range`
#### Scenario: 分配覆盖为实名即生效的购买即生效套餐
- **WHEN** 套餐本身为 `from_purchase`,但某店铺分配覆盖为 `from_activation`,且套餐未配置默认沉默期
- **THEN** 该套餐出现在未配置清单中并计入打开前检查;管理员可以为它配置默认沉默期,配置后该店铺的新购买进入沉默期,历史待实名记录可被回填
#### Scenario: 修改默认值不影响已购买记录
- **WHEN** 将套餐默认沉默期从 30 改为 45
@@ -39,13 +46,18 @@
### Requirement: 禁止绕过沉默期的生效条件变更
系统 MUST 在套餐服务与店铺分配(单个、批量、系列授权)写入路径的共用校验中阻断:套餐 `from_activation` 改为 `from_purchase`、分配覆盖把 `from_activation` 套餐改为 `from_purchase`、分配覆盖把 `from_purchase` 套餐改为 `from_activation`。套餐 `from_purchase` 改为 `from_activation` 时同一请求 MUST 提供默认沉默期。
系统 MUST 在套餐服务与店铺分配(单个、批量、系列授权)写入路径的共用校验中阻断:套餐 `from_activation` 改为 `from_purchase`、分配覆盖把 `from_activation` 套餐改为 `from_purchase`、套餐未配置默认沉默期时分配覆盖把 `from_purchase` 套餐改为 `from_activation`(套餐已配置默认沉默期时放行)。套餐 `from_purchase` 改为 `from_activation` 时同一请求 MUST 提供默认沉默期。
#### Scenario: 实名即生效改为购买即生效
- **WHEN** 管理员把实名即生效套餐改为购买即生效,或在店铺分配上覆盖为购买即生效
- **THEN** 系统拒绝,返回 `package_expiry_base_change_forbidden`
#### Scenario: 购买即生效套餐在分配上覆盖为实名即生效
- **WHEN** 管理员在店铺分配上把 `from_purchase` 套餐覆盖为 `from_activation`
- **THEN** 套餐已配置默认沉默期时允许保存;未配置时拒绝,返回 `package_expiry_base_change_forbidden`
### Requirement: 后台购买时调整沉默期
后台给资产购买会进入沉默期的套餐时,有 `package_silence:override` 权限的运营 SHALL 可以设置本次沉默期(≥ 1 天,不设上限),必须填写原因;优先级为本次设置值高于套餐默认值。

View File

@@ -28,6 +28,8 @@
- [x] 3.1 套餐新增 / 编辑 DTO 与校验:实名即生效套餐必填 `silence_period_days`(1~90)、`silence_extension_enabled`;普通套餐禁止写入;修改默认值写审计 `package.silence_default_changed`
- [x] 3.2 生效条件变更约束:套餐服务与店铺分配(单个、批量、系列授权)共用校验函数,阻断 §3.3 的三种变更,`from_purchase → from_activation` 要求同时提供默认沉默期
- [x] 3.3 "未配置沉默期的实名即生效套餐"列表接口(筛选、导出)
- [ ] 3.4 分配覆盖口径(design D11):共用条件"正式套餐本身 `from_activation` 或存在未删除的分配覆盖为 `from_activation`",用于未配置清单(列表、导出)与打开前检查;购买即生效正式套餐允许选填默认沉默期(不强制),加油包仍禁止;分配覆盖 `from_purchase → from_activation` 在套餐已配置默认沉默期时放行(单个、批量、系列授权共用校验)
- [ ] 3.5 测试环境核对:套餐 708 / 710 / 712 出现在未配置清单与打开前检查中;配置默认沉默期后回填套餐记录 20005;未配置时分配覆盖为实名即生效被拒绝,配置后放行
## 4. 沉默期设置、判定与激活拦截(§4、§5)
@@ -99,7 +101,10 @@
## 11. 测试环境验证(§14、§17.6)
- [ ] 11.1 部署后确认总开关关闭时行为不变:新购买写入沉默期字段,过期后实名仍激活,延期接口返回 `silence_feature_disabled`
- [x] 11.1 部署后确认总开关关闭时行为不变:新购买写入沉默期字段,过期后实名仍激活,延期接口返回 `silence_feature_disabled`
- 验证结果(2026-10-27,部署 `69ec98aa`):总开关 `enabled=false`;代理 539 未实名卡 8019 后台钱包购买验证套餐 823(订单 892)→ 套餐记录 20175 `waiting`、30 天、截止 2026-11-26 23:59:59,流水 `set / order_paid`,首次购买套餐时间与记录写入;资格、试算、二维码下单、线下登记、特殊延期均返回 1274,未产生延期订单 / 批次 / 明细;把 20175 截止改为 2026-10-20 后将卡标为已实名,孤儿扫描 10 秒内激活(status=1),`silence_status` 保持 `waiting`、不写判定流水。
- 发现并修复(`69ec98aa`):后台平台自营资产与赠送套餐线下订单 `buyer_type=personal`,`activateMainPackage` 误判为 C 端购买,未实名也直接生效、不进沉默期;增加 `order.Source == client` 条件后,代理卡 8030 线下购买赠送套餐 824(订单 893)→ 记录 20176 待实名、`waiting`、30 天。
- 待 11.10 清理:套餐 822 / 823 / 824、分配 288、订单 891(卡 11895)/ 892(卡 8019,代理 539 钱包扣 1 分)/ 893(卡 8030)及其套餐记录、沉默期流水、首次购买套餐时间与记录;卡 8019 恢复 `real_name_status=0`、`first_realname_at=NULL`、`enable_polling=true`。
- [ ] 11.2 总开关打开前检查:逐项制造不满足条件,确认拒绝及返回内容;全部满足后打开成功并写审计
- [ ] 11.3 沉默期设置、购买时调整(直接生效 / 审批通过 / 驳回 / 免审批)、实名与过期判定(§14 第 1~12 条)
- [ ] 11.4 延期资格与计费(§14 第 13~15 条,含示例表四种情况)