TorbatYar/backend/services/loyalty/app/schemas/foundation.py
Mortezakoohjani 071c484530 Ship Loyalty phases 7.2-7.6 (points through wallet) with production deploy.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 21:27:22 +03:30

382 lines
10 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
stock_limit: int | None = Field(default=None, ge=0)
max_per_member: int | None = Field(default=None, ge=0)
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
stock_limit: int | None = Field(default=None, ge=0)
max_per_member: int | None = Field(default=None, ge=0)
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
stock_limit: int | None
redeemed_count: int
max_per_member: int | 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