249 lines
8.5 KiB
Python
249 lines
8.5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
|
|
import httpx
|
|
from fastapi import APIRouter, HTTPException, Query, Request
|
|
from fastapi.responses import Response
|
|
from fastapi.templating import Jinja2Templates
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.config import settings
|
|
from app.services import packaging_nas, projects
|
|
|
|
router = APIRouter(tags=["packaging"])
|
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
TOOLS = settings.TOOLS_API_URL.rstrip("/")
|
|
|
|
|
|
class PackagingGenerateBody(BaseModel):
|
|
type: str = Field(default="folding_box")
|
|
width_mm: float = Field(default=120, gt=0)
|
|
height_mm: float = Field(default=80, gt=0)
|
|
depth_mm: float = Field(default=40, ge=0)
|
|
bleed_mm: float = Field(default=3, ge=0)
|
|
elements: dict[str, bool] = Field(default_factory=dict)
|
|
brand: dict[str, str] = Field(default_factory=dict)
|
|
text: dict[str, Any] = Field(default_factory=dict)
|
|
barcode_value: str | None = None
|
|
qr_value: str | None = None
|
|
design_name: str | None = None
|
|
project_id: int | None = None
|
|
project_name: str | None = None
|
|
client_id: int | None = None
|
|
created_by: str = "ceo"
|
|
|
|
|
|
class PackagingEmailBody(BaseModel):
|
|
to: list[str] = Field(..., min_length=1)
|
|
subject: str = Field(..., min_length=1)
|
|
body: str = ""
|
|
cc: list[str] = Field(default_factory=list)
|
|
client_id: int | None = None
|
|
|
|
|
|
class CopyToProjectBody(BaseModel):
|
|
target_project_id: int
|
|
|
|
|
|
async def _tools_request(method: str, path: str, **kwargs) -> httpx.Response:
|
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
|
r = await client.request(method, f"{TOOLS}{path}", **kwargs)
|
|
return r
|
|
|
|
|
|
def _tools_error(exc: httpx.HTTPStatusError) -> HTTPException:
|
|
detail = exc.response.text[:500] if exc.response else str(exc)
|
|
return HTTPException(status_code=exc.response.status_code if exc.response else 502, detail=detail)
|
|
|
|
|
|
async def _ensure_project(body: PackagingGenerateBody) -> PackagingGenerateBody:
|
|
if body.project_id or not body.project_name:
|
|
return body
|
|
proj = projects.create_project(
|
|
body.project_name.strip(),
|
|
client_id=body.client_id,
|
|
description="Packaging design project",
|
|
project_type="packaging",
|
|
ensure_nas=True,
|
|
)
|
|
data = body.model_dump()
|
|
data["project_id"] = proj.get("id")
|
|
return PackagingGenerateBody(**data)
|
|
|
|
|
|
async def _post_nas_export(result: dict[str, Any]) -> dict[str, Any]:
|
|
pid = result.get("cockpit_project_id")
|
|
packaging_id = result.get("id")
|
|
svg = result.get("svg") or ""
|
|
spec = result.get("spec") or {}
|
|
if not pid or not packaging_id or not svg:
|
|
return result
|
|
png_bytes = pdf_bytes = None
|
|
try:
|
|
png_r = await _tools_request("GET", f"/packaging/download/{packaging_id}", params={"format": "png"})
|
|
if png_r.status_code < 400:
|
|
png_bytes = png_r.content
|
|
pdf_r = await _tools_request("GET", f"/packaging/download/{packaging_id}", params={"format": "pdf"})
|
|
if pdf_r.status_code < 400:
|
|
pdf_bytes = pdf_r.content
|
|
except Exception:
|
|
pass
|
|
try:
|
|
nas_info = packaging_nas.export_packaging_files(packaging_id, int(pid), svg, spec, png_bytes, pdf_bytes)
|
|
result["nas"] = nas_info
|
|
except Exception as exc:
|
|
result["nas"] = {"ok": False, "error": str(exc)}
|
|
return result
|
|
|
|
|
|
@router.get("/packaging")
|
|
async def packaging_page(request: Request, project_id: Optional[int] = None):
|
|
return templates.TemplateResponse(
|
|
"packaging.html",
|
|
{"request": request, "page_title": "Packaging Studio", "initial_project_id": project_id},
|
|
)
|
|
|
|
|
|
@router.get("/api/packaging/types")
|
|
async def proxy_packaging_types():
|
|
try:
|
|
r = await _tools_request("GET", "/packaging/types")
|
|
r.raise_for_status()
|
|
return r.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.post("/api/packaging/preview")
|
|
async def proxy_packaging_preview(body: PackagingGenerateBody):
|
|
try:
|
|
r = await _tools_request("POST", "/packaging/preview", json=body.model_dump())
|
|
r.raise_for_status()
|
|
return r.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.post("/api/packaging/generate")
|
|
async def proxy_packaging_generate(body: PackagingGenerateBody):
|
|
try:
|
|
body = await _ensure_project(body)
|
|
r = await _tools_request("POST", "/packaging/generate", json=body.model_dump())
|
|
r.raise_for_status()
|
|
result = r.json()
|
|
return await _post_nas_export(result)
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.get("/api/packaging/projects")
|
|
async def proxy_packaging_projects(limit: int = 30):
|
|
try:
|
|
r = await _tools_request("GET", "/packaging/projects", params={"limit": limit})
|
|
r.raise_for_status()
|
|
return r.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.get("/api/packaging/download/{project_id}")
|
|
async def proxy_packaging_download(project_id: str, format: str = "svg"):
|
|
try:
|
|
r = await _tools_request("GET", f"/packaging/download/{project_id}", params={"format": format})
|
|
r.raise_for_status()
|
|
media = r.headers.get("content-type", "application/octet-stream")
|
|
disposition = r.headers.get("content-disposition")
|
|
headers = {"content-disposition": disposition} if disposition else {}
|
|
return Response(content=r.content, media_type=media, headers=headers)
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.get("/api/packaging/{packaging_id}")
|
|
async def proxy_packaging_get(packaging_id: str):
|
|
try:
|
|
r = await _tools_request("GET", f"/packaging/{packaging_id}")
|
|
r.raise_for_status()
|
|
return r.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.patch("/api/packaging/{packaging_id}")
|
|
async def proxy_packaging_update(packaging_id: str, body: PackagingGenerateBody):
|
|
try:
|
|
r = await _tools_request("PATCH", f"/packaging/{packaging_id}", json={"spec": body.model_dump()})
|
|
r.raise_for_status()
|
|
result = r.json()
|
|
return await _post_nas_export(result)
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.post("/api/packaging/{packaging_id}/duplicate")
|
|
async def proxy_packaging_duplicate(packaging_id: str):
|
|
try:
|
|
r = await _tools_request("POST", f"/packaging/{packaging_id}/duplicate")
|
|
r.raise_for_status()
|
|
result = r.json()
|
|
return await _post_nas_export(result)
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.post("/api/packaging/{packaging_id}/copy-to-project")
|
|
async def proxy_packaging_copy(packaging_id: str, body: CopyToProjectBody):
|
|
try:
|
|
r = await _tools_request(
|
|
"POST",
|
|
f"/packaging/{packaging_id}/copy-to-project",
|
|
json=body.model_dump(),
|
|
)
|
|
r.raise_for_status()
|
|
result = r.json()
|
|
return await _post_nas_export(result)
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.delete("/api/packaging/{packaging_id}")
|
|
async def proxy_packaging_delete(packaging_id: str):
|
|
try:
|
|
r = await _tools_request("DELETE", f"/packaging/{packaging_id}")
|
|
r.raise_for_status()
|
|
return r.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|
|
|
|
|
|
@router.post("/api/packaging/{packaging_id}/email")
|
|
async def proxy_packaging_email(packaging_id: str, body: PackagingEmailBody):
|
|
base = settings.COCKPIT_PUBLIC_URL if hasattr(settings, "COCKPIT_PUBLIC_URL") else "http://10.4.7.18:8600"
|
|
links = "\n".join(
|
|
f"- {fmt.upper()}: {base}/api/packaging/download/{packaging_id}?format={fmt}"
|
|
for fmt in ("svg", "png", "pdf")
|
|
)
|
|
email_body = (body.body or "").strip()
|
|
if email_body:
|
|
email_body += "\n\n"
|
|
email_body += f"Packaging downloads:\n{links}"
|
|
try:
|
|
r = await _tools_request(
|
|
"POST",
|
|
"/emails/send",
|
|
json={
|
|
"to": body.to,
|
|
"cc": body.cc,
|
|
"subject": body.subject,
|
|
"body": email_body,
|
|
"client_id": body.client_id,
|
|
},
|
|
)
|
|
r.raise_for_status()
|
|
return r.json()
|
|
except httpx.HTTPStatusError as exc:
|
|
raise _tools_error(exc) from exc
|