From 25982b12bf29622391606bc296f22afbcf6e8ead Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Tue, 12 May 2026 15:44:00 +0800 Subject: [PATCH] feat: add one-to-one box mode with duplicate bind check Add box_mode field to BoxSaveRequest and DuplicateZongpaiBindError exception. In one-to-one mode, prevent the same zongpai from being packed into multiple boxes, returning 409 with existing box info. Co-Authored-By: Claude Opus 4.6 --- app/main.py | 16 +++++++++++++++- app/schemas/box.py | 1 + app/services/box_service.py | 20 ++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index a06f4a3..2f9e19e 100644 --- a/app/main.py +++ b/app/main.py @@ -19,7 +19,7 @@ except Exception as e: from app.api.v1.location import router as location_router from app.api.v1.box import router as box_router from app.services.location_service import DuplicateLocationError -from app.services.box_service import ZongpaiNotFoundError, DuplicateBoxItemError, InvalidZongpaiError +from app.services.box_service import ZongpaiNotFoundError, DuplicateBoxItemError, InvalidZongpaiError, DuplicateZongpaiBindError app = FastAPI(title="CargoTrace API", version="0.1.0") @@ -106,6 +106,20 @@ async def duplicate_box_handler(request: Request, exc: DuplicateBoxItemError): ) +@app.exception_handler(DuplicateZongpaiBindError) +async def duplicate_zongpai_bind_handler(request: Request, exc: DuplicateZongpaiBindError): + return JSONResponse( + status_code=409, + content={ + "error_code": "DUPLICATE_ZONGPAI_BIND", + "message": f"总排号 {exc.zongpai_no} 已绑定箱号 {exc.box_no},请勿重复装箱", + "paichan_no": exc.paichan_no, + "box_no": exc.box_no, + "zongpai_no": exc.zongpai_no, + }, + ) + + @app.get("/") async def root(): return {"message": "Welcome to CargoTrace API"} diff --git a/app/schemas/box.py b/app/schemas/box.py index 8cf0b48..85bea8a 100644 --- a/app/schemas/box.py +++ b/app/schemas/box.py @@ -32,6 +32,7 @@ class BoxSaveRequest(BaseModel): zongpai_no: str box_no: int quantity: int + box_mode: str | None = None # one-to-one | one-to-many | many-to-one @field_validator("zongpai_no") @classmethod diff --git a/app/services/box_service.py b/app/services/box_service.py index ab4eab3..11aa676 100644 --- a/app/services/box_service.py +++ b/app/services/box_service.py @@ -23,6 +23,13 @@ class DuplicateBoxItemError(Exception): self.box_no = box_no +class DuplicateZongpaiBindError(Exception): + def __init__(self, paichan_no: str, box_no: int, zongpai_no: str): + self.paichan_no = paichan_no + self.box_no = box_no + self.zongpai_no = zongpai_no + + def query_erp_info(db: Session, zongpai_no: str) -> dict: """从 ERP 视图查询总排号对应的排产号和数量。""" sql = text(_erp_info_sql(db)) @@ -101,6 +108,19 @@ def save_box_record(db: Session, req: BoxSaveRequest) -> dict: erp = query_erp_info(db, req.zongpai_no) paichan_no = erp["paichan_no"] + if req.box_mode == "one-to-one": + existing_box_no = ( + db.query(FinishedGoodsBox.box_no) + .join(FinishedGoodsBoxItem, FinishedGoodsBox.id == FinishedGoodsBoxItem.box_id) + .filter( + FinishedGoodsBox.paichan_no == paichan_no, + FinishedGoodsBoxItem.zongpai_no == req.zongpai_no, + ) + .scalar() + ) + if existing_box_no is not None: + raise DuplicateZongpaiBindError(paichan_no, existing_box_no, req.zongpai_no) + box = ( db.query(FinishedGoodsBox) .filter(