style(cupang): 매출 조회 조건 1행 고정 + 주차 필터, 상단 합계 KPI 제거

- 조회 조건은 flex-wrap: nowrap 로 한 줄 유지(좁은 화면은 가로 스크롤)
- 주차(week_label) 필터 추가 — 최근 주차부터 목록
- 공급가/원가/물류비/마진/순마진 합계 카드 삭제, 표 영역 높이 확대

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 16:13:49 +09:00
parent 0979a89ed9
commit 9ae27bce8a
9 changed files with 50 additions and 47 deletions
+14 -1
View File
@@ -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)
+2
View File
@@ -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">
+8 -33
View File
@@ -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
View File
@@ -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 {