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:
@@ -13,13 +13,12 @@ None 을 반환하고, 라우터가 "설정 필요" 안내 페이지를 보여
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .router import router
|
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__ = [
|
__all__ = [
|
||||||
"router",
|
"router",
|
||||||
"STATUSES",
|
"STATUSES",
|
||||||
"SHIP_METHODS",
|
"SHIP_METHODS",
|
||||||
"SHEET_COLUMNS",
|
|
||||||
"DEFAULT_CENTERS",
|
"DEFAULT_CENTERS",
|
||||||
"compute_boxes",
|
"compute_boxes",
|
||||||
"build_cupang_store",
|
"build_cupang_store",
|
||||||
|
|||||||
@@ -144,17 +144,6 @@ class CupangDBStore:
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
return [self._rule_serialize(r) for r in rows]
|
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(
|
def upsert_box_rule(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -188,15 +177,6 @@ class CupangDBStore:
|
|||||||
).fetchone()
|
).fetchone()
|
||||||
return self._rule_serialize(row)
|
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:
|
def delete_box_rule(self, *, rule_id: int) -> None:
|
||||||
"""완전 삭제(hard). 라인의 box_rule_id 는 ON DELETE 미설정이므로
|
"""완전 삭제(hard). 라인의 box_rule_id 는 ON DELETE 미설정이므로
|
||||||
참조 중이면 FK 위반 가능 → 참조 라인의 box_rule_id 를 먼저 NULL 처리."""
|
참조 중이면 FK 위반 가능 → 참조 라인의 box_rule_id 를 먼저 NULL 처리."""
|
||||||
@@ -212,10 +192,6 @@ class CupangDBStore:
|
|||||||
if cur.rowcount == 0:
|
if cur.rowcount == 0:
|
||||||
raise KeyError(rule_id)
|
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)
|
# 제품명 카탈로그 (cupang_products)
|
||||||
# itemcode_db 에서 가져와 등록한 제품 목록. 폼의 제품명 드롭다운 소스.
|
# itemcode_db 에서 가져와 등록한 제품 목록. 폼의 제품명 드롭다운 소스.
|
||||||
@@ -498,21 +474,6 @@ class CupangDBStore:
|
|||||||
_accumulate(arr, "arrival")
|
_accumulate(arr, "arrival")
|
||||||
return out
|
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
|
# 정규화 / 직렬화 helpers
|
||||||
# ════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -10,21 +10,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import calendar as _calendar
|
import calendar as _calendar
|
||||||
import io
|
|
||||||
import json
|
import json
|
||||||
from datetime import date, datetime
|
from datetime import date
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, Form, HTTPException, Request
|
from fastapi import APIRouter, Body, Depends, Form, HTTPException, Request
|
||||||
from fastapi.responses import (
|
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||||
HTMLResponse,
|
|
||||||
JSONResponse,
|
|
||||||
RedirectResponse,
|
|
||||||
StreamingResponse,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .holidays import is_holiday
|
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"])
|
router = APIRouter(prefix="/cupang", tags=["cupang"])
|
||||||
|
|
||||||
@@ -184,24 +178,10 @@ async def index(request: Request) -> HTMLResponse:
|
|||||||
"cal_weeks": cal_weeks,
|
"cal_weeks": cal_weeks,
|
||||||
"selected_date": sel,
|
"selected_date": sel,
|
||||||
"sel_shipments": sel_shipments,
|
"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(),
|
"box_rules": store.list_box_rules(),
|
||||||
"products": store.list_products(),
|
"products": store.list_products(),
|
||||||
"ship_methods": list(SHIP_METHODS),
|
"ship_methods": list(SHIP_METHODS),
|
||||||
"statuses": list(STATUSES),
|
|
||||||
"search_enabled": bool(reader and reader.enabled),
|
"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_title": f"출고 #{ship['id']}",
|
||||||
"page_subtitle": f"{ship['ship_date']} · {ship['center_name_snapshot']}",
|
"page_subtitle": f"{ship['ship_date']} · {ship['center_name_snapshot']}",
|
||||||
"shipment": ship,
|
"shipment": ship,
|
||||||
"statuses": list(STATUSES),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -383,25 +361,6 @@ async def update(
|
|||||||
return RedirectResponse(url=f"/cupang/{shipment_id}", status_code=303)
|
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")
|
@router.post("/{shipment_id:int}/delete")
|
||||||
async def delete(
|
async def delete(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -725,144 +684,6 @@ async def product_delete(
|
|||||||
return RedirectResponse(url="/cupang/products", status_code=303)
|
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")
|
@router.get("/health")
|
||||||
async def health() -> dict[str, str]:
|
async def health() -> dict[str, str]:
|
||||||
return {"status": "ok", "module": "cupang"}
|
return {"status": "ok", "module": "cupang"}
|
||||||
|
|||||||
@@ -23,21 +23,6 @@ STATUSES: tuple[str, ...] = (
|
|||||||
# 출고방식 기본 후보 (자유 입력 허용, 아래는 select 기본값)
|
# 출고방식 기본 후보 (자유 입력 허용, 아래는 select 기본값)
|
||||||
SHIP_METHODS: tuple[str, ...] = ("택배", "직접배송", "화물", "파렛트", "기타")
|
SHIP_METHODS: tuple[str, ...] = ("택배", "직접배송", "화물", "파렛트", "기타")
|
||||||
|
|
||||||
# 엑셀/시트 컬럼 순서 — 기존 구글시트와 동일하게 유지
|
|
||||||
SHEET_COLUMNS: tuple[str, ...] = (
|
|
||||||
"구분",
|
|
||||||
"작성일",
|
|
||||||
"출고일",
|
|
||||||
"센터입고일",
|
|
||||||
"입고센터",
|
|
||||||
"출고방식",
|
|
||||||
"제품코드",
|
|
||||||
"제품명",
|
|
||||||
"수량",
|
|
||||||
"출고",
|
|
||||||
"작업자",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 초기 입고센터 seed — cupang_db_init.sql 에도 동일 목록을 INSERT 한다.
|
# 초기 입고센터 seed — cupang_db_init.sql 에도 동일 목록을 INSERT 한다.
|
||||||
# 화면에서 추가/수정/비활성화 가능. 사용 중인 센터는 hard delete 하지 않는다.
|
# 화면에서 추가/수정/비활성화 가능. 사용 중인 센터는 hard delete 하지 않는다.
|
||||||
DEFAULT_CENTERS: tuple[str, ...] = (
|
DEFAULT_CENTERS: tuple[str, ...] = (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530j" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530k" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530j" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530k" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530j" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530k" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530j" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530k" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
@@ -96,9 +96,6 @@
|
|||||||
|
|
||||||
</div><!-- /cpg-form-2col -->
|
</div><!-- /cpg-form-2col -->
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- 검색 결과 드롭다운 (JS 가 위치 이동) -->
|
|
||||||
<div id="cpg-search-pop" class="cpg-search-pop" hidden></div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script type="application/json" id="cpg-init-lines">
|
<script type="application/json" id="cpg-init-lines">
|
||||||
@@ -110,13 +107,6 @@
|
|||||||
<script type="application/json" id="cpg-products">
|
<script type="application/json" id="cpg-products">
|
||||||
{{ products | tojson }}
|
{{ products | tojson }}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
|
||||||
window.CPG = {
|
|
||||||
searchEnabled: {{ 'true' if search_enabled else 'false' }},
|
|
||||||
searchUrl: "/cupang/api/products/search",
|
|
||||||
calcUrl: "/cupang/api/box-calc"
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}<script src="/static/cupang.js?v=20260530j" defer></script>{% endblock %}
|
{% block scripts %}<script src="/static/cupang.js?v=20260530k" defer></script>{% endblock %}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530j" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530k" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530j" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530k" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -270,16 +270,3 @@
|
|||||||
.cpg-upb-unit { font-size: 13px; color: var(--color-midtone-gray); }
|
.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 .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); }
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
수량 입력 시 박스 수 미리보기(서버가 저장 시 store.compute_boxes 로 재계산). */
|
수량 입력 시 박스 수 미리보기(서버가 저장 시 store.compute_boxes 로 재계산). */
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
var CFG = window.CPG || {};
|
|
||||||
var body = document.getElementById("cpg-lines-body");
|
var body = document.getElementById("cpg-lines-body");
|
||||||
var form = document.getElementById("cpg-form");
|
var form = document.getElementById("cpg-form");
|
||||||
if (!body || !form) return;
|
if (!body || !form) return;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>{{ page_title or "ERP" }} — DBX Corporation</title>
|
<title>{{ page_title or "ERP" }} — DBX Corporation</title>
|
||||||
<link rel="stylesheet" href="/static/erp.css?v=20260530j" />
|
<link rel="stylesheet" href="/static/erp.css?v=20260530k" />
|
||||||
<link rel="stylesheet" href="/static/erp-shell.css?v=20260530j" />
|
<link rel="stylesheet" href="/static/erp-shell.css?v=20260530k" />
|
||||||
<link rel="stylesheet" href="/static/erp-attach-viewer.css" />
|
<link rel="stylesheet" href="/static/erp-attach-viewer.css" />
|
||||||
{% block head_extra %}{% endblock %}
|
{% block head_extra %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user