mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 15:29:36 +00:00
232 lines
8.2 KiB
Python
232 lines
8.2 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from typing import Optional
|
|
from datetime import datetime, timezone
|
|
from pydantic import BaseModel, ConfigDict
|
|
from uuid import UUID
|
|
import uuid
|
|
|
|
from app.database import get_db
|
|
from app.models.affiliate import AffiliateMarketer, AffiliatePerformance, AffiliateDeal, AffiliateStatus
|
|
|
|
router = APIRouter(prefix="/affiliates", tags=["affiliates"])
|
|
|
|
|
|
# ─── Schemas ─────────────────────────────────────────────
|
|
|
|
class AffiliateRegisterRequest(BaseModel):
|
|
full_name: str
|
|
full_name_ar: Optional[str] = None
|
|
email: str
|
|
phone: str
|
|
whatsapp: Optional[str] = None
|
|
city: Optional[str] = None
|
|
national_id: Optional[str] = None
|
|
|
|
|
|
class AffiliateResponse(BaseModel):
|
|
id: UUID
|
|
full_name: str
|
|
email: str
|
|
phone: str
|
|
status: str
|
|
referral_code: str
|
|
total_deals_closed: int
|
|
total_commission_earned: float
|
|
current_month_deals: int
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class AffiliateDealRequest(BaseModel):
|
|
client_company: str
|
|
client_contact: Optional[str] = None
|
|
client_phone: Optional[str] = None
|
|
client_email: Optional[str] = None
|
|
plan_type: str # basic, professional, enterprise
|
|
|
|
|
|
class AffiliatePerformanceResponse(BaseModel):
|
|
month: str
|
|
leads_generated: int
|
|
calls_made: int
|
|
meetings_booked: int
|
|
deals_closed: int
|
|
commission_earned: float
|
|
bonus_earned: float
|
|
payment_status: str
|
|
|
|
|
|
# ─── Commission Rates ────────────────────────────────────
|
|
|
|
COMMISSION_RATES = {
|
|
"basic": {"price": 299, "rate": 0.15},
|
|
"professional": {"price": 699, "rate": 0.20},
|
|
"enterprise": {"price": 1499, "rate": 0.25},
|
|
}
|
|
|
|
BONUS_TIERS = [
|
|
{"min_deals": 5, "bonus": 500},
|
|
{"min_deals": 10, "bonus": 1500},
|
|
{"min_deals": 15, "bonus": 3000},
|
|
]
|
|
|
|
|
|
def generate_referral_code() -> str:
|
|
return f"DLX-{uuid.uuid4().hex[:8].upper()}"
|
|
|
|
|
|
# ─── Endpoints ───────────────────────────────────────────
|
|
|
|
@router.post("/register", response_model=AffiliateResponse, status_code=status.HTTP_201_CREATED)
|
|
async def register_affiliate(data: AffiliateRegisterRequest, db: AsyncSession = Depends(get_db)):
|
|
"""Register a new affiliate marketer."""
|
|
existing = await db.execute(
|
|
select(AffiliateMarketer).where(AffiliateMarketer.email == data.email)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(status_code=400, detail="Email already registered")
|
|
|
|
affiliate = AffiliateMarketer(
|
|
full_name=data.full_name,
|
|
full_name_ar=data.full_name_ar,
|
|
email=data.email,
|
|
phone=data.phone,
|
|
whatsapp=data.whatsapp or data.phone,
|
|
city=data.city,
|
|
national_id=data.national_id,
|
|
status=AffiliateStatus.PENDING,
|
|
referral_code=generate_referral_code(),
|
|
)
|
|
db.add(affiliate)
|
|
await db.commit()
|
|
await db.refresh(affiliate)
|
|
return affiliate
|
|
|
|
|
|
@router.get("/program")
|
|
async def affiliate_program_public():
|
|
"""رحلة المسوق + شرائح العمولة — للواجهة والتسويق (بدون DB)."""
|
|
return {
|
|
"title_ar": "برنامج الشراكة Dealix",
|
|
"journey_ar": [
|
|
{"step": 1, "title": "التسجيل", "detail_ar": "بياناتك ورمز إحالة فريد خلال دقائق."},
|
|
{"step": 2, "title": "التفعيل", "detail_ar": "موافقة فريقنا ثم تفعيل الحساب وربط القنوات."},
|
|
{"step": 3, "title": "أول عميل", "detail_ar": "مشاركة الرابط أو تسجيل صفقة من اللوحة."},
|
|
{"step": 4, "title": "تتبع العمولة", "detail_ar": "شفافية من الصفقة حتى الاعتماد والدفع."},
|
|
{"step": 5, "title": "نمو وترقية", "detail_ar": "مكافآت إضافية ومسار توظيف عند الأداء العالي."},
|
|
],
|
|
"commission_rates": COMMISSION_RATES,
|
|
"bonus_tiers": BONUS_TIERS,
|
|
"auto_employ_rule_ar": "عند 10 صفقات مُسجّلة في الشهر والحالة نشط — يُقيَّد كمرشح توظيف تلقائي.",
|
|
}
|
|
|
|
|
|
@router.get("/leaderboard/top")
|
|
async def get_leaderboard(limit: int = 10, db: AsyncSession = Depends(get_db)):
|
|
"""Get top performing affiliates."""
|
|
result = await db.execute(
|
|
select(AffiliateMarketer)
|
|
.where(AffiliateMarketer.status.in_([AffiliateStatus.ACTIVE, AffiliateStatus.EMPLOYED]))
|
|
.order_by(AffiliateMarketer.total_deals_closed.desc())
|
|
.limit(limit)
|
|
)
|
|
affiliates = result.scalars().all()
|
|
return [
|
|
{
|
|
"name": a.full_name_ar or a.full_name,
|
|
"deals": a.total_deals_closed,
|
|
"commission": a.total_commission_earned,
|
|
"status": a.status.value,
|
|
}
|
|
for a in affiliates
|
|
]
|
|
|
|
|
|
@router.get("/{affiliate_id}", response_model=AffiliateResponse)
|
|
async def get_affiliate(affiliate_id: UUID, db: AsyncSession = Depends(get_db)):
|
|
"""Get affiliate details."""
|
|
result = await db.execute(
|
|
select(AffiliateMarketer).where(AffiliateMarketer.id == affiliate_id)
|
|
)
|
|
affiliate = result.scalar_one_or_none()
|
|
if not affiliate:
|
|
raise HTTPException(status_code=404, detail="Affiliate not found")
|
|
return affiliate
|
|
|
|
|
|
@router.post("/{affiliate_id}/activate", response_model=AffiliateResponse)
|
|
async def activate_affiliate(affiliate_id: UUID, db: AsyncSession = Depends(get_db)):
|
|
"""Activate an affiliate after onboarding."""
|
|
result = await db.execute(
|
|
select(AffiliateMarketer).where(AffiliateMarketer.id == affiliate_id)
|
|
)
|
|
affiliate = result.scalar_one_or_none()
|
|
if not affiliate:
|
|
raise HTTPException(status_code=404, detail="Affiliate not found")
|
|
|
|
affiliate.status = AffiliateStatus.ACTIVE
|
|
affiliate.onboarded_at = datetime.now(timezone.utc)
|
|
await db.commit()
|
|
await db.refresh(affiliate)
|
|
return affiliate
|
|
|
|
|
|
@router.post("/{affiliate_id}/deals", status_code=status.HTTP_201_CREATED)
|
|
async def submit_deal(
|
|
affiliate_id: UUID,
|
|
data: AffiliateDealRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Submit a new deal for commission tracking."""
|
|
result = await db.execute(
|
|
select(AffiliateMarketer).where(AffiliateMarketer.id == affiliate_id)
|
|
)
|
|
affiliate = result.scalar_one_or_none()
|
|
if not affiliate:
|
|
raise HTTPException(status_code=404, detail="Affiliate not found")
|
|
|
|
if data.plan_type not in COMMISSION_RATES:
|
|
raise HTTPException(status_code=400, detail="Invalid plan type")
|
|
|
|
rate_info = COMMISSION_RATES[data.plan_type]
|
|
commission = rate_info["price"] * rate_info["rate"]
|
|
|
|
deal = AffiliateDeal(
|
|
affiliate_id=affiliate_id,
|
|
client_company=data.client_company,
|
|
client_contact=data.client_contact,
|
|
client_phone=data.client_phone,
|
|
client_email=data.client_email,
|
|
plan_type=data.plan_type,
|
|
plan_price=rate_info["price"],
|
|
commission_rate=rate_info["rate"],
|
|
commission_amount=commission,
|
|
)
|
|
db.add(deal)
|
|
|
|
# Update affiliate counters
|
|
affiliate.total_deals_closed += 1
|
|
affiliate.current_month_deals += 1
|
|
affiliate.total_commission_earned += commission
|
|
|
|
# Auto-employ if target reached (10 deals/month)
|
|
if affiliate.current_month_deals >= 10 and affiliate.status == AffiliateStatus.ACTIVE:
|
|
affiliate.status = AffiliateStatus.EMPLOYED
|
|
affiliate.employed_at = datetime.now(timezone.utc)
|
|
|
|
await db.commit()
|
|
return {"message": "Deal submitted successfully", "commission": commission}
|
|
|
|
|
|
@router.get("/{affiliate_id}/performance", response_model=list[AffiliatePerformanceResponse])
|
|
async def get_performance(affiliate_id: UUID, db: AsyncSession = Depends(get_db)):
|
|
"""Get affiliate performance history."""
|
|
result = await db.execute(
|
|
select(AffiliatePerformance)
|
|
.where(AffiliatePerformance.affiliate_id == affiliate_id)
|
|
.order_by(AffiliatePerformance.month.desc())
|
|
)
|
|
return result.scalars().all()
|