L1L2 gen1Gen2 kialakítása

This commit is contained in:
Roo
2026-07-24 09:56:21 +00:00
parent 4594de6c11
commit 33c4d793db
49 changed files with 4108 additions and 127 deletions

View File

@@ -30,6 +30,9 @@ from .staged_data import (
from .service_request import ServiceRequest
# Commission Rules (Phase 2)
from .commission import CommissionRule, CommissionRuleType, CommissionTier
__all__ = [
"Organization",
"OrganizationMember",
@@ -52,4 +55,7 @@ __all__ = [
"LocationType",
"StagedVehicleData",
"ServiceRequest",
"CommissionRule",
"CommissionRuleType",
"CommissionTier",
]

View File

@@ -0,0 +1,184 @@
# /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})>"
)

View File

@@ -111,12 +111,9 @@ class ExpertiseTag(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
key: Mapped[str] = mapped_column(String(50), unique=True, index=True)
name_hu: Mapped[Optional[str]] = mapped_column(String(100))
name_en: Mapped[Optional[str]] = mapped_column(String(100))
# --- 🏗️ i18n JSONB Migration (Phase 1) ---
# --- 🏗️ i18n JSONB Migration (Phase 1 - Complete) ---
name_i18n: Mapped[dict] = mapped_column(JSONB, nullable=False, server_default=text("'{\"hu\": \"\"}'::jsonb"))
description_i18n: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
name_translations: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
category: Mapped[Optional[str]] = mapped_column(String(30), index=True)
# --- 🏗️ 4-LEVEL HIERARCHY (2026-06-17) ---