style(cupang): 매출 조회 조건 1행 고정 + 주차 필터, 상단 합계 KPI 제거
- 조회 조건은 flex-wrap: nowrap 로 한 줄 유지(좁은 화면은 가로 스크롤) - 주차(week_label) 필터 추가 — 최근 주차부터 목록 - 공급가/원가/물류비/마진/순마진 합계 카드 삭제, 표 영역 높이 확대 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -679,9 +679,10 @@ class CupangDBStore:
|
|||||||
center_name: str = "",
|
center_name: str = "",
|
||||||
keyword: str = "",
|
keyword: str = "",
|
||||||
po_type: str = "",
|
po_type: str = "",
|
||||||
|
week_label: str = "",
|
||||||
limit: int = 5000,
|
limit: int = 5000,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""출고일 기준 조회. 기간·센터·발주유형·검색어(품목/SKU/발주번호/바코드)."""
|
"""출고일 기준 조회. 기간·센터·발주유형·주차·검색어(품목/SKU/발주번호/바코드)."""
|
||||||
where: list[str] = []
|
where: list[str] = []
|
||||||
params: list[Any] = []
|
params: list[Any] = []
|
||||||
if date_from:
|
if date_from:
|
||||||
@@ -696,6 +697,9 @@ class CupangDBStore:
|
|||||||
if po_type:
|
if po_type:
|
||||||
where.append("po_type = %s")
|
where.append("po_type = %s")
|
||||||
params.append(po_type)
|
params.append(po_type)
|
||||||
|
if week_label:
|
||||||
|
where.append("week_label = %s")
|
||||||
|
params.append(week_label)
|
||||||
if keyword:
|
if keyword:
|
||||||
where.append(
|
where.append(
|
||||||
"(item_name ILIKE %s OR sku_id ILIKE %s OR po_no ILIKE %s OR barcode ILIKE %s)"
|
"(item_name ILIKE %s OR sku_id ILIKE %s OR po_no ILIKE %s OR barcode ILIKE %s)"
|
||||||
@@ -728,6 +732,15 @@ class CupangDBStore:
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
return [r["center_name"] for r in rows]
|
return [r["center_name"] for r in rows]
|
||||||
|
|
||||||
|
def sales_weeks(self) -> list[str]:
|
||||||
|
"""필터용 — 매출 자료의 주차 목록(최근 주차부터)."""
|
||||||
|
with self._pool.connection() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT week_label, MIN(ship_date) AS d FROM cupang_sales "
|
||||||
|
"WHERE week_label <> '' GROUP BY week_label ORDER BY d DESC NULLS LAST"
|
||||||
|
).fetchall()
|
||||||
|
return [r["week_label"] for r in rows]
|
||||||
|
|
||||||
def create_sale(self, *, data: dict[str, Any]) -> dict[str, Any]:
|
def create_sale(self, *, data: dict[str, Any]) -> dict[str, Any]:
|
||||||
row = self._normalize_sale(data)
|
row = self._normalize_sale(data)
|
||||||
cols = ", ".join(self.SALES_FIELDS)
|
cols = ", ".join(self.SALES_FIELDS)
|
||||||
|
|||||||
@@ -590,6 +590,7 @@ def _sales_filters(request: Request) -> dict[str, str]:
|
|||||||
"center_name": (q.get("center") or "").strip(),
|
"center_name": (q.get("center") or "").strip(),
|
||||||
"keyword": (q.get("q") or "").strip(),
|
"keyword": (q.get("q") or "").strip(),
|
||||||
"po_type": (q.get("type") or "").strip(),
|
"po_type": (q.get("type") or "").strip(),
|
||||||
|
"week_label": (q.get("week") or "").strip(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -641,6 +642,7 @@ async def sales_page(request: Request) -> HTMLResponse:
|
|||||||
"totals": totals,
|
"totals": totals,
|
||||||
"filters": filters,
|
"filters": filters,
|
||||||
"centers": store.sales_centers(),
|
"centers": store.sales_centers(),
|
||||||
|
"weeks": store.sales_weeks(),
|
||||||
"po_types": ["일반", "벤더플렉스"],
|
"po_types": ["일반", "벤더플렉스"],
|
||||||
"weekly": sorted(weekly.values(), key=lambda w: w.get("week_from") or ""),
|
"weekly": sorted(weekly.values(), key=lambda w: w.get("week_from") or ""),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904a" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904b" />{% 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=20260904a" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904b" />{% 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=20260904a" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904b" />{% 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=20260904a" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904b" />{% 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=20260904a" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904b" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# 쿠팡 로켓 매출 — 발주 라인별 공급가·원가·물류비·마진 #}
|
{# 쿠팡 로켓 매출 — 발주 라인별 공급가·원가·물류비·마진 #}
|
||||||
@@ -29,6 +29,13 @@
|
|||||||
<option value="{{ c }}" {% if c == filters.center_name %}selected{% endif %}>{{ c }}</option>
|
<option value="{{ c }}" {% if c == filters.center_name %}selected{% endif %}>{{ c }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select></label>
|
</select></label>
|
||||||
|
<label class="erp-field"><span>주차</span>
|
||||||
|
<select class="erp-select" name="week">
|
||||||
|
<option value="">전체</option>
|
||||||
|
{% for w in weeks %}
|
||||||
|
<option value="{{ w }}" {% if w == filters.week_label %}selected{% endif %}>{{ w }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select></label>
|
||||||
<label class="erp-field"><span>발주유형</span>
|
<label class="erp-field"><span>발주유형</span>
|
||||||
<select class="erp-select" name="type">
|
<select class="erp-select" name="type">
|
||||||
<option value="">전체</option>
|
<option value="">전체</option>
|
||||||
@@ -45,38 +52,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- 합계 -->
|
|
||||||
<div class="cpg-sum-kpis cpg-sales-kpis">
|
|
||||||
<div class="cpg-kpi">
|
|
||||||
<span class="cpg-kpi-label">공급가</span>
|
|
||||||
<strong class="cpg-kpi-value">{{ "{:,.0f}".format(totals.supply_amount) }}</strong>
|
|
||||||
<span class="cpg-kpi-hint">{{ totals.count }}건 · {{ "{:,}".format(totals.quantity) }}개</span>
|
|
||||||
</div>
|
|
||||||
<div class="cpg-kpi">
|
|
||||||
<span class="cpg-kpi-label">원가</span>
|
|
||||||
<strong class="cpg-kpi-value">{{ "{:,.0f}".format(totals.cost_amount) }}</strong>
|
|
||||||
<span class="cpg-kpi-hint">발주량 기준</span>
|
|
||||||
</div>
|
|
||||||
<div class="cpg-kpi">
|
|
||||||
<span class="cpg-kpi-label">물류비</span>
|
|
||||||
<strong class="cpg-kpi-value">{{ "{:,.0f}".format(totals.logistics_total) }}</strong>
|
|
||||||
<span class="cpg-kpi-hint">물류비중 {{ totals.logistics_ratio }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="cpg-kpi">
|
|
||||||
<span class="cpg-kpi-label">마진</span>
|
|
||||||
<strong class="cpg-kpi-value">{{ "{:,.0f}".format(totals.margin) }}</strong>
|
|
||||||
<span class="cpg-kpi-hint">마진율 {{ totals.margin_rate }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="cpg-kpi">
|
|
||||||
<span class="cpg-kpi-label">순마진</span>
|
|
||||||
<strong class="cpg-kpi-value">{{ "{:,.0f}".format(totals.net_margin) }}</strong>
|
|
||||||
<span class="cpg-kpi-hint">
|
|
||||||
광고 {{ "{:,.0f}".format(totals.ad_cost) }} · 할인 {{ "{:,.0f}".format(totals.promo_discount) }}
|
|
||||||
· 장려 {{ "{:,.0f}".format(totals.incentive) }} → {{ totals.net_margin_rate }}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 표 -->
|
<!-- 표 -->
|
||||||
<div class="erp-card cpg-form-card cpg-sales-card">
|
<div class="erp-card cpg-form-card cpg-sales-card">
|
||||||
<div class="erp-table-wrap cpg-sales-wrap">
|
<div class="erp-table-wrap cpg-sales-wrap">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904a" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260904b" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
|
{# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
|
||||||
|
|||||||
+21
-8
@@ -1508,19 +1508,32 @@ a.cpg-boxno-dl:hover { background: #1a5c38; border-color: #1a5c38; font-weight:
|
|||||||
.cpg-sales-pick { cursor: pointer; }
|
.cpg-sales-pick { cursor: pointer; }
|
||||||
.cpg-sales .cpg-po-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
.cpg-sales .cpg-po-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||||
|
|
||||||
|
/* 조회 조건 — 한 줄 고정(줄바꿈 없음), 좁으면 가로 스크롤 */
|
||||||
.cpg-sales-filter {
|
.cpg-sales-filter {
|
||||||
display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px;
|
display: flex; flex-wrap: nowrap; align-items: flex-end; gap: 8px;
|
||||||
padding: 12px 14px;
|
padding: 10px 12px;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
.cpg-sales-filter .erp-field { margin: 0; }
|
.cpg-sales-filter .erp-field {
|
||||||
.cpg-sales-filter .erp-field > span { font-size: 11px; color: var(--color-midtone-gray); }
|
display: flex; flex-direction: column; gap: 2px;
|
||||||
.cpg-sales-q { flex: 1 1 240px; min-width: 0; }
|
margin: 0; flex: 0 0 auto;
|
||||||
.cpg-sales-q .erp-input { width: 100%; box-sizing: border-box; }
|
}
|
||||||
.cpg-sales-filter-act { display: flex; gap: 6px; margin-left: auto; }
|
.cpg-sales-filter .erp-field > span {
|
||||||
|
font-size: 11px; color: var(--color-midtone-gray); white-space: nowrap;
|
||||||
|
}
|
||||||
|
.cpg-sales-filter .erp-input,
|
||||||
|
.cpg-sales-filter .erp-select { height: 32px; box-sizing: border-box; }
|
||||||
|
.cpg-sales-filter input[type="date"] { width: 140px; }
|
||||||
|
.cpg-sales-filter select[name="center"] { width: 110px; }
|
||||||
|
.cpg-sales-filter select[name="week"] { width: 170px; }
|
||||||
|
.cpg-sales-filter select[name="type"] { width: 110px; }
|
||||||
|
.cpg-sales-q { flex: 1 1 180px; min-width: 140px; }
|
||||||
|
.cpg-sales-q .erp-input { width: 100%; }
|
||||||
|
.cpg-sales-filter-act { display: flex; gap: 6px; flex: 0 0 auto; }
|
||||||
|
|
||||||
.cpg-sales-kpis { flex-shrink: 0; }
|
.cpg-sales-kpis { flex-shrink: 0; }
|
||||||
.cpg-sales-card { padding: 0; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
.cpg-sales-card { padding: 0; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
||||||
.cpg-sales-wrap { overflow: auto; max-height: calc(100vh - 330px); }
|
.cpg-sales-wrap { overflow: auto; max-height: calc(100vh - 230px); }
|
||||||
|
|
||||||
.cpg-sales-table { table-layout: auto; width: 100%; white-space: nowrap; }
|
.cpg-sales-table { table-layout: auto; width: 100%; white-space: nowrap; }
|
||||||
.cpg-sales-table th, .cpg-sales-table td {
|
.cpg-sales-table th, .cpg-sales-table td {
|
||||||
|
|||||||
Reference in New Issue
Block a user