feat(server): add PATCH/DELETE /attachment/box mirroring product box ops
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,8 +4,11 @@ from sqlalchemy.orm import Session
|
||||
from app.core.database import get_db
|
||||
from app.models.attachment import AttachmentCategory
|
||||
from app.schemas.attachment import (
|
||||
AttachmentBoxDeleteResponse,
|
||||
AttachmentBoxSaveRequest,
|
||||
AttachmentBoxSaveResponse,
|
||||
AttachmentBoxUpdateRequest,
|
||||
AttachmentBoxUpdateResponse,
|
||||
AttachmentConfigRequest,
|
||||
AttachmentLocationRequest,
|
||||
AttachmentLocationResponse,
|
||||
@@ -15,9 +18,11 @@ from app.schemas.attachment import (
|
||||
from app.schemas.common import ErrorResponse
|
||||
from app.services.attachment_service import (
|
||||
configure_attachment,
|
||||
delete_attachment_box,
|
||||
get_attachment_status,
|
||||
register_attachment_location,
|
||||
save_attachment_box,
|
||||
update_attachment_box,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["attachment"])
|
||||
@@ -82,3 +87,29 @@ def post_attachment_box(
|
||||
req: AttachmentBoxSaveRequest, db: Session = Depends(get_db)
|
||||
):
|
||||
return save_attachment_box(db, req)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/attachment/box/{item_id}",
|
||||
response_model=AttachmentBoxUpdateResponse,
|
||||
responses={
|
||||
400: {"description": "参数非法或超量", "model": ErrorResponse},
|
||||
404: {"description": "明细不存在", "model": ErrorResponse},
|
||||
409: {"description": "同箱同类型重复", "model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
def patch_attachment_box(
|
||||
item_id: int, req: AttachmentBoxUpdateRequest, db: Session = Depends(get_db)
|
||||
):
|
||||
return update_attachment_box(db, item_id, req)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/attachment/box/{item_id}",
|
||||
response_model=AttachmentBoxDeleteResponse,
|
||||
responses={404: {"description": "明细不存在", "model": ErrorResponse}},
|
||||
)
|
||||
def delete_attachment_box_endpoint(
|
||||
item_id: int, db: Session = Depends(get_db)
|
||||
):
|
||||
return delete_attachment_box(db, item_id)
|
||||
|
||||
11
app/main.py
11
app/main.py
@@ -34,6 +34,7 @@ from app.services.box_service import (
|
||||
)
|
||||
from app.services.attachment_service import (
|
||||
AlreadyOffShelfAttachmentError,
|
||||
AttachmentItemNotFoundError,
|
||||
CategoryHasBoxesError,
|
||||
CategoryNotFoundError,
|
||||
DuplicateAttachmentBoxItemError,
|
||||
@@ -247,6 +248,16 @@ async def duplicate_attachment_box_handler(
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(AttachmentItemNotFoundError)
|
||||
async def attachment_item_not_found_handler(
|
||||
request: Request, exc: AttachmentItemNotFoundError
|
||||
):
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"error_code": "BOX_ITEM_NOT_FOUND", "message": "指定装箱明细不存在"},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Welcome to CargoTrace API"}
|
||||
|
||||
@@ -371,3 +371,119 @@ def save_attachment_box(db: Session, req) -> dict:
|
||||
"quantity": req.quantity,
|
||||
"created_at": record.created_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
def update_attachment_box(db: Session, item_id: int, req) -> dict:
|
||||
record = (
|
||||
db.query(FinishedGoodsBoxAttachmentItem)
|
||||
.filter(FinishedGoodsBoxAttachmentItem.id == item_id)
|
||||
.first()
|
||||
)
|
||||
if record is None:
|
||||
raise AttachmentItemNotFoundError()
|
||||
|
||||
current_box = (
|
||||
db.query(FinishedGoodsBox)
|
||||
.filter(FinishedGoodsBox.id == record.box_id)
|
||||
.first()
|
||||
)
|
||||
if current_box is None:
|
||||
raise AttachmentItemNotFoundError()
|
||||
paichan_no = current_box.paichan_no
|
||||
|
||||
target_box = (
|
||||
db.query(FinishedGoodsBox)
|
||||
.filter(
|
||||
FinishedGoodsBox.paichan_no == paichan_no,
|
||||
FinishedGoodsBox.box_no == req.box_no,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if target_box is None:
|
||||
target_box = FinishedGoodsBox(paichan_no=paichan_no, box_no=req.box_no)
|
||||
db.add(target_box)
|
||||
db.flush()
|
||||
else:
|
||||
dup = (
|
||||
db.query(FinishedGoodsBoxAttachmentItem)
|
||||
.filter(
|
||||
FinishedGoodsBoxAttachmentItem.box_id == target_box.id,
|
||||
FinishedGoodsBoxAttachmentItem.zongpai_no == record.zongpai_no,
|
||||
FinishedGoodsBoxAttachmentItem.category_id == record.category_id,
|
||||
FinishedGoodsBoxAttachmentItem.id != record.id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if dup is not None:
|
||||
raise DuplicateAttachmentBoxItemError(paichan_no, req.box_no)
|
||||
|
||||
item = (
|
||||
db.query(FinishedGoodsAttachmentItem)
|
||||
.filter(
|
||||
FinishedGoodsAttachmentItem.zongpai_no == record.zongpai_no,
|
||||
FinishedGoodsAttachmentItem.category_id == record.category_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
expected_qty = item.expected_qty if item is not None else 0
|
||||
already_boxed = _boxed_for_category(
|
||||
db, record.zongpai_no, record.category_id, exclude_item_id=record.id
|
||||
)
|
||||
if already_boxed + req.quantity > expected_qty:
|
||||
raise InvalidQuantityError()
|
||||
|
||||
old_box_id = record.box_id
|
||||
record.box_id = target_box.id
|
||||
record.quantity = req.quantity
|
||||
|
||||
if old_box_id != target_box.id:
|
||||
_cleanup_empty_box(db, old_box_id, exclude_item_id=record.id)
|
||||
|
||||
db.commit()
|
||||
db.refresh(record)
|
||||
return {
|
||||
"box_item_id": record.id,
|
||||
"paichan_no": paichan_no,
|
||||
"box_no": req.box_no,
|
||||
"zongpai_no": record.zongpai_no,
|
||||
"category_id": record.category_id,
|
||||
"quantity": int(record.quantity or 0),
|
||||
"updated_at": record.created_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
def delete_attachment_box(db: Session, item_id: int) -> dict:
|
||||
record = (
|
||||
db.query(FinishedGoodsBoxAttachmentItem)
|
||||
.filter(FinishedGoodsBoxAttachmentItem.id == item_id)
|
||||
.first()
|
||||
)
|
||||
if record is None:
|
||||
raise AttachmentItemNotFoundError()
|
||||
box_id = record.box_id
|
||||
db.delete(record)
|
||||
db.flush()
|
||||
_cleanup_empty_box(db, box_id)
|
||||
db.commit()
|
||||
return {"box_item_id": item_id, "deleted": True}
|
||||
|
||||
|
||||
def _cleanup_empty_box(db: Session, box_id: int, exclude_item_id: int | None = None) -> None:
|
||||
"""If a box has no product items and no attachment items, delete it."""
|
||||
from app.models.finished_goods import FinishedGoodsBoxItem
|
||||
|
||||
product_q = db.query(FinishedGoodsBoxItem.id).filter(
|
||||
FinishedGoodsBoxItem.box_id == box_id
|
||||
)
|
||||
if product_q.first() is not None:
|
||||
return
|
||||
att_q = db.query(FinishedGoodsBoxAttachmentItem.id).filter(
|
||||
FinishedGoodsBoxAttachmentItem.box_id == box_id
|
||||
)
|
||||
if exclude_item_id is not None:
|
||||
att_q = att_q.filter(FinishedGoodsBoxAttachmentItem.id != exclude_item_id)
|
||||
if att_q.first() is not None:
|
||||
return
|
||||
box = db.query(FinishedGoodsBox).filter(FinishedGoodsBox.id == box_id).first()
|
||||
if box is not None:
|
||||
db.delete(box)
|
||||
|
||||
Reference in New Issue
Block a user