feat(cupang): 출고 박스 구성(box_plan) 저장 + 달력 상세를 센터 분배 형식으로
- 마이그레이션 005: cupang_shipments.box_plan JSONB (확정 당시 박스 구성) 확정 payload 에서 제품별/혼합 상자와 내용물·상자 종류를 받아 저장 - 달력 오른쪽 목록을 ③ 센터 분배 카드 형식으로 변경 — 혼합 상자는 내용물 트리까지 표시. box_plan 이 없는 예전 출고는 기존 합계 표시로 폴백 - 출고 보기(view.html) ③ 목록도 동일하게 box_plan 기준 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1244,6 +1244,59 @@ async def product_delete(
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 분배 확정 — 센터별 출고 묶음 생성
|
||||
# ════════════════════════════════════════════════════════════
|
||||
def _clean_box_plan(raw: Any) -> list[dict[str, Any]]:
|
||||
"""화면이 보낸 박스 구성을 표시용으로만 정리해서 저장한다.
|
||||
|
||||
수량·박스 수는 라인(제품 합계)이 정답이고, 이 값은 "어느 상자에 무엇이
|
||||
담겼는지"를 보여주기 위한 스냅샷이다.
|
||||
"""
|
||||
if not isinstance(raw, list):
|
||||
return []
|
||||
out: list[dict[str, Any]] = []
|
||||
for entry in raw[:200]:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
kind = "mix" if str(entry.get("kind")) == "mix" else "product"
|
||||
try:
|
||||
count = max(int(entry.get("count") or 0), 0)
|
||||
units = max(int(entry.get("units") or 0), 0)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if count <= 0:
|
||||
continue
|
||||
item: dict[str, Any] = {
|
||||
"kind": kind,
|
||||
"name": str(entry.get("name") or "")[:120],
|
||||
"count": count,
|
||||
"units": units,
|
||||
"quantity": count * units,
|
||||
}
|
||||
if kind == "product":
|
||||
item["product_code"] = str(entry.get("product_code") or "")[:60]
|
||||
else:
|
||||
item["box_type"] = str(entry.get("box_type") or "")[:40]
|
||||
contents = []
|
||||
for it in (entry.get("items") or [])[:50]:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
try:
|
||||
qty = max(int(it.get("quantity") or 0), 0)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if qty <= 0:
|
||||
continue
|
||||
contents.append(
|
||||
{
|
||||
"product_name": str(it.get("product_name") or "")[:120],
|
||||
"product_code": str(it.get("product_code") or "")[:60],
|
||||
"quantity": qty,
|
||||
}
|
||||
)
|
||||
item["items"] = contents
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
@router.post("/api/box-calc/confirm")
|
||||
async def box_calc_confirm(
|
||||
request: Request,
|
||||
@@ -1334,7 +1387,15 @@ async def box_calc_confirm(
|
||||
if not summary:
|
||||
summary = f"{boxes}박스 · {pieces}개" if boxes else f"{pieces}개"
|
||||
|
||||
plans.append({"center": center, "method": method, "lines": lines, "summary": summary})
|
||||
plans.append(
|
||||
{
|
||||
"center": center,
|
||||
"method": method,
|
||||
"lines": lines,
|
||||
"summary": summary,
|
||||
"box_plan": _clean_box_plan(raw.get("box_plan")),
|
||||
}
|
||||
)
|
||||
|
||||
if not plans:
|
||||
raise HTTPException(status_code=400, detail="담긴 품목이 없습니다.")
|
||||
@@ -1357,6 +1418,7 @@ async def box_calc_confirm(
|
||||
"worker": worker,
|
||||
"status": "출고준비",
|
||||
"memo": "박스 계산에서 분배 확정",
|
||||
"box_plan": plan["box_plan"],
|
||||
},
|
||||
lines=plan["lines"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user