Repository navigation
Conversation
The plan editor loaded the stored group selection and submitted it unchanged, while only rendering checkboxes for groups that still exist. A group deleted after the plan was saved therefore stayed in the request without any control that could remove it, so the plan kept naming a group that no longer resolves. Loading the group choices now prunes selected identifiers missing from the loaded list together with their rate overrides, and reports how many were dropped. A disabled group remains selectable, and a failed load leaves the draft untouched so a temporary error cannot erase the selection.
A plan keeps the group identifiers recorded when it was saved, so a group deleted afterwards still appeared in a subscriber's plan card as a bare identifier with no name. The lookup result was discarded, leaving an identifier-only entry for every unresolved group. Enrichment now removes an entry when storage reports that the group does not exist, and keeps the identifier-only entry for a nil row or another read failure, where the group's existence is undetermined.
Group deletion removes a group's permissions and provider memberships, but plans keep the identifiers they recorded. A later plan save then resubmitted a selection pointing at a group that no longer resolves, and nothing rejected it because plan writes only deduplicate and drop nonpositive identifiers. Plan writes now confirm a declared group selection against stored groups before persistence. CreatePlan checks the selection it normalizes, and UpdatePlan checks the selection it is about to store, including a legacy leading identifier. An update that leaves group_ids out keeps the recorded selection and runs no lookup, so an unrelated edit still works on a plan that already names a deleted group.
The plan and subscription documents described what a nonempty group selection means without saying what happens when a selected group is deleted. Add the write-time check, the display behaviour for a confirmed missing group, and the editor repair, and list the plan endpoints with their GROUP_NOT_FOUND response.
SHLE1
force-pushed
the
fix/plan-group-references
branch
from
October 24, 2026 04:59
fd7a6a0 to
819d2a9
Compare
Owner
Author
|
Superseded by the upstream pull request: TokenFlux#1990. The branch stays on the fork until upstream merges, then the fork syncs from upstream. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
套餐记录的分组可以在分组被删除之后继续保留,订阅页于是把这个分组显示成
#15这样的裸编号。实际触发场景是一个 Max 订阅,套餐里仍然列着已经删除的分组 15。原因
分组删除走的是软删除(
mixins.SoftDeleteMixin),行还留在表里,subscription_plan_groups.group_id上的外键级联永远不会触发。其余环节都没有清掉这条引用:NormalizePlanGroupIDs只过滤非正数和重复值,套餐写入接受任何正整数。GroupStore.DeleteCascade清理分组授权和提供商关联,套餐侧不动。EnrichSubscriptionPlanGroups对任何无法利用的查询结果都保留只有 ID 的条目,SubscriptionsView.vue把它渲染成#{id}。这个编号从来没有带来权限:额度判定读的是同一份列表,而没有任何分组能与它匹配。它占着套餐里的一个位置,还让订阅者看到一个没有信息量的标签。
改动
PlanEditDialog.vue):分组列表加载成功后,不在列表中的编号连同它们的倍率覆盖一起从草稿里移除,并提示移除了几个。停用的分组仍然可以勾选。加载失败时草稿保持不变;请求代次守卫保证迟到的旧响应不会用过期列表去裁剪新草稿。EnrichSubscriptionPlanGroups):只有存储明确报告分组不存在时才移除条目。返回空行、读取器缺失或读取失败都保留只有 ID 的条目,因为这些结果说明分组是否存在仍未确定。Plans.CreatePlan、Plans.UpdatePlan):提交的分组选择在落库前核对现存分组,指向已删除或不存在的分组时返回GROUP_NOT_FOUND(404)。billing定义PlanGroupValidator,由app适配到routing.ValidateGroupIDs;billing不能直接 importrouting,否则会经provider形成 import cycle。docs/domains/payments_and_entitlements.md记录这条规则和展示行为,docs/interfaces/http_api.md补充两个套餐接口的GROUP_NOT_FOUND响应。边界决定
group_ids时保留原有分组选择,也不做分组查询。已经记录旧编号的套餐仍能改价格和额度,编辑页在下一次保存时顺带修复分组。routing反向写套餐存储会把两个模块按错误的方向耦合起来。验证
make test-go:后端全量测试,含tests/integrationpnpm run test:run:400 个文件、3101 个测试通过make lint-go:架构规则、文件布局检查、golangci-lint(0 issues)make fmt-check、前端typecheck、注释与 UI token 检查新增覆盖:删除的分组连同倍率覆盖被移除并给出提示;停用分组及其覆盖保留;加载失败保留原有选择;迟到的旧响应不能裁剪新草稿;展示层移除确认不存在的分组,保留不可读或空行的分组;创建和更新拒绝指向已删除分组的列表,包含 legacy
group_id的合并,省略分组时不做查询。上线处理
已部署实例的套餐里可能仍有悬空编号。订阅页不再显示它们,管理员下一次在编辑页保存时写入会得到修正。不经过编辑页的修复方式是把去掉该编号的
group_ids提交到PUT /api/v1/admin/payment/plans/{id}。