' +
- '
' + esc(labels[a.key] || a.key) + "" +
+ '
' + esc(labels[a.key] || a.key) +
+ (fillTxt ? ' ' + fillTxt + "" : "") + "" +
'
' +
'
상자 · ' + (a.count * per) + "개" +
'
' +
@@ -863,6 +1090,13 @@
(rows.length
? '
"
: '
여기로 상자를 끌어다 놓기
') +
+ poolHtml(cid) +
+ (rows.length
+ ? '"
+ : "") +
"
";
});
distList.innerHTML = html;
@@ -1049,7 +1283,7 @@
mixType[key] = sel.value;
calc.mixes.forEach(function (m) {
m.boxes.forEach(function (b, i) {
- var k = mixKey(m.box_name, i);
+ var k = mixKeyFor(b);
if (k === key) labels[k] = mixLabel(k, i);
});
});
@@ -1057,6 +1291,137 @@
msg.textContent = "상자 종류를 " + sel.value + " 로 바꿨습니다.";
});
+ // ── 혼합 상자 내용물 편집 이벤트 ──────────────────
+ if (distList) {
+ distList.addEventListener("click", function (e) {
+ var dec = e.target.closest("[data-mix-dec]");
+ if (dec) {
+ var moved = mixTake(dec.getAttribute("data-key"), dec.getAttribute("data-code"), 1);
+ if (moved) { render(); msg.textContent = "1개를 미배치로 뺐습니다."; }
+ return;
+ }
+ var inc = e.target.closest("[data-mix-inc]");
+ if (inc) {
+ var key = inc.getAttribute("data-key");
+ var code = inc.getAttribute("data-code");
+ var cid = centerOfKey(key);
+ var got = poolTake(cid, code, Math.min(1, mixRoomFor(key, code)));
+ if (!got) {
+ msg.textContent = mixRoomFor(key, code)
+ ? "미배치 자투리에 남은 수량이 없습니다."
+ : "이 상자에 더 들어갈 자리가 없습니다.";
+ return;
+ }
+ var name = (mixItem(key, code) || {}).product_name || code;
+ mixPut(key, code, name, got);
+ render();
+ msg.textContent = "1개를 담았습니다.";
+ return;
+ }
+ // 혼합 상자의 ✕ — 내용물을 미배치로 옮기고 상자를 없앤다.
+ var del = e.target.closest(".cpg-dist-del");
+ if (del) {
+ var li = del.closest(".cpg-dist-item");
+ var zone = del.closest("[data-drop-for]");
+ var dcid = zone && zone.getAttribute("data-drop-for");
+ var entry = (alloc[dcid] || []).filter(function (a) {
+ return String(a.id) === li.getAttribute("data-entry");
+ })[0];
+ if (entry && entry.key.indexOf("m:") === 0) {
+ e.stopImmediatePropagation(); // 뒤 핸들러가 또 지우지 않게
+ (contents[entry.key] || []).slice().forEach(function (it) {
+ poolAdd(dcid, it.product_code, it.product_name, it.quantity);
+ });
+ contents[entry.key] = [];
+ syncMix(entry.key);
+ render();
+ msg.textContent = "혼합 상자를 비우고 미배치로 옮겼습니다.";
+ }
+ return;
+ }
+
+ var neu = e.target.closest(".cpg-mix-new");
+ if (neu) {
+ newMixBox(neu.getAttribute("data-center"));
+ render();
+ msg.textContent = "빈 혼합 상자를 추가했습니다.";
+ return;
+ }
+ var auto = e.target.closest(".cpg-pool-auto");
+ if (auto) {
+ var acid = auto.getAttribute("data-center");
+ var left = 0;
+ poolOf(acid).slice().forEach(function (it) {
+ var need = it.quantity;
+ mixKeysOf(acid).forEach(function (k) {
+ if (need <= 0) return;
+ var put = mixPut(k, it.product_code, it.product_name, Math.min(need, mixRoomFor(k, it.product_code)));
+ if (put) { poolTake(acid, it.product_code, put); need -= put; }
+ });
+ if (need > 0) {
+ var nk = newMixBox(acid);
+ var put2 = mixPut(nk, it.product_code, it.product_name, Math.min(need, mixRoomFor(nk, it.product_code)));
+ if (put2) { poolTake(acid, it.product_code, put2); need -= put2; }
+ }
+ left += Math.max(need, 0);
+ });
+ render();
+ msg.textContent = left ? "일부는 담지 못했습니다 (" + left + "개)" : "미배치 자투리를 모두 담았습니다.";
+ }
+ });
+
+ distList.addEventListener("change", function (e) {
+ var mv = e.target.closest(".cpg-mix-move");
+ if (mv) {
+ var fromKey = mv.getAttribute("data-key");
+ var code = mv.getAttribute("data-code");
+ var target = mv.value;
+ mv.value = "";
+ if (!target) return;
+ var src = mixItem(fromKey, code);
+ if (!src) return;
+ var qty = src.quantity;
+ var name = src.product_name;
+ var cid = centerOfKey(fromKey);
+ if (target === "__pool__") {
+ mixTake(fromKey, code, qty);
+ render();
+ msg.textContent = name + " " + qty + "개를 미배치로 뺐습니다.";
+ return;
+ }
+ var toKey = target === "__new__" ? newMixBox(cid) : target;
+ var room = mixRoomFor(toKey, code);
+ var move = Math.min(qty, room);
+ if (move <= 0) { msg.textContent = "옮길 자리가 없습니다."; render(); return; }
+ mixTake(fromKey, code, move); // 일단 미배치로
+ poolTake(cid, code, move); // 바로 꺼내서
+ mixPut(toKey, code, name, move); // 대상 상자에 담기
+ render();
+ msg.textContent = name + " " + move + "개를 " + (labels[toKey] || "새 상자") + " 로 옮겼습니다." +
+ (move < qty ? " (자리가 부족해 " + (qty - move) + "개는 남음)" : "");
+ return;
+ }
+
+ var pm = e.target.closest(".cpg-pool-move");
+ if (pm) {
+ var pcid = pm.getAttribute("data-center");
+ var pcode = pm.getAttribute("data-code");
+ var dest = pm.value;
+ pm.value = "";
+ if (!dest) return;
+ var entry = poolOf(pcid).filter(function (it) { return it.product_code === pcode; })[0];
+ if (!entry) return;
+ var key2 = dest === "__new__" ? newMixBox(pcid) : dest;
+ var put = Math.min(entry.quantity, mixRoomFor(key2, pcode));
+ if (put <= 0) { msg.textContent = "그 상자에는 자리가 없습니다."; render(); return; }
+ poolTake(pcid, pcode, put);
+ mixPut(key2, pcode, entry.product_name, put);
+ render();
+ msg.textContent = entry.product_name + " " + put + "개를 담았습니다.";
+ }
+ });
+ }
+
if (distList) {
distList.addEventListener("input", function (e) {
if (!e.target.classList.contains("cpg-dist-qty")) return;
@@ -1189,7 +1554,7 @@
calc.mixes.forEach(function (m) {
m.boxes.forEach(function (b, i) {
total += 1;
- remain += remainFor(mixKey(m.box_name, i));
+ remain += remainFor(mixKeyFor(b));
});
});
return { total: total, remain: remain };
@@ -1208,6 +1573,9 @@
if (!cids.length) return "센터에 담긴 상자가 없습니다.";
var missing = cids.filter(function (cid) { return !methods[String(cid)]; });
if (missing.length) return "출고방식 미선택: " + missing.map(centerName).join(", ");
+ var leftover = 0;
+ openCenters.forEach(function (cid) { leftover += poolTotal(cid); });
+ if (leftover) return "미배치 자투리 " + leftover + "개가 남았습니다.";
return "";
}
@@ -1478,7 +1846,7 @@
});
calc.mixes.forEach(function (m) {
m.boxes.forEach(function (b, i) {
- var k = mixKey(m.box_name, i);
+ var k = mixKeyFor(b);
labels[k] = mixLabel(k, i);
});
});
diff --git a/app/modules/cupang/templates/cupang/box_rules.html b/app/modules/cupang/templates/cupang/box_rules.html
index ecb7fc1..bd52dc9 100644
--- a/app/modules/cupang/templates/cupang/box_rules.html
+++ b/app/modules/cupang/templates/cupang/box_rules.html
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
-{% block head_extra %}