feat(subscription): let admins set expiry, renew or replace on grant, and grant in bulk - #6324
feat(subscription): let admins set expiry, renew or replace on grant, and grant in bulk#6324YiKongk wants to merge 2 commits into
Conversation
Admin subscription grants left no trace: neither a manage log for the target user nor an operator audit entry was written, even though the grant can also upgrade the user's group. The reset handlers in the same file already do both. The MaxPurchasePerUser count in CreateUserSubscriptionFromPlanTx also ran without a row lock, so two concurrent grants could both read count-1 and both insert. Locking the user row serializes them; the limit check itself is unchanged. This covers the order, balance and admin paths at once. Locking the row also rejects grants for a non-existent user up front instead of creating an orphan subscription.
… and grant in bulk Admin subscription grants could only create a new record following the plan's own duration. Three gaps this closes: - custom expiry: pass an absolute end time instead of the plan duration - renew/replace: renew extends the existing active subscription in place (an UPDATE, so it does not consume another MaxPurchasePerUser slot) and falls back to creating one when there is none; replace cancels the existing active rows first, then inserts the new one with the purchase limit skipped (the swap is net-zero on active subscriptions) and the cancelled rows' PrevUserGroup carried over, so expiry can still revert the user to their origin group. Default stays "create", so existing behaviour is unchanged. - batch: POST /subscription/admin/bind/batch grants one plan to several users, each in its own transaction, reporting per-user failures instead of rolling the whole set back. The failed list serializes as [] rather than null so the result dialog can index into it unconditionally. Renew keeps an already-scheduled future quota reset so mid-cycle renewals do not shift base-relative (custom-period) schedules; it only recomputes the schedule when it was switched off or the new end time invalidates it (calcNextResetTime zeroes the schedule when the next reset would fall past the end time). GetDBTimestamp read through the global DB handle, which checks out a second pooled connection when called inside a transaction. The subscription paths now read it through their tx, which also makes them testable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (27)
WalkthroughAdmin subscription grants now support create, renew, replace, custom expiration, and batch assignment. The changes update transactional model logic, admin APIs and auditing, classic and default management interfaces, routing, tests, and localization. ChangesSubscription grant behavior
Default subscription management
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant BatchAssignSubscriptionDialog
participant SubscriptionAPI
participant AdminBindSubscriptionBatch
participant AdminBindSubscription
participant Database
Admin->>BatchAssignSubscriptionDialog: Select users, plan, mode, and end time
BatchAssignSubscriptionDialog->>SubscriptionAPI: Submit batch grant request
SubscriptionAPI->>AdminBindSubscriptionBatch: POST batch grant
AdminBindSubscriptionBatch->>AdminBindSubscription: Grant each user independently
AdminBindSubscription->>Database: Create, renew, or replace subscription
AdminBindSubscriptionBatch-->>SubscriptionAPI: Success and failure results
SubscriptionAPI-->>BatchAssignSubscriptionDialog: Render batch outcome
Possibly related PRs
Suggested reviewers: Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Important
📝 变更描述 / Description
管理员手动绑定订阅原先只能按套餐自身时长新建一条记录。本 PR 补齐三个能力:
end_time覆盖套餐默认时长(须晚于当前时间,否则拒绝)。mode:create(默认):与现状完全一致,新增记录;renew:对同套餐现有活跃订阅原地 UPDATE 延长到期时间(不新增记录,因此不占用MaxPurchasePerUser名额;无活跃订阅时回退为新建)。已排定且仍有效的额度重置时间保持不变,避免自定义周期(custom)订阅在周期中途续期时重置计划漂移;replace:先取消同套餐现有活跃记录,再插入新记录。取消+新建对活跃订阅数是净零,因此新建时跳过购买上限计数(上限查询本身未改动);同时将被取消记录的PrevUserGroup快照带入新记录,保证到期后用户仍能回退到原始分组。POST /api/subscription/admin/bind/batch,逐用户独立事务,单个用户失败(上限、账号不存在)不回滚其他用户,按用户返回失败原因;failed字段序列化为[]而非null,前端结果视图可直接遍历。前端:用户订阅弹窗与用户表格批量操作各新增授予模式与自定义到期时间选择;提示文案抽取为
getEndTimeHint共享 helper;新增文案已按localeCompare排序插入全部 7 个语言文件。说明:本 PR 代码为 AI 辅助生成(AI-assisted),已由提交者本地验证。
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
新增回归测试(
model/subscription_admin_grant_test.go)覆盖:renew 不占用购买名额、renew 保持 custom 周期重置计划、replace 在用户已达MaxPurchasePerUser时成功且普通 create 仍被拒、replace 保留PrevUserGroup且到期后正确降组(经ExpireDueSubscriptions验证)、自定义到期时间生效且过去时间被拒、批量授予按用户报告失败、全部成功时failed序列化为[]。🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes