# /opt/docker/dev/service_finder/backend/app/schemas/commission.py """ Pydantic schemas for CommissionRule CRUD operations. THOUGHT PROCESS: - CommissionRuleCreate: All fields required for creating a new rule. Uses Optional for type-specific fields (L1 vs L2). - CommissionRuleUpdate: All fields optional for partial updates. - CommissionRuleResponse: Full read model with from_attributes=True for ORM compatibility. - CommissionRuleListResponse: Paginated wrapper for admin listing. """ from pydantic import BaseModel, Field from datetime import date, datetime from typing import Optional, List from enum import Enum class CommissionRuleTypeEnum(str, Enum): """Jutalék típus: L1 = egyéni jutalom, L2 = céges jutalék.""" L1_REWARD = "L1_REWARD" L2_COMMISSION = "L2_COMMISSION" class CommissionTierEnum(str, Enum): """Jutalék szintek.""" STANDARD = "STANDARD" VIP = "VIP" PLATINUM = "PLATINUM" ENTERPRISE = "ENTERPRISE" CONTRACTED = "CONTRACTED" class CommissionRuleCreate(BaseModel): """Schema új jutalék szabály létrehozásához.""" rule_type: CommissionRuleTypeEnum tier: CommissionTierEnum = CommissionTierEnum.STANDARD region_code: str = "GLOBAL" xp_reward: Optional[int] = None credit_reward: Optional[int] = None commission_percent: Optional[float] = None upline_commission_percent: Optional[float] = None renewal_commission_percent: Optional[float] = None commission_max_amount: Optional[float] = None # Phase 2: Szintenkénti plafonok (NULL/0 = korlátlan, fallback: commission_max_amount) gen1_max_amount: Optional[float] = None gen2_max_amount: Optional[float] = None gen2_renewal_percent: Optional[float] = None is_campaign: bool = False start_date: Optional[date] = None end_date: Optional[date] = None name: str description: Optional[str] = None is_active: bool = True # Phase 3: Conflict override — admin acknowledges overlapping rule force_override: bool = False class CommissionRuleUpdate(BaseModel): """Schema meglévő jutalék szabály módosításához (minden mező opcionális).""" rule_type: Optional[CommissionRuleTypeEnum] = None tier: Optional[CommissionTierEnum] = None region_code: Optional[str] = None xp_reward: Optional[int] = None credit_reward: Optional[int] = None commission_percent: Optional[float] = None upline_commission_percent: Optional[float] = None renewal_commission_percent: Optional[float] = None commission_max_amount: Optional[float] = None # Phase 2: Szintenkénti plafonok gen1_max_amount: Optional[float] = None gen2_max_amount: Optional[float] = None gen2_renewal_percent: Optional[float] = None is_campaign: Optional[bool] = None start_date: Optional[date] = None end_date: Optional[date] = None name: Optional[str] = None description: Optional[str] = None is_active: Optional[bool] = None # Phase 3: Conflict override — admin acknowledges overlapping rule force_override: bool = False class CommissionRuleResponse(BaseModel): """Schema jutalék szabály adatainak lekéréséhez.""" id: int rule_type: CommissionRuleTypeEnum tier: CommissionTierEnum region_code: str xp_reward: Optional[int] = None credit_reward: Optional[int] = None commission_percent: Optional[float] = None upline_commission_percent: Optional[float] = None renewal_commission_percent: Optional[float] = None commission_max_amount: Optional[float] = None # Phase 2: Szintenkénti plafonok gen1_max_amount: Optional[float] = None gen2_max_amount: Optional[float] = None gen2_renewal_percent: Optional[float] = None is_campaign: bool start_date: Optional[date] = None end_date: Optional[date] = None name: str description: Optional[str] = None is_active: bool created_by: Optional[int] = None created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class CommissionRuleListResponse(BaseModel): """Paginated list response for admin commission rules.""" items: List[CommissionRuleResponse] total: int page: int page_size: int class CommissionDistributionRequest(BaseModel): """ Request schema for the 2-level MLM commission distribution engine. When a referred company makes a purchase, this request triggers the distribution logic: - Gen1 (direct referrer) receives commission_percent - Gen2 (upline / Gen1's referrer) receives upline_commission_percent """ buyer_user_id: int = Field(..., description="The user ID who made the purchase") transaction_amount: float = Field(..., gt=0, description="The purchase/subscription amount") transaction_date: date = Field(..., description="Date of the transaction") region_code: str = Field("GLOBAL", max_length=10, description="Region code for rule matching") # Phase 2: Renewal flag - TRUE = renewal transaction, FALSE = first-time purchase is_renewal: bool = Field(False, description="TRUE = renewal transaction, FALSE = first-time purchase") class CommissionDistributionItem(BaseModel): """Single commission payout item for one level.""" level: int = Field(..., description="1 = Gen1 (direct referrer), 2 = Gen2 (upline)") user_id: int = Field(..., description="The user ID receiving the commission") commission_percent: float = Field(..., description="The applied commission percentage") commission_amount: float = Field(..., description="The calculated commission amount") rule_id: int = Field(..., description="The CommissionRule ID that was applied") class CommissionDistributionResponse(BaseModel): """ Response schema for the 2-level MLM commission distribution. Contains the payout breakdown for both Gen1 and Gen2 levels. """ transaction_amount: float items: List[CommissionDistributionItem] total_commission: float = Field(..., description="Sum of all commission payouts") class ConflictingRuleInfo(BaseModel): """Information about a conflicting rule returned in a 409 response.""" id: int name: str rule_type: CommissionRuleTypeEnum tier: CommissionTierEnum region_code: str is_campaign: bool is_active: bool class ConflictResponse(BaseModel): """ Response schema returned when a conflicting active rule is detected and force_override was not set. """ detail: str = "A conflicting active rule already exists for this combination." conflicting_rule: ConflictingRuleInfo