feat: grant transition plan to existing users
This commit is contained in:
@@ -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(
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user