update: Major Overhault to all subsystems
This commit is contained in:
101
backend/crm/quotations_router.py
Normal file
101
backend/crm/quotations_router.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
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("/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)
|
||||
Reference in New Issue
Block a user