"""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