185 lines
6.6 KiB
Python
185 lines
6.6 KiB
Python
# /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"<CommissionRule(id={self.id}, type='{self.rule_type}', "
|
|
f"tier='{self.tier}', region='{self.region_code}', "
|
|
f"active={self.is_active})>"
|
|
)
|