diff --git a/app/modules/cupang/__init__.py b/app/modules/cupang/__init__.py
index b047d18..5fa598a 100644
--- a/app/modules/cupang/__init__.py
+++ b/app/modules/cupang/__init__.py
@@ -13,13 +13,12 @@ None 을 반환하고, 라우터가 "설정 필요" 안내 페이지를 보여
from typing import Any
from .router import router
-from .store import DEFAULT_CENTERS, SHEET_COLUMNS, SHIP_METHODS, STATUSES, compute_boxes
+from .store import DEFAULT_CENTERS, SHIP_METHODS, STATUSES, compute_boxes
__all__ = [
"router",
"STATUSES",
"SHIP_METHODS",
- "SHEET_COLUMNS",
"DEFAULT_CENTERS",
"compute_boxes",
"build_cupang_store",
diff --git a/app/modules/cupang/db.py b/app/modules/cupang/db.py
index 582876c..0d2abf3 100644
--- a/app/modules/cupang/db.py
+++ b/app/modules/cupang/db.py
@@ -144,17 +144,6 @@ class CupangDBStore:
).fetchall()
return [self._rule_serialize(r) for r in rows]
- def get_box_rule(self, *, product_code: str) -> dict[str, Any] | None:
- code = (product_code or "").strip()
- if not code:
- return None
- with self._pool.connection() as conn:
- row = conn.execute(
- "SELECT * FROM cupang_box_rules WHERE product_code = %s AND active = TRUE",
- (code,),
- ).fetchone()
- return self._rule_serialize(row) if row else None
-
def upsert_box_rule(
self,
*,
@@ -188,15 +177,6 @@ class CupangDBStore:
).fetchone()
return self._rule_serialize(row)
- def deactivate_box_rule(self, *, rule_id: int) -> None:
- with self._pool.connection() as conn:
- cur = conn.execute(
- "UPDATE cupang_box_rules SET active = FALSE WHERE id = %s",
- (rule_id,),
- )
- if cur.rowcount == 0:
- raise KeyError(rule_id)
-
def delete_box_rule(self, *, rule_id: int) -> None:
"""완전 삭제(hard). 라인의 box_rule_id 는 ON DELETE 미설정이므로
참조 중이면 FK 위반 가능 → 참조 라인의 box_rule_id 를 먼저 NULL 처리."""
@@ -212,10 +192,6 @@ class CupangDBStore:
if cur.rowcount == 0:
raise KeyError(rule_id)
- def box_rule_map(self) -> dict[str, dict[str, Any]]:
- """product_code → rule. 폼/계산에서 빠르게 조회하기 위한 dict."""
- return {r["product_code"]: r for r in self.list_box_rules()}
-
# ════════════════════════════════════════════════════════════
# 제품명 카탈로그 (cupang_products)
# itemcode_db 에서 가져와 등록한 제품 목록. 폼의 제품명 드롭다운 소스.
@@ -498,21 +474,6 @@ class CupangDBStore:
_accumulate(arr, "arrival")
return out
- def shipments_for_export(
- self, *, year: int | None = None, month: int | None = None, shipment_id: int | None = None
- ) -> list[dict[str, Any]]:
- """엑셀 내보내기용 — 헤더+라인 평탄화 전 단계. 라인 포함 묶음 리스트 반환."""
- if shipment_id is not None:
- s = self.get_shipment(shipment_id=shipment_id)
- return [s] if s else []
- heads = self.list_shipments(year=year, month=month)
- result = []
- for h in heads:
- full = self.get_shipment(shipment_id=h["id"])
- if full:
- result.append(full)
- return result
-
# ════════════════════════════════════════════════════════════
# 정규화 / 직렬화 helpers
# ════════════════════════════════════════════════════════════
diff --git a/app/modules/cupang/router.py b/app/modules/cupang/router.py
index c293be7..00d9ab9 100644
--- a/app/modules/cupang/router.py
+++ b/app/modules/cupang/router.py
@@ -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"}
diff --git a/app/modules/cupang/store.py b/app/modules/cupang/store.py
index 6280119..da4353c 100644
--- a/app/modules/cupang/store.py
+++ b/app/modules/cupang/store.py
@@ -23,21 +23,6 @@ STATUSES: tuple[str, ...] = (
# 출고방식 기본 후보 (자유 입력 허용, 아래는 select 기본값)
SHIP_METHODS: tuple[str, ...] = ("택배", "직접배송", "화물", "파렛트", "기타")
-# 엑셀/시트 컬럼 순서 — 기존 구글시트와 동일하게 유지
-SHEET_COLUMNS: tuple[str, ...] = (
- "구분",
- "작성일",
- "출고일",
- "센터입고일",
- "입고센터",
- "출고방식",
- "제품코드",
- "제품명",
- "수량",
- "출고",
- "작업자",
-)
-
# 초기 입고센터 seed — cupang_db_init.sql 에도 동일 목록을 INSERT 한다.
# 화면에서 추가/수정/비활성화 가능. 사용 중인 센터는 hard delete 하지 않는다.
DEFAULT_CENTERS: tuple[str, ...] = (
diff --git a/app/modules/cupang/templates/cupang/box_rules.html b/app/modules/cupang/templates/cupang/box_rules.html
index df15139..5f2f76d 100644
--- a/app/modules/cupang/templates/cupang/box_rules.html
+++ b/app/modules/cupang/templates/cupang/box_rules.html
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
-{% block head_extra %}{% endblock %}
+{% block head_extra %}{% endblock %}
{% block content %}
diff --git a/app/modules/cupang/templates/cupang/centers.html b/app/modules/cupang/templates/cupang/centers.html
index 0d9ae21..c922698 100644
--- a/app/modules/cupang/templates/cupang/centers.html
+++ b/app/modules/cupang/templates/cupang/centers.html
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
-{% block head_extra %}{% endblock %}
+{% block head_extra %}{% endblock %}
{% block content %}
diff --git a/app/modules/cupang/templates/cupang/detail.html b/app/modules/cupang/templates/cupang/detail.html
index 82cc5f5..343d15e 100644
--- a/app/modules/cupang/templates/cupang/detail.html
+++ b/app/modules/cupang/templates/cupang/detail.html
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
-{% block head_extra %}{% endblock %}
+{% block head_extra %}{% endblock %}
{% block content %}
diff --git a/app/modules/cupang/templates/cupang/form.html b/app/modules/cupang/templates/cupang/form.html
index f314430..44f9d16 100644
--- a/app/modules/cupang/templates/cupang/form.html
+++ b/app/modules/cupang/templates/cupang/form.html
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
-{% block head_extra %}{% endblock %}
+{% block head_extra %}{% endblock %}
{% block content %}
-
{% endblock %}
-{% block scripts %}{% endblock %}
+{% block scripts %}{% endblock %}
diff --git a/app/modules/cupang/templates/cupang/index.html b/app/modules/cupang/templates/cupang/index.html
index bb76207..c0ae8ef 100644
--- a/app/modules/cupang/templates/cupang/index.html
+++ b/app/modules/cupang/templates/cupang/index.html
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
-{% block head_extra %}{% endblock %}
+{% block head_extra %}{% endblock %}
{% block content %}
diff --git a/app/modules/cupang/templates/cupang/products.html b/app/modules/cupang/templates/cupang/products.html
index f51275b..d904b16 100644
--- a/app/modules/cupang/templates/cupang/products.html
+++ b/app/modules/cupang/templates/cupang/products.html
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
-{% block head_extra %}{% endblock %}
+{% block head_extra %}{% endblock %}
{% block content %}
diff --git a/app/static/cupang.css b/app/static/cupang.css
index 90c5a4d..931ee0a 100644
--- a/app/static/cupang.css
+++ b/app/static/cupang.css
@@ -270,16 +270,3 @@
.cpg-upb-unit { font-size: 13px; color: var(--color-midtone-gray); }
/* 메모: 프레임 전체 너비 */
.cpg .cpg-brule-fields .cpg-brule-memo { width: 100%; min-width: 0; max-width: 100%; box-sizing: border-box; }
-
-/* ── 상품 검색 드롭다운 ── */
-.cpg-search-pop {
- position: absolute; z-index: 50;
- background: #fff; border: 1px solid var(--color-subtle-ash);
- border-radius: 10px; box-shadow: 0 4px 16px rgba(0,0,0,.08);
- max-height: 260px; overflow-y: auto; min-width: 280px;
-}
-.cpg-search-item { padding: 8px 12px; cursor: pointer; font-size: 13px; }
-.cpg-search-item:hover, .cpg-search-item.is-active { background: var(--color-ghost-gray); }
-.cpg-search-item .cpg-sc { font-weight: 600; }
-.cpg-search-item .cpg-sn { color: var(--color-midtone-gray); margin-left: 8px; }
-.cpg-search-empty { padding: 10px 12px; font-size: 12px; color: var(--color-midtone-gray); }
diff --git a/app/static/cupang.js b/app/static/cupang.js
index 5a03e5f..fa9ac94 100644
--- a/app/static/cupang.js
+++ b/app/static/cupang.js
@@ -3,7 +3,6 @@
수량 입력 시 박스 수 미리보기(서버가 저장 시 store.compute_boxes 로 재계산). */
(function () {
"use strict";
- var CFG = window.CPG || {};
var body = document.getElementById("cpg-lines-body");
var form = document.getElementById("cpg-form");
if (!body || !form) return;
diff --git a/app/templates/erp_base.html b/app/templates/erp_base.html
index 8fa686a..9324a27 100644
--- a/app/templates/erp_base.html
+++ b/app/templates/erp_base.html
@@ -4,8 +4,8 @@
{{ page_title or "ERP" }} — DBX Corporation
-
-
+
+
{% block head_extra %}{% endblock %}