Files
orc-order-v2/web/backend/routers/memory.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

202 lines
5.5 KiB
Python

"""Product memory CRUD endpoints."""
from typing import Optional, List, Dict
from pathlib import Path
from fastapi import APIRouter, HTTPException, Depends, Query
from pydantic import BaseModel
from ..auth.dependencies import get_current_user
router = APIRouter(prefix="/api/memory", tags=["memory"])
_project_root = Path(__file__).resolve().parent.parent.parent.parent
_db_path = str(_project_root / "data" / "product_cache.db")
_excel_source = str(_project_root / "templates" / "商品资料.xlsx")
class MemoryItem(BaseModel):
barcode: str
name: str
specification: Optional[str] = None
unit: Optional[str] = None
price: Optional[float] = None
avg_price: Optional[float] = None
min_price: Optional[float] = None
max_price: Optional[float] = None
price_count: int = 0
confidence: int = 0
source: str = "ocr"
last_used: Optional[str] = None
use_count: int = 0
class MemoryCreate(BaseModel):
barcode: str
name: Optional[str] = ""
specification: Optional[str] = None
unit: Optional[str] = None
price: Optional[float] = None
confidence: int = 50
class MemoryUpdate(BaseModel):
name: Optional[str] = None
specification: Optional[str] = None
unit: Optional[str] = None
price: Optional[float] = None
confidence: Optional[int] = None
class MemoryListResponse(BaseModel):
items: List[MemoryItem]
total: int
page: int
page_size: int
def _get_db():
from app.core.db.product_db import ProductDatabase
return ProductDatabase(_db_path, _excel_source)
def _row_to_item(row: Dict) -> MemoryItem:
return MemoryItem(
barcode=row.get("barcode", ""),
name=row.get("name", ""),
specification=row.get("specification"),
unit=row.get("unit"),
price=row.get("price"),
avg_price=row.get("avg_price"),
min_price=row.get("min_price"),
max_price=row.get("max_price"),
price_count=row.get("price_count", 0),
confidence=row.get("confidence", 0),
source=row.get("source", "ocr"),
last_used=row.get("last_used"),
use_count=row.get("use_count", 0),
)
@router.get("", response_model=MemoryListResponse)
async def list_memory(
search: str = "",
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=200),
current_user: dict = Depends(get_current_user),
):
db = _get_db()
results = db.get_all_memories()
if search:
s = search.lower()
results = [r for r in results if s in r.get("barcode", "").lower() or s in r.get("name", "").lower()]
total = len(results)
start = (page - 1) * page_size
page_items = results[start:start + page_size]
return MemoryListResponse(
items=[_row_to_item(r) for r in page_items],
total=total,
page=page,
page_size=page_size,
)
@router.get("/{barcode}")
async def get_memory(
barcode: str,
current_user: dict = Depends(get_current_user),
):
db = _get_db()
product = db.get_memory(barcode)
if not product:
raise HTTPException(404, f"未找到条码 {barcode} 的记忆记录")
return product
@router.post("")
async def create_memory(
body: MemoryCreate,
current_user: dict = Depends(get_current_user),
):
db = _get_db()
existing = db.get_memory(body.barcode)
if existing:
raise HTTPException(409, f"条码 {body.barcode} 已存在,请使用编辑功能")
db.learn_from_product({
"barcode": body.barcode,
"name": body.name or "",
"specification": body.specification or "",
"unit": body.unit or "",
"price": body.price or 0,
}, source="user_confirmed")
return {"message": f"已创建记忆记录 {body.barcode}"}
@router.put("/{barcode}")
async def update_memory(
barcode: str,
body: MemoryUpdate,
current_user: dict = Depends(get_current_user),
):
db = _get_db()
existing = db.get_memory(barcode)
if not existing:
raise HTTPException(404, f"未找到条码 {barcode}")
update_data = body.model_dump(exclude_none=True)
if not update_data:
raise HTTPException(400, "没有提供更新数据")
db.update_memory(barcode, update_data)
return {"message": f"已更新 {barcode}", "updated_fields": list(update_data.keys())}
@router.delete("/{barcode}")
async def delete_memory(
barcode: str,
current_user: dict = Depends(get_current_user),
):
db = _get_db()
existing = db.get_memory(barcode)
if not existing:
raise HTTPException(404, f"未找到条码 {barcode}")
db.delete_memory(barcode)
return {"message": f"已删除 {barcode}"}
@router.post("/reimport")
async def reimport_memory(
current_user: dict = Depends(get_current_user),
):
db = _get_db()
try:
count = db.reimport()
return {"message": f"重新导入完成,共导入 {count} 条记录", "count": count}
except Exception as e:
raise HTTPException(500, f"导入失败: {e}")
@router.get("/export/sync")
async def export_memory(
current_user: dict = Depends(get_current_user),
):
db = _get_db()
data = db.export_for_sync()
return {"data": data, "count": len(data)}
@router.post("/import/sync")
async def import_memory(
data: dict,
current_user: dict = Depends(get_current_user),
):
db = _get_db()
try:
count = db.import_from_sync(data.get("data", []))
return {"message": f"导入完成,共 {count}", "count": count}
except Exception as e:
raise HTTPException(500, f"导入失败: {e}")