feat(api): 核心端点 — 接收/驳回/转交/打印/上传/用户/物料/记录CRUD

新增端点:
- POST /tasks/{id}/receive — 确认接收 (PENDING→WIP)
- POST /tasks/{id}/reject  — 品质驳回 + 自动返工
- POST /tasks/{id}/transfer — 完工裂变转交 (多路分支+入库)
- PATCH /tasks/{id}/records — 追加图文记录
- PATCH /products/scan/{sn}/status — 宏观状态定调
- POST /upload/ + GET /upload/files/{name} — 文件上传
- GET /users/ — MOM用户列表
- GET /materials/groups + /materials/items — BOM物料手风琴
- POST /print/preview + /execute — 标签预览/打印
- PUT/DELETE /records/{id} — 记录编辑/删除
- GET /tasks/ 新增 assignee_id 过滤
This commit is contained in:
2026-08-05 14:00:43 +08:00
parent f9e10a7dd1
commit 79583cf8f4
8 changed files with 451 additions and 2 deletions

View File

@ -0,0 +1,92 @@
"""标签打印 API — 预览 / 执行 / 打印机配置"""
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field
from app.services.label_service import generate_preview_image, send_to_printer
from app.services.print_config import PrintConfigManager
router = APIRouter(prefix="/print", tags=["标签打印"])
# ============================================================
# 请求体
# ============================================================
class LabelPreviewRequest(BaseModel):
serial_number: str = Field(..., min_length=1, description="16位HEX系统ID二维码内容")
material_name: str = Field("", description="物料名称")
spec_model: str = Field("", description="规格型号")
order_no: str = Field("", description="订单号(条件渲染)")
class PrintExecuteRequest(LabelPreviewRequest):
copies: int = Field(1, ge=1, le=100, description="打印份数")
printer_ip: str | None = Field(None, description="覆盖配置的打印机 IP")
printer_port: int | None = Field(None, description="覆盖配置的打印机端口")
class PrinterConfigUpdate(BaseModel):
ip: str = Field(..., description="打印机 IP 地址")
port: int = Field(9100, ge=1, le=65535, description="打印机端口")
enabled: bool = Field(True, description="是否启用")
# ============================================================
# 端点
# ============================================================
@router.post("/preview")
def print_preview(data: LabelPreviewRequest) -> dict:
"""生成标签预览图Base64 JPEG"""
try:
data_url = generate_preview_image(**data.model_dump())
return {"data_url": data_url}
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"生成预览失败: {str(e)}",
)
@router.post("/execute")
def print_execute(data: PrintExecuteRequest) -> dict:
"""发送打印指令到物理打标机"""
payload = data.model_dump()
copies = payload.pop("copies", 1)
printer_ip = payload.pop("printer_ip", None)
printer_port = payload.pop("printer_port", None)
result = send_to_printer(
copies=copies,
printer_ip=printer_ip,
printer_port=printer_port,
**payload,
)
if not result["success"]:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=result["message"],
)
return result
@router.get("/config")
def get_printer_config() -> dict:
"""获取打印机当前配置"""
return PrintConfigManager.get_config()
@router.post("/config")
def update_printer_config(data: PrinterConfigUpdate) -> dict:
"""更新打印机配置IP/端口)"""
current = PrintConfigManager.get_config()
current["label_printer"] = {
"ip": data.ip,
"port": data.port,
"enabled": data.enabled,
}
PrintConfigManager.save_config(current)
return {
"message": "打印机配置已更新",
"config": current["label_printer"],
}