81bafaf557
- 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>
179 lines
5.8 KiB
Python
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}"}
|