feat(cupang): 달력 월 표기 확대, 출고 보기 ②를 상자 번호별 내용물 목록으로

- 달력 월 이름/연도 글자 크기 2배·굵게
- 출고 상세 ② 상자 요약 → ② 상자 목록: 전체 상자에 1번부터 번호를 붙이고
  상자마다 담긴 제품·수량을 표시. box_plan 스냅샷 우선, 없으면 계산 결과로 대체

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 19:49:42 +09:00
parent e49abc4b8c
commit 15947e73ed
8 changed files with 143 additions and 56 deletions
+76
View File
@@ -425,6 +425,7 @@ async def detail(request: Request, shipment_id: int) -> HTMLResponse:
"shipment": ship,
"items": items,
"calc": calc,
"box_list": _expand_box_list(ship, calc, items),
"total_qty": total_qty,
},
)
@@ -1052,6 +1053,81 @@ def _compute_boxes(store: Any, raw_items: list[Any]) -> dict[str, Any]:
}
def _expand_box_list(
shipment: dict[str, Any], calc: dict[str, Any], items: list[dict[str, Any]]
) -> list[dict[str, Any]]:
"""전체 상자를 1번부터 번호 붙여 펼친다 — 상자마다 무엇이 몇 개 들었는지.
확정 당시 스냅샷(`box_plan`)이 있으면 그것을 그대로 펼치고,
없는 예전 데이터는 계산 결과(`calc`)로 대신 만든다.
"""
box_name_by_code = {
r["product_code"]: (r.get("box_name") or "") for r in calc.get("results", [])
}
out: list[dict[str, Any]] = []
plan = shipment.get("box_plan") or []
if plan:
for entry in plan:
count = int(entry.get("count") or 0)
units = int(entry.get("units") or 0)
if count <= 0:
continue
if entry.get("kind") == "mix":
contents = [
{
"product_name": it.get("product_name") or it.get("product_code") or "",
"quantity": int(it.get("quantity") or 0),
}
for it in (entry.get("items") or [])
]
box_name = entry.get("box_type") or ""
for _ in range(count):
out.append({"kind": "mix", "box_name": box_name, "items": contents})
else:
code = str(entry.get("product_code") or "")
contents = [
{"product_name": entry.get("name") or code, "quantity": units}
]
box_name = box_name_by_code.get(code, "")
for _ in range(count):
out.append({"kind": "product", "box_name": box_name, "items": contents})
else:
for r in calc.get("results", []):
if not r.get("configured"):
continue
for _ in range(int(r.get("full_boxes") or 0)):
out.append(
{
"kind": "product",
"box_name": r.get("box_name") or "",
"items": [
{"product_name": r["product_name"], "quantity": r["units_per_box"]}
],
}
)
for mix in calc.get("mixes", []):
for box in mix.get("boxes", []):
out.append(
{
"kind": "mix",
"box_name": mix.get("box_name") or "",
"items": [
{
"product_name": it.get("product_name") or "",
"quantity": int(it.get("quantity") or 0),
}
for it in (box.get("items") or [])
],
}
)
for idx, box in enumerate(out, start=1):
box["no"] = idx
box["quantity"] = sum(int(it["quantity"]) for it in box["items"])
return out
def _pack_leftovers(results: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""제품별 자투리(remainder_units)를 같은 상자명끼리 모아 혼합 상자에 담는다.