feat: add boxing module API — GET /box/info and POST /box
- Add box schemas (BoxInfoResponse, BoxSaveRequest, BoxSaveResponse) - Add box service with ERP view query and save logic (supports many2one append) - Add box router with GET /box/info and POST /box endpoints - Register exception handlers for InvalidZongpaiError, ZongpaiNotFoundError, DuplicateBoxItemError - Update RequestValidationError handler to cover INVALID_BOX_NO and INVALID_QUANTITY - Add 7 integration tests for box API Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
135
app/services/box_service.py
Normal file
135
app/services/box_service.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import re
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.finished_goods import FinishedGoodsBox, FinishedGoodsBoxItem
|
||||
from app.schemas.box import BoxSaveRequest
|
||||
|
||||
ZONGPAI_PATTERN = re.compile(r"^(\d{2}(B|C|T)\d+|\d{2}(BW|CW)\d{4})$")
|
||||
|
||||
|
||||
class InvalidZongpaiError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ZongpaiNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DuplicateBoxItemError(Exception):
|
||||
def __init__(self, paichan_no: str, box_no: int):
|
||||
self.paichan_no = paichan_no
|
||||
self.box_no = box_no
|
||||
|
||||
|
||||
def query_erp_info(db: Session, zongpai_no: str) -> dict:
|
||||
"""从 ERP 视图查询总排号对应的排产号和数量。"""
|
||||
sql = text(
|
||||
"SELECT TOP 1 [总排号], [排产号], [数量] "
|
||||
"FROM [ERPAuto].[vw_productionContractData] "
|
||||
"WHERE [总排号] = :zongpai_no"
|
||||
)
|
||||
row = db.execute(sql, {"zongpai_no": zongpai_no}).fetchone()
|
||||
if not row:
|
||||
raise ZongpaiNotFoundError()
|
||||
return {
|
||||
"zongpai_no": row[0],
|
||||
"paichan_no": row[1],
|
||||
"quantity": int(row[2]),
|
||||
}
|
||||
|
||||
|
||||
def get_box_info(db: Session, zongpai_no: str) -> dict:
|
||||
"""获取装箱信息:排产号、数量、已有箱号明细。"""
|
||||
zongpai_no = zongpai_no.strip().upper()
|
||||
if not ZONGPAI_PATTERN.match(zongpai_no):
|
||||
raise InvalidZongpaiError()
|
||||
erp = query_erp_info(db, zongpai_no)
|
||||
paichan_no = erp["paichan_no"]
|
||||
quantity = erp["quantity"]
|
||||
|
||||
boxes = (
|
||||
db.query(FinishedGoodsBox)
|
||||
.filter(FinishedGoodsBox.paichan_no == paichan_no)
|
||||
.order_by(FinishedGoodsBox.box_no)
|
||||
.all()
|
||||
)
|
||||
|
||||
existing_boxes = []
|
||||
max_box_no = 0
|
||||
for box in boxes:
|
||||
items = (
|
||||
db.query(FinishedGoodsBoxItem)
|
||||
.filter(FinishedGoodsBoxItem.box_id == box.id)
|
||||
.all()
|
||||
)
|
||||
existing_boxes.append({
|
||||
"box_no": box.box_no,
|
||||
"items": [
|
||||
{"zongpai_no": item.zongpai_no, "quantity": int(item.quantity or 0)}
|
||||
for item in items
|
||||
],
|
||||
})
|
||||
if box.box_no > max_box_no:
|
||||
max_box_no = box.box_no
|
||||
|
||||
return {
|
||||
"zongpai_no": zongpai_no,
|
||||
"paichan_no": paichan_no,
|
||||
"quantity": quantity,
|
||||
"existing_boxes": existing_boxes,
|
||||
"max_box_no": max_box_no,
|
||||
"suggested_box_no": max_box_no + 1,
|
||||
}
|
||||
|
||||
|
||||
def save_box_record(db: Session, req: BoxSaveRequest) -> dict:
|
||||
"""保存装箱记录。箱已存在时仅追加明细(多码一箱场景)。"""
|
||||
erp = query_erp_info(db, req.zongpai_no)
|
||||
paichan_no = erp["paichan_no"]
|
||||
|
||||
box = (
|
||||
db.query(FinishedGoodsBox)
|
||||
.filter(
|
||||
FinishedGoodsBox.paichan_no == paichan_no,
|
||||
FinishedGoodsBox.box_no == req.box_no,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if box:
|
||||
existing_item = (
|
||||
db.query(FinishedGoodsBoxItem)
|
||||
.filter(
|
||||
FinishedGoodsBoxItem.box_id == box.id,
|
||||
FinishedGoodsBoxItem.zongpai_no == req.zongpai_no,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if existing_item:
|
||||
raise DuplicateBoxItemError(paichan_no, req.box_no)
|
||||
else:
|
||||
box = FinishedGoodsBox(
|
||||
paichan_no=paichan_no,
|
||||
box_no=req.box_no,
|
||||
)
|
||||
db.add(box)
|
||||
db.flush()
|
||||
|
||||
item = FinishedGoodsBoxItem(
|
||||
box_id=box.id,
|
||||
zongpai_no=req.zongpai_no,
|
||||
quantity=req.quantity,
|
||||
)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
|
||||
return {
|
||||
"paichan_no": paichan_no,
|
||||
"box_no": req.box_no,
|
||||
"zongpai_no": req.zongpai_no,
|
||||
"quantity": req.quantity,
|
||||
"created_at": item.created_at.isoformat(),
|
||||
}
|
||||
Reference in New Issue
Block a user