feat(cupang): 등록 제품명 표 정렬 + 박스 입수량 시드 SQL

제품명/제품코드 헤더 클릭으로 오름·내림차순 토글. Intl.Collator(ko,
numeric) 사용해 "미라네 2호"가 "미라네 10호"보다 앞에 오도록 정렬.
검색 비활성 상태에서도 동작하게 스크립트를 search_enabled 밖에 배치.

박스 입수량 14건(미라네 1~7호, 황토 김치통 1~7호) 시드 SQL 추가.
피킹비 전용 컬럼이 없어 memo 에 기록.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 12:13:22 +09:00
parent 55d1c1d6f3
commit 7eb63ff664
8 changed files with 118 additions and 10 deletions
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530p" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260828s" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530p" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260828s" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530p" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260828s" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530p" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260828s" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530p" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260828s" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530p" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260828s" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -38,10 +38,17 @@
<span class="erp-muted">폼의 제품명 드롭다운에 노출</span></div>
<div class="erp-table-wrap cpg-reg-scroll">
<table class="erp-table">
<thead><tr><th>제품명</th><th>제품코드</th><th>상태</th><th>동작</th></tr></thead>
<tbody>
<thead>
<tr>
<th><button type="button" class="cpg-sort" data-sort-key="name">제품명<span class="cpg-sort-ind" aria-hidden="true"></span></button></th>
<th><button type="button" class="cpg-sort" data-sort-key="code">제품코드<span class="cpg-sort-ind" aria-hidden="true"></span></button></th>
<th>상태</th>
<th>동작</th>
</tr>
</thead>
<tbody id="cpg-prod-tbody">
{% for p in products %}
<tr {% if not p.active %}style="opacity:.55"{% endif %}>
<tr data-name="{{ p.product_name }}" data-code="{{ p.product_code }}" {% if not p.active %}style="opacity:.55"{% endif %}>
<td>{{ p.product_name }}</td>
<td>{{ p.product_code }}</td>
<td>{% if p.active %}<span class="erp-badge erp-badge-success">활성</span>{% else %}<span class="erp-badge erp-badge-neutral">비활성</span>{% endif %}</td>
@@ -67,7 +74,7 @@
</tr>
{% endfor %}
{% if not products %}
<tr><td colspan="4" class="erp-muted">등록된 제품명이 없습니다. 왼쪽에서 선택해 등록하세요.</td></tr>
<tr class="cpg-no-sort"><td colspan="4" class="erp-muted">등록된 제품명이 없습니다. 왼쪽에서 선택해 등록하세요.</td></tr>
{% endif %}
</tbody>
</table>
@@ -77,6 +84,43 @@
</div>
</section>
<script>
// 등록된 제품명 표 — 제품명/제품코드 오름차순·내림차순 정렬 (클라이언트).
(function () {
var tbody = document.getElementById("cpg-prod-tbody");
if (!tbody) return;
var buttons = Array.prototype.slice.call(document.querySelectorAll(".cpg-sort"));
var state = { key: null, dir: 1 };
var collator = new Intl.Collator("ko", { numeric: true, sensitivity: "base" });
function apply() {
var rows = Array.prototype.slice.call(tbody.querySelectorAll("tr:not(.cpg-no-sort)"));
if (rows.length < 2) return;
rows.sort(function (a, b) {
var av = a.getAttribute("data-" + state.key) || "";
var bv = b.getAttribute("data-" + state.key) || "";
return collator.compare(av, bv) * state.dir;
});
rows.forEach(function (r) { tbody.appendChild(r); });
}
buttons.forEach(function (btn) {
btn.addEventListener("click", function () {
var key = btn.getAttribute("data-sort-key");
if (state.key === key) { state.dir = -state.dir; }
else { state.key = key; state.dir = 1; }
buttons.forEach(function (b) {
var on = b === btn;
b.classList.toggle("is-asc", on && state.dir === 1);
b.classList.toggle("is-desc", on && state.dir === -1);
b.closest("th").setAttribute("aria-sort", on ? (state.dir === 1 ? "ascending" : "descending") : "none");
});
apply();
});
});
})();
</script>
{% if search_enabled %}
<script type="application/json" id="cpg-registered">{{ registered_codes | tojson }}</script>
<script>
+21
View File
@@ -305,3 +305,24 @@
.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-sort {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0;
border: 0;
background: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.cpg-sort:hover { text-decoration: underline; }
.cpg-sort-ind::after {
content: "↕";
opacity: .35;
font-size: .85em;
}
.cpg-sort.is-asc .cpg-sort-ind::after { content: "▲"; opacity: 1; }
.cpg-sort.is-desc .cpg-sort-ind::after { content: "▼"; opacity: 1; }
+43
View File
@@ -0,0 +1,43 @@
-- ════════════════════════════════════════════════════════════
-- 쿠팡 밀크런 — 박스 입수량 규칙 초기 입력 (cupang_db)
--
-- 실행:
-- docker exec -i postgres-db psql -U postgres -d cupang_db \
-- < /opt/www/main/scripts/sql/cupang_box_rules_seed.sql
--
-- 제품코드는 cupang_products 카탈로그 기준(2026-08-28 확인).
-- 같은 product_code 는 덮어쓴다(ON CONFLICT DO UPDATE).
-- 피킹비는 전용 컬럼이 없어 memo 에 기록한다.
-- ════════════════════════════════════════════════════════════
BEGIN;
INSERT INTO cupang_box_rules
(product_code, product_name_snapshot, box_name, units_per_box, memo, active)
VALUES
('MS-1001', '미라네 1호 세트', '쿠팡 2호', 8, '피킹비 750원', TRUE),
('MS-1002', '미라네 2호 세트', '쿠팡 2호', 8, '피킹비 1100원', TRUE),
('MS-1003', '미라네 3호 세트', '쿠팡 2호', 8, '피킹비 1200원', TRUE),
('MS-1004', '미라네 4호 세트', '쿠팡 2호', 8, '피킹비 1000원', TRUE),
('MS-1005', '미라네 5호 세트', '쿠팡 2호', 8, '피킹비 650원', TRUE),
('MS-1006', '미라네 6호 세트', '쿠팡 2호', 8, '피킹비 750원', TRUE),
('MS-1007', '미라네 7호 세트', '쿠팡 2호', 8, '피킹비 850원', TRUE),
('MS-9812', '황토 김치통 1호세트', '쿠팡 2호', 8, '피킹비 750원', TRUE),
('MS-9813', '황토 김치통 2호세트', '쿠팡 2호', 8, '피킹비 650원', TRUE),
('MS-9814', '황토 김치통 3호세트', '쿠팡 2호', 8, '피킹비 750원', TRUE),
('MS-9815', '황토 김치통 4호세트', '쿠팡 3호', 2, '피킹비 850원', TRUE),
('MS-9816', '황토 김치통 5호세트', '쿠팡 3호', 2, '피킹비 900원', TRUE),
('MS-9817', '황토 김치통 6호세트', '쿠팡 6호', 1, '피킹비 1000원', TRUE),
('MS-9818', '황토 김치통 7호세트', '쿠팡 6호', 1, '피킹비 1100원', TRUE)
ON CONFLICT (product_code) DO UPDATE
SET product_name_snapshot = EXCLUDED.product_name_snapshot,
box_name = EXCLUDED.box_name,
units_per_box = EXCLUDED.units_per_box,
memo = EXCLUDED.memo,
active = TRUE;
COMMIT;
-- 결과 확인
SELECT product_code, product_name_snapshot, box_name, units_per_box, memo
FROM cupang_box_rules
ORDER BY product_code;