Files
orc-order-v2/web/backend/routers/barcodes.py
T
houhuan 81bafaf557 fix: sync/barcode/memory overhaul + detailed logs + preview + result tracking
- Sync: fix GiteaSync constructor + add push()/pull() methods
- Barcode: two-tab layout matching GUI (mapping + special rules)
- Memory: spec→specification unification, manual add, confidence/price tracking
- Processing: TaskLogHandler captures detailed logs (barcode mapping, unit conversion)
- Preview: fullscreen dialog for file preview (image/Excel) in Orders/Tables/Images
- Detail: per-file log filtering in file pages
- Tasks: result files now per-task, add copy path button
- Config: reactive edited state + save_config fix
- Dashboard: sync task isolation, log limit 10

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 19:37:10 +08:00

179 lines
5.8 KiB
Python

"""Barcode mapping CRUD endpoints."""
import json
from pathlib import Path
from typing import Dict, Optional, List
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from ..auth.dependencies import get_current_user
router = APIRouter(prefix="/api/barcodes", tags=["barcodes"])
_project_root = Path(__file__).resolve().parent.parent.parent.parent
_mappings_file = _project_root / "config" / "barcode_mappings.json"
class BarcodeMapping(BaseModel):
barcode: str
target: Optional[str] = None
description: Optional[str] = None
# Special rule fields
multiplier: Optional[int] = None
target_unit: Optional[str] = None
fixed_price: Optional[float] = None
specification: Optional[str] = None
class BarcodeMappingUpdate(BaseModel):
target: Optional[str] = None
description: Optional[str] = None
multiplier: Optional[int] = None
target_unit: Optional[str] = None
fixed_price: Optional[float] = None
specification: Optional[str] = None
def _load_mappings() -> Dict:
if not _mappings_file.is_file():
return {}
try:
return json.loads(_mappings_file.read_text(encoding="utf-8"))
except Exception:
return {}
def _save_mappings(data: Dict):
_mappings_file.parent.mkdir(parents=True, exist_ok=True)
_mappings_file.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
@router.get("")
async def list_barcodes(
search: str = "",
current_user: dict = Depends(get_current_user),
):
mappings = _load_mappings()
items = []
for barcode, info in mappings.items():
if isinstance(info, dict):
target = info.get("map_to", info.get("target", ""))
desc = info.get("description", "")
item = {
"barcode": barcode,
"target": target,
"description": desc,
"multiplier": info.get("multiplier"),
"target_unit": info.get("target_unit"),
"fixed_price": info.get("fixed_price"),
"specification": info.get("specification"),
}
else:
item = {
"barcode": barcode,
"target": str(info),
"description": "",
"multiplier": None,
"target_unit": None,
"fixed_price": None,
"specification": None,
}
s = search.lower() if search else ""
if s and s not in barcode.lower() and s not in item["target"].lower() and s not in (desc or "").lower():
continue
items.append(item)
return {"items": items, "total": len(items)}
@router.get("/{barcode}")
async def get_barcode(
barcode: str,
current_user: dict = Depends(get_current_user),
):
mappings = _load_mappings()
if barcode not in mappings:
raise HTTPException(404, f"未找到条码映射 {barcode}")
info = mappings[barcode]
if isinstance(info, dict):
return {"barcode": barcode, "target": info.get("map_to", info.get("target", "")), "description": info.get("description", "")}
return {"barcode": barcode, "target": str(info), "description": ""}
@router.post("")
async def create_barcode(
body: BarcodeMapping,
current_user: dict = Depends(get_current_user),
):
mappings = _load_mappings()
if body.barcode in mappings:
raise HTTPException(409, f"条码 {body.barcode} 已存在")
entry: dict = {"description": body.description or ""}
if body.multiplier:
entry["multiplier"] = body.multiplier
if body.target_unit:
entry["target_unit"] = body.target_unit
if body.fixed_price is not None:
entry["fixed_price"] = body.fixed_price
if body.specification:
entry["specification"] = body.specification
else:
entry["map_to"] = body.target or ""
mappings[body.barcode] = entry
_save_mappings(mappings)
return {"message": f"已创建规则 {body.barcode}"}
@router.put("/{barcode}")
async def update_barcode(
barcode: str,
body: BarcodeMappingUpdate,
current_user: dict = Depends(get_current_user),
):
mappings = _load_mappings()
if barcode not in mappings:
raise HTTPException(404, f"未找到条码规则 {barcode}")
existing = mappings[barcode]
if not isinstance(existing, dict):
existing = {"map_to": str(existing), "description": ""}
# Check if this is a special rule (has multiplier) or being converted to one
if body.multiplier is not None:
# Convert to special rule: remove map_to, add multiplier fields
existing.pop("map_to", None)
existing["multiplier"] = body.multiplier
if body.target_unit is not None:
existing["target_unit"] = body.target_unit
if body.fixed_price is not None:
existing["fixed_price"] = body.fixed_price
if body.specification is not None:
existing["specification"] = body.specification
elif body.target is not None:
# Convert to simple mapping: remove special fields, add map_to
for k in ("multiplier", "target_unit", "fixed_price", "specification"):
existing.pop(k, None)
existing["map_to"] = body.target
if body.description is not None:
existing["description"] = body.description
mappings[barcode] = existing
_save_mappings(mappings)
return {"message": f"已更新规则 {barcode}"}
@router.delete("/{barcode}")
async def delete_barcode(
barcode: str,
current_user: dict = Depends(get_current_user),
):
mappings = _load_mappings()
if barcode not in mappings:
raise HTTPException(404, f"未找到条码映射 {barcode}")
del mappings[barcode]
_save_mappings(mappings)
return {"message": f"已删除映射 {barcode}"}