feat(cupang): 센터에 담긴 혼합 박스 내용물 트리 표시

③ 센터 분배에서 혼합 박스 줄 아래에 무엇이 몇 개 들어가는지 트리로
펼쳐 보여준다. 2박스 이상이면 총 개수와 함께 "(4개 × 2박스)" 를 덧붙인다.
수량을 바꾸면 트리 수치도 즉시 갱신된다. 단일 제품 박스는 트리가 없다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 03:42:39 +09:00
parent 87b49abb71
commit c9a16d8cc4
9 changed files with 64 additions and 11 deletions
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829h" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829i" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
@@ -173,6 +173,7 @@
// ── 상태 ───────────────────────────────────────── // ── 상태 ─────────────────────────────────────────
var calc = { results: [], mixes: [] }; // 서버 계산 결과 var calc = { results: [], mixes: [] }; // 서버 계산 결과
var units = {}; // key -> 박스 1개당 상품 수 var units = {}; // key -> 박스 1개당 상품 수
var contents = {}; // key -> 혼합 박스 내용물 [{product_name, quantity}]
var labels = {}; // key -> 표시 이름 var labels = {}; // key -> 표시 이름
var openCenters = []; // ③ 에 추가한 센터 id (문자열) var openCenters = []; // ③ 에 추가한 센터 id (문자열)
var alloc = {}; // centerId -> [{id, key, count}] var alloc = {}; // centerId -> [{id, key, count}]
@@ -230,6 +231,7 @@
calc.mixes = (data && data.mixes) || []; calc.mixes = (data && data.mixes) || [];
units = {}; units = {};
labels = {}; labels = {};
contents = {};
calc.results.forEach(function (r) { calc.results.forEach(function (r) {
units[prodKey(r.product_code)] = r.units_per_box; units[prodKey(r.product_code)] = r.units_per_box;
labels[prodKey(r.product_code)] = r.product_name; labels[prodKey(r.product_code)] = r.product_name;
@@ -240,6 +242,9 @@
b.items.forEach(function (it) { n += it.quantity; }); b.items.forEach(function (it) { n += it.quantity; });
units[mixKey(m.box_name, i)] = n; units[mixKey(m.box_name, i)] = n;
labels[mixKey(m.box_name, i)] = m.box_name + " 혼합 #" + (i + 1); labels[mixKey(m.box_name, i)] = m.box_name + " 혼합 #" + (i + 1);
contents[mixKey(m.box_name, i)] = b.items.map(function (it) {
return { product_name: it.product_name, quantity: it.quantity };
});
}); });
}); });
var had = Object.keys(alloc).some(function (k) { return alloc[k].length; }); var had = Object.keys(alloc).some(function (k) { return alloc[k].length; });
@@ -388,6 +393,18 @@
} }
} }
// 혼합 박스는 안에 무엇이 몇 개 들어가는지 트리로 펼쳐 보여준다.
function treeHtml(entry) {
var list = contents[entry.key];
if (!list || !list.length) return "";
return '<ul class="cpg-dist-tree">' + list.map(function (it) {
return '<li><span class="cpg-tree-name">' + esc(it.product_name) + "</span>" +
'<span class="cpg-tree-qty">' + (it.quantity * entry.count) + "개</span>" +
(entry.count > 1 ? '<span class="cpg-tree-per">(' + it.quantity + "개 × " + entry.count + "박스)</span>" : "") +
"</li>";
}).join("") + "</ul>";
}
// ── ③ 센터 분배 렌더 ────────────────────────────── // ── ③ 센터 분배 렌더 ──────────────────────────────
function renderDist() { function renderDist() {
if (!distList) return; if (!distList) return;
@@ -402,10 +419,13 @@
boxes += a.count; boxes += a.count;
pieces += a.count * per; pieces += a.count * per;
items += '<li class="cpg-dist-item" data-entry="' + a.id + '">' + items += '<li class="cpg-dist-item" data-entry="' + a.id + '">' +
'<div class="cpg-dist-line">' +
'<span class="cpg-dist-name">' + esc(labels[a.key] || a.key) + "</span>" + '<span class="cpg-dist-name">' + esc(labels[a.key] || a.key) + "</span>" +
'<input class="erp-input cpg-dist-qty" type="number" min="1" step="1" value="' + a.count + '" />' + '<input class="erp-input cpg-dist-qty" type="number" min="1" step="1" value="' + a.count + '" />' +
'<span class="cpg-dist-unit">박스 · ' + (a.count * per) + "개</span>" + '<span class="cpg-dist-unit">박스 · ' + (a.count * per) + "개</span>" +
'<button type="button" class="erp-btn erp-btn-danger cpg-dist-del" title="빼기" aria-label="빼기">✕</button>' + '<button type="button" class="erp-btn erp-btn-danger cpg-dist-del" title="빼기" aria-label="빼기">✕</button>' +
"</div>" +
treeHtml(a) +
"</li>"; "</li>";
}); });
html += '<div class="cpg-dist-center' + (rows.length ? " has-items" : "") + '" data-drop-for="' + esc(cid) + '">' + html += '<div class="cpg-dist-center' + (rows.length ? " has-items" : "") + '" data-drop-for="' + esc(cid) + '">' +
@@ -608,6 +628,8 @@
if (want > max) { want = max; e.target.value = max; } if (want > max) { want = max; e.target.value = max; }
entry.count = want; entry.count = want;
li.querySelector(".cpg-dist-unit").textContent = "박스 · " + (want * (units[entry.key] || 0)) + "개"; li.querySelector(".cpg-dist-unit").textContent = "박스 · " + (want * (units[entry.key] || 0)) + "개";
var tree = li.querySelector(".cpg-dist-tree");
if (tree) tree.outerHTML = treeHtml(entry);
renderSummary(); renderSummary();
updateCenterTotals(cid); updateCenterTotals(cid);
}); });
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829h" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829i" />{% 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=20260829h" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829i" />{% 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=20260829h" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829i" />{% 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=20260829h" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829i" />{% 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=20260829h" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829i" />{% 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=20260829h" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829i" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
+25
View File
@@ -705,3 +705,28 @@ body.cpg-dragging { cursor: grabbing; user-select: none; }
overflow-y: auto; overflow-y: auto;
} }
.cpg-dlg-center-btn { padding: 4px 12px; } .cpg-dlg-center-btn { padding: 4px 12px; }
/* 센터에 담긴 혼합 박스의 내용물 트리 */
.cpg-dist-item { display: block; }
.cpg-dist-line { display: flex; align-items: center; gap: 6px; }
.cpg-dist-tree {
list-style: none;
margin: 4px 0 2px;
padding: 0 0 0 10px;
border-left: 1px solid var(--color-subtle-ash);
}
.cpg-dist-tree li {
display: flex;
align-items: baseline;
gap: 6px;
padding: 1px 0;
font-size: 12px;
color: var(--color-midtone-gray);
}
.cpg-dist-tree li::before {
content: "└";
color: var(--color-subtle-ash);
}
.cpg-tree-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cpg-tree-qty { font-weight: 600; color: var(--color-rich-black); }
.cpg-tree-per { font-size: 11px; }
+6
View File
@@ -143,6 +143,12 @@ function dragCardTo(card, target) {
$("#cpg-dlg-ok").click(); $("#cpg-dlg-ok").click();
check("혼합 박스가 센터에 담긴다", $$(".cpg-dist-item").length === 2); check("혼합 박스가 센터에 담긴다", $$(".cpg-dist-item").length === 2);
check("혼합 담은 뒤 배분됨 표시", $(".cpg-box-card").className.includes("is-done")); check("혼합 담은 뒤 배분됨 표시", $(".cpg-box-card").className.includes("is-done"));
check("혼합 박스 아래 내용물 트리", $$(".cpg-dist-tree li").length === 2,
String($$(".cpg-dist-tree li").length));
check("트리에 제품명·개수", text(".cpg-dist-tree li").includes("미라네 4호 세트") &&
text(".cpg-dist-tree li").includes("4개"), text(".cpg-dist-tree li"));
check("단일 제품 항목에는 트리 없음",
!$$(".cpg-dist-item")[0].querySelector(".cpg-dist-tree"));
// ── 항목 빼기 / 센터 빼기 ───────────────────────── // ── 항목 빼기 / 센터 빼기 ─────────────────────────
$$(".cpg-dist-del")[1].click(); $$(".cpg-dist-del")[1].click();