|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
{% extends "erp_base.html" %}
|
|
|
|
|
|
|
|
|
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829b" />{% endblock %}
|
|
|
|
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829c" />{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
<section class="cpg">
|
|
|
|
@@ -59,7 +59,7 @@
|
|
|
|
|
<div class="erp-card cpg-form-card cpg-calc-sum">
|
|
|
|
|
<div class="cpg-card-head">
|
|
|
|
|
<h2>② 박스 요약</h2>
|
|
|
|
|
<span class="erp-muted">카드를 ③ 센터로 끌어다 놓으면 잔여가 줄어듭니다.</span>
|
|
|
|
|
<span class="erp-muted">카드를 ③ 센터로 끌어다 놓거나, 카드를 클릭해 담습니다.</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="cpg-sum-empty" class="erp-muted">왼쪽에서 제품과 수량을 넣고 계산하세요.</div>
|
|
|
|
@@ -75,11 +75,11 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- ③ 센터 분배 — 드래그해서 담고 수량 입력 -->
|
|
|
|
|
<!-- ③ 센터 분배 — 선택한 센터만 표시 -->
|
|
|
|
|
<div class="erp-card cpg-form-card cpg-dist-card">
|
|
|
|
|
<div class="cpg-card-head">
|
|
|
|
|
<h2>③ 센터 분배</h2>
|
|
|
|
|
<span class="erp-muted">입고센터 관리에 등록한 센터만 표시됩니다.</span>
|
|
|
|
|
<span class="erp-muted">담을 센터를 골라 추가하세요.</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% if not centers %}
|
|
|
|
@@ -87,31 +87,47 @@
|
|
|
|
|
등록된 입고센터가 없습니다. <a href="/cupang/centers">입고센터 관리</a>에서 먼저 센터를 추가하세요.
|
|
|
|
|
</p>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="cpg-dist-list" id="cpg-dist-list">
|
|
|
|
|
{% for c in centers %}
|
|
|
|
|
<div class="cpg-dist-center" data-center-id="{{ c.id }}" data-center-name="{{ c.name }}">
|
|
|
|
|
<div class="cpg-dist-head">
|
|
|
|
|
<strong>{{ c.name }}</strong>
|
|
|
|
|
<span class="erp-badge erp-badge-neutral cpg-dist-total" data-total-for="{{ c.id }}">0박스</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cpg-dist-drop" data-drop-for="{{ c.id }}">
|
|
|
|
|
<span class="cpg-dist-hint">여기로 박스를 끌어다 놓기</span>
|
|
|
|
|
<ul class="cpg-dist-items" data-items-for="{{ c.id }}"></ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
<div class="cpg-inline-form cpg-center-pick">
|
|
|
|
|
<select class="erp-select" id="cpg-center-pick">
|
|
|
|
|
{% for c in centers %}
|
|
|
|
|
<option value="{{ c.id }}">{{ c.name }}</option>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</select>
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-primary" id="cpg-center-add">+ 센터 추가</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cpg-dist-list" id="cpg-dist-list"></div>
|
|
|
|
|
<p class="erp-muted" id="cpg-dist-empty">추가한 센터가 여기에 표시됩니다.</p>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div><!-- /cpg-calc3 -->
|
|
|
|
|
|
|
|
|
|
<!-- 담을 박스 수량 입력 대화상자 -->
|
|
|
|
|
<div class="cpg-modal" id="cpg-dlg" hidden>
|
|
|
|
|
<div class="cpg-modal-back" data-dlg-close></div>
|
|
|
|
|
<div class="cpg-modal-box" role="dialog" aria-modal="true" aria-labelledby="cpg-dlg-title">
|
|
|
|
|
<h3 id="cpg-dlg-title">센터에 담기</h3>
|
|
|
|
|
<p class="cpg-dlg-item" id="cpg-dlg-item"></p>
|
|
|
|
|
<label class="erp-field"><span>센터</span>
|
|
|
|
|
<select class="erp-select" id="cpg-dlg-center"></select></label>
|
|
|
|
|
<label class="erp-field"><span>박스 수량</span>
|
|
|
|
|
<input class="erp-input" type="number" id="cpg-dlg-qty" min="1" step="1" /></label>
|
|
|
|
|
<p class="erp-muted" id="cpg-dlg-hint"></p>
|
|
|
|
|
<div class="erp-page-actions cpg-dlg-actions">
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-primary" id="cpg-dlg-ok">담기</button>
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-outline" id="cpg-dlg-all">잔여 전부 담기</button>
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-outline" data-dlg-close>취소</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script type="application/json" id="cpg-calc-rules">{{ box_rules | tojson }}</script>
|
|
|
|
|
<script type="application/json" id="cpg-centers">{{ centers | tojson }}</script>
|
|
|
|
|
<script>
|
|
|
|
|
// 박스 계산 3열 화면.
|
|
|
|
|
// ① 제품명·수량 입력 → 서버(POST /cupang/api/box-calc)에서 계산
|
|
|
|
|
// ② 계산 결과 요약(제품별 박스 / 자투리 혼합 박스) — 드래그 소스
|
|
|
|
|
// ③ 센터별 분배 — 드롭 대상. 배분한 수량만큼 ② 의 잔여가 줄어든다.
|
|
|
|
|
// ② 계산 결과 요약 — 드래그(또는 클릭) 소스
|
|
|
|
|
// ③ 선택한 센터에만 분배. 담은 수량만큼 ② 의 잔여가 즉시 줄어든다.
|
|
|
|
|
// 분배 내역은 화면 안에서만 유지된다(DB 저장 없음).
|
|
|
|
|
(function () {
|
|
|
|
|
var tbody = document.getElementById("cpg-calc-rows");
|
|
|
|
@@ -127,9 +143,23 @@
|
|
|
|
|
var sumBoxes = document.getElementById("cpg-sum-boxes");
|
|
|
|
|
var sumMixNote = document.getElementById("cpg-sum-mix-note");
|
|
|
|
|
var distList = document.getElementById("cpg-dist-list");
|
|
|
|
|
var distEmpty = document.getElementById("cpg-dist-empty");
|
|
|
|
|
var centerPick = document.getElementById("cpg-center-pick");
|
|
|
|
|
var centerAdd = document.getElementById("cpg-center-add");
|
|
|
|
|
var dlg = document.getElementById("cpg-dlg");
|
|
|
|
|
var dlgItem = document.getElementById("cpg-dlg-item");
|
|
|
|
|
var dlgCenter = document.getElementById("cpg-dlg-center");
|
|
|
|
|
var dlgQty = document.getElementById("cpg-dlg-qty");
|
|
|
|
|
var dlgHint = document.getElementById("cpg-dlg-hint");
|
|
|
|
|
var dlgOk = document.getElementById("cpg-dlg-ok");
|
|
|
|
|
var dlgAll = document.getElementById("cpg-dlg-all");
|
|
|
|
|
|
|
|
|
|
var rules = [];
|
|
|
|
|
try { rules = JSON.parse(document.getElementById("cpg-calc-rules").textContent || "[]"); } catch (e) {}
|
|
|
|
|
function readJson(id) {
|
|
|
|
|
var el = document.getElementById(id);
|
|
|
|
|
try { return JSON.parse((el && el.textContent) || "[]"); } catch (e) { return []; }
|
|
|
|
|
}
|
|
|
|
|
var rules = readJson("cpg-calc-rules");
|
|
|
|
|
var centers = readJson("cpg-centers");
|
|
|
|
|
|
|
|
|
|
function esc(s) { var d = document.createElement("div"); d.textContent = s == null ? "" : s; return d.innerHTML; }
|
|
|
|
|
|
|
|
|
@@ -139,10 +169,22 @@
|
|
|
|
|
esc(r.product_name_snapshot || r.product_code) + "</option>";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 계산 결과 + 센터 배분 상태
|
|
|
|
|
var calc = { results: [], mixes: [] };
|
|
|
|
|
var alloc = {}; // centerId -> [{id, key, kind, label, count}]
|
|
|
|
|
// ── 상태 ─────────────────────────────────────────
|
|
|
|
|
var calc = { results: [], mixes: [] }; // 서버 계산 결과
|
|
|
|
|
var units = {}; // key -> 박스 1개당 상품 수
|
|
|
|
|
var labels = {}; // key -> 표시 이름
|
|
|
|
|
var openCenters = []; // ③ 에 추가한 센터 id (문자열)
|
|
|
|
|
var alloc = {}; // centerId -> [{id, key, count}]
|
|
|
|
|
var seq = 0;
|
|
|
|
|
var pending = null; // 대화상자 대상 {key, centerId}
|
|
|
|
|
|
|
|
|
|
function prodKey(code) { return "p:" + code; }
|
|
|
|
|
function mixKey(boxName, idx) { return "m:" + boxName + "#" + idx; }
|
|
|
|
|
|
|
|
|
|
function centerName(cid) {
|
|
|
|
|
var hit = centers.filter(function (c) { return String(c.id) === String(cid); })[0];
|
|
|
|
|
return hit ? hit.name : cid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── ① 입력 표 ─────────────────────────────────────
|
|
|
|
|
function addRow(code, qty) {
|
|
|
|
@@ -185,6 +227,20 @@
|
|
|
|
|
.then(function (data) {
|
|
|
|
|
calc.results = ((data && data.results) || []).filter(function (r) { return r.configured; });
|
|
|
|
|
calc.mixes = (data && data.mixes) || [];
|
|
|
|
|
units = {};
|
|
|
|
|
labels = {};
|
|
|
|
|
calc.results.forEach(function (r) {
|
|
|
|
|
units[prodKey(r.product_code)] = r.units_per_box;
|
|
|
|
|
labels[prodKey(r.product_code)] = r.product_name;
|
|
|
|
|
});
|
|
|
|
|
calc.mixes.forEach(function (m) {
|
|
|
|
|
m.boxes.forEach(function (b, i) {
|
|
|
|
|
var n = 0;
|
|
|
|
|
b.items.forEach(function (it) { n += it.quantity; });
|
|
|
|
|
units[mixKey(m.box_name, i)] = n;
|
|
|
|
|
labels[mixKey(m.box_name, i)] = m.box_name + " 혼합 #" + (i + 1);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
var had = Object.keys(alloc).some(function (k) { return alloc[k].length; });
|
|
|
|
|
alloc = {}; // 계산이 바뀌면 이전 배분은 근거가 사라지므로 비운다.
|
|
|
|
|
render();
|
|
|
|
@@ -203,9 +259,6 @@
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mixKey(boxName, idx) { return "m:" + boxName + "#" + idx; }
|
|
|
|
|
function prodKey(code) { return "p:" + code; }
|
|
|
|
|
|
|
|
|
|
function totalFor(key) {
|
|
|
|
|
if (key.indexOf("p:") === 0) {
|
|
|
|
|
var code = key.slice(2);
|
|
|
|
@@ -217,6 +270,18 @@
|
|
|
|
|
|
|
|
|
|
function remainFor(key) { return Math.max(0, totalFor(key) - allocatedFor(key)); }
|
|
|
|
|
|
|
|
|
|
function addAlloc(cid, key, count) {
|
|
|
|
|
cid = String(cid);
|
|
|
|
|
var room = remainFor(key);
|
|
|
|
|
var n = Math.min(Math.max(parseInt(count, 10) || 0, 0), room);
|
|
|
|
|
if (n <= 0) return 0;
|
|
|
|
|
alloc[cid] = alloc[cid] || [];
|
|
|
|
|
var same = alloc[cid].filter(function (a) { return a.key === key; })[0];
|
|
|
|
|
if (same) { same.count += n; }
|
|
|
|
|
else { seq += 1; alloc[cid].push({ id: seq, key: key, count: n }); }
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── ② 요약 렌더 ───────────────────────────────────
|
|
|
|
|
function boxSvg(fill, label) {
|
|
|
|
|
var f = Math.max(0, Math.min(100, fill || 0));
|
|
|
|
@@ -274,14 +339,15 @@
|
|
|
|
|
var used = r.full_boxes - remain;
|
|
|
|
|
ph += '<div class="cpg-sum-prod' + (remain ? "" : " is-done") + '"' +
|
|
|
|
|
(remain ? ' draggable="true"' : "") +
|
|
|
|
|
' data-key="' + esc(key) + '" data-kind="product" data-label="' + esc(r.product_name) + '">' +
|
|
|
|
|
' data-key="' + esc(key) + '">' +
|
|
|
|
|
'<div class="cpg-sum-prod-head">' +
|
|
|
|
|
'<span class="cpg-sum-prod-name">' + esc(r.product_name) + "</span>" +
|
|
|
|
|
'<span class="erp-badge erp-badge-neutral">' + esc(r.box_name) + "</span>" +
|
|
|
|
|
"</div>" +
|
|
|
|
|
'<div class="cpg-sum-prod-nums">' +
|
|
|
|
|
"<strong>" + remain + "박스</strong>" +
|
|
|
|
|
'<span class="erp-muted">총 ' + r.full_boxes + "박스" + (used ? " · 배분 " + used : "") + "</span>" +
|
|
|
|
|
'<span class="erp-muted">' + (remain * r.units_per_box) + "개 · 총 " + r.full_boxes + "박스" +
|
|
|
|
|
(used ? " · 배분 " + used : "") + "</span>" +
|
|
|
|
|
(r.remainder_units
|
|
|
|
|
? '<span class="cpg-sum-left">자투리 ' + r.remainder_units + "개</span>"
|
|
|
|
|
: '<span class="cpg-sum-exact">딱 맞음</span>') +
|
|
|
|
@@ -305,8 +371,7 @@
|
|
|
|
|
}).join("");
|
|
|
|
|
bh += '<div class="cpg-box-card' + (remain ? "" : " is-done") + '"' +
|
|
|
|
|
(remain ? ' draggable="true"' : "") +
|
|
|
|
|
' data-key="' + esc(key) + '" data-kind="mix"' +
|
|
|
|
|
' data-label="' + esc(m.box_name + " 혼합 #" + (i + 1)) + '"' +
|
|
|
|
|
' data-key="' + esc(key) + '"' +
|
|
|
|
|
' style="animation-delay:' + (n * 60) + 'ms">' +
|
|
|
|
|
'<div class="cpg-box-art">' + boxSvg(b.fill_percent, m.box_name) + "</div>" +
|
|
|
|
|
'<div class="cpg-box-info">' +
|
|
|
|
@@ -314,7 +379,7 @@
|
|
|
|
|
(remain ? "" : ' <span class="erp-badge erp-badge-success">배분됨</span>') + "</div>" +
|
|
|
|
|
'<ul class="cpg-box-items">' + items + "</ul>" +
|
|
|
|
|
'<div class="cpg-box-bar"><span style="width:' + b.fill_percent + '%"></span></div>' +
|
|
|
|
|
'<div class="cpg-box-fillnum">' + b.fill_percent + "% 채움</div>" +
|
|
|
|
|
'<div class="cpg-box-fillnum">' + b.fill_percent + "% 채움 · " + units[key] + "개</div>" +
|
|
|
|
|
"</div>" +
|
|
|
|
|
"</div>";
|
|
|
|
|
n += 1;
|
|
|
|
@@ -327,41 +392,94 @@
|
|
|
|
|
// ── ③ 센터 분배 렌더 ──────────────────────────────
|
|
|
|
|
function renderDist() {
|
|
|
|
|
if (!distList) return;
|
|
|
|
|
Array.prototype.forEach.call(distList.querySelectorAll(".cpg-dist-center"), function (box) {
|
|
|
|
|
var cid = box.getAttribute("data-center-id");
|
|
|
|
|
var list = box.querySelector("[data-items-for]");
|
|
|
|
|
var totalEl = box.querySelector("[data-total-for]");
|
|
|
|
|
if (distEmpty) distEmpty.hidden = openCenters.length > 0;
|
|
|
|
|
|
|
|
|
|
var html = "";
|
|
|
|
|
openCenters.forEach(function (cid) {
|
|
|
|
|
var rows = alloc[cid] || [];
|
|
|
|
|
var total = 0;
|
|
|
|
|
var html = "";
|
|
|
|
|
var boxes = 0, pieces = 0, items = "";
|
|
|
|
|
rows.forEach(function (a) {
|
|
|
|
|
total += a.count;
|
|
|
|
|
var max = a.count + remainFor(a.key);
|
|
|
|
|
html += '<li class="cpg-dist-item" data-entry="' + a.id + '">' +
|
|
|
|
|
'<span class="cpg-dist-name">' + esc(a.label) + "</span>" +
|
|
|
|
|
'<input class="erp-input cpg-dist-qty" type="number" min="1" max="' + max + '" value="' + a.count + '" />' +
|
|
|
|
|
'<span class="cpg-dist-unit">박스</span>' +
|
|
|
|
|
'<button type="button" class="erp-btn erp-btn-danger cpg-dist-del" title="빼기" aria-label="빼기">✕</button>' +
|
|
|
|
|
"</li>";
|
|
|
|
|
var per = units[a.key] || 0;
|
|
|
|
|
boxes += a.count;
|
|
|
|
|
pieces += a.count * per;
|
|
|
|
|
items += '<li class="cpg-dist-item" data-entry="' + a.id + '">' +
|
|
|
|
|
'<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 + '" />' +
|
|
|
|
|
'<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>' +
|
|
|
|
|
"</li>";
|
|
|
|
|
});
|
|
|
|
|
list.innerHTML = html;
|
|
|
|
|
box.classList.toggle("has-items", rows.length > 0);
|
|
|
|
|
totalEl.textContent = total + "박스";
|
|
|
|
|
html += '<div class="cpg-dist-center' + (rows.length ? " has-items" : "") + '" data-drop-for="' + esc(cid) + '">' +
|
|
|
|
|
'<div class="cpg-dist-head">' +
|
|
|
|
|
"<strong>" + esc(centerName(cid)) + "</strong>" +
|
|
|
|
|
'<span class="cpg-dist-sum">' +
|
|
|
|
|
'<span class="erp-badge erp-badge-neutral">' + boxes + "박스</span>" +
|
|
|
|
|
'<span class="erp-badge erp-badge-neutral">' + pieces + "개</span>" +
|
|
|
|
|
'<button type="button" class="erp-btn erp-btn-outline cpg-dist-close" title="센터 빼기" aria-label="센터 빼기">✕</button>' +
|
|
|
|
|
"</span>" +
|
|
|
|
|
"</div>" +
|
|
|
|
|
(rows.length
|
|
|
|
|
? '<ul class="cpg-dist-items">' + items + "</ul>"
|
|
|
|
|
: '<p class="cpg-dist-hint">여기로 박스를 끌어다 놓기</p>') +
|
|
|
|
|
"</div>";
|
|
|
|
|
});
|
|
|
|
|
distList.innerHTML = html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function render() { renderSummary(); renderDist(); }
|
|
|
|
|
|
|
|
|
|
// ── 드래그 앤 드롭 ────────────────────────────────
|
|
|
|
|
// ── 수량 대화상자 ─────────────────────────────────
|
|
|
|
|
function openDialog(key, centerId) {
|
|
|
|
|
if (!openCenters.length) { msg.textContent = "먼저 ③ 에서 센터를 추가하세요."; return; }
|
|
|
|
|
var remain = remainFor(key);
|
|
|
|
|
if (remain <= 0) { msg.textContent = "남은 박스가 없습니다."; return; }
|
|
|
|
|
|
|
|
|
|
pending = { key: key };
|
|
|
|
|
dlgItem.textContent = (labels[key] || key) + " — 잔여 " + remain + "박스 (" + (remain * (units[key] || 0)) + "개)";
|
|
|
|
|
dlgCenter.innerHTML = openCenters.map(function (cid) {
|
|
|
|
|
return '<option value="' + esc(cid) + '"' + (String(cid) === String(centerId) ? " selected" : "") + ">" +
|
|
|
|
|
esc(centerName(cid)) + "</option>";
|
|
|
|
|
}).join("");
|
|
|
|
|
dlgQty.max = remain;
|
|
|
|
|
dlgQty.value = key.indexOf("m:") === 0 ? 1 : remain;
|
|
|
|
|
dlgHint.textContent = "1박스 = " + (units[key] || 0) + "개 · 최대 " + remain + "박스";
|
|
|
|
|
dlg.hidden = false;
|
|
|
|
|
dlgQty.focus();
|
|
|
|
|
dlgQty.select();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeDialog() { dlg.hidden = true; pending = null; }
|
|
|
|
|
|
|
|
|
|
function commitDialog(all) {
|
|
|
|
|
if (!pending) return;
|
|
|
|
|
var key = pending.key;
|
|
|
|
|
var cid = dlgCenter.value;
|
|
|
|
|
var want = all ? remainFor(key) : parseInt(dlgQty.value, 10);
|
|
|
|
|
var got = addAlloc(cid, key, want);
|
|
|
|
|
closeDialog();
|
|
|
|
|
render();
|
|
|
|
|
msg.textContent = got
|
|
|
|
|
? centerName(cid) + " ← " + (labels[key] || key) + " " + got + "박스"
|
|
|
|
|
: "담을 수량이 없습니다.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dlgOk.addEventListener("click", function () { commitDialog(false); });
|
|
|
|
|
dlgAll.addEventListener("click", function () { commitDialog(true); });
|
|
|
|
|
dlg.addEventListener("click", function (e) {
|
|
|
|
|
if (e.target.hasAttribute && e.target.hasAttribute("data-dlg-close")) closeDialog();
|
|
|
|
|
});
|
|
|
|
|
dlgQty.addEventListener("keydown", function (e) {
|
|
|
|
|
if (e.key === "Enter") { e.preventDefault(); commitDialog(false); }
|
|
|
|
|
});
|
|
|
|
|
document.addEventListener("keydown", function (e) {
|
|
|
|
|
if (e.key === "Escape" && !dlg.hidden) closeDialog();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ── 드래그 앤 드롭 (+ 클릭 대체 경로) ─────────────
|
|
|
|
|
sumBody.addEventListener("dragstart", function (e) {
|
|
|
|
|
var card = e.target.closest("[data-key]");
|
|
|
|
|
if (!card || card.getAttribute("draggable") !== "true") return;
|
|
|
|
|
var payload = {
|
|
|
|
|
key: card.getAttribute("data-key"),
|
|
|
|
|
kind: card.getAttribute("data-kind"),
|
|
|
|
|
label: card.getAttribute("data-label")
|
|
|
|
|
};
|
|
|
|
|
e.dataTransfer.setData("text/plain", JSON.stringify(payload));
|
|
|
|
|
e.dataTransfer.setData("text/plain", card.getAttribute("data-key"));
|
|
|
|
|
e.dataTransfer.effectAllowed = "copy";
|
|
|
|
|
card.classList.add("is-dragging");
|
|
|
|
|
});
|
|
|
|
@@ -369,8 +487,14 @@
|
|
|
|
|
var card = e.target.closest("[data-key]");
|
|
|
|
|
if (card) card.classList.remove("is-dragging");
|
|
|
|
|
});
|
|
|
|
|
sumBody.addEventListener("click", function (e) {
|
|
|
|
|
var card = e.target.closest("[data-key]");
|
|
|
|
|
if (!card) return;
|
|
|
|
|
openDialog(card.getAttribute("data-key"), openCenters[0]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (distList) {
|
|
|
|
|
// 드롭 대상은 센터 패널 전체 — 여백에 놓아도 인식된다.
|
|
|
|
|
distList.addEventListener("dragover", function (e) {
|
|
|
|
|
var zone = e.target.closest("[data-drop-for]");
|
|
|
|
|
if (!zone) return;
|
|
|
|
@@ -387,27 +511,15 @@
|
|
|
|
|
if (!zone) return;
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
zone.classList.remove("is-over");
|
|
|
|
|
var data;
|
|
|
|
|
try { data = JSON.parse(e.dataTransfer.getData("text/plain") || "{}"); } catch (err) { return; }
|
|
|
|
|
if (!data.key) return;
|
|
|
|
|
if (remainFor(data.key) <= 0) { msg.textContent = "남은 박스가 없습니다."; return; }
|
|
|
|
|
|
|
|
|
|
var cid = zone.getAttribute("data-drop-for");
|
|
|
|
|
alloc[cid] = alloc[cid] || [];
|
|
|
|
|
var same = alloc[cid].filter(function (a) { return a.key === data.key; })[0];
|
|
|
|
|
if (same) { same.count += 1; }
|
|
|
|
|
else {
|
|
|
|
|
seq += 1;
|
|
|
|
|
alloc[cid].push({ id: seq, key: data.key, kind: data.kind, label: data.label, count: 1 });
|
|
|
|
|
}
|
|
|
|
|
render();
|
|
|
|
|
var key = e.dataTransfer.getData("text/plain");
|
|
|
|
|
if (!key) return;
|
|
|
|
|
openDialog(key, zone.getAttribute("data-drop-for"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
distList.addEventListener("input", function (e) {
|
|
|
|
|
if (!e.target.classList.contains("cpg-dist-qty")) return;
|
|
|
|
|
var li = e.target.closest(".cpg-dist-item");
|
|
|
|
|
var centerEl = e.target.closest(".cpg-dist-center");
|
|
|
|
|
var cid = centerEl.getAttribute("data-center-id");
|
|
|
|
|
var cid = e.target.closest("[data-drop-for]").getAttribute("data-drop-for");
|
|
|
|
|
var entry = (alloc[cid] || []).filter(function (a) { return String(a.id) === li.getAttribute("data-entry"); })[0];
|
|
|
|
|
if (!entry) return;
|
|
|
|
|
var want = parseInt(e.target.value, 10);
|
|
|
|
@@ -415,19 +527,49 @@
|
|
|
|
|
var max = entry.count + remainFor(entry.key);
|
|
|
|
|
if (want > max) { want = max; e.target.value = max; }
|
|
|
|
|
entry.count = want;
|
|
|
|
|
// 입력 중에는 목록을 다시 그리지 않는다(포커스 유지) — 합계와 요약만 갱신.
|
|
|
|
|
var sum = 0;
|
|
|
|
|
(alloc[cid] || []).forEach(function (a) { sum += a.count; });
|
|
|
|
|
centerEl.querySelector("[data-total-for]").textContent = sum + "박스";
|
|
|
|
|
li.querySelector(".cpg-dist-unit").textContent = "박스 · " + (want * (units[entry.key] || 0)) + "개";
|
|
|
|
|
renderSummary();
|
|
|
|
|
updateCenterTotals(cid);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
distList.addEventListener("click", function (e) {
|
|
|
|
|
if (!e.target.closest(".cpg-dist-del")) return;
|
|
|
|
|
var li = e.target.closest(".cpg-dist-item");
|
|
|
|
|
var cid = e.target.closest(".cpg-dist-center").getAttribute("data-center-id");
|
|
|
|
|
alloc[cid] = (alloc[cid] || []).filter(function (a) { return String(a.id) !== li.getAttribute("data-entry"); });
|
|
|
|
|
render();
|
|
|
|
|
var zone = e.target.closest("[data-drop-for]");
|
|
|
|
|
if (!zone) return;
|
|
|
|
|
var cid = zone.getAttribute("data-drop-for");
|
|
|
|
|
if (e.target.closest(".cpg-dist-close")) { // 센터 자체를 목록에서 뺀다
|
|
|
|
|
openCenters = openCenters.filter(function (x) { return x !== cid; });
|
|
|
|
|
delete alloc[cid];
|
|
|
|
|
render();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (e.target.closest(".cpg-dist-del")) { // 담은 항목 제거 → 잔여로 복귀
|
|
|
|
|
var li = e.target.closest(".cpg-dist-item");
|
|
|
|
|
alloc[cid] = (alloc[cid] || []).filter(function (a) { return String(a.id) !== li.getAttribute("data-entry"); });
|
|
|
|
|
render();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateCenterTotals(cid) {
|
|
|
|
|
var zone = distList.querySelector('[data-drop-for="' + cid + '"]');
|
|
|
|
|
if (!zone) return;
|
|
|
|
|
var boxes = 0, pieces = 0;
|
|
|
|
|
(alloc[cid] || []).forEach(function (a) {
|
|
|
|
|
boxes += a.count;
|
|
|
|
|
pieces += a.count * (units[a.key] || 0);
|
|
|
|
|
});
|
|
|
|
|
var badges = zone.querySelectorAll(".cpg-dist-sum .erp-badge");
|
|
|
|
|
if (badges[0]) badges[0].textContent = boxes + "박스";
|
|
|
|
|
if (badges[1]) badges[1].textContent = pieces + "개";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (centerAdd) {
|
|
|
|
|
centerAdd.addEventListener("click", function () {
|
|
|
|
|
var cid = centerPick.value;
|
|
|
|
|
if (!cid) return;
|
|
|
|
|
if (openCenters.indexOf(cid) >= 0) { msg.textContent = "이미 추가된 센터입니다."; return; }
|
|
|
|
|
openCenters.push(cid);
|
|
|
|
|
renderDist();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|