Files
maskanx_crm_backend/app/api/catalog.py
T

120 lines
4.0 KiB
Python
Raw Normal View History

2026-08-01 10:28:41 +05:30
from fastapi import APIRouter, HTTPException, Query, status
from sqlalchemy import func, select
from sqlalchemy.exc import IntegrityError
from app.core.security import CurrentUser, Database, Writer
from app.models import Contact, Lead, Organization, Product, Quote, User
from app.schemas import Page, ProductCreate, ProductOut, QuoteCreate, QuoteOut
from app.services import add_audit, model_or_404, verify_optional_reference
router = APIRouter(tags=["Catalog and Quotes"])
@router.get("/products", response_model=Page[ProductOut])
def list_products(
user: CurrentUser,
db: Database,
page: int = Query(default=1, ge=1),
page_size: int = Query(default=25, ge=1, le=100),
) -> Page[ProductOut]:
filters = [Product.tenant_id == user.tenant_id]
total = db.scalar(select(func.count(Product.id)).where(*filters)) or 0
records = db.scalars(
select(Product)
.where(*filters)
.order_by(Product.updated_at.desc())
.offset((page - 1) * page_size)
.limit(page_size),
).all()
return Page(
items=[ProductOut.model_validate(item) for item in records],
total=total,
page=page,
page_size=page_size,
)
@router.post("/products", response_model=ProductOut, status_code=status.HTTP_201_CREATED)
def create_product(payload: ProductCreate, user: Writer, db: Database) -> ProductOut:
product = Product(tenant_id=user.tenant_id, **payload.model_dump())
db.add(product)
try:
db.flush()
add_audit(
db,
actor=user,
tenant_id=user.tenant_id,
action="product.created",
entity_type="product",
entity_id=product.id,
)
db.commit()
except IntegrityError as exc:
db.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="A product with this SKU already exists.",
) from exc
db.refresh(product)
return ProductOut.model_validate(product)
@router.get("/quotes", response_model=Page[QuoteOut])
def list_quotes(
user: CurrentUser,
db: Database,
page: int = Query(default=1, ge=1),
page_size: int = Query(default=25, ge=1, le=100),
) -> Page[QuoteOut]:
filters = [Quote.tenant_id == user.tenant_id]
total = db.scalar(select(func.count(Quote.id)).where(*filters)) or 0
records = db.scalars(
select(Quote)
.where(*filters)
.order_by(Quote.updated_at.desc())
.offset((page - 1) * page_size)
.limit(page_size),
).all()
return Page(
items=[QuoteOut.model_validate(item) for item in records],
total=total,
page=page,
page_size=page_size,
)
@router.post("/quotes", response_model=QuoteOut, status_code=status.HTTP_201_CREATED)
def create_quote(payload: QuoteCreate, user: Writer, db: Database) -> QuoteOut:
verify_optional_reference(db, Contact, payload.contact_id, user.tenant_id)
verify_optional_reference(db, Organization, payload.organization_id, user.tenant_id)
verify_optional_reference(db, Lead, payload.lead_id, user.tenant_id)
verify_optional_reference(db, User, payload.owner_id, user.tenant_id)
quote = Quote(tenant_id=user.tenant_id, **payload.model_dump())
db.add(quote)
try:
db.flush()
add_audit(
db,
actor=user,
tenant_id=user.tenant_id,
action="quote.created",
entity_type="quote",
entity_id=quote.id,
)
db.commit()
except IntegrityError as exc:
db.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="A quote with this number already exists.",
) from exc
db.refresh(quote)
return QuoteOut.model_validate(quote)
@router.get("/products/{product_id}", response_model=ProductOut)
def get_product(product_id: str, user: CurrentUser, db: Database) -> ProductOut:
return ProductOut.model_validate(
model_or_404(db, Product, product_id, user.tenant_id),
)