feat(box): simplify boxing mode API

This commit is contained in:
Misaka_Company
2026-05-14 12:29:33 +08:00
parent bd926050c2
commit 5a7e88c3c9
5 changed files with 156 additions and 58 deletions

View File

@@ -37,7 +37,7 @@ def create_box(req: BoxSaveRequest, db: Session = Depends(get_db)):
@router.patch("/box/{box_item_id}", response_model=BoxUpdateResponse)
def update_box(box_item_id: int, req: BoxUpdateRequest, db: Session = Depends(get_db)):
"""更新一码多箱已分配装箱明细。"""
"""更新已分配装箱明细。"""
return update_box_item(db, box_item_id, req)

View File

@@ -22,8 +22,8 @@ from app.services.location_service import AlreadyOffShelfError, DuplicateLocatio
from app.services.box_service import (
BoxItemNotFoundError,
DuplicateBoxItemError,
DuplicateZongpaiBindError,
InvalidBoxItemError,
InvalidQuantityError,
InvalidZongpaiError,
ZongpaiNotFoundError,
)
@@ -126,20 +126,6 @@ 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.exception_handler(BoxItemNotFoundError)
async def box_item_not_found_handler(request: Request, exc: BoxItemNotFoundError):
return JSONResponse(
@@ -151,6 +137,17 @@ async def box_item_not_found_handler(request: Request, exc: BoxItemNotFoundError
)
@app.exception_handler(InvalidQuantityError)
async def invalid_quantity_handler(request: Request, exc: InvalidQuantityError):
return JSONResponse(
status_code=400,
content={
"error_code": "INVALID_QUANTITY",
"message": "装箱数量超出可装数量上限",
},
)
@app.exception_handler(InvalidBoxItemError)
async def invalid_box_item_handler(request: Request, exc: InvalidBoxItemError):
return JSONResponse(

View File

@@ -44,7 +44,6 @@ 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
@@ -83,7 +82,6 @@ class BoxUpdateRequest(BaseModel):
"""PATCH /box/{box_item_id} 请求"""
box_no: int
quantity: int
box_mode: str = "one-to-many"
@field_validator("box_no")
@classmethod

View File

@@ -23,13 +23,6 @@ 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
class BoxItemNotFoundError(Exception):
pass
@@ -38,6 +31,10 @@ class InvalidBoxItemError(Exception):
pass
class InvalidQuantityError(Exception):
pass
def query_erp_info(db: Session, zongpai_no: str) -> dict:
"""从 ERP 视图查询总排号对应的排产号、工令号和数量。"""
sql = text(_erp_info_sql(db))
@@ -172,24 +169,48 @@ def get_box_info(db: Session, zongpai_no: str) -> dict:
}
def _packed_quantity_for_zongpai(
db: Session,
paichan_no: str,
zongpai_no: str,
exclude_box_item_id: int | None = None,
) -> int:
query = (
db.query(FinishedGoodsBoxItem)
.join(FinishedGoodsBox, FinishedGoodsBoxItem.box_id == FinishedGoodsBox.id)
.filter(
FinishedGoodsBox.paichan_no == paichan_no,
FinishedGoodsBoxItem.zongpai_no == zongpai_no,
)
)
if exclude_box_item_id is not None:
query = query.filter(FinishedGoodsBoxItem.id != exclude_box_item_id)
return sum(int(item.quantity or 0) for item in query.all())
def _ensure_quantity_within_erp_total(
db: Session,
paichan_no: str,
zongpai_no: str,
erp_quantity: int,
submitted_quantity: int,
exclude_box_item_id: int | None = None,
) -> None:
packed_quantity = _packed_quantity_for_zongpai(
db,
paichan_no,
zongpai_no,
exclude_box_item_id=exclude_box_item_id,
)
if packed_quantity + submitted_quantity > erp_quantity:
raise InvalidQuantityError()
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(
@@ -200,8 +221,6 @@ def save_box_record(db: Session, req: BoxSaveRequest) -> dict:
)
if box:
if req.box_mode in ("one-to-one", "one-to-many"):
raise DuplicateBoxItemError(paichan_no, req.box_no)
existing_item = (
db.query(FinishedGoodsBoxItem)
.filter(
@@ -212,7 +231,16 @@ def save_box_record(db: Session, req: BoxSaveRequest) -> dict:
)
if existing_item:
raise DuplicateBoxItemError(paichan_no, req.box_no)
else:
_ensure_quantity_within_erp_total(
db,
paichan_no,
req.zongpai_no,
erp["quantity"],
req.quantity,
)
if box is None:
box = FinishedGoodsBox(
paichan_no=paichan_no,
box_no=req.box_no,
@@ -240,7 +268,7 @@ def save_box_record(db: Session, req: BoxSaveRequest) -> dict:
def update_box_item(db: Session, box_item_id: int, req: BoxUpdateRequest) -> dict:
"""更新一码多箱已分配明细的箱号和数量。"""
"""更新已分配明细的箱号和数量。"""
item = (
db.query(FinishedGoodsBoxItem)
.filter(FinishedGoodsBoxItem.id == box_item_id)
@@ -267,13 +295,32 @@ def update_box_item(db: Session, box_item_id: int, req: BoxUpdateRequest) -> dic
.first()
)
if target_box is not None and target_box.id != current_box.id:
raise DuplicateBoxItemError(paichan_no, req.box_no)
if target_box is None:
target_box = FinishedGoodsBox(paichan_no=paichan_no, box_no=req.box_no)
db.add(target_box)
db.flush()
else:
duplicate_item = (
db.query(FinishedGoodsBoxItem)
.filter(
FinishedGoodsBoxItem.box_id == target_box.id,
FinishedGoodsBoxItem.zongpai_no == item.zongpai_no,
FinishedGoodsBoxItem.id != item.id,
)
.first()
)
if duplicate_item is not None:
raise DuplicateBoxItemError(paichan_no, req.box_no)
erp = query_erp_info(db, item.zongpai_no)
_ensure_quantity_within_erp_total(
db,
paichan_no,
item.zongpai_no,
erp["quantity"],
req.quantity,
exclude_box_item_id=box_item_id,
)
old_box_id = item.box_id
item.box_id = target_box.id