122 lines
4.0 KiB
Python
122 lines
4.0 KiB
Python
from fastapi import APIRouter, Depends, Query, UploadFile, File
|
|
from fastapi.responses import StreamingResponse
|
|
from typing import Optional
|
|
import io
|
|
|
|
from auth.dependencies import require_permission
|
|
from auth.models import TokenPayload
|
|
from crm.quotation_models import (
|
|
NextNumberResponse,
|
|
QuotationCreate,
|
|
QuotationInDB,
|
|
QuotationListResponse,
|
|
QuotationUpdate,
|
|
)
|
|
from crm import quotations_service as svc
|
|
|
|
router = APIRouter(prefix="/api/crm/quotations", tags=["crm-quotations"])
|
|
|
|
|
|
# IMPORTANT: Static paths must come BEFORE /{id} to avoid route collision in FastAPI
|
|
|
|
@router.get("/next-number", response_model=NextNumberResponse)
|
|
async def get_next_number(
|
|
_user: TokenPayload = Depends(require_permission("crm", "view")),
|
|
):
|
|
"""Returns the next available quotation number (preview only — does not commit)."""
|
|
next_num = await svc.get_next_number()
|
|
return NextNumberResponse(next_number=next_num)
|
|
|
|
|
|
@router.get("/all", response_model=list[dict])
|
|
async def list_all_quotations(
|
|
_user: TokenPayload = Depends(require_permission("crm", "view")),
|
|
):
|
|
"""Returns all quotations across all customers, each including customer_name."""
|
|
return await svc.list_all_quotations()
|
|
|
|
|
|
@router.get("/customer/{customer_id}", response_model=QuotationListResponse)
|
|
async def list_quotations_for_customer(
|
|
customer_id: str,
|
|
_user: TokenPayload = Depends(require_permission("crm", "view")),
|
|
):
|
|
quotations = await svc.list_quotations(customer_id)
|
|
return QuotationListResponse(quotations=quotations, total=len(quotations))
|
|
|
|
|
|
@router.get("/{quotation_id}/pdf")
|
|
async def proxy_quotation_pdf(
|
|
quotation_id: str,
|
|
_user: TokenPayload = Depends(require_permission("crm", "view")),
|
|
):
|
|
"""Proxy the quotation PDF from Nextcloud to bypass browser cookie restrictions."""
|
|
pdf_bytes = await svc.get_quotation_pdf_bytes(quotation_id)
|
|
return StreamingResponse(
|
|
io.BytesIO(pdf_bytes),
|
|
media_type="application/pdf",
|
|
headers={"Content-Disposition": "inline"},
|
|
)
|
|
|
|
|
|
@router.get("/{quotation_id}", response_model=QuotationInDB)
|
|
async def get_quotation(
|
|
quotation_id: str,
|
|
_user: TokenPayload = Depends(require_permission("crm", "view")),
|
|
):
|
|
return await svc.get_quotation(quotation_id)
|
|
|
|
|
|
@router.post("", response_model=QuotationInDB, status_code=201)
|
|
async def create_quotation(
|
|
body: QuotationCreate,
|
|
generate_pdf: bool = Query(False),
|
|
_user: TokenPayload = Depends(require_permission("crm", "edit")),
|
|
):
|
|
"""
|
|
Create a quotation. Pass ?generate_pdf=true to immediately generate and upload the PDF.
|
|
"""
|
|
return await svc.create_quotation(body, generate_pdf=generate_pdf)
|
|
|
|
|
|
@router.put("/{quotation_id}", response_model=QuotationInDB)
|
|
async def update_quotation(
|
|
quotation_id: str,
|
|
body: QuotationUpdate,
|
|
generate_pdf: bool = Query(False),
|
|
_user: TokenPayload = Depends(require_permission("crm", "edit")),
|
|
):
|
|
"""
|
|
Update a quotation. Pass ?generate_pdf=true to regenerate the PDF.
|
|
"""
|
|
return await svc.update_quotation(quotation_id, body, generate_pdf=generate_pdf)
|
|
|
|
|
|
@router.delete("/{quotation_id}", status_code=204)
|
|
async def delete_quotation(
|
|
quotation_id: str,
|
|
_user: TokenPayload = Depends(require_permission("crm", "edit")),
|
|
):
|
|
await svc.delete_quotation(quotation_id)
|
|
|
|
|
|
@router.post("/{quotation_id}/regenerate-pdf", response_model=QuotationInDB)
|
|
async def regenerate_pdf(
|
|
quotation_id: str,
|
|
_user: TokenPayload = Depends(require_permission("crm", "edit")),
|
|
):
|
|
"""Force PDF regeneration and re-upload to Nextcloud."""
|
|
return await svc.regenerate_pdf(quotation_id)
|
|
|
|
|
|
@router.post("/{quotation_id}/legacy-pdf", response_model=QuotationInDB)
|
|
async def upload_legacy_pdf(
|
|
quotation_id: str,
|
|
file: UploadFile = File(...),
|
|
_user: TokenPayload = Depends(require_permission("crm", "edit")),
|
|
):
|
|
"""Upload a PDF file for a legacy quotation and store its Nextcloud path."""
|
|
pdf_bytes = await file.read()
|
|
filename = file.filename or f"legacy-{quotation_id}.pdf"
|
|
return await svc.upload_legacy_pdf(quotation_id, pdf_bytes, filename)
|