Include Loyalty/Communication/Sports Center backends and registry updates alongside production nginx and compose wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
375 lines
9.7 KiB
Python
375 lines
9.7 KiB
Python
"""Loyalty foundation DTOs — Phase 7.0."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
from app.models.types import (
|
|
CampaignStatus,
|
|
MemberStatus,
|
|
PointAccountStatus,
|
|
ProgramStatus,
|
|
RewardStatus,
|
|
RewardType,
|
|
TierStatus,
|
|
)
|
|
from app.schemas.common import ORMBase
|
|
|
|
_FORBID = ConfigDict(extra="forbid")
|
|
|
|
|
|
class LoyaltyProgramCreate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
code: str = Field(max_length=50)
|
|
name: str = Field(max_length=255)
|
|
description: str | None = None
|
|
status: ProgramStatus = ProgramStatus.DRAFT
|
|
points_currency_name: str = Field(default="points", max_length=50)
|
|
currency_code: str = Field(default="IRR", max_length=3)
|
|
is_default: bool = False
|
|
settings: dict | None = None
|
|
|
|
|
|
class LoyaltyProgramUpdate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
name: str | None = Field(default=None, max_length=255)
|
|
description: str | None = None
|
|
status: ProgramStatus | None = None
|
|
points_currency_name: str | None = Field(default=None, max_length=50)
|
|
currency_code: str | None = Field(default=None, max_length=3)
|
|
is_default: bool | None = None
|
|
settings: dict | None = None
|
|
version: int = Field(ge=1)
|
|
|
|
|
|
class LoyaltyProgramRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
code: str
|
|
name: str
|
|
description: str | None
|
|
status: ProgramStatus
|
|
points_currency_name: str
|
|
currency_code: str
|
|
is_default: bool
|
|
settings: dict | None
|
|
version: int
|
|
is_deleted: bool
|
|
created_by: str | None
|
|
updated_by: str | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class MembershipTierCreate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
program_id: UUID
|
|
code: str = Field(max_length=50)
|
|
name: str = Field(max_length=255)
|
|
description: str | None = None
|
|
rank: int = Field(default=0, ge=0)
|
|
min_points: int = Field(default=0, ge=0)
|
|
status: TierStatus = TierStatus.ACTIVE
|
|
benefits: dict | None = None
|
|
rules: dict | None = None
|
|
|
|
|
|
class MembershipTierUpdate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
name: str | None = Field(default=None, max_length=255)
|
|
description: str | None = None
|
|
rank: int | None = Field(default=None, ge=0)
|
|
min_points: int | None = Field(default=None, ge=0)
|
|
status: TierStatus | None = None
|
|
benefits: dict | None = None
|
|
rules: dict | None = None
|
|
|
|
|
|
class MembershipTierRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
program_id: UUID
|
|
code: str
|
|
name: str
|
|
description: str | None
|
|
rank: int
|
|
min_points: int
|
|
status: TierStatus
|
|
benefits: dict | None
|
|
rules: dict | None
|
|
is_deleted: bool
|
|
created_by: str | None
|
|
updated_by: str | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class MemberCreate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
program_id: UUID
|
|
display_name: str = Field(max_length=255)
|
|
membership_number: str | None = Field(default=None, max_length=50)
|
|
external_customer_ref: str | None = Field(default=None, max_length=100)
|
|
email: str | None = Field(default=None, max_length=255)
|
|
mobile: str | None = Field(default=None, max_length=50)
|
|
status: MemberStatus = MemberStatus.PENDING
|
|
tier_id: UUID | None = None
|
|
profile: dict | None = None
|
|
enroll: bool = False
|
|
|
|
|
|
class MemberUpdate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
display_name: str | None = Field(default=None, max_length=255)
|
|
external_customer_ref: str | None = Field(default=None, max_length=100)
|
|
email: str | None = Field(default=None, max_length=255)
|
|
mobile: str | None = Field(default=None, max_length=50)
|
|
status: MemberStatus | None = None
|
|
tier_id: UUID | None = None
|
|
profile: dict | None = None
|
|
version: int = Field(ge=1)
|
|
|
|
|
|
class MemberEnrollRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
tier_id: UUID | None = None
|
|
term_days: int | None = Field(default=None, ge=1, le=3650)
|
|
expires_at: datetime | None = None
|
|
reason: str | None = Field(default=None, max_length=500)
|
|
|
|
|
|
class MemberActivateRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
tier_id: UUID | None = None
|
|
term_days: int | None = Field(default=None, ge=1, le=3650)
|
|
expires_at: datetime | None = None
|
|
reason: str | None = Field(default=None, max_length=500)
|
|
|
|
|
|
class MemberRenewRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
term_days: int | None = Field(default=None, ge=1, le=3650)
|
|
expires_at: datetime | None = None
|
|
reason: str | None = Field(default=None, max_length=500)
|
|
|
|
|
|
class MemberFreezeRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
reason: str = Field(max_length=500)
|
|
|
|
|
|
class MemberResumeRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
reason: str | None = Field(default=None, max_length=500)
|
|
|
|
|
|
class MemberCancelRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
reason: str = Field(max_length=500)
|
|
|
|
|
|
class MemberExpireRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
reason: str | None = Field(default=None, max_length=500)
|
|
|
|
|
|
class MemberTransferRequest(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
target_program_id: UUID
|
|
target_tier_id: UUID | None = None
|
|
reason: str | None = Field(default=None, max_length=500)
|
|
|
|
|
|
class MemberRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
program_id: UUID
|
|
membership_number: str
|
|
external_customer_ref: str | None
|
|
display_name: str
|
|
email: str | None
|
|
mobile: str | None
|
|
status: MemberStatus
|
|
tier_id: UUID | None
|
|
enrolled_at: datetime | None
|
|
activated_at: datetime | None
|
|
membership_started_at: datetime | None
|
|
membership_expires_at: datetime | None
|
|
frozen_at: datetime | None
|
|
freeze_reason: str | None
|
|
cancelled_at: datetime | None
|
|
cancel_reason: str | None
|
|
expired_at: datetime | None
|
|
transferred_to_member_id: UUID | None
|
|
profile: dict | None
|
|
version: int
|
|
is_deleted: bool
|
|
created_by: str | None
|
|
updated_by: str | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class MembershipLifecycleEventRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
member_id: UUID
|
|
action: str
|
|
from_status: str
|
|
to_status: str
|
|
reason: str | None
|
|
metadata_json: dict | None
|
|
actor_user_id: str | None
|
|
created_at: datetime
|
|
|
|
|
|
class PointAccountCreate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
program_id: UUID
|
|
member_id: UUID
|
|
account_number: str | None = Field(default=None, max_length=50)
|
|
currency_name: str = Field(default="points", max_length=50)
|
|
status: PointAccountStatus = PointAccountStatus.OPEN
|
|
|
|
|
|
class PointAccountUpdate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
status: PointAccountStatus | None = None
|
|
currency_name: str | None = Field(default=None, max_length=50)
|
|
version: int = Field(ge=1)
|
|
|
|
|
|
class PointAccountRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
program_id: UUID
|
|
member_id: UUID
|
|
account_number: str
|
|
status: PointAccountStatus
|
|
currency_name: str
|
|
version: int
|
|
is_deleted: bool
|
|
created_by: str | None
|
|
updated_by: str | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class RewardCreate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
program_id: UUID
|
|
code: str = Field(max_length=50)
|
|
name: str = Field(max_length=255)
|
|
description: str | None = None
|
|
reward_type: RewardType = RewardType.CUSTOM
|
|
status: RewardStatus = RewardStatus.DRAFT
|
|
points_cost: int = Field(default=0, ge=0)
|
|
eligibility_rules: dict | None = None
|
|
metadata_json: dict | None = None
|
|
|
|
|
|
class RewardUpdate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
name: str | None = Field(default=None, max_length=255)
|
|
description: str | None = None
|
|
reward_type: RewardType | None = None
|
|
status: RewardStatus | None = None
|
|
points_cost: int | None = Field(default=None, ge=0)
|
|
eligibility_rules: dict | None = None
|
|
metadata_json: dict | None = None
|
|
|
|
|
|
class RewardRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
program_id: UUID
|
|
code: str
|
|
name: str
|
|
description: str | None
|
|
reward_type: RewardType
|
|
status: RewardStatus
|
|
points_cost: int
|
|
eligibility_rules: dict | None
|
|
metadata_json: dict | None
|
|
is_deleted: bool
|
|
created_by: str | None
|
|
updated_by: str | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class CampaignCreate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
program_id: UUID
|
|
code: str = Field(max_length=50)
|
|
name: str = Field(max_length=255)
|
|
description: str | None = None
|
|
status: CampaignStatus = CampaignStatus.DRAFT
|
|
rules: dict | None = None
|
|
starts_on: date | None = None
|
|
ends_on: date | None = None
|
|
|
|
|
|
class CampaignUpdate(BaseModel):
|
|
model_config = _FORBID
|
|
|
|
name: str | None = Field(default=None, max_length=255)
|
|
description: str | None = None
|
|
status: CampaignStatus | None = None
|
|
rules: dict | None = None
|
|
starts_on: date | None = None
|
|
ends_on: date | None = None
|
|
bump_rule_version: bool = False
|
|
|
|
|
|
class CampaignRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
program_id: UUID
|
|
code: str
|
|
name: str
|
|
description: str | None
|
|
status: CampaignStatus
|
|
rule_version: int
|
|
rules: dict | None
|
|
starts_on: date | None
|
|
ends_on: date | None
|
|
is_deleted: bool
|
|
created_by: str | None
|
|
updated_by: str | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class LoyaltyAuditLogRead(ORMBase):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
entity_type: str
|
|
entity_id: UUID
|
|
action: str
|
|
actor_user_id: str | None
|
|
changes: dict | None
|
|
message: str | None
|
|
created_at: datetime
|