mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
Full-stack AI-powered sales automation platform for Saudi SMEs: Backend (FastAPI + PostgreSQL): - Multi-tenant architecture with row-level isolation - JWT auth with RBAC (owner/manager/agent/admin) - Lead, Customer, Deal, Pipeline, Activity, Message, Proposal models - Dashboard analytics API (overview, pipeline, revenue) - WhatsApp Business API, Email (SMTP/SendGrid), SMS (Unifonic) integrations - Celery + Redis workers for automated follow-ups and scheduled messages - Property model for Real Estate module (Riyadh districts) - Hijri date utilities, Arabic/English localization Frontend (Next.js + Tailwind): - Professional Arabic RTL landing page with 10 sections - Brand identity: SalesMatic (سيلزماتك) with custom SVG logo - Color system: Trust Blue #0F4C81, Growth Teal #00BFA6, CTA Orange #FF6B35 - IBM Plex Sans Arabic + Inter typography - Responsive design, dark hero section, pricing table, FAQ Industry Templates: - Healthcare/Clinics: pipeline stages, WhatsApp message templates, auto-workflows - Real Estate Riyadh: 20 districts, property tours, payment plans, matching Infrastructure: - Docker Compose (PostgreSQL, Redis, Backend, Celery, Frontend, Nginx) - Nginx reverse proxy config - Makefile for common operations https://claude.ai/code/session_01LLR7jzpyNRwDA9kojtT3CW
111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
from datetime import datetime, timezone
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func
|
|
from uuid import UUID
|
|
from decimal import Decimal
|
|
from app.database import get_db
|
|
from app.api.deps import get_current_user
|
|
from app.models.user import User
|
|
from app.models.deal import Deal
|
|
from app.schemas.deal import DealCreate, DealUpdate, DealResponse, StageUpdate, PipelineResponse
|
|
|
|
router = APIRouter()
|
|
|
|
PIPELINE_STAGES = ["new", "negotiation", "proposal", "closed_won", "closed_lost"]
|
|
|
|
|
|
@router.get("", response_model=list[DealResponse])
|
|
async def list_deals(
|
|
stage: str = Query(None),
|
|
assigned_to: UUID = Query(None),
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
query = select(Deal).where(Deal.tenant_id == current_user.tenant_id)
|
|
if stage:
|
|
query = query.where(Deal.stage == stage)
|
|
if assigned_to:
|
|
query = query.where(Deal.assigned_to == assigned_to)
|
|
|
|
query = query.order_by(Deal.created_at.desc())
|
|
result = await db.execute(query)
|
|
return [DealResponse.model_validate(d) for d in result.scalars().all()]
|
|
|
|
|
|
@router.get("/pipeline", response_model=PipelineResponse)
|
|
async def get_pipeline(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(select(Deal).where(Deal.tenant_id == current_user.tenant_id))
|
|
deals = result.scalars().all()
|
|
|
|
stages = {s: [] for s in PIPELINE_STAGES}
|
|
total_value = Decimal("0")
|
|
for deal in deals:
|
|
stage_key = deal.stage if deal.stage in stages else "new"
|
|
stages[stage_key].append(DealResponse.model_validate(deal))
|
|
if deal.value:
|
|
total_value += deal.value
|
|
|
|
return PipelineResponse(stages=stages, total_value=total_value, total_deals=len(deals))
|
|
|
|
|
|
@router.post("", response_model=DealResponse, status_code=201)
|
|
async def create_deal(
|
|
data: DealCreate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
deal = Deal(tenant_id=current_user.tenant_id, **data.model_dump(exclude_none=True))
|
|
db.add(deal)
|
|
await db.flush()
|
|
await db.refresh(deal)
|
|
return DealResponse.model_validate(deal)
|
|
|
|
|
|
@router.put("/{deal_id}", response_model=DealResponse)
|
|
async def update_deal(
|
|
deal_id: UUID,
|
|
data: DealUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(select(Deal).where(Deal.id == deal_id, Deal.tenant_id == current_user.tenant_id))
|
|
deal = result.scalar_one_or_none()
|
|
if not deal:
|
|
raise HTTPException(status_code=404, detail="Deal not found")
|
|
|
|
for field, value in data.model_dump(exclude_none=True).items():
|
|
setattr(deal, field, value)
|
|
|
|
await db.flush()
|
|
await db.refresh(deal)
|
|
return DealResponse.model_validate(deal)
|
|
|
|
|
|
@router.put("/{deal_id}/stage", response_model=DealResponse)
|
|
async def update_deal_stage(
|
|
deal_id: UUID,
|
|
data: StageUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
if data.stage not in PIPELINE_STAGES:
|
|
raise HTTPException(status_code=400, detail=f"Invalid stage. Must be one of: {PIPELINE_STAGES}")
|
|
|
|
result = await db.execute(select(Deal).where(Deal.id == deal_id, Deal.tenant_id == current_user.tenant_id))
|
|
deal = result.scalar_one_or_none()
|
|
if not deal:
|
|
raise HTTPException(status_code=404, detail="Deal not found")
|
|
|
|
deal.stage = data.stage
|
|
if data.stage in ("closed_won", "closed_lost"):
|
|
deal.closed_at = datetime.now(timezone.utc)
|
|
deal.probability = 100 if data.stage == "closed_won" else 0
|
|
|
|
await db.flush()
|
|
await db.refresh(deal)
|
|
return DealResponse.model_validate(deal)
|