"""Trade-in lead capture endpoint (issue #2376, sub-issue родителя #1971). POST /api/v1/trade-in/lead — контактная заявка с результата оценки (или лендинга): телефон + явное согласие на обработку персональных данных. Persist в trade_in_leads. Notification (Telegram/email) — вне scope: нет существующей SMTP/Telegram интеграции в коде (подтверждено при разборе issue), только persist + log; `notified_at` в таблице зарезервирован под будущую доставку. """ from __future__ import annotations import logging from typing import Annotated, Any, Literal from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel, Field from sqlalchemy import text from sqlalchemy.orm import Session from app.core.db import get_db logger = logging.getLogger(__name__) router = APIRouter() # Простая маска телефона (RU/международная): опциональный "+", цифры/пробелы/ # скобки/дефисы/точки, 5-32 символа. Полная нормализация в E.164 — вне scope MVP, # см. #2376 DoD ("простая regex, не EmailStr-подобное"). _PHONE_PATTERN = r"^[+]?[\d\s().-]{5,32}$" class TradeInLeadInput(BaseModel): phone: str = Field(min_length=5, max_length=32, pattern=_PHONE_PATTERN) estimate_id: UUID | None = None consent: Literal[True] source: Literal["result", "landing"] = "result" @router.post("/lead") async def create_trade_in_lead( payload: TradeInLeadInput, request: Request, db: Annotated[Session, Depends(get_db)], ) -> dict[str, Any]: """Сохраняет лид (телефон + согласие) в trade_in_leads.""" if payload.estimate_id is not None: exists = db.execute( text("SELECT 1 FROM trade_in_estimates WHERE id = CAST(:id AS uuid)"), {"id": str(payload.estimate_id)}, ).fetchone() if exists is None: raise HTTPException(status_code=404, detail="estimate not found") user_agent = request.headers.get("user-agent") row = ( db.execute( text( """ INSERT INTO trade_in_leads (estimate_id, phone, consent, source, user_agent) VALUES (CAST(:estimate_id AS uuid), :phone, :consent, :source, :user_agent) RETURNING CAST(id AS text), created_at """ ), { "estimate_id": str(payload.estimate_id) if payload.estimate_id else None, "phone": payload.phone, "consent": payload.consent, "source": payload.source, "user_agent": user_agent, }, ) .mappings() .one() ) db.commit() logger.info( "trade_in_lead saved id=%s estimate_id=%s source=%s", row["id"], payload.estimate_id, payload.source, ) return { "id": row["id"], "created_at": row["created_at"].isoformat(), "status": "received", }