1
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from asyncpg import Connection
|
||||
|
||||
from app.auth import require_auth
|
||||
from app.dependencies import PaginationParams, get_db
|
||||
from app.models.cliente import ClienteCreate, ClienteUpdate, ClienteResponse
|
||||
from app.models.common import PaginatedResponse, Meta, ErrorResponse, OkResponse
|
||||
|
||||
router = APIRouter(prefix="/clientes", tags=["Clientes"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=PaginatedResponse[ClienteResponse],
|
||||
summary="List clients with pagination and search",
|
||||
)
|
||||
async def list_clientes(
|
||||
db: Connection = Depends(get_db),
|
||||
auth: dict = Depends(require_auth),
|
||||
pagination: PaginationParams = Depends(PaginationParams),
|
||||
q: str | None = Query(None, description="Search by nombre, empresa, or email"),
|
||||
):
|
||||
where_clauses: list[str] = []
|
||||
params: list = []
|
||||
idx = 1
|
||||
|
||||
if q:
|
||||
where_clauses.append(
|
||||
f"(nombre ILIKE ${idx} OR empresa ILIKE ${idx} OR email ILIKE ${idx})"
|
||||
)
|
||||
params.append(f"%{q}%")
|
||||
idx += 1
|
||||
|
||||
where_sql = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else ""
|
||||
|
||||
count_row = await db.fetchrow(
|
||||
f"SELECT COUNT(*) AS total FROM \"Cliente\" {where_sql}", *params
|
||||
)
|
||||
total = count_row["total"]
|
||||
pages = math.ceil(total / pagination.limit) if total > 0 else 0
|
||||
|
||||
rows = await db.fetch(
|
||||
f'SELECT * FROM "Cliente" {where_sql} ORDER BY "createdAt" DESC '
|
||||
f"LIMIT ${idx} OFFSET ${idx + 1}",
|
||||
*params,
|
||||
pagination.limit,
|
||||
pagination.offset,
|
||||
)
|
||||
|
||||
data = [
|
||||
ClienteResponse(
|
||||
id=r["id"],
|
||||
nombre=r["nombre"],
|
||||
empresa=r["empresa"],
|
||||
email=r["email"],
|
||||
telefono=r["telefono"],
|
||||
createdAt=r["createdAt"],
|
||||
updatedAt=r["updatedAt"],
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
|
||||
return PaginatedResponse(
|
||||
data=data,
|
||||
meta=Meta(total=total, page=pagination.page, limit=pagination.limit, pages=pages),
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ClienteResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create a new client",
|
||||
)
|
||||
async def create_cliente(
|
||||
body: ClienteCreate,
|
||||
db: Connection = Depends(get_db),
|
||||
auth: dict = Depends(require_auth),
|
||||
):
|
||||
row = await db.fetchrow(
|
||||
'INSERT INTO "Cliente" (nombre, empresa, email, telefono) '
|
||||
"VALUES ($1, $2, $3, $4) RETURNING *",
|
||||
body.nombre,
|
||||
body.empresa,
|
||||
body.email,
|
||||
body.telefono,
|
||||
)
|
||||
return ClienteResponse(
|
||||
id=row["id"],
|
||||
nombre=row["nombre"],
|
||||
empresa=row["empresa"],
|
||||
email=row["email"],
|
||||
telefono=row["telefono"],
|
||||
createdAt=row["createdAt"],
|
||||
updatedAt=row["updatedAt"],
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{cliente_id}",
|
||||
response_model=ClienteResponse,
|
||||
responses={404: {"model": ErrorResponse}},
|
||||
summary="Get client by ID with recent cotizaciones",
|
||||
)
|
||||
async def get_cliente(
|
||||
cliente_id: str,
|
||||
db: Connection = Depends(get_db),
|
||||
auth: dict = Depends(require_auth),
|
||||
):
|
||||
row = await db.fetchrow('SELECT * FROM "Cliente" WHERE id = $1', cliente_id)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Cliente no encontrado")
|
||||
|
||||
cotizaciones = await db.fetch(
|
||||
'SELECT * FROM "Cotizacion" WHERE "clienteId" = $1 '
|
||||
'ORDER BY "createdAt" DESC LIMIT 10',
|
||||
cliente_id,
|
||||
)
|
||||
|
||||
cliente = ClienteResponse(
|
||||
id=row["id"],
|
||||
nombre=row["nombre"],
|
||||
empresa=row["empresa"],
|
||||
email=row["email"],
|
||||
telefono=row["telefono"],
|
||||
createdAt=row["createdAt"],
|
||||
updatedAt=row["updatedAt"],
|
||||
)
|
||||
|
||||
return cliente.model_copy(
|
||||
update={"cotizaciones": [dict(c) for c in cotizaciones]}
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{cliente_id}",
|
||||
response_model=ClienteResponse,
|
||||
responses={404: {"model": ErrorResponse}},
|
||||
summary="Update a client",
|
||||
)
|
||||
async def update_cliente(
|
||||
cliente_id: str,
|
||||
body: ClienteUpdate,
|
||||
db: Connection = Depends(get_db),
|
||||
auth: dict = Depends(require_auth),
|
||||
):
|
||||
existing = await db.fetchrow('SELECT id FROM "Cliente" WHERE id = $1', cliente_id)
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail="Cliente no encontrado")
|
||||
|
||||
updates: list[str] = []
|
||||
params: list = []
|
||||
idx = 1
|
||||
|
||||
for field in ("nombre", "empresa", "email", "telefono"):
|
||||
value = getattr(body, field, None)
|
||||
if value is not None:
|
||||
updates.append(f"{field} = ${idx}")
|
||||
params.append(value)
|
||||
idx += 1
|
||||
|
||||
if not updates:
|
||||
raise HTTPException(status_code=400, detail="No fields to update")
|
||||
|
||||
params.append(cliente_id)
|
||||
row = await db.fetchrow(
|
||||
f'UPDATE "Cliente" SET {", ".join(updates)} WHERE id = ${idx} RETURNING *',
|
||||
*params,
|
||||
)
|
||||
|
||||
return ClienteResponse(
|
||||
id=row["id"],
|
||||
nombre=row["nombre"],
|
||||
empresa=row["empresa"],
|
||||
email=row["email"],
|
||||
telefono=row["telefono"],
|
||||
createdAt=row["createdAt"],
|
||||
updatedAt=row["updatedAt"],
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{cliente_id}",
|
||||
response_model=OkResponse,
|
||||
responses={409: {"model": ErrorResponse}, 404: {"model": ErrorResponse}},
|
||||
summary="Delete a client (fails if cotizaciones exist)",
|
||||
)
|
||||
async def delete_cliente(
|
||||
cliente_id: str,
|
||||
db: Connection = Depends(get_db),
|
||||
auth: dict = Depends(require_auth),
|
||||
):
|
||||
existing = await db.fetchrow('SELECT id FROM "Cliente" WHERE id = $1', cliente_id)
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail="Cliente no encontrado")
|
||||
|
||||
cot_count = await db.fetchval(
|
||||
'SELECT COUNT(*) FROM "Cotizacion" WHERE "clienteId" = $1', cliente_id
|
||||
)
|
||||
if cot_count > 0:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="No se puede eliminar: el cliente tiene cotizaciones asociadas",
|
||||
)
|
||||
|
||||
await db.execute('DELETE FROM "Cliente" WHERE id = $1', cliente_id)
|
||||
return OkResponse()
|
||||
Reference in New Issue
Block a user