mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 23:39:34 +00:00
343 lines
12 KiB
Python
343 lines
12 KiB
Python
"""
|
|
Affiliate Service — Recruitment, commissions, career path, performance tracking.
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from decimal import Decimal
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import select, func
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
TIER_THRESHOLDS = {
|
|
"bronze": {"min_deals": 0, "commission_rate": 10.0},
|
|
"silver": {"min_deals": 5, "commission_rate": 12.5},
|
|
"gold": {"min_deals": 15, "commission_rate": 15.0},
|
|
"platinum": {"min_deals": 30, "commission_rate": 20.0},
|
|
}
|
|
|
|
CAREER_PATH = {
|
|
"affiliate": {"next": "senior_affiliate", "deals_required": 10, "months": 3},
|
|
"senior_affiliate": {"next": "team_lead", "deals_required": 25, "months": 6},
|
|
"team_lead": {"next": "employee", "deals_required": 50, "months": 12},
|
|
}
|
|
|
|
TEAM_LEAD_OVERRIDE_RATE = 2.5 # Extra 2.5% for team leaders on their team's sales
|
|
|
|
|
|
class AffiliateService:
|
|
"""Full affiliate lifecycle: recruitment, performance, commissions, career path."""
|
|
|
|
def __init__(self, db: AsyncSession):
|
|
self.db = db
|
|
|
|
# ── Recruitment ───────────────────────────────
|
|
|
|
async def apply(
|
|
self,
|
|
tenant_id: str,
|
|
user_id: str,
|
|
referral_code: str = None,
|
|
) -> dict:
|
|
from app.models.affiliate import Affiliate
|
|
import secrets
|
|
|
|
affiliate = Affiliate(
|
|
id=uuid.uuid4(),
|
|
tenant_id=uuid.UUID(tenant_id),
|
|
user_id=uuid.UUID(user_id),
|
|
status="applied",
|
|
tier="bronze",
|
|
referral_code=referral_code or secrets.token_urlsafe(8).upper()[:8],
|
|
commission_rate=Decimal("10.0"),
|
|
)
|
|
self.db.add(affiliate)
|
|
await self.db.flush()
|
|
return self._to_dict(affiliate)
|
|
|
|
async def approve(self, tenant_id: str, affiliate_id: str) -> Optional[dict]:
|
|
from app.models.affiliate import Affiliate
|
|
|
|
result = await self.db.execute(
|
|
select(Affiliate).where(
|
|
Affiliate.id == uuid.UUID(affiliate_id),
|
|
Affiliate.tenant_id == uuid.UUID(tenant_id),
|
|
)
|
|
)
|
|
aff = result.scalar_one_or_none()
|
|
if not aff:
|
|
return None
|
|
|
|
aff.status = "active"
|
|
aff.approved_at = datetime.now(timezone.utc)
|
|
await self.db.flush()
|
|
return self._to_dict(aff)
|
|
|
|
async def suspend(self, tenant_id: str, affiliate_id: str, reason: str = "") -> Optional[dict]:
|
|
from app.models.affiliate import Affiliate
|
|
|
|
result = await self.db.execute(
|
|
select(Affiliate).where(
|
|
Affiliate.id == uuid.UUID(affiliate_id),
|
|
Affiliate.tenant_id == uuid.UUID(tenant_id),
|
|
)
|
|
)
|
|
aff = result.scalar_one_or_none()
|
|
if not aff:
|
|
return None
|
|
|
|
aff.status = "suspended"
|
|
await self.db.flush()
|
|
return self._to_dict(aff)
|
|
|
|
# ── Commission Calculation ────────────────────
|
|
|
|
async def calculate_commission(
|
|
self,
|
|
tenant_id: str,
|
|
affiliate_id: str,
|
|
deal_id: str,
|
|
deal_value: float,
|
|
) -> dict:
|
|
from app.models.commission import Commission
|
|
from app.models.affiliate import Affiliate
|
|
|
|
result = await self.db.execute(
|
|
select(Affiliate).where(
|
|
Affiliate.id == uuid.UUID(affiliate_id),
|
|
Affiliate.tenant_id == uuid.UUID(tenant_id),
|
|
)
|
|
)
|
|
aff = result.scalar_one_or_none()
|
|
if not aff:
|
|
return {}
|
|
|
|
rate = float(aff.commission_rate)
|
|
amount = round(deal_value * rate / 100, 2)
|
|
|
|
commission = Commission(
|
|
id=uuid.uuid4(),
|
|
tenant_id=uuid.UUID(tenant_id),
|
|
affiliate_id=uuid.UUID(affiliate_id),
|
|
deal_id=uuid.UUID(deal_id),
|
|
amount=Decimal(str(amount)),
|
|
currency="SAR",
|
|
rate=aff.commission_rate,
|
|
status="pending",
|
|
period=datetime.now(timezone.utc).date().replace(day=1),
|
|
)
|
|
self.db.add(commission)
|
|
await self.db.flush()
|
|
|
|
results = [{
|
|
"commission_id": str(commission.id),
|
|
"affiliate_id": affiliate_id,
|
|
"amount": amount,
|
|
"rate": rate,
|
|
"status": "pending",
|
|
}]
|
|
|
|
# 🍯 Strategic Enhancement: Team Lead Override
|
|
if hasattr(aff, 'team_lead_id') and aff.team_lead_id:
|
|
lead_amount = round(deal_value * TEAM_LEAD_OVERRIDE_RATE / 100, 2)
|
|
lead_comm = Commission(
|
|
id=uuid.uuid4(),
|
|
tenant_id=uuid.UUID(tenant_id),
|
|
affiliate_id=aff.team_lead_id,
|
|
deal_id=uuid.UUID(deal_id),
|
|
amount=Decimal(str(lead_amount)),
|
|
currency="SAR",
|
|
rate=Decimal(str(TEAM_LEAD_OVERRIDE_RATE)),
|
|
status="pending",
|
|
period=datetime.now(timezone.utc).date().replace(day=1),
|
|
notes=f"Team override from affiliate {aff.referral_code}"
|
|
)
|
|
self.db.add(lead_comm)
|
|
results.append({
|
|
"commission_id": str(lead_comm.id),
|
|
"affiliate_id": str(aff.team_lead_id),
|
|
"amount": lead_amount,
|
|
"rate": TEAM_LEAD_OVERRIDE_RATE,
|
|
"status": "pending",
|
|
"type": "team_override"
|
|
})
|
|
|
|
await self.db.flush()
|
|
return {"commissions": results}
|
|
|
|
# ── Tier Progression ──────────────────────────
|
|
|
|
async def check_tier_upgrade(self, tenant_id: str, affiliate_id: str) -> Optional[dict]:
|
|
from app.models.affiliate import Affiliate, AffiliatePerformance
|
|
|
|
result = await self.db.execute(
|
|
select(Affiliate).where(
|
|
Affiliate.id == uuid.UUID(affiliate_id),
|
|
Affiliate.tenant_id == uuid.UUID(tenant_id),
|
|
)
|
|
)
|
|
aff = result.scalar_one_or_none()
|
|
if not aff:
|
|
return None
|
|
|
|
# Get total deals closed
|
|
perf_q = select(func.coalesce(func.sum(AffiliatePerformance.deals_closed), 0)).where(
|
|
AffiliatePerformance.affiliate_id == uuid.UUID(affiliate_id),
|
|
)
|
|
total_deals = (await self.db.execute(perf_q)).scalar() or 0
|
|
|
|
# Check upgrade
|
|
tiers = ["bronze", "silver", "gold", "platinum"]
|
|
current_idx = tiers.index(aff.tier) if aff.tier in tiers else 0
|
|
|
|
for i in range(current_idx + 1, len(tiers)):
|
|
tier = tiers[i]
|
|
if total_deals >= TIER_THRESHOLDS[tier]["min_deals"]:
|
|
aff.tier = tier
|
|
aff.commission_rate = Decimal(str(TIER_THRESHOLDS[tier]["commission_rate"]))
|
|
await self.db.flush()
|
|
return {
|
|
"upgraded": True,
|
|
"new_tier": tier,
|
|
"new_rate": TIER_THRESHOLDS[tier]["commission_rate"],
|
|
"total_deals": total_deals,
|
|
}
|
|
|
|
return {
|
|
"upgraded": False,
|
|
"current_tier": aff.tier,
|
|
"total_deals": total_deals,
|
|
"next_tier": tiers[current_idx + 1] if current_idx < len(tiers) - 1 else None,
|
|
"deals_needed": TIER_THRESHOLDS[tiers[min(current_idx + 1, len(tiers) - 1)]]["min_deals"] - total_deals,
|
|
}
|
|
|
|
# ── Career Path (Affiliate → Employee) ────────
|
|
|
|
async def check_career_path(self, tenant_id: str, affiliate_id: str) -> dict:
|
|
from app.models.affiliate import Affiliate, AffiliatePerformance
|
|
|
|
result = await self.db.execute(
|
|
select(Affiliate).where(
|
|
Affiliate.id == uuid.UUID(affiliate_id),
|
|
Affiliate.tenant_id == uuid.UUID(tenant_id),
|
|
)
|
|
)
|
|
aff = result.scalar_one_or_none()
|
|
if not aff:
|
|
return {}
|
|
|
|
perf_q = select(func.coalesce(func.sum(AffiliatePerformance.deals_closed), 0)).where(
|
|
AffiliatePerformance.affiliate_id == uuid.UUID(affiliate_id),
|
|
)
|
|
total_deals = (await self.db.execute(perf_q)).scalar() or 0
|
|
|
|
months_active = 0
|
|
if aff.approved_at:
|
|
delta = datetime.now(timezone.utc) - aff.approved_at.replace(tzinfo=timezone.utc)
|
|
months_active = delta.days // 30
|
|
|
|
# Employee eligibility
|
|
eligible = total_deals >= 50 and months_active >= 12
|
|
|
|
return {
|
|
"affiliate_id": str(affiliate_id),
|
|
"total_deals": total_deals,
|
|
"months_active": months_active,
|
|
"eligible_for_employment": eligible,
|
|
"current_tier": aff.tier,
|
|
"progress": {
|
|
"deals": {"current": total_deals, "required": 50, "percent": min(100, total_deals * 100 // 50)},
|
|
"months": {"current": months_active, "required": 12, "percent": min(100, months_active * 100 // 12)},
|
|
},
|
|
}
|
|
|
|
# ── Leaderboard ───────────────────────────────
|
|
|
|
async def get_leaderboard(self, tenant_id: str, limit: int = 20) -> list:
|
|
from app.models.affiliate import Affiliate, AffiliatePerformance
|
|
|
|
q = (
|
|
select(
|
|
Affiliate.id,
|
|
Affiliate.tier,
|
|
Affiliate.referral_code,
|
|
func.coalesce(func.sum(AffiliatePerformance.deals_closed), 0).label("total_deals"),
|
|
func.coalesce(func.sum(AffiliatePerformance.revenue_attributed), 0).label("total_revenue"),
|
|
func.coalesce(func.sum(AffiliatePerformance.commission_earned), 0).label("total_commission"),
|
|
)
|
|
.outerjoin(AffiliatePerformance, Affiliate.id == AffiliatePerformance.affiliate_id)
|
|
.where(
|
|
Affiliate.tenant_id == uuid.UUID(tenant_id),
|
|
Affiliate.status == "active",
|
|
)
|
|
.group_by(Affiliate.id, Affiliate.tier, Affiliate.referral_code)
|
|
.order_by(func.sum(AffiliatePerformance.revenue_attributed).desc().nullslast())
|
|
.limit(limit)
|
|
)
|
|
|
|
rows = (await self.db.execute(q)).all()
|
|
return [
|
|
{
|
|
"rank": i + 1,
|
|
"affiliate_id": str(row.id),
|
|
"tier": row.tier,
|
|
"referral_code": row.referral_code,
|
|
"total_deals": int(row.total_deals),
|
|
"total_revenue": float(row.total_revenue),
|
|
"total_commission": float(row.total_commission),
|
|
}
|
|
for i, row in enumerate(rows)
|
|
]
|
|
|
|
# ── Performance Summary ───────────────────────
|
|
|
|
async def get_performance(self, tenant_id: str, affiliate_id: str) -> dict:
|
|
from app.models.affiliate import AffiliatePerformance
|
|
|
|
q = select(AffiliatePerformance).where(
|
|
AffiliatePerformance.affiliate_id == uuid.UUID(affiliate_id),
|
|
).order_by(AffiliatePerformance.period.desc()).limit(12)
|
|
|
|
rows = (await self.db.execute(q)).scalars().all()
|
|
|
|
monthly = [
|
|
{
|
|
"period": row.period.isoformat() if row.period else None,
|
|
"leads_generated": row.leads_generated,
|
|
"deals_closed": row.deals_closed,
|
|
"revenue_attributed": float(row.revenue_attributed) if row.revenue_attributed else 0,
|
|
"commission_earned": float(row.commission_earned) if row.commission_earned else 0,
|
|
"conversion_rate": float(row.conversion_rate) if row.conversion_rate else 0,
|
|
}
|
|
for row in rows
|
|
]
|
|
|
|
return {
|
|
"affiliate_id": str(affiliate_id),
|
|
"monthly": monthly,
|
|
"totals": {
|
|
"leads": sum(m["leads_generated"] for m in monthly),
|
|
"deals": sum(m["deals_closed"] for m in monthly),
|
|
"revenue": sum(m["revenue_attributed"] for m in monthly),
|
|
"commission": sum(m["commission_earned"] for m in monthly),
|
|
},
|
|
}
|
|
|
|
@staticmethod
|
|
def _to_dict(aff) -> dict:
|
|
if not aff:
|
|
return {}
|
|
return {
|
|
"id": str(aff.id),
|
|
"tenant_id": str(aff.tenant_id),
|
|
"user_id": str(aff.user_id),
|
|
"status": aff.status,
|
|
"tier": aff.tier,
|
|
"referral_code": aff.referral_code,
|
|
"commission_rate": float(aff.commission_rate) if aff.commission_rate else 0,
|
|
"approved_at": aff.approved_at.isoformat() if aff.approved_at else None,
|
|
"created_at": aff.created_at.isoformat() if aff.created_at else None,
|
|
}
|