# /opt/docker/dev/service_finder/backend/app/models/marketplace/commission.py """ CommissionRule: Dynamic, tiered, time-bound commission and reward rules. Supports: - L1 rewards (XP/credits for individual referrals) - L2 commissions (% for company-to-company purchases) - Tier-based multipliers (Standard, VIP, Platinum) - Regional overrides (HU, Global, etc.) - Time-bound promotional campaigns THOUGHT PROCESS: - Single table for both L1 and L2 avoids join complexity; nullable fields differentiate type-specific values. - region_code as simple string (ISO 3166-1 alpha-2) follows existing pattern (see InsuranceProvider.country_code). - start_date/end_date as Date (day-granular campaigns, no timezone issues). - is_campaign boolean enables quick filtering without date parsing. - UniqueConstraint on 5 columns prevents duplicate rules for the same type/tier/region/date combination. - metadata_json JSONB for future-proof UI fields (colors, icons, tooltips). """ import enum from datetime import datetime, date from typing import Optional from sqlalchemy import ( String, Integer, Float, Boolean, DateTime, Date, Text, Enum as SQLEnum, Numeric, UniqueConstraint, text ) from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB from sqlalchemy.sql import func from app.database import Base class CommissionRuleType(str, enum.Enum): """ Két elsődleges jutalom típus: - L1_REWARD: XP/kredit jutalom egyéni ajánlóknak (referral) - L2_COMMISSION: Százalékos jutalék céges vásárlások után """ L1_REWARD = "L1_REWARD" # XP/credits for individual referrers L2_COMMISSION = "L2_COMMISSION" # % commission for company purchases class CommissionTier(str, enum.Enum): """Jutalék szintek / rétegek.""" STANDARD = "STANDARD" VIP = "VIP" PLATINUM = "PLATINUM" ENTERPRISE = "ENTERPRISE" CONTRACTED = "CONTRACTED" class CommissionRule(Base): """ Dinamikus jutalék/jutalom szabályok. Minden szabály egy adott típushoz (L1/L2), szinthez (tier), régióhoz és opcionális időablakhoz tartozik. A lekérdező motor a tranzakció időpontjában érvényes, legspecifikusabb szabályt alkalmazza. """ __tablename__ = "commission_rules" __table_args__ = ( UniqueConstraint( 'rule_type', 'tier', 'region_code', 'start_date', 'end_date', name='uix_commission_rule_unique' ), {"schema": "marketplace", "extend_existing": True} ) id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) # --- Rule Classification --- rule_type: Mapped[CommissionRuleType] = mapped_column( SQLEnum(CommissionRuleType, name="commission_rule_type", schema="marketplace"), nullable=False, index=True, comment="L1_REWARD = XP/kredit jutalom, L2_COMMISSION = % jutalék" ) tier: Mapped[CommissionTier] = mapped_column( SQLEnum(CommissionTier, name="commission_tier", schema="marketplace"), nullable=False, default=CommissionTier.STANDARD, index=True, comment="Jutalék szint (STANDARD, VIP, PLATINUM, ENTERPRISE)" ) # --- Regional Scope --- region_code: Mapped[str] = mapped_column( String(10), nullable=False, default="GLOBAL", index=True, comment="ISO 3166-1 alpha-2 országkód (pl. 'HU', 'DE') vagy 'GLOBAL'" ) # --- Reward / Commission Values --- # L1_REWARD fields xp_reward: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, default=0, comment="L1: XP jutalom értéke" ) credit_reward: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, default=0, comment="L1: Kredit jutalom értéke" ) # L2_COMMISSION fields commission_percent: Mapped[Optional[float]] = mapped_column( Numeric(5, 2), nullable=True, default=0.00, comment="L2: Jutalék százalék (pl. 5.00 = 5%)" ) upline_commission_percent: Mapped[Optional[float]] = mapped_column( Numeric(5, 2), nullable=True, default=0.00, comment="L2: Upline (Gen2) jutalék százalék — a közvetlen ajánló feletti szintnek járó jutalék (pl. 2.00 = 2%)" ) renewal_commission_percent: Mapped[Optional[float]] = mapped_column( Numeric(5, 2), nullable=True, default=0.00, comment="L2: Megújítási jutalék százalék (pl. 2.50 = 2.5%) - havi/éves előfizetés megújításakor járó jutalék" ) commission_max_amount: Mapped[Optional[float]] = mapped_column( Numeric(18, 2), nullable=True, comment="L2: Maximális jutalék összeg (opcionális felső korlát)" ) # --- Time-Bound Campaign --- is_campaign: Mapped[bool] = mapped_column( Boolean, default=False, server_default=text("false"), comment="TRUE = időkorlátos kampány, FALSE = állandó szabály" ) start_date: Mapped[Optional[date]] = mapped_column( Date, nullable=True, index=True, comment="Kampány kezdő dátuma (NULL = azonnal érvényes)" ) end_date: Mapped[Optional[date]] = mapped_column( Date, nullable=True, index=True, comment="Kampány záró dátuma (NULL = nincs lejárat)" ) # --- Metadata --- name: Mapped[str] = mapped_column( String(200), nullable=False, comment="Emberi olvasható szabály név (pl. 'HU VIP Nyári Kampány')" ) description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="Részletes leírás a szabályról" ) is_active: Mapped[bool] = mapped_column( Boolean, default=True, server_default=text("true"), index=True, comment="TRUE = aktív és használható, FALSE = letiltva" ) # --- Audit Trail --- created_by: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, comment="Létrehozó admin felhasználó ID" ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) # --- Extensibility --- metadata_json: Mapped[Optional[dict]] = mapped_column( JSONB, nullable=True, server_default=text("'{}'::jsonb"), comment="Bővíthető metaadatok (pl. campaign banner URL, notes)" ) def __repr__(self) -> str: return ( f"" )