feat: grant transition plan to existing users

This commit is contained in:
QiuSW
2026-07-22 16:56:13 +08:00
parent 09d13ff64a
commit 82c338cbe7
10 changed files with 374 additions and 4 deletions
+79
View File
@@ -7,6 +7,7 @@ from decimal import Decimal
from decimal import InvalidOperation
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import IntegrityError, transaction
from django.utils import timezone
@@ -74,6 +75,14 @@ class SoftwarePaymentResult:
applied: bool
@dataclass(frozen=True)
class BulkEntitlementGrantResult:
eligible_count: int
skipped_existing_count: int
grant_count: int
executed: bool
@dataclass(frozen=True)
class AuthorizationDecision:
product_code: str
@@ -344,6 +353,76 @@ def grant_software_entitlement(*, user, plan: SoftwarePlan, reason: str, actor=N
return entitlement
@transaction.atomic
def grant_plan_to_existing_users(
*,
plan: SoftwarePlan,
reason: str,
execute: bool = False,
expected_grant_count: int | None = None,
actor=None,
now=None,
) -> BulkEntitlementGrantResult:
reason = _required_reason(reason)
if execute and expected_grant_count is None:
raise LicensingError("expected_grant_count_required", "实际执行必须提供预期授予人数")
if expected_grant_count is not None and expected_grant_count < 0:
raise LicensingError("invalid_expected_grant_count", "预期授予人数不能为负数")
plan_query = SoftwarePlan.objects
if execute:
plan_query = plan_query.select_for_update()
locked_plan = plan_query.get(pk=plan.pk)
if locked_plan.status != SoftwarePlan.Status.ACTIVE:
raise LicensingError("plan_inactive", "套餐已停用,不能批量授予权益")
users_query = get_user_model().objects.filter(
is_active=True,
is_staff=False,
is_superuser=False,
).order_by("pk")
if execute:
users_query = users_query.select_for_update()
eligible_users = list(users_query)
eligible_user_ids = [user.pk for user in eligible_users]
effective_now = now or timezone.now()
existing_user_ids = set(
SoftwareEntitlement.objects.filter(
user_id__in=eligible_user_ids,
product_code=locked_plan.product_code,
status=SoftwareEntitlement.Status.ACTIVE,
grace_expires_at__gt=effective_now,
).values_list("user_id", flat=True)
)
users_to_grant = [
user for user in eligible_users if user.pk not in existing_user_ids
]
grant_count = len(users_to_grant)
if execute and expected_grant_count != grant_count:
raise LicensingError(
"grant_count_mismatch",
f"预期授予 {expected_grant_count} 人,当前实际应授予 {grant_count} 人",
)
if execute:
for user in users_to_grant:
grant_software_entitlement(
user=user,
plan=locked_plan,
reason=reason,
actor=actor,
starts_at=effective_now,
)
return BulkEntitlementGrantResult(
eligible_count=len(eligible_users),
skipped_existing_count=len(existing_user_ids),
grant_count=grant_count,
executed=execute,
)
@transaction.atomic
def renew_software_entitlement(
*,