refactor(cupang): 입고센터 관리 화면 제거

- /cupang/centers GET/POST, 수정·삭제 라우트와 centers.html 삭제
- 달력 상단 "입고센터 관리" 버튼 제거, 상자 계산의 안내 문구 정리
- 센터 관리 화면 전용 CSS 제거(공용 .cpg-icon-btn/.cpg-btn-sm 은 유지)
- 센터 데이터(cupang_centers)와 db 계층은 그대로 — 출고 확정·발주 업로드에서 계속 사용

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 20:34:41 +09:00
parent 46ce6d78b8
commit 1a2291f510
21 changed files with 2268 additions and 447 deletions
-146
View File
@@ -11,10 +11,8 @@ from __future__ import annotations
import calendar as _calendar
import json
import re
from fractions import Fraction
from typing import Any
from urllib.parse import quote
from datetime import date as _date, timedelta as _timedelta
@@ -558,150 +556,6 @@ async def day_delete(
)
# ════════════════════════════════════════════════════════════
# 입고센터 관리
# ════════════════════════════════════════════════════════════
_CENTER_PREFIX_RE = re.compile(r"^\D*")
def _center_group(name: str) -> str:
"""센터명 앞부분(숫자 전까지)을 지역 그룹 키로 쓴다. 예) 인천14 → 인천."""
name = (name or "").strip()
prefix = _CENTER_PREFIX_RE.match(name).group(0).strip()
return prefix or name or "기타"
def _center_sort_key(name: str) -> list[Any]:
"""숫자를 숫자로 비교하는 자연 정렬. 예) 인천4 < 인천14."""
parts = re.split(r"(\d+)", (name or "").strip())
return [(1, int(p), "") if p.isdigit() else (0, 0, p.lower()) for p in parts]
def _group_centers(centers: list[dict[str, Any]]) -> list[dict[str, Any]]:
buckets: dict[str, list[dict[str, Any]]] = {}
for c in centers:
buckets.setdefault(_center_group(c["name"]), []).append(c)
groups = []
for key, items in buckets.items():
items.sort(key=lambda c: _center_sort_key(c["name"]))
groups.append({"name": key, "items": items, "count": len(items)})
groups.sort(key=lambda g: _center_sort_key(g["name"]))
return groups
@router.get("/centers", response_class=HTMLResponse)
async def centers_page(request: Request) -> HTMLResponse:
from app.main import build_erp_nav, render_template # noqa: WPS433
from app.store import is_admin # noqa: WPS433
guard = _guard(request)
if not isinstance(guard, tuple):
return guard
store, user = guard
centers = sorted(
store.list_centers(include_inactive=True),
key=lambda c: _center_sort_key(c["name"]),
)
return render_template(
request,
"cupang/centers.html",
{
"user": user,
"is_admin": is_admin(user),
"nav_items": build_erp_nav(user, active="cupang"),
"page_title": "쿠팡 밀크런 — 입고센터 관리",
"page_subtitle": "센터명 등록 · 수정 · 활성/비활성 · 삭제.",
"centers": centers,
"center_groups": _group_centers(centers),
"flash": request.query_params.get("msg", ""),
"flash_name": request.query_params.get("name", ""),
},
)
@router.post("/centers")
async def center_create(
request: Request,
name: str = Form(...),
sort_order: int = Form(0),
user: dict[str, Any] = Depends(_require_user),
) -> RedirectResponse:
store = _store(request)
if store is None:
raise HTTPException(status_code=503, detail="cupang_db 미설정")
name = (name or "").strip()
if not name:
return RedirectResponse(url="/cupang/centers", status_code=303)
# 같은 이름이 이미 있으면 새로 만들지 않고 알림만 돌려준다.
existing = next(
(
c
for c in store.list_centers(include_inactive=True)
if (c.get("name") or "").strip().lower() == name.lower()
),
None,
)
if existing is not None:
msg = "dup" if existing.get("active") else "dup_inactive"
return RedirectResponse(
url=f"/cupang/centers?msg={msg}&name={quote(name)}", status_code=303
)
try:
store.create_center(name=name, sort_order=sort_order)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
return RedirectResponse(
url=f"/cupang/centers?msg=added&name={quote(name)}", status_code=303
)
@router.post("/centers/{center_id}/edit")
async def center_edit(
request: Request,
center_id: int,
active: str = Form(""),
sort_order: 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 미설정")
# 이름 변경은 지원하지 않는다(기존 출고 이력과 어긋날 수 있음). 활성/정렬만 수정.
kwargs: dict[str, Any] = {"center_id": center_id}
if active != "":
kwargs["active"] = active in ("1", "true", "on", "True")
if sort_order.strip():
try:
kwargs["sort_order"] = int(sort_order)
except ValueError:
pass
try:
store.update_center(**kwargs)
except KeyError:
raise HTTPException(status_code=404, detail="센터를 찾을 수 없습니다.")
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
return RedirectResponse(url="/cupang/centers", status_code=303)
@router.post("/centers/{center_id}/delete")
async def center_delete(
request: Request,
center_id: int,
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.delete_center(center_id=center_id)
except KeyError:
raise HTTPException(status_code=404, detail="센터를 찾을 수 없습니다.")
return RedirectResponse(url="/cupang/centers", status_code=303)
# ════════════════════════════════════════════════════════════
# 상자 입수량 관리
# ════════════════════════════════════════════════════════════