feat(cupang): 출고 등록 경로를 박스 계산으로 일원화, 달력 표시 정리

출고 묶음을 만드는 길이 둘(신규 등록 폼 / 박스 계산 분배 확정)이라 헷갈렸다.
폼 경로를 없애고 [+ 신규 등록] 이 박스 계산을 열게 한다. 중복이던 오른쪽
[박스 계산] 버튼은 제거. /cupang/new 는 북마크가 깨지지 않게 박스 계산으로
리다이렉트하고 POST /new(생성)는 삭제했다. 기존 묶음 수정은 그대로 동작한다.

달력 칸은 작성/출고/입고 세 배지를 늘어놓아 읽기 어려웠다. 출고 기준으로
"출고 N건 · 센터 M곳"만 남긴다. 오른쪽 상세도 같은 기준(출고일)으로 맞추고
센터별 출고방식·총 박스/개수와 상품별 수량·박스를 카드 안에 펼친다. 품목이
바로 보이므로 hover 툴팁은 제거.

임시 저장 기본 제목은 "2026.08.31(월) 오후 04시 50분" 형식으로.
This commit is contained in:
2026-08-31 16:58:39 +09:00
parent 7f879d279a
commit 7a01c140b0
10 changed files with 95 additions and 138 deletions
+32 -67
View File
@@ -129,28 +129,43 @@ async def index(request: Request) -> HTMLResponse:
store, user = guard
year, month = _ym(request)
counts = store.calendar_counts(year=year, month=month)
shipments = store.list_shipments(year=year, month=month)
# 달력 칸에는 출고 건수와 센터 수만 보여준다.
counts: dict[str, dict[str, Any]] = {}
for s in shipments:
d = s.get("ship_date")
if not d:
continue
cell = counts.setdefault(str(d), {"ship": 0, "centers": set()})
cell["ship"] += 1
cell["centers"].add(s.get("center_id") or s.get("center_name_snapshot") or "")
for cell in counts.values():
cell["centers"] = len(cell["centers"])
# 선택 날짜 (기본: 오늘이 이번 달이면 오늘, 아니면 1일)
sel = request.query_params.get("date") or ""
today = today_kst()
if not sel:
sel = today.isoformat() if (today.year == year and today.month == month) else f"{year:04d}-{month:02d}-01"
# 선택일에 걸친 묶음(출고일 기준 우선, 작성/입고 포함)
sel_shipments = [
s for s in shipments
if sel in (s.get("ship_date"), s.get("document_date"), s.get("center_arrival_date"))
]
# 각 묶음에 품목 요약(제품명/수량) 첨부 — hover 툴팁용
# 선택일의 묶음 — 달력과 같은 기준(출고일)
sel_shipments = [s for s in shipments if s.get("ship_date") == sel]
# 오른쪽 상세: 센터별 상품 목록·수량·박스 수·출고방식
for s in sel_shipments:
full = store.get_shipment(shipment_id=s["id"])
s["tip_items"] = [
{"name": ln.get("product_name_snapshot") or ln.get("product_code"),
"qty": ln.get("quantity", 0)}
for ln in (full.get("lines") if full else [])
lines = (full.get("lines") if full else []) or []
s["items"] = [
{
"name": ln.get("product_name_snapshot") or ln.get("product_code"),
"qty": int(ln.get("quantity") or 0),
"boxes": int(ln.get("calculated_boxes") or 0),
}
for ln in lines
]
s["total_qty"] = sum(it["qty"] for it in s["items"])
s["total_boxes"] = sum(it["boxes"] for it in s["items"])
cal = _calendar.Calendar(firstweekday=6) # 일요일 시작
weeks = cal.monthdatescalendar(year, month)
@@ -216,63 +231,13 @@ def _form_context(request: Request, store: Any, user: dict[str, Any]) -> dict[st
}
@router.get("/new", response_class=HTMLResponse)
async def new_form(request: Request) -> HTMLResponse:
from app.main import render_template # noqa: WPS433
@router.get("/new")
async def new_form(request: Request) -> RedirectResponse:
"""신규 등록 폼은 없앴다. 출고 묶음은 박스 계산의 [분배 확정] 으로만 만든다.
guard = _guard(request)
if not isinstance(guard, tuple):
return guard
store, user = guard
ctx = _form_context(request, store, user)
ctx.update(
{
"page_title": "쿠팡 밀크런 — 신규 등록",
"page_subtitle": "공통 헤더 1개 + 품목 라인",
"mode": "new",
"shipment": None,
"default_date": today_kst().isoformat(),
}
)
return render_template(request, "cupang/form.html", ctx)
@router.post("/new")
async def create(
request: Request,
lines_json: str = Form("[]"),
document_date: str = Form(...),
ship_date: str = Form(...),
center_arrival_date: str = Form(...),
center_id: str = Form(""),
center_name_snapshot: str = Form(""),
ship_method: str = Form("택배"),
outbound_summary: str = Form(""),
worker: str = Form(""),
memo: str = Form(""),
user: dict[str, Any] = Depends(_require_user),
) -> RedirectResponse:
store = _store(request)
if store is None:
raise HTTPException(status_code=503, detail="cupang_db 미설정")
header = {
"document_date": document_date,
"ship_date": ship_date,
"center_arrival_date": center_arrival_date,
"center_id": center_id,
"center_name_snapshot": center_name_snapshot,
"ship_method": ship_method,
"outbound_summary": outbound_summary,
"worker": worker,
"memo": memo,
}
try:
ship = store.create_shipment(
created_by=user["email"], header=header, lines=_parse_lines(lines_json)
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
return RedirectResponse(url=f"/cupang/{ship['id']}", status_code=303)
예전 링크/북마크가 404 나지 않도록 박스 계산으로 보낸다.
"""
return RedirectResponse(url="/cupang/box-calc", status_code=303)
@router.get("/{shipment_id:int}", response_class=HTMLResponse)