refactor(cupang): UI에서 제거된 기능의 죽은 코드 정리
- 엑셀 내보내기 전체 제거: /export, /{id}/export, _build_xlsx,
_flatten_for_sheet, shipments_for_export, SHEET_COLUMNS
- 미사용 라우트 제거: /api/calendar, /api/products/search, /api/box-calc,
/{id}/status(상태변경 UI 삭제됨)
- 미사용 db 메서드 제거: deactivate_box_rule, get_box_rule, box_rule_map
- form.html window.CPG + cpg-search-pop, cupang.js CFG, 검색 드롭다운 CSS 제거
- 템플릿 미사용 statuses 컨텍스트 제거, router import 정리
- 캐시 버전 k
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,21 +10,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import calendar as _calendar
|
||||
import io
|
||||
import json
|
||||
from datetime import date, datetime
|
||||
from datetime import date
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Form, HTTPException, Request
|
||||
from fastapi.responses import (
|
||||
HTMLResponse,
|
||||
JSONResponse,
|
||||
RedirectResponse,
|
||||
StreamingResponse,
|
||||
)
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
|
||||
from .holidays import is_holiday
|
||||
from .store import DEFAULT_CENTERS, SHEET_COLUMNS, SHIP_METHODS, STATUSES, compute_boxes
|
||||
from .store import SHIP_METHODS
|
||||
|
||||
router = APIRouter(prefix="/cupang", tags=["cupang"])
|
||||
|
||||
@@ -184,24 +178,10 @@ async def index(request: Request) -> HTMLResponse:
|
||||
"cal_weeks": cal_weeks,
|
||||
"selected_date": sel,
|
||||
"sel_shipments": sel_shipments,
|
||||
"statuses": list(STATUSES),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/calendar")
|
||||
async def api_calendar(
|
||||
request: Request, _: dict[str, Any] = Depends(_require_user)
|
||||
) -> JSONResponse:
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
year, month = _ym(request)
|
||||
return JSONResponse(
|
||||
{"year": year, "month": month, "counts": store.calendar_counts(year=year, month=month)}
|
||||
)
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 출고 묶음 — 등록 / 수정 / 상세
|
||||
# ════════════════════════════════════════════════════════════
|
||||
@@ -218,7 +198,6 @@ def _form_context(request: Request, store: Any, user: dict[str, Any]) -> dict[st
|
||||
"box_rules": store.list_box_rules(),
|
||||
"products": store.list_products(),
|
||||
"ship_methods": list(SHIP_METHODS),
|
||||
"statuses": list(STATUSES),
|
||||
"search_enabled": bool(reader and reader.enabled),
|
||||
}
|
||||
|
||||
@@ -308,7 +287,6 @@ async def detail(request: Request, shipment_id: int) -> HTMLResponse:
|
||||
"page_title": f"출고 #{ship['id']}",
|
||||
"page_subtitle": f"{ship['ship_date']} · {ship['center_name_snapshot']}",
|
||||
"shipment": ship,
|
||||
"statuses": list(STATUSES),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -383,25 +361,6 @@ async def update(
|
||||
return RedirectResponse(url=f"/cupang/{shipment_id}", status_code=303)
|
||||
|
||||
|
||||
@router.post("/{shipment_id:int}/status")
|
||||
async def change_status(
|
||||
request: Request,
|
||||
shipment_id: int,
|
||||
status: 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 미설정")
|
||||
try:
|
||||
store.set_status(shipment_id=shipment_id, status=status)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="출고 묶음을 찾을 수 없습니다.")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
return RedirectResponse(url=f"/cupang/{shipment_id}", status_code=303)
|
||||
|
||||
|
||||
@router.post("/{shipment_id:int}/delete")
|
||||
async def delete(
|
||||
request: Request,
|
||||
@@ -725,144 +684,6 @@ async def product_delete(
|
||||
return RedirectResponse(url="/cupang/products", status_code=303)
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 상품 검색 (itemcode_db 읽기 전용) + 박스 계산 미리보기
|
||||
# ════════════════════════════════════════════════════════════
|
||||
@router.get("/api/products/search")
|
||||
async def product_search(
|
||||
request: Request,
|
||||
q: str = "",
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
reader = _itemcode(request)
|
||||
store = _store(request)
|
||||
results = reader.search(q) if reader else []
|
||||
# box_rule 의 units_per_box 를 결과에 병합 (있으면)
|
||||
rule_map = store.box_rule_map() if store else {}
|
||||
for r in results:
|
||||
rule = rule_map.get(r["code"])
|
||||
r["units_per_box"] = rule["units_per_box"] if rule else None
|
||||
r["box_rule_id"] = rule["id"] if rule else None
|
||||
return JSONResponse(
|
||||
{
|
||||
"enabled": bool(reader and reader.enabled),
|
||||
"reason": (reader.reason if reader else "itemcode 리더 미초기화"),
|
||||
"results": results,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/box-calc")
|
||||
async def box_calc(
|
||||
request: Request,
|
||||
product_code: str = "",
|
||||
quantity: int = 0,
|
||||
units_per_box: str = "",
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
store = _store(request)
|
||||
upb: int | None
|
||||
if units_per_box.strip():
|
||||
try:
|
||||
upb = int(units_per_box)
|
||||
except ValueError:
|
||||
upb = None
|
||||
else:
|
||||
rule = store.get_box_rule(product_code=product_code) if store else None
|
||||
upb = rule["units_per_box"] if rule else None
|
||||
return JSONResponse(compute_boxes(quantity, upb))
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 엑셀 내보내기 (구글시트 컬럼 순서 유지)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
def _flatten_for_sheet(shipments: list[dict[str, Any]]) -> list[list[Any]]:
|
||||
"""묶음 → 시트 행. 같은 묶음 2번째 라인부터 공통 헤더 칸은 비운다."""
|
||||
rows: list[list[Any]] = []
|
||||
seq = 0
|
||||
for s in shipments:
|
||||
lines = s.get("lines") or []
|
||||
for i, ln in enumerate(lines):
|
||||
seq += 1
|
||||
if i == 0:
|
||||
rows.append([
|
||||
seq,
|
||||
s.get("document_date", ""),
|
||||
s.get("ship_date", ""),
|
||||
s.get("center_arrival_date", ""),
|
||||
s.get("center_name_snapshot", ""),
|
||||
s.get("ship_method", ""),
|
||||
ln.get("product_code", ""),
|
||||
ln.get("product_name_snapshot", ""),
|
||||
ln.get("quantity", 0),
|
||||
s.get("outbound_summary", ""),
|
||||
s.get("worker", ""),
|
||||
])
|
||||
else:
|
||||
# 공통 헤더 칸 비움 (구분/제품/수량만 채움)
|
||||
rows.append([
|
||||
seq, "", "", "", "", "",
|
||||
ln.get("product_code", ""),
|
||||
ln.get("product_name_snapshot", ""),
|
||||
ln.get("quantity", 0),
|
||||
"", "",
|
||||
])
|
||||
return rows
|
||||
|
||||
|
||||
@router.get("/export")
|
||||
async def export_month(
|
||||
request: Request,
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> StreamingResponse:
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
year, month = _ym(request)
|
||||
shipments = store.shipments_for_export(year=year, month=month)
|
||||
fname = f"cupang_milkrun_{year:04d}{month:02d}.xlsx"
|
||||
return _build_xlsx(shipments, fname, sheet_title=f"{year}-{month:02d}")
|
||||
|
||||
|
||||
@router.get("/{shipment_id:int}/export")
|
||||
async def export_one(
|
||||
request: Request,
|
||||
shipment_id: int,
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> StreamingResponse:
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
shipments = store.shipments_for_export(shipment_id=shipment_id)
|
||||
if not shipments:
|
||||
raise HTTPException(status_code=404, detail="출고 묶음을 찾을 수 없습니다.")
|
||||
fname = f"cupang_milkrun_{shipment_id}.xlsx"
|
||||
return _build_xlsx(shipments, fname, sheet_title=f"출고{shipment_id}")
|
||||
|
||||
|
||||
def _build_xlsx(shipments: list[dict[str, Any]], fname: str, *, sheet_title: str) -> StreamingResponse:
|
||||
from openpyxl import Workbook # 지연 import
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = sheet_title[:31] or "cupang"
|
||||
ws.append(list(SHEET_COLUMNS))
|
||||
for row in _flatten_for_sheet(shipments):
|
||||
ws.append(row)
|
||||
widths = [6, 12, 12, 12, 12, 10, 14, 26, 8, 24, 12]
|
||||
for col, w in enumerate(widths, start=1):
|
||||
ws.column_dimensions[ws.cell(row=1, column=col).column_letter].width = w
|
||||
|
||||
buf = io.BytesIO()
|
||||
wb.save(buf)
|
||||
buf.seek(0)
|
||||
return StreamingResponse(
|
||||
buf,
|
||||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
headers={"Content-Disposition": f'attachment; filename="{fname}"'},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health() -> dict[str, str]:
|
||||
return {"status": "ok", "module": "cupang"}
|
||||
|
||||
Reference in New Issue
Block a user