feat(server): add POST /attachment/box with real-evidence flip + cap

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-08-12 14:12:42 +08:00
parent 76d2095117
commit 0ef4f0e990
4 changed files with 300 additions and 1 deletions

View File

@@ -11,8 +11,13 @@ from app.models.attachment import (
FinishedGoodsAttachmentLocation,
FinishedGoodsBoxAttachmentItem,
)
from app.models.finished_goods import FinishedGoodsBox
from app.schemas.attachment import AttachmentConfigRequest
from app.services.box_service import InvalidZongpaiError
from app.services.box_service import (
InvalidQuantityError,
InvalidZongpaiError,
query_erp_info,
)
ZONGPAI_PATTERN = re.compile(r"^(\d{2}(B|C|T)\d+|\d{2}(BW|CW)\d{4})$")
@@ -257,3 +262,112 @@ def register_attachment_location(db: Session, req) -> FinishedGoodsAttachmentLoc
db.commit()
db.refresh(record)
return record
def _boxed_for_category(
db: Session, zongpai_no: str, category_id: int, exclude_item_id: int | None = None
) -> int:
q = db.query(FinishedGoodsBoxAttachmentItem).filter(
FinishedGoodsBoxAttachmentItem.zongpai_no == zongpai_no,
FinishedGoodsBoxAttachmentItem.category_id == category_id,
)
if exclude_item_id is not None:
q = q.filter(FinishedGoodsBoxAttachmentItem.id != exclude_item_id)
return sum(int(r.quantity or 0) for r in q.all())
def _ensure_attachment_item(db: Session, zongpai_no: str, category_id: int, erp_qty: int):
"""实物证据:不存在则建 itemexpected 兜底 = ERP 数量),并保证 determination=has。"""
if not _category_name_map(db, [category_id]):
raise CategoryNotFoundError(category_id)
att = (
db.query(FinishedGoodsAttachment)
.filter(FinishedGoodsAttachment.zongpai_no == zongpai_no)
.first()
)
if att is None:
att = FinishedGoodsAttachment(zongpai_no=zongpai_no, determination="has")
db.add(att)
elif att.determination in ("undetermined", "none"):
att.determination = "has"
att.updated_at = datetime.now()
db.flush()
item = (
db.query(FinishedGoodsAttachmentItem)
.filter(
FinishedGoodsAttachmentItem.zongpai_no == zongpai_no,
FinishedGoodsAttachmentItem.category_id == category_id,
)
.first()
)
if item is None:
item = FinishedGoodsAttachmentItem(
zongpai_no=zongpai_no,
category_id=category_id,
expected_qty=erp_qty,
)
db.add(item)
db.flush()
return item
def save_attachment_box(db: Session, req) -> dict:
zongpai_no = _validate_zongpai(req.zongpai_no)
erp = query_erp_info(db, zongpai_no) # raises ZongpaiNotFoundError if missing
paichan_no = erp["paichan_no"]
erp_qty = erp["quantity"]
item = _ensure_attachment_item(db, zongpai_no, req.category_id, erp_qty)
box = (
db.query(FinishedGoodsBox)
.filter(
FinishedGoodsBox.paichan_no == paichan_no,
FinishedGoodsBox.box_no == req.box_no,
)
.first()
)
if box is not None:
existing_in_box = (
db.query(FinishedGoodsBoxAttachmentItem)
.filter(
FinishedGoodsBoxAttachmentItem.box_id == box.id,
FinishedGoodsBoxAttachmentItem.zongpai_no == zongpai_no,
FinishedGoodsBoxAttachmentItem.category_id == req.category_id,
)
.first()
)
if existing_in_box is not None:
raise DuplicateAttachmentBoxItemError(paichan_no, req.box_no)
else:
box = FinishedGoodsBox(paichan_no=paichan_no, box_no=req.box_no)
db.add(box)
db.flush()
# Cap check against expected_qty (mirrors product _ensure_quantity_within_erp_total)
already_boxed = _boxed_for_category(db, zongpai_no, req.category_id)
if already_boxed + req.quantity > item.expected_qty:
raise InvalidQuantityError()
record = FinishedGoodsBoxAttachmentItem(
box_id=box.id,
zongpai_no=zongpai_no,
category_id=req.category_id,
quantity=req.quantity,
)
db.add(record)
db.commit()
db.refresh(record)
return {
"box_item_id": record.id,
"paichan_no": paichan_no,
"box_no": req.box_no,
"zongpai_no": zongpai_no,
"category_id": req.category_id,
"quantity": req.quantity,
"created_at": record.created_at.isoformat(),
}