change(cupang): 미배치 자투리 제거 — 상자 간 직접 이동만, ③ 스크롤 보정
- "미배치로 빼기" 옵션과 미배치 풀 UI/상태 삭제 - [−] 는 자리가 있는 다른 상자로 1개 이동(없으면 새 상자 자동 생성), [+] 는 같은 제품이 있는 다른 상자에서 1개 가져오기 → 총량 항상 보존 - 혼합 상자 ✕ 는 내용물을 다른 상자로 옮긴 뒤 상자를 없앤다 - 3열이 2열/1열로 접히는 폭에서 카드 max-height 를 줘서 ③ 목록이 카드 안에서 스크롤되도록 수정(편집으로 줄이 늘어나도 페이지가 안 밀림) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901p" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901q" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
@@ -351,7 +351,6 @@
|
|||||||
var had = Object.keys(alloc).some(function (k) { return alloc[k].length; });
|
var had = Object.keys(alloc).some(function (k) { return alloc[k].length; });
|
||||||
alloc = {};
|
alloc = {};
|
||||||
stamped = {};
|
stamped = {};
|
||||||
pool = {};
|
|
||||||
allStamped = false; // 계산이 바뀌면 이전 배분은 근거가 사라지므로 비운다.
|
allStamped = false; // 계산이 바뀌면 이전 배분은 근거가 사라지므로 비운다.
|
||||||
var skipped = restore ? applyRestore(restore) : 0;
|
var skipped = restore ? applyRestore(restore) : 0;
|
||||||
render();
|
render();
|
||||||
@@ -433,7 +432,7 @@
|
|||||||
units: units, labels: labels, contents: contents,
|
units: units, labels: labels, contents: contents,
|
||||||
openCenters: openCenters.slice(), alloc: alloc, methods: methods,
|
openCenters: openCenters.slice(), alloc: alloc, methods: methods,
|
||||||
stamped: stamped, allStamped: allStamped,
|
stamped: stamped, allStamped: allStamped,
|
||||||
mixType: mixType, mixOwner: mixOwner, pool: pool
|
mixType: mixType, mixOwner: mixOwner
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +449,6 @@
|
|||||||
alloc = st.alloc; methods = st.methods;
|
alloc = st.alloc; methods = st.methods;
|
||||||
stamped = st.stamped; allStamped = st.allStamped;
|
stamped = st.stamped; allStamped = st.allStamped;
|
||||||
mixType = st.mixType || {}; mixOwner = st.mixOwner || {};
|
mixType = st.mixType || {}; mixOwner = st.mixOwner || {};
|
||||||
pool = st.pool || {};
|
|
||||||
|
|
||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
(st.items || []).forEach(function (it) { addRow(it.product_code, it.quantity); });
|
(st.items || []).forEach(function (it) { addRow(it.product_code, it.quantity); });
|
||||||
@@ -482,7 +480,7 @@
|
|||||||
units: {}, labels: {}, contents: {},
|
units: {}, labels: {}, contents: {},
|
||||||
openCenters: [], alloc: {}, methods: {},
|
openCenters: [], alloc: {}, methods: {},
|
||||||
stamped: {}, allStamped: false,
|
stamped: {}, allStamped: false,
|
||||||
mixType: {}, mixOwner: {}, pool: {}
|
mixType: {}, mixOwner: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
function put(cid, key, count) {
|
function put(cid, key, count) {
|
||||||
@@ -691,8 +689,6 @@
|
|||||||
// ── 혼합 상자 편집 ────────────────────────────────
|
// ── 혼합 상자 편집 ────────────────────────────────
|
||||||
// 상자 용량은 부피 합으로 본다: 제품 1개 = 1/입수량 상자.
|
// 상자 용량은 부피 합으로 본다: 제품 1개 = 1/입수량 상자.
|
||||||
// 빼낸 수량은 센터별 "미배치 자투리" 풀에 남고, 거기서 다시 담는다.
|
// 빼낸 수량은 센터별 "미배치 자투리" 풀에 남고, 거기서 다시 담는다.
|
||||||
var pool = {}; // centerId -> [{product_code, product_name, quantity}]
|
|
||||||
|
|
||||||
function upbOf(code) {
|
function upbOf(code) {
|
||||||
var hit = calc.results.filter(function (r) { return r.product_code === code; })[0];
|
var hit = calc.results.filter(function (r) { return r.product_code === code; })[0];
|
||||||
return hit ? (hit.units_per_box || 0) : 0;
|
return hit ? (hit.units_per_box || 0) : 0;
|
||||||
@@ -766,36 +762,6 @@
|
|||||||
.map(function (a) { return a.key; });
|
.map(function (a) { return a.key; });
|
||||||
}
|
}
|
||||||
|
|
||||||
function poolOf(cid) {
|
|
||||||
cid = String(cid);
|
|
||||||
if (!pool[cid]) pool[cid] = [];
|
|
||||||
return pool[cid];
|
|
||||||
}
|
|
||||||
|
|
||||||
function poolAdd(cid, code, name, qty) {
|
|
||||||
if (qty <= 0) return;
|
|
||||||
var list = poolOf(cid);
|
|
||||||
var hit = list.filter(function (it) { return it.product_code === code; })[0];
|
|
||||||
if (hit) { hit.quantity += qty; return; }
|
|
||||||
list.push({ product_code: code, product_name: name, quantity: qty });
|
|
||||||
}
|
|
||||||
|
|
||||||
function poolTake(cid, code, qty) {
|
|
||||||
var list = poolOf(cid);
|
|
||||||
var hit = list.filter(function (it) { return it.product_code === code; })[0];
|
|
||||||
if (!hit) return 0;
|
|
||||||
var take = Math.min(hit.quantity, qty);
|
|
||||||
hit.quantity -= take;
|
|
||||||
pool[String(cid)] = list.filter(function (it) { return it.quantity > 0; });
|
|
||||||
return take;
|
|
||||||
}
|
|
||||||
|
|
||||||
function poolTotal(cid) {
|
|
||||||
var n = 0;
|
|
||||||
poolOf(cid).forEach(function (it) { n += it.quantity; });
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mixItem(key, code) {
|
function mixItem(key, code) {
|
||||||
return (contents[key] || []).filter(function (it) { return it.product_code === code; })[0];
|
return (contents[key] || []).filter(function (it) { return it.product_code === code; })[0];
|
||||||
}
|
}
|
||||||
@@ -812,19 +778,48 @@
|
|||||||
return put;
|
return put;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 상자에서 빼기 — 뺀 수량은 그 센터의 미배치 풀로.
|
// 상자에서 덜어내기(내부용) — 호출한 쪽이 곧바로 다른 상자에 담는다.
|
||||||
function mixTake(key, code, qty) {
|
function mixPull(key, code, qty) {
|
||||||
var hit = mixItem(key, code);
|
var hit = mixItem(key, code);
|
||||||
if (!hit) return 0;
|
if (!hit) return 0;
|
||||||
var take = Math.min(hit.quantity, qty);
|
var take = Math.min(hit.quantity, qty);
|
||||||
if (take <= 0) return 0;
|
if (take <= 0) return 0;
|
||||||
var cid = centerOfKey(key);
|
|
||||||
hit.quantity -= take;
|
hit.quantity -= take;
|
||||||
syncMix(key);
|
syncMix(key);
|
||||||
if (cid) poolAdd(cid, code, hit.product_name, take);
|
|
||||||
return take;
|
return take;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 자리가 있는 다른 상자 찾기 (없으면 "")
|
||||||
|
function boxWithRoom(cid, code, exceptKey) {
|
||||||
|
var found = "";
|
||||||
|
mixKeysOf(cid).forEach(function (k) {
|
||||||
|
if (found || k === exceptKey) return;
|
||||||
|
if (mixRoomFor(k, code) > 0) found = k;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 그 제품이 담겨 있는 다른 상자 찾기 (없으면 "")
|
||||||
|
function boxHaving(cid, code, exceptKey) {
|
||||||
|
var found = "";
|
||||||
|
mixKeysOf(cid).forEach(function (k) {
|
||||||
|
if (found || k === exceptKey) return;
|
||||||
|
if (mixItem(k, code)) found = k;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상자 → 상자 이동. 실제로 옮긴 수량을 돌려준다(자리만큼만).
|
||||||
|
function moveUnits(fromKey, toKey, code, name, qty) {
|
||||||
|
var room = mixRoomFor(toKey, code);
|
||||||
|
var move = Math.min(qty, room);
|
||||||
|
if (move <= 0) return 0;
|
||||||
|
var pulled = mixPull(fromKey, code, move);
|
||||||
|
if (!pulled) return 0;
|
||||||
|
mixPut(toKey, code, name, pulled);
|
||||||
|
return pulled;
|
||||||
|
}
|
||||||
|
|
||||||
// 새 혼합 상자 (같은 센터, 비어 있는 상태로 추가)
|
// 새 혼합 상자 (같은 센터, 비어 있는 상태로 추가)
|
||||||
function newMixBox(cid, boxName) {
|
function newMixBox(cid, boxName) {
|
||||||
var name = boxName || (calc.mixes[0] && calc.mixes[0].box_name) || MIX_TYPES[0];
|
var name = boxName || (calc.mixes[0] && calc.mixes[0].box_name) || MIX_TYPES[0];
|
||||||
@@ -983,7 +978,6 @@
|
|||||||
esc(labels[k] || k) + (room ? " (여유 " + room + "개)" : " (가득)") + "</option>";
|
esc(labels[k] || k) + (room ? " (여유 " + room + "개)" : " (가득)") + "</option>";
|
||||||
});
|
});
|
||||||
opts += '<option value="__new__">+ 새 상자로</option>';
|
opts += '<option value="__new__">+ 새 상자로</option>';
|
||||||
opts += '<option value="__pool__">미배치로 빼기</option>';
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,15 +994,16 @@
|
|||||||
(entry.count > 1 ? '<span class="cpg-tree-per">(' + it.quantity + "개 × " + entry.count + "상자)</span>" : "") +
|
(entry.count > 1 ? '<span class="cpg-tree-per">(' + it.quantity + "개 × " + entry.count + "상자)</span>" : "") +
|
||||||
"</li>";
|
"</li>";
|
||||||
}
|
}
|
||||||
var canAdd = mixRoomFor(entry.key, it.product_code) > 0;
|
var canAdd = mixRoomFor(entry.key, it.product_code) > 0 &&
|
||||||
|
!!boxHaving(cid, it.product_code, entry.key);
|
||||||
return '<li><span class="cpg-tree-name">' + esc(it.product_name) + "</span>" +
|
return '<li><span class="cpg-tree-name">' + esc(it.product_name) + "</span>" +
|
||||||
'<span class="cpg-mix-ctl">' +
|
'<span class="cpg-mix-ctl">' +
|
||||||
'<button type="button" class="cpg-mix-step" data-mix-dec data-key="' + esc(entry.key) +
|
'<button type="button" class="cpg-mix-step" data-mix-dec data-key="' + esc(entry.key) +
|
||||||
'" data-code="' + esc(it.product_code) + '" title="1개 빼기">−</button>' +
|
'" data-code="' + esc(it.product_code) + '" title="다른 상자로 1개 옮기기">−</button>' +
|
||||||
'<span class="cpg-tree-qty">' + qty + "개</span>" +
|
'<span class="cpg-tree-qty">' + qty + "개</span>" +
|
||||||
'<button type="button" class="cpg-mix-step" data-mix-inc data-key="' + esc(entry.key) +
|
'<button type="button" class="cpg-mix-step" data-mix-inc data-key="' + esc(entry.key) +
|
||||||
'" data-code="' + esc(it.product_code) + '"' + (canAdd ? "" : " disabled") +
|
'" data-code="' + esc(it.product_code) + '"' + (canAdd ? "" : " disabled") +
|
||||||
' title="미배치에서 1개 담기">+</button>' +
|
' title="다른 상자에서 1개 가져오기">+</button>' +
|
||||||
'<select class="erp-select cpg-mix-move" data-key="' + esc(entry.key) +
|
'<select class="erp-select cpg-mix-move" data-key="' + esc(entry.key) +
|
||||||
'" data-code="' + esc(it.product_code) + '" aria-label="이동">' +
|
'" data-code="' + esc(it.product_code) + '" aria-label="이동">' +
|
||||||
moveOptions(entry.key, cid, it.product_code) +
|
moveOptions(entry.key, cid, it.product_code) +
|
||||||
@@ -1017,34 +1012,6 @@
|
|||||||
}).join("") + "</ul>";
|
}).join("") + "</ul>";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 센터별 미배치 자투리 — 상자에서 뺀 수량이 여기 모인다.
|
|
||||||
function poolHtml(cid) {
|
|
||||||
var list = poolOf(cid);
|
|
||||||
if (!list.length) return "";
|
|
||||||
var rows = list.map(function (it) {
|
|
||||||
var opts = '<option value="">담을 상자…</option>';
|
|
||||||
mixKeysOf(cid).forEach(function (k) {
|
|
||||||
var room = mixRoomFor(k, it.product_code);
|
|
||||||
opts += '<option value="' + esc(k) + '"' + (room ? "" : " disabled") + ">" +
|
|
||||||
esc(labels[k] || k) + (room ? " (여유 " + room + "개)" : " (가득)") + "</option>";
|
|
||||||
});
|
|
||||||
opts += '<option value="__new__">+ 새 상자에</option>';
|
|
||||||
return '<li><span class="cpg-tree-name">' + esc(it.product_name) + "</span>" +
|
|
||||||
'<span class="cpg-mix-ctl">' +
|
|
||||||
'<span class="cpg-tree-qty">' + it.quantity + "개</span>" +
|
|
||||||
'<select class="erp-select cpg-pool-move" data-center="' + esc(cid) +
|
|
||||||
'" data-code="' + esc(it.product_code) + '" aria-label="담을 상자">' + opts + "</select>" +
|
|
||||||
"</span></li>";
|
|
||||||
}).join("");
|
|
||||||
return '<div class="cpg-pool">' +
|
|
||||||
'<div class="cpg-pool-head">미배치 자투리 <b>' + poolTotal(cid) + "개</b>" +
|
|
||||||
'<button type="button" class="erp-btn erp-btn-outline cpg-pool-auto" data-center="' + esc(cid) +
|
|
||||||
'">자동 담기</button>' +
|
|
||||||
"</div>" +
|
|
||||||
'<ul class="cpg-dist-tree">' + rows + "</ul>" +
|
|
||||||
"</div>";
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── ③ 센터 분배 렌더 ──────────────────────────────
|
// ── ③ 센터 분배 렌더 ──────────────────────────────
|
||||||
function renderDist() {
|
function renderDist() {
|
||||||
if (!distList) return;
|
if (!distList) return;
|
||||||
@@ -1090,7 +1057,6 @@
|
|||||||
(rows.length
|
(rows.length
|
||||||
? '<ul class="cpg-dist-items">' + items + "</ul>"
|
? '<ul class="cpg-dist-items">' + items + "</ul>"
|
||||||
: '<p class="cpg-dist-hint">여기로 상자를 끌어다 놓기</p>') +
|
: '<p class="cpg-dist-hint">여기로 상자를 끌어다 놓기</p>') +
|
||||||
poolHtml(cid) +
|
|
||||||
(rows.length
|
(rows.length
|
||||||
? '<div class="cpg-dist-foot">' +
|
? '<div class="cpg-dist-foot">' +
|
||||||
'<button type="button" class="erp-btn erp-btn-outline cpg-mix-new" data-center="' +
|
'<button type="button" class="erp-btn erp-btn-outline cpg-mix-new" data-center="' +
|
||||||
@@ -1292,33 +1258,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── 혼합 상자 내용물 편집 이벤트 ──────────────────
|
// ── 혼합 상자 내용물 편집 이벤트 ──────────────────
|
||||||
|
// 수량은 상자 사이에서만 오간다(총량 보존). 갈 곳이 없으면 새 상자를 만든다.
|
||||||
if (distList) {
|
if (distList) {
|
||||||
distList.addEventListener("click", function (e) {
|
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");
|
var del = e.target.closest(".cpg-dist-del");
|
||||||
if (del) {
|
if (del) {
|
||||||
var li = del.closest(".cpg-dist-item");
|
var li = del.closest(".cpg-dist-item");
|
||||||
@@ -1330,95 +1273,81 @@
|
|||||||
if (entry && entry.key.indexOf("m:") === 0) {
|
if (entry && entry.key.indexOf("m:") === 0) {
|
||||||
e.stopImmediatePropagation(); // 뒤 핸들러가 또 지우지 않게
|
e.stopImmediatePropagation(); // 뒤 핸들러가 또 지우지 않게
|
||||||
(contents[entry.key] || []).slice().forEach(function (it) {
|
(contents[entry.key] || []).slice().forEach(function (it) {
|
||||||
poolAdd(dcid, it.product_code, it.product_name, it.quantity);
|
var left = it.quantity;
|
||||||
|
while (left > 0) {
|
||||||
|
var dest = boxWithRoom(dcid, it.product_code, entry.key) || newMixBox(dcid);
|
||||||
|
var moved = moveUnits(entry.key, dest, it.product_code, it.product_name, left);
|
||||||
|
if (!moved) break;
|
||||||
|
left -= moved;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
contents[entry.key] = [];
|
|
||||||
syncMix(entry.key);
|
syncMix(entry.key);
|
||||||
render();
|
render();
|
||||||
msg.textContent = "혼합 상자를 비우고 미배치로 옮겼습니다.";
|
msg.textContent = "혼합 상자를 비우고 내용물을 다른 상자로 옮겼습니다.";
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dec = e.target.closest("[data-mix-dec]");
|
||||||
|
if (dec) {
|
||||||
|
var key = dec.getAttribute("data-key");
|
||||||
|
var code = dec.getAttribute("data-code");
|
||||||
|
var cid = centerOfKey(key);
|
||||||
|
var item = mixItem(key, code);
|
||||||
|
if (!item) return;
|
||||||
|
var to = boxWithRoom(cid, code, key) || newMixBox(cid);
|
||||||
|
var n = moveUnits(key, to, code, item.product_name, 1);
|
||||||
|
if (!n) { msg.textContent = "옮길 자리가 없습니다."; return; }
|
||||||
|
render();
|
||||||
|
msg.textContent = "1개를 " + (labels[to] || "다른 상자") + " 로 옮겼습니다.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inc = e.target.closest("[data-mix-inc]");
|
||||||
|
if (inc) {
|
||||||
|
var ikey = inc.getAttribute("data-key");
|
||||||
|
var icode = inc.getAttribute("data-code");
|
||||||
|
var icid = centerOfKey(ikey);
|
||||||
|
if (mixRoomFor(ikey, icode) <= 0) {
|
||||||
|
msg.textContent = "이 상자에 더 들어갈 자리가 없습니다."; return;
|
||||||
|
}
|
||||||
|
var from = boxHaving(icid, icode, ikey);
|
||||||
|
if (!from) { msg.textContent = "다른 상자에 이 제품이 없습니다."; return; }
|
||||||
|
var src = mixItem(from, icode);
|
||||||
|
var got = moveUnits(from, ikey, icode, src.product_name, 1);
|
||||||
|
if (!got) { msg.textContent = "가져올 수 없습니다."; return; }
|
||||||
|
render();
|
||||||
|
msg.textContent = "1개를 " + (labels[from] || "다른 상자") + " 에서 가져왔습니다.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var neu = e.target.closest(".cpg-mix-new");
|
var neu = e.target.closest(".cpg-mix-new");
|
||||||
if (neu) {
|
if (neu) {
|
||||||
newMixBox(neu.getAttribute("data-center"));
|
newMixBox(neu.getAttribute("data-center"));
|
||||||
render();
|
render();
|
||||||
msg.textContent = "빈 혼합 상자를 추가했습니다.";
|
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) {
|
distList.addEventListener("change", function (e) {
|
||||||
var mv = e.target.closest(".cpg-mix-move");
|
var mv = e.target.closest(".cpg-mix-move");
|
||||||
if (mv) {
|
if (!mv) return;
|
||||||
var fromKey = mv.getAttribute("data-key");
|
var fromKey = mv.getAttribute("data-key");
|
||||||
var code = mv.getAttribute("data-code");
|
var code = mv.getAttribute("data-code");
|
||||||
var target = mv.value;
|
var target = mv.value;
|
||||||
mv.value = "";
|
mv.value = "";
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
var src = mixItem(fromKey, code);
|
var src = mixItem(fromKey, code);
|
||||||
if (!src) return;
|
if (!src) return;
|
||||||
var qty = src.quantity;
|
var qty = src.quantity;
|
||||||
var name = src.product_name;
|
var name = src.product_name;
|
||||||
var cid = centerOfKey(fromKey);
|
var cid = centerOfKey(fromKey);
|
||||||
if (target === "__pool__") {
|
var toKey = target === "__new__" ? newMixBox(cid) : target;
|
||||||
mixTake(fromKey, code, qty);
|
var moved = moveUnits(fromKey, toKey, code, name, qty);
|
||||||
render();
|
if (!moved) { msg.textContent = "옮길 자리가 없습니다."; render(); return; }
|
||||||
msg.textContent = name + " " + qty + "개를 미배치로 뺐습니다.";
|
render();
|
||||||
return;
|
msg.textContent = name + " " + moved + "개를 " + (labels[toKey] || "새 상자") + " 로 옮겼습니다." +
|
||||||
}
|
(moved < qty ? " (자리가 부족해 " + (qty - moved) + "개는 남음)" : "");
|
||||||
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 + "개를 담았습니다.";
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1573,9 +1502,6 @@
|
|||||||
if (!cids.length) return "센터에 담긴 상자가 없습니다.";
|
if (!cids.length) return "센터에 담긴 상자가 없습니다.";
|
||||||
var missing = cids.filter(function (cid) { return !methods[String(cid)]; });
|
var missing = cids.filter(function (cid) { return !methods[String(cid)]; });
|
||||||
if (missing.length) return "출고방식 미선택: " + missing.map(centerName).join(", ");
|
if (missing.length) return "출고방식 미선택: " + missing.map(centerName).join(", ");
|
||||||
var leftover = 0;
|
|
||||||
openCenters.forEach(function (cid) { leftover += poolTotal(cid); });
|
|
||||||
if (leftover) return "미배치 자투리 " + leftover + "개가 남았습니다.";
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901p" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901q" />{% 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=20260901p" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901q" />{% 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=20260901p" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901q" />{% 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=20260901p" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901q" />{% 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=20260901p" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901q" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
|
{# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
|
||||||
|
|||||||
+12
-15
@@ -663,10 +663,14 @@
|
|||||||
@media (max-width: 1500px) {
|
@media (max-width: 1500px) {
|
||||||
.cpg-calc3 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
.cpg-calc3 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
||||||
.cpg-calc3 > .cpg-dist-card { grid-column: 1 / -1; }
|
.cpg-calc3 > .cpg-dist-card { grid-column: 1 / -1; }
|
||||||
|
/* 3열이 접히면 그리드 행 높이가 auto 라 카드가 계속 늘어난다 →
|
||||||
|
카드마다 최대 높이를 줘서 내부 목록이 스크롤되게 한다. */
|
||||||
|
.cpg-calc3 > .erp-card { max-height: min(78vh, 860px); }
|
||||||
}
|
}
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) {
|
||||||
.cpg-calc3 { grid-template-columns: minmax(0, 1fr); }
|
.cpg-calc3 { grid-template-columns: minmax(0, 1fr); }
|
||||||
.cpg-calc3 > .cpg-dist-card { grid-column: auto; }
|
.cpg-calc3 > .cpg-dist-card { grid-column: auto; }
|
||||||
|
.cpg-calc3 > .erp-card { max-height: min(70vh, 720px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ② 요약: 카드가 드래그 가능함을 알린다 */
|
/* ② 요약: 카드가 드래그 가능함을 알린다 */
|
||||||
@@ -1434,25 +1438,18 @@ body.cpg-dragging { cursor: grabbing; user-select: none; }
|
|||||||
}
|
}
|
||||||
.cpg-mix-step:hover:not(:disabled) { background: var(--color-ghost-gray); border-color: var(--color-midtone-gray); }
|
.cpg-mix-step:hover:not(:disabled) { background: var(--color-ghost-gray); border-color: var(--color-midtone-gray); }
|
||||||
.cpg-mix-step:disabled { opacity: .35; cursor: not-allowed; }
|
.cpg-mix-step:disabled { opacity: .35; cursor: not-allowed; }
|
||||||
.cpg-mix-move, .cpg-pool-move {
|
.cpg-mix-move {
|
||||||
width: auto; min-width: 74px; max-width: 132px;
|
width: auto; min-width: 74px; max-width: 132px;
|
||||||
padding: 1px 4px; font-size: 11px; line-height: 1.5; cursor: pointer;
|
padding: 1px 4px; font-size: 11px; line-height: 1.5; cursor: pointer;
|
||||||
}
|
}
|
||||||
.cpg-dist-tree .cpg-tree-qty { min-width: 38px; text-align: right; }
|
.cpg-dist-tree .cpg-tree-qty { min-width: 38px; text-align: right; }
|
||||||
|
|
||||||
.cpg-pool {
|
|
||||||
margin: 6px 10px 8px;
|
|
||||||
padding: 8px 10px;
|
|
||||||
border: 1px dashed var(--color-midtone-gray); border-radius: 10px;
|
|
||||||
background: var(--color-ghost-gray);
|
|
||||||
}
|
|
||||||
.cpg-pool-head {
|
|
||||||
display: flex; align-items: center; gap: 8px;
|
|
||||||
font-size: 12px; font-weight: 600; margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.cpg-pool-head > b { font-family: var(--font-geist-mono); }
|
|
||||||
.cpg-pool-auto { margin-left: auto; padding: 2px 8px; font-size: 11px; line-height: 1.5; }
|
|
||||||
.cpg-pool .cpg-dist-tree { border-left: 0; padding-left: 0; }
|
|
||||||
.cpg-pool .cpg-dist-tree li::before { content: ""; }
|
|
||||||
.cpg-dist-foot { padding: 0 10px 10px; }
|
.cpg-dist-foot { padding: 0 10px 10px; }
|
||||||
.cpg-mix-new { padding: 3px 10px; font-size: 12px; line-height: 1.5; }
|
.cpg-mix-new { padding: 3px 10px; font-size: 12px; line-height: 1.5; }
|
||||||
|
|
||||||
|
/* ③ 센터 목록은 항상 카드 안에서만 스크롤 (편집으로 줄이 늘어나도 페이지가 밀리지 않게) */
|
||||||
|
.cpg-dist-card { min-height: 0; overflow: hidden; }
|
||||||
|
.cpg-dist-card .cpg-dist-list {
|
||||||
|
flex: 1 1 auto; min-height: 0;
|
||||||
|
overflow-y: auto; overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user