L1L2 gen1Gen2 kialakítása
This commit is contained in:
528
backend/app/services/commission_service.py
Normal file
528
backend/app/services/commission_service.py
Normal file
@@ -0,0 +1,528 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/services/commission_service.py
|
||||
"""
|
||||
CommissionRule service layer — CRUD + Priority Resolution Engine + 2-Level MLM Distribution.
|
||||
|
||||
THOUGHT PROCESS:
|
||||
- The Priority Algorithm (Campaign > Region > Tier) is implemented in
|
||||
get_active_rule() using SQLAlchemy case() expressions for server-side
|
||||
ordering. This avoids loading all matching rules into Python memory.
|
||||
- Campaign rules (is_campaign=True) always sort before permanent rules.
|
||||
- Specific region match (e.g. "HU") sorts before "GLOBAL".
|
||||
- Higher tiers (PLATINUM=0, VIP=1, STANDARD=2, ENTERPRISE=3) sort first.
|
||||
- Soft-delete is handled via is_active=False (not actual row deletion).
|
||||
- The service uses async/await throughout for FastAPI compatibility.
|
||||
- 2-Level MLM: distribute_commission() resolves Gen1 (direct referrer) and
|
||||
Gen2 (upline) payouts using commission_percent and upline_commission_percent.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from typing import Optional, List, Sequence
|
||||
from sqlalchemy import select, func, case, or_, and_, delete as sa_delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.models.marketplace.commission import (
|
||||
CommissionRule,
|
||||
CommissionRuleType,
|
||||
CommissionTier,
|
||||
)
|
||||
from app.models.identity.identity import User
|
||||
from app.schemas.commission import (
|
||||
CommissionRuleCreate,
|
||||
CommissionRuleUpdate,
|
||||
CommissionDistributionRequest,
|
||||
CommissionDistributionItem,
|
||||
CommissionDistributionResponse,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# CRUD Operations
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def create_commission_rule(
|
||||
db: AsyncSession,
|
||||
data: CommissionRuleCreate,
|
||||
admin_user_id: int,
|
||||
) -> CommissionRule:
|
||||
"""
|
||||
Create a new commission rule with validation.
|
||||
|
||||
Args:
|
||||
db: Database session.
|
||||
data: Pydantic schema with rule data.
|
||||
admin_user_id: ID of the admin creating the rule.
|
||||
|
||||
Returns:
|
||||
The newly created CommissionRule instance.
|
||||
"""
|
||||
rule = CommissionRule(
|
||||
rule_type=CommissionRuleType(data.rule_type.value),
|
||||
tier=CommissionTier(data.tier.value),
|
||||
region_code=data.region_code,
|
||||
xp_reward=data.xp_reward,
|
||||
credit_reward=data.credit_reward,
|
||||
commission_percent=data.commission_percent,
|
||||
upline_commission_percent=data.upline_commission_percent,
|
||||
renewal_commission_percent=data.renewal_commission_percent,
|
||||
commission_max_amount=data.commission_max_amount,
|
||||
is_campaign=data.is_campaign,
|
||||
start_date=data.start_date,
|
||||
end_date=data.end_date,
|
||||
name=data.name,
|
||||
description=data.description,
|
||||
is_active=data.is_active,
|
||||
created_by=admin_user_id,
|
||||
)
|
||||
db.add(rule)
|
||||
await db.commit()
|
||||
await db.refresh(rule)
|
||||
logger.info(
|
||||
"Commission rule created: id=%d type=%s tier=%s region=%s",
|
||||
rule.id, rule.rule_type, rule.tier, rule.region_code,
|
||||
)
|
||||
return rule
|
||||
|
||||
|
||||
async def update_commission_rule(
|
||||
db: AsyncSession,
|
||||
rule_id: int,
|
||||
data: CommissionRuleUpdate,
|
||||
) -> Optional[CommissionRule]:
|
||||
"""
|
||||
Partially update an existing commission rule.
|
||||
|
||||
Only the fields explicitly set in the update schema are applied.
|
||||
Returns None if the rule does not exist.
|
||||
"""
|
||||
stmt = select(CommissionRule).where(CommissionRule.id == rule_id)
|
||||
result = await db.execute(stmt)
|
||||
rule = result.scalar_one_or_none()
|
||||
if not rule:
|
||||
return None
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
# Map enum fields from schema enums to model enums
|
||||
if field == "rule_type" and value is not None:
|
||||
setattr(rule, field, CommissionRuleType(value.value))
|
||||
elif field == "tier" and value is not None:
|
||||
setattr(rule, field, CommissionTier(value.value))
|
||||
else:
|
||||
setattr(rule, field, value)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(rule)
|
||||
logger.info("Commission rule updated: id=%d", rule.id)
|
||||
return rule
|
||||
|
||||
|
||||
async def deactivate_commission_rule(
|
||||
db: AsyncSession,
|
||||
rule_id: int,
|
||||
) -> Optional[CommissionRule]:
|
||||
"""
|
||||
Soft-delete a commission rule by setting is_active=False.
|
||||
|
||||
Returns the deactivated rule, or None if not found.
|
||||
"""
|
||||
stmt = select(CommissionRule).where(CommissionRule.id == rule_id)
|
||||
result = await db.execute(stmt)
|
||||
rule = result.scalar_one_or_none()
|
||||
if not rule:
|
||||
return None
|
||||
|
||||
rule.is_active = False
|
||||
await db.commit()
|
||||
await db.refresh(rule)
|
||||
logger.info("Commission rule deactivated: id=%d", rule.id)
|
||||
return rule
|
||||
|
||||
|
||||
async def hard_delete_commission_rule(
|
||||
db: AsyncSession,
|
||||
rule_id: int,
|
||||
) -> bool:
|
||||
"""
|
||||
Permanently delete a commission rule (admin-only, use with caution).
|
||||
|
||||
Returns True if deleted, False if not found.
|
||||
"""
|
||||
stmt = select(CommissionRule).where(CommissionRule.id == rule_id)
|
||||
result = await db.execute(stmt)
|
||||
rule = result.scalar_one_or_none()
|
||||
if not rule:
|
||||
return False
|
||||
|
||||
await db.delete(rule)
|
||||
await db.commit()
|
||||
logger.info("Commission rule hard-deleted: id=%d", rule_id)
|
||||
return True
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Priority Resolution Engine
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def get_active_rule(
|
||||
db: AsyncSession,
|
||||
rule_type: CommissionRuleType,
|
||||
tier: CommissionTier,
|
||||
region_code: str,
|
||||
transaction_date: date,
|
||||
) -> Optional[CommissionRule]:
|
||||
"""
|
||||
Priority Resolution Algorithm: Find the most specific active rule.
|
||||
|
||||
Resolution order (highest priority first):
|
||||
1. Campaign rules (is_campaign=True) over permanent rules
|
||||
2. Most specific region match (e.g. "HU" > "GLOBAL")
|
||||
3. Highest tier match (PLATINUM > VIP > STANDARD > ENTERPRISE)
|
||||
|
||||
Falls back to GLOBAL/STANDARD default if no specific match exists.
|
||||
|
||||
Args:
|
||||
db: Database session.
|
||||
rule_type: L1_REWARD or L2_COMMISSION.
|
||||
tier: The referrer/buyer's tier.
|
||||
region_code: ISO 3166-1 alpha-2 region code.
|
||||
transaction_date: The date of the transaction.
|
||||
|
||||
Returns:
|
||||
The best matching CommissionRule, or None if no rule exists.
|
||||
"""
|
||||
# Build priority expressions using SQLAlchemy case()
|
||||
# Lower numeric value = higher priority
|
||||
campaign_priority = case(
|
||||
(CommissionRule.is_campaign == True, 0), # campaigns first
|
||||
else_=1
|
||||
)
|
||||
|
||||
region_priority = case(
|
||||
(CommissionRule.region_code == region_code, 0), # exact region match
|
||||
(CommissionRule.region_code == "GLOBAL", 1), # global fallback
|
||||
else_=2
|
||||
)
|
||||
|
||||
# Tier ordering: PLATINUM (0) > VIP (1) > STANDARD (2) > ENTERPRISE (3) > CONTRACTED (4)
|
||||
tier_order = {
|
||||
CommissionTier.PLATINUM: 0,
|
||||
CommissionTier.VIP: 1,
|
||||
CommissionTier.STANDARD: 2,
|
||||
CommissionTier.ENTERPRISE: 3,
|
||||
CommissionTier.CONTRACTED: 4,
|
||||
}
|
||||
tier_priority = case(
|
||||
*[(CommissionRule.tier == k, v) for k, v in tier_order.items()],
|
||||
else_=99
|
||||
)
|
||||
|
||||
stmt = (
|
||||
select(CommissionRule)
|
||||
.where(
|
||||
CommissionRule.rule_type == rule_type,
|
||||
CommissionRule.is_active == True,
|
||||
# Date range: NULL means "always valid"
|
||||
or_(
|
||||
CommissionRule.start_date.is_(None),
|
||||
CommissionRule.start_date <= transaction_date
|
||||
),
|
||||
or_(
|
||||
CommissionRule.end_date.is_(None),
|
||||
CommissionRule.end_date >= transaction_date
|
||||
),
|
||||
# Region: match exact or GLOBAL
|
||||
or_(
|
||||
CommissionRule.region_code == region_code,
|
||||
CommissionRule.region_code == "GLOBAL"
|
||||
),
|
||||
# Tier: match exact or STANDARD fallback
|
||||
or_(
|
||||
CommissionRule.tier == tier,
|
||||
CommissionRule.tier == CommissionTier.STANDARD
|
||||
)
|
||||
)
|
||||
.order_by(campaign_priority, region_priority, tier_priority)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rule = result.scalar_one_or_none()
|
||||
|
||||
if rule:
|
||||
logger.debug(
|
||||
"Active rule resolved: id=%d type=%s tier=%s region=%s campaign=%s",
|
||||
rule.id, rule.rule_type, rule.tier, rule.region_code, rule.is_campaign,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"No active rule found for type=%s tier=%s region=%s date=%s",
|
||||
rule_type, tier, region_code, transaction_date,
|
||||
)
|
||||
|
||||
return rule
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Admin Listing with Filters & Pagination
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def list_rules(
|
||||
db: AsyncSession,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
rule_type: Optional[CommissionRuleType] = None,
|
||||
tier: Optional[CommissionTier] = None,
|
||||
region_code: Optional[str] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
is_campaign: Optional[bool] = None,
|
||||
) -> tuple[Sequence[CommissionRule], int]:
|
||||
"""
|
||||
List commission rules with optional filters and pagination.
|
||||
|
||||
Returns:
|
||||
Tuple of (rules_list, total_count).
|
||||
"""
|
||||
# Build base query
|
||||
base_query = select(CommissionRule)
|
||||
|
||||
# Apply filters
|
||||
conditions = []
|
||||
if rule_type is not None:
|
||||
conditions.append(CommissionRule.rule_type == rule_type)
|
||||
if tier is not None:
|
||||
conditions.append(CommissionRule.tier == tier)
|
||||
if region_code is not None:
|
||||
conditions.append(CommissionRule.region_code == region_code)
|
||||
if is_active is not None:
|
||||
conditions.append(CommissionRule.is_active == is_active)
|
||||
if is_campaign is not None:
|
||||
conditions.append(CommissionRule.is_campaign == is_campaign)
|
||||
|
||||
if conditions:
|
||||
base_query = base_query.where(and_(*conditions))
|
||||
|
||||
# Get total count
|
||||
count_query = select(func.count()).select_from(base_query.subquery())
|
||||
count_result = await db.execute(count_query)
|
||||
total = count_result.scalar() or 0
|
||||
|
||||
# Apply pagination and ordering
|
||||
offset = (page - 1) * page_size
|
||||
stmt = (
|
||||
base_query
|
||||
.order_by(CommissionRule.updated_at.desc())
|
||||
.offset(offset)
|
||||
.limit(page_size)
|
||||
)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rules = result.scalars().all()
|
||||
|
||||
return rules, total
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 2-Level MLM Commission Distribution Engine
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def _lookup_user(
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
) -> Optional[User]:
|
||||
"""Look up a user by ID, return None if not found or deleted."""
|
||||
stmt = select(User).where(
|
||||
User.id == user_id,
|
||||
User.is_deleted == False,
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def _calculate_commission(
|
||||
amount: float,
|
||||
percent: Optional[float],
|
||||
max_amount: Optional[float],
|
||||
) -> float:
|
||||
"""
|
||||
Calculate commission amount from a percentage, capped by max_amount.
|
||||
|
||||
Args:
|
||||
amount: The transaction/subscription amount.
|
||||
percent: The commission percentage (e.g. 5.00 = 5%).
|
||||
max_amount: Optional cap on the commission payout.
|
||||
|
||||
Returns:
|
||||
The calculated commission amount.
|
||||
"""
|
||||
if not percent or percent <= 0:
|
||||
return 0.0
|
||||
|
||||
commission = float(Decimal(str(amount)) * Decimal(str(percent)) / Decimal("100"))
|
||||
|
||||
if max_amount is not None and max_amount > 0:
|
||||
commission = min(commission, float(max_amount))
|
||||
|
||||
return round(commission, 2)
|
||||
|
||||
|
||||
async def distribute_commission(
|
||||
db: AsyncSession,
|
||||
request: CommissionDistributionRequest,
|
||||
) -> CommissionDistributionResponse:
|
||||
"""
|
||||
2-Level MLM Commission Distribution Engine.
|
||||
|
||||
When a referred company makes a purchase, this function:
|
||||
1. Looks up the buyer and their referrer (Gen1)
|
||||
2. Finds the active commission rule for Gen1's tier/region
|
||||
3. Calculates Gen1's commission using commission_percent
|
||||
4. If Gen1 has a referrer (Gen2/upline), calculates Gen2's commission
|
||||
using upline_commission_percent from the SAME rule
|
||||
|
||||
Args:
|
||||
db: Database session.
|
||||
request: Distribution request with buyer_user_id, transaction_amount,
|
||||
transaction_date, and region_code.
|
||||
|
||||
Returns:
|
||||
CommissionDistributionResponse with payout breakdown for Gen1 and Gen2.
|
||||
"""
|
||||
items: List[CommissionDistributionItem] = []
|
||||
total_commission = 0.0
|
||||
|
||||
# 1. Look up the buyer
|
||||
buyer = await _lookup_user(db, request.buyer_user_id)
|
||||
if not buyer:
|
||||
logger.warning(
|
||||
"Commission distribution: buyer user %d not found or deleted",
|
||||
request.buyer_user_id,
|
||||
)
|
||||
return CommissionDistributionResponse(
|
||||
transaction_amount=request.transaction_amount,
|
||||
items=[],
|
||||
total_commission=0.0,
|
||||
)
|
||||
|
||||
# 2. Find Gen1 (the buyer's direct referrer)
|
||||
gen1_user_id = buyer.referred_by_id
|
||||
if not gen1_user_id:
|
||||
logger.info(
|
||||
"Commission distribution: buyer %d has no referrer (Gen1)",
|
||||
request.buyer_user_id,
|
||||
)
|
||||
return CommissionDistributionResponse(
|
||||
transaction_amount=request.transaction_amount,
|
||||
items=[],
|
||||
total_commission=0.0,
|
||||
)
|
||||
|
||||
gen1 = await _lookup_user(db, gen1_user_id)
|
||||
if not gen1:
|
||||
logger.warning(
|
||||
"Commission distribution: Gen1 user %d not found or deleted",
|
||||
gen1_user_id,
|
||||
)
|
||||
return CommissionDistributionResponse(
|
||||
transaction_amount=request.transaction_amount,
|
||||
items=[],
|
||||
total_commission=0.0,
|
||||
)
|
||||
|
||||
# 3. Resolve the active commission rule for Gen1
|
||||
gen1_tier = CommissionTier(gen1.commission_tier) if hasattr(gen1, 'commission_tier') and gen1.commission_tier else CommissionTier.STANDARD
|
||||
try:
|
||||
gen1_tier_enum = CommissionTier(gen1_tier)
|
||||
except ValueError:
|
||||
gen1_tier_enum = CommissionTier.STANDARD
|
||||
|
||||
rule = await get_active_rule(
|
||||
db,
|
||||
rule_type=CommissionRuleType.L2_COMMISSION,
|
||||
tier=gen1_tier_enum,
|
||||
region_code=request.region_code,
|
||||
transaction_date=request.transaction_date,
|
||||
)
|
||||
|
||||
if not rule:
|
||||
logger.warning(
|
||||
"Commission distribution: no active L2_COMMISSION rule for "
|
||||
"tier=%s region=%s date=%s",
|
||||
gen1_tier_enum, request.region_code, request.transaction_date,
|
||||
)
|
||||
return CommissionDistributionResponse(
|
||||
transaction_amount=request.transaction_amount,
|
||||
items=[],
|
||||
total_commission=0.0,
|
||||
)
|
||||
|
||||
# 4. Calculate Gen1 commission
|
||||
gen1_amount = await _calculate_commission(
|
||||
request.transaction_amount,
|
||||
float(rule.commission_percent) if rule.commission_percent else None,
|
||||
float(rule.commission_max_amount) if rule.commission_max_amount else None,
|
||||
)
|
||||
|
||||
if gen1_amount > 0:
|
||||
items.append(CommissionDistributionItem(
|
||||
level=1,
|
||||
user_id=gen1.id,
|
||||
commission_percent=float(rule.commission_percent) if rule.commission_percent else 0.0,
|
||||
commission_amount=gen1_amount,
|
||||
rule_id=rule.id,
|
||||
))
|
||||
total_commission += gen1_amount
|
||||
|
||||
# 5. Find Gen2 (Gen1's referrer / upline)
|
||||
gen2_user_id = gen1.referred_by_id
|
||||
if gen2_user_id:
|
||||
gen2 = await _lookup_user(db, gen2_user_id)
|
||||
if gen2:
|
||||
# 6. Calculate Gen2 commission using upline_commission_percent
|
||||
gen2_amount = await _calculate_commission(
|
||||
request.transaction_amount,
|
||||
float(rule.upline_commission_percent) if rule.upline_commission_percent else None,
|
||||
float(rule.commission_max_amount) if rule.commission_max_amount else None,
|
||||
)
|
||||
|
||||
if gen2_amount > 0:
|
||||
items.append(CommissionDistributionItem(
|
||||
level=2,
|
||||
user_id=gen2.id,
|
||||
commission_percent=float(rule.upline_commission_percent) if rule.upline_commission_percent else 0.0,
|
||||
commission_amount=gen2_amount,
|
||||
rule_id=rule.id,
|
||||
))
|
||||
total_commission += gen2_amount
|
||||
else:
|
||||
logger.warning(
|
||||
"Commission distribution: Gen2 user %d not found or deleted",
|
||||
gen2_user_id,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Commission distributed: buyer=%d gen1=%d(%.2f%%) gen2=%d(%.2f%%) "
|
||||
"amount=%.2f total_commission=%.2f rule=%d",
|
||||
request.buyer_user_id,
|
||||
gen1.id,
|
||||
float(rule.commission_percent) if rule.commission_percent else 0,
|
||||
gen2_user_id or 0,
|
||||
float(rule.upline_commission_percent) if rule.upline_commission_percent else 0,
|
||||
request.transaction_amount,
|
||||
total_commission,
|
||||
rule.id,
|
||||
)
|
||||
|
||||
return CommissionDistributionResponse(
|
||||
transaction_amount=request.transaction_amount,
|
||||
items=items,
|
||||
total_commission=round(total_commission, 2),
|
||||
)
|
||||
@@ -479,8 +479,7 @@ async def search_providers(
|
||||
select(
|
||||
ServiceProfile.organization_id,
|
||||
ExpertiseTag.id,
|
||||
ExpertiseTag.name_hu,
|
||||
ExpertiseTag.name_en,
|
||||
ExpertiseTag.name_i18n,
|
||||
ExpertiseTag.level,
|
||||
ExpertiseTag.key,
|
||||
)
|
||||
@@ -499,8 +498,7 @@ async def search_providers(
|
||||
org_categories_map[org_id] = []
|
||||
org_categories_map[org_id].append({
|
||||
"id": cat_row.id,
|
||||
"name_hu": cat_row.name_hu,
|
||||
"name_en": cat_row.name_en,
|
||||
"name_i18n": cat_row.name_i18n,
|
||||
"level": cat_row.level,
|
||||
"key": cat_row.key,
|
||||
})
|
||||
@@ -518,8 +516,7 @@ async def search_providers(
|
||||
categories_out = [
|
||||
CategoryInfo(
|
||||
id=cat["id"],
|
||||
name_hu=cat["name_hu"],
|
||||
name_en=cat["name_en"],
|
||||
name_i18n=cat["name_i18n"],
|
||||
level=cat["level"],
|
||||
key=cat["key"],
|
||||
)
|
||||
@@ -871,8 +868,7 @@ async def _create_new_tags(
|
||||
existing_stmt = select(ExpertiseTag).where(
|
||||
or_(
|
||||
ExpertiseTag.key == _slugify(tag_name),
|
||||
ExpertiseTag.name_hu == tag_name,
|
||||
ExpertiseTag.name_en == tag_name,
|
||||
cast(ExpertiseTag.name_i18n, String).ilike(f"%{tag_name}%"),
|
||||
)
|
||||
)
|
||||
existing_result = await db.execute(existing_stmt)
|
||||
@@ -890,15 +886,13 @@ async def _create_new_tags(
|
||||
new_key = _slugify(tag_name)
|
||||
new_tag = ExpertiseTag(
|
||||
key=new_key,
|
||||
name_hu=tag_name,
|
||||
name_en=tag_name,
|
||||
name_i18n={"hu": tag_name, "en": tag_name},
|
||||
level=3,
|
||||
is_official=False,
|
||||
category="user_created",
|
||||
parent_id=None,
|
||||
path=None,
|
||||
search_keywords=[tag_name.lower()],
|
||||
description=f"User-created tag: {tag_name}",
|
||||
)
|
||||
db.add(new_tag)
|
||||
await db.flush()
|
||||
|
||||
@@ -5,14 +5,24 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.identity import User, Person, SocialAccount, UserRole
|
||||
from app.services.security_service import security_service
|
||||
from app.core.security import generate_secure_slug
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SocialAuthService:
|
||||
@staticmethod
|
||||
async def get_or_create_social_user(db: AsyncSession, provider: str, social_id: str, email: str, first_name: str, last_name: str):
|
||||
"""
|
||||
LOGIKA MEGŐRIZVE: Step 1 regisztráció slug és flotta nélkül.
|
||||
async def get_or_create_social_user(
|
||||
db: AsyncSession,
|
||||
provider: str,
|
||||
social_id: str,
|
||||
email: str,
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
referred_by_code: str = None
|
||||
):
|
||||
"""
|
||||
LOGIKA MEGŐRIZVE: Step 1 regisztráció slug és flotta nélkül.
|
||||
Támogatja a meghívó kód (referral code) átvételét Google SSO regisztrációnál.
|
||||
"""
|
||||
# 1. Meglévő fiók ellenőrzése
|
||||
stmt = select(SocialAccount).where(SocialAccount.provider == provider, SocialAccount.social_id == social_id)
|
||||
@@ -26,11 +36,37 @@ class SocialAuthService:
|
||||
user = (await db.execute(stmt_u)).scalar_one_or_none()
|
||||
|
||||
if not user:
|
||||
new_person = Person(first_name=first_name or "Social", last_name=last_name or "User", is_active=False)
|
||||
# Meghívó keresése referral kód alapján
|
||||
referred_by_id = None
|
||||
if referred_by_code:
|
||||
referrer_stmt = select(User).where(User.referral_code == referred_by_code)
|
||||
referrer = (await db.execute(referrer_stmt)).scalar_one_or_none()
|
||||
if referrer:
|
||||
referred_by_id = referrer.id
|
||||
logger.info(f"Social user {email} referred by {referrer.email} (ID: {referrer.id})")
|
||||
else:
|
||||
logger.warning(f"Referral code '{referred_by_code}' not found for social registration, ignoring.")
|
||||
|
||||
new_person = Person(
|
||||
first_name=first_name or "Social",
|
||||
last_name=last_name or "User",
|
||||
is_active=False,
|
||||
identity_docs={},
|
||||
ice_contact={}
|
||||
)
|
||||
db.add(new_person)
|
||||
await db.flush()
|
||||
|
||||
user = User(email=email, person_id=new_person.id, role=UserRole.USER, is_active=False)
|
||||
referral_code = generate_secure_slug(8).upper()
|
||||
|
||||
user = User(
|
||||
email=email,
|
||||
person_id=new_person.id,
|
||||
role=UserRole.USER,
|
||||
is_active=False,
|
||||
referral_code=referral_code,
|
||||
referred_by_id=referred_by_id
|
||||
)
|
||||
db.add(user)
|
||||
await db.flush()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user