feat(cupang): 상자 목록을 번호·제품명·제품코드·수량 표로, 엑셀 다운로드 추가
- ② 상자 목록: 카드 대신 표. 한 상자에 여러 제품이면 상자번호 칸 rowspan 병합
- GET /cupang/{id}/boxes.xlsx — 같은 표를 xlsx 로 (export.build_box_list_workbook)
- box_list 항목에 product_code 포함
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -431,6 +431,47 @@ async def detail(request: Request, shipment_id: int) -> HTMLResponse:
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{shipment_id:int}/boxes.xlsx")
|
||||
async def export_box_list_xlsx(request: Request, shipment_id: int) -> Any:
|
||||
"""상자 목록(상자번호·제품명·제품코드·수량) 엑셀 다운로드."""
|
||||
from io import BytesIO # noqa: WPS433
|
||||
|
||||
from fastapi.responses import StreamingResponse # noqa: WPS433
|
||||
|
||||
from .export import build_box_list_workbook # noqa: WPS433
|
||||
|
||||
guard = _guard(request)
|
||||
if not isinstance(guard, tuple):
|
||||
return guard
|
||||
store, _user = guard
|
||||
|
||||
ship = store.get_shipment(shipment_id=shipment_id)
|
||||
if not ship:
|
||||
raise HTTPException(status_code=404, detail="출고 묶음을 찾을 수 없습니다.")
|
||||
|
||||
items = [
|
||||
{
|
||||
"product_code": ln.get("product_code"),
|
||||
"product_name": ln.get("product_name_snapshot") or ln.get("product_code"),
|
||||
"quantity": int(ln.get("quantity") or 0),
|
||||
}
|
||||
for ln in (ship.get("lines") or [])
|
||||
]
|
||||
box_list = _expand_box_list(ship, _compute_boxes(store, items), items)
|
||||
|
||||
wb = build_box_list_workbook(ship, box_list)
|
||||
buf = BytesIO()
|
||||
wb.save(buf)
|
||||
buf.seek(0)
|
||||
stamp = str(ship.get("ship_date") or "").replace("-", "")
|
||||
filename = f"{stamp}_cupang_boxes_{shipment_id}.xlsx"
|
||||
return StreamingResponse(
|
||||
buf,
|
||||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
|
||||
|
||||
def _safe_next(raw: str, fallback: str) -> str:
|
||||
"""열린 리다이렉트 방지 — /cupang/ 안쪽 경로만 허용."""
|
||||
nxt = (raw or "").strip()
|
||||
@@ -1077,6 +1118,7 @@ def _expand_box_list(
|
||||
contents = [
|
||||
{
|
||||
"product_name": it.get("product_name") or it.get("product_code") or "",
|
||||
"product_code": it.get("product_code") or "",
|
||||
"quantity": int(it.get("quantity") or 0),
|
||||
}
|
||||
for it in (entry.get("items") or [])
|
||||
@@ -1087,7 +1129,11 @@ def _expand_box_list(
|
||||
else:
|
||||
code = str(entry.get("product_code") or "")
|
||||
contents = [
|
||||
{"product_name": entry.get("name") or code, "quantity": units}
|
||||
{
|
||||
"product_name": entry.get("name") or code,
|
||||
"product_code": code,
|
||||
"quantity": units,
|
||||
}
|
||||
]
|
||||
box_name = box_name_by_code.get(code, "")
|
||||
for _ in range(count):
|
||||
@@ -1102,7 +1148,11 @@ def _expand_box_list(
|
||||
"kind": "product",
|
||||
"box_name": r.get("box_name") or "",
|
||||
"items": [
|
||||
{"product_name": r["product_name"], "quantity": r["units_per_box"]}
|
||||
{
|
||||
"product_name": r["product_name"],
|
||||
"product_code": r["product_code"],
|
||||
"quantity": r["units_per_box"],
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
@@ -1115,6 +1165,7 @@ def _expand_box_list(
|
||||
"items": [
|
||||
{
|
||||
"product_name": it.get("product_name") or "",
|
||||
"product_code": it.get("product_code") or "",
|
||||
"quantity": int(it.get("quantity") or 0),
|
||||
}
|
||||
for it in (box.get("items") or [])
|
||||
|
||||
Reference in New Issue
Block a user