feat(cupang): 출고 등록 경로를 박스 계산으로 일원화, 달력 표시 정리
출고 묶음을 만드는 길이 둘(신규 등록 폼 / 박스 계산 분배 확정)이라 헷갈렸다. 폼 경로를 없애고 [+ 신규 등록] 이 박스 계산을 열게 한다. 중복이던 오른쪽 [박스 계산] 버튼은 제거. /cupang/new 는 북마크가 깨지지 않게 박스 계산으로 리다이렉트하고 POST /new(생성)는 삭제했다. 기존 묶음 수정은 그대로 동작한다. 달력 칸은 작성/출고/입고 세 배지를 늘어놓아 읽기 어려웠다. 출고 기준으로 "출고 N건 · 센터 M곳"만 남긴다. 오른쪽 상세도 같은 기준(출고일)으로 맞추고 센터별 출고방식·총 박스/개수와 상품별 수량·박스를 카드 안에 펼친다. 품목이 바로 보이므로 hover 툴팁은 제거. 임시 저장 기본 제목은 "2026.08.31(월) 오후 04시 50분" 형식으로.
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user