Add HTTP print API for packing list and sign-off receipt

- core/printing.py: SumatraPDF silent print backend (dry_run, timeout, error capture)
- core/print_document.py: orchestrate generate() + print, per-report paper size, printer fallback
- core/print_api.py: FastAPI POST /api/print + GET /api/health, HTTPBearer auth, English Swagger at /api/docs
- serve.py: uvicorn entrypoint
- config: printers/print/api sections in settings.yaml + core/settings.py
- requirements.txt: add fastapi, uvicorn
- .gitignore: ignore logs/
This commit is contained in:
Misaka_Company
2026-08-05 16:22:59 +08:00
parent df8f25f6da
commit bf9199189a
7 changed files with 399 additions and 1 deletions

64
core/printing.py Normal file
View File

@@ -0,0 +1,64 @@
"""打印后端:把已生成的 PDF 静默送到物理/网络打印机。
MVP 使用 SumatraPDF 命令行打印(轻量、支持 -print-to 指定打印机与纸张,
配合 -silent 适合服务端无人值守打印)。
"""
from __future__ import annotations
import subprocess
from pathlib import Path
def build_command(
sumatrapdf_path: str, printer: str, settings: str, pdf_path: str
) -> list[str]:
"""构造 SumatraPDF 打印命令。"""
return [
sumatrapdf_path,
"-print-to",
printer,
"-print-settings",
settings,
"-silent",
pdf_path,
]
def print_pdf(
pdf_path: str | Path,
printer: str,
settings: str = "paper=A4,orientation=portrait,color=bw",
sumatrapdf_path: str = "C:/Program Files/SumatraPDF/SumatraPDF.exe",
timeout: int = 60,
dry_run: bool = False,
) -> dict:
"""调用 SumatraPDF 把 PDF 静默打印到指定打印机。
返回结构化结果dry_run=True 时只构造命令并上报,不真正执行(用于验证链路)。
"""
pdf_path = str(pdf_path)
cmd = build_command(sumatrapdf_path, printer, settings, pdf_path)
result: dict = {
"printer": printer,
"pdf": pdf_path,
"command": cmd,
"dry_run": dry_run,
}
if dry_run:
result["ok"] = True
result["note"] = "dry_run: command constructed, not executed"
return result
try:
proc = subprocess.run(
cmd, capture_output=True, text=True, timeout=timeout, check=False
)
result["returncode"] = proc.returncode
result["stdout"] = proc.stdout
result["stderr"] = proc.stderr
result["ok"] = proc.returncode == 0
if proc.returncode != 0:
result["error"] = (proc.stderr or proc.stdout or "unknown error").strip()
except subprocess.TimeoutExpired:
result["ok"] = False
result["error"] = f"print timeout after {timeout}s"
return result