|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
{% extends "erp_base.html" %}
|
|
|
|
|
|
|
|
|
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831e" />{% endblock %}
|
|
|
|
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831f" />{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
<section class="cpg">
|
|
|
|
@@ -51,6 +51,8 @@
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-primary" id="cpg-calc-run">계산 / 재계산</button>
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-outline" id="cpg-calc-add">+ 제품 추가</button>
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-outline" id="cpg-calc-reset">초기화</button>
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-outline" id="cpg-calc-save">임시 저장</button>
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-outline" id="cpg-calc-load">불러오기</button>
|
|
|
|
|
<span class="erp-muted" id="cpg-calc-msg"></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
@@ -121,6 +123,31 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 임시 저장 / 불러오기 -->
|
|
|
|
|
<div class="cpg-modal" id="cpg-draft-dlg" hidden>
|
|
|
|
|
<div class="cpg-modal-back" data-draft-close></div>
|
|
|
|
|
<div class="cpg-modal-box cpg-draft-box" role="dialog" aria-modal="true" aria-labelledby="cpg-draft-title">
|
|
|
|
|
<h3 id="cpg-draft-title">임시 저장</h3>
|
|
|
|
|
|
|
|
|
|
<label class="erp-field"><span>제목</span>
|
|
|
|
|
<input class="erp-input" type="text" id="cpg-draft-name" maxlength="100"
|
|
|
|
|
placeholder="예: 8/31 인천분" autocomplete="off" /></label>
|
|
|
|
|
<div class="cpg-draft-saveact">
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-primary" id="cpg-draft-save">이 내용 저장</button>
|
|
|
|
|
<span class="erp-muted" id="cpg-draft-msg"></span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<h4 class="cpg-draft-h4">저장된 목록</h4>
|
|
|
|
|
<div class="cpg-draft-list" id="cpg-draft-list">
|
|
|
|
|
<p class="erp-muted">불러오는 중…</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="cpg-dlg-actions cpg-draft-foot">
|
|
|
|
|
<button type="button" class="erp-btn erp-btn-outline" data-draft-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>
|
|
|
|
@@ -215,7 +242,7 @@
|
|
|
|
|
return items;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function run() {
|
|
|
|
|
function run(restore) {
|
|
|
|
|
var items = collect();
|
|
|
|
|
if (!items.length) { msg.textContent = "제품명을 하나 이상 선택하세요."; return; }
|
|
|
|
|
msg.textContent = "계산 중…";
|
|
|
|
@@ -251,8 +278,14 @@
|
|
|
|
|
alloc = {};
|
|
|
|
|
stamped = {};
|
|
|
|
|
allStamped = false; // 계산이 바뀌면 이전 배분은 근거가 사라지므로 비운다.
|
|
|
|
|
var skipped = restore ? applyRestore(restore) : 0;
|
|
|
|
|
render();
|
|
|
|
|
msg.textContent = "계산 완료 (" + calc.results.length + "건)" + (had ? " — 센터 분배 초기화됨" : "");
|
|
|
|
|
if (restore) {
|
|
|
|
|
msg.textContent = "불러오기 완료 (" + calc.results.length + "건)" +
|
|
|
|
|
(skipped ? " — 담을 수 없는 " + skipped + "건은 제외" : "");
|
|
|
|
|
} else {
|
|
|
|
|
msg.textContent = "계산 완료 (" + calc.results.length + "건)" + (had ? " — 센터 분배 초기화됨" : "");
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(function () { msg.textContent = "계산 실패 — 새로고침 후 다시 시도하세요."; })
|
|
|
|
|
.then(function () { runBtn.disabled = false; });
|
|
|
|
@@ -745,6 +778,157 @@
|
|
|
|
|
msg.textContent = "";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ── 임시 저장 / 불러오기 ──────────────────────────
|
|
|
|
|
var saveBtn = document.getElementById("cpg-calc-save");
|
|
|
|
|
var loadBtn = document.getElementById("cpg-calc-load");
|
|
|
|
|
var draftDlg = document.getElementById("cpg-draft-dlg");
|
|
|
|
|
var draftName = document.getElementById("cpg-draft-name");
|
|
|
|
|
var draftSave = document.getElementById("cpg-draft-save");
|
|
|
|
|
var draftMsg = document.getElementById("cpg-draft-msg");
|
|
|
|
|
var draftList = document.getElementById("cpg-draft-list");
|
|
|
|
|
|
|
|
|
|
function snapshot() {
|
|
|
|
|
return {
|
|
|
|
|
items: collect(),
|
|
|
|
|
openCenters: openCenters.slice(),
|
|
|
|
|
alloc: alloc,
|
|
|
|
|
stamped: stamped,
|
|
|
|
|
allStamped: allStamped
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 불러온 분배 내역을 현재 계산 결과 위에 다시 얹는다.
|
|
|
|
|
// 계산이 달라져 사라진 항목은 건너뛰고 그 건수를 돌려준다.
|
|
|
|
|
function applyRestore(snap) {
|
|
|
|
|
var skipped = 0;
|
|
|
|
|
openCenters = [];
|
|
|
|
|
(snap.openCenters || []).forEach(function (cid) {
|
|
|
|
|
cid = String(cid);
|
|
|
|
|
if (openCenters.indexOf(cid) < 0) openCenters.push(cid);
|
|
|
|
|
});
|
|
|
|
|
var src = snap.alloc || {};
|
|
|
|
|
Object.keys(src).forEach(function (cid) {
|
|
|
|
|
(src[cid] || []).forEach(function (a) {
|
|
|
|
|
if (!a || !(a.key in units)) { skipped += 1; return; }
|
|
|
|
|
if (openCenters.indexOf(String(cid)) < 0) openCenters.push(String(cid));
|
|
|
|
|
if (addAlloc(cid, a.key, a.count) <= 0) skipped += 1;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
stamped = snap.stamped && typeof snap.stamped === "object" ? snap.stamped : {};
|
|
|
|
|
allStamped = !!snap.allStamped;
|
|
|
|
|
return skipped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function defaultTitle() {
|
|
|
|
|
var d = new Date();
|
|
|
|
|
function pad(n) { return (n < 10 ? "0" : "") + n; }
|
|
|
|
|
return (d.getMonth() + 1) + "/" + d.getDate() + " " + pad(d.getHours()) + ":" + pad(d.getMinutes());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeDraft() { draftDlg.hidden = true; }
|
|
|
|
|
|
|
|
|
|
function openDraft(mode) {
|
|
|
|
|
draftMsg.textContent = "";
|
|
|
|
|
document.getElementById("cpg-draft-title").textContent =
|
|
|
|
|
mode === "load" ? "저장된 내용 불러오기" : "임시 저장";
|
|
|
|
|
if (mode !== "load") draftName.value = defaultTitle();
|
|
|
|
|
draftDlg.hidden = false;
|
|
|
|
|
loadDrafts();
|
|
|
|
|
if (mode !== "load") { draftName.focus(); draftName.select(); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadDrafts() {
|
|
|
|
|
draftList.innerHTML = '<p class="erp-muted">불러오는 중…</p>';
|
|
|
|
|
fetch("/cupang/api/box-calc/drafts")
|
|
|
|
|
.then(function (r) { if (!r.ok) throw new Error("http " + r.status); return r.json(); })
|
|
|
|
|
.then(function (data) {
|
|
|
|
|
var rows = (data && data.drafts) || [];
|
|
|
|
|
if (!rows.length) {
|
|
|
|
|
draftList.innerHTML = '<p class="erp-muted">저장된 내용이 없습니다.</p>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
draftList.innerHTML = rows.map(function (d) {
|
|
|
|
|
return '<div class="cpg-draft-row">' +
|
|
|
|
|
'<span class="cpg-draft-rname">' + esc(d.title) + "</span>" +
|
|
|
|
|
'<span class="cpg-draft-rmeta">' + esc((d.updated_at || "").replace("T", " ").slice(5, 16)) +
|
|
|
|
|
(d.created_by ? " · " + esc(d.created_by) : "") + "</span>" +
|
|
|
|
|
'<button type="button" class="erp-btn erp-btn-outline cpg-btn-sm" data-draft-load="' + d.id + '">불러오기</button>' +
|
|
|
|
|
'<button type="button" class="erp-btn erp-btn-danger cpg-btn-sm" data-draft-del="' + d.id + '">삭제</button>' +
|
|
|
|
|
"</div>";
|
|
|
|
|
}).join("");
|
|
|
|
|
})
|
|
|
|
|
.catch(function () { draftList.innerHTML = '<p class="erp-muted">목록을 불러오지 못했습니다.</p>'; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function doSave() {
|
|
|
|
|
var title = (draftName.value || "").trim();
|
|
|
|
|
if (!title) { draftMsg.textContent = "제목을 입력하세요."; draftName.focus(); return; }
|
|
|
|
|
draftSave.disabled = true;
|
|
|
|
|
draftMsg.textContent = "저장 중…";
|
|
|
|
|
fetch("/cupang/api/box-calc/drafts", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ title: title, payload: snapshot() })
|
|
|
|
|
})
|
|
|
|
|
.then(function (r) { if (!r.ok) throw new Error("http " + r.status); return r.json(); })
|
|
|
|
|
.then(function () {
|
|
|
|
|
draftMsg.textContent = "저장됨 (같은 제목은 덮어씀)";
|
|
|
|
|
msg.textContent = '"' + title + '" 임시 저장됨';
|
|
|
|
|
loadDrafts();
|
|
|
|
|
})
|
|
|
|
|
.catch(function () { draftMsg.textContent = "저장 실패 — 다시 시도하세요."; })
|
|
|
|
|
.then(function () { draftSave.disabled = false; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function doLoad(id) {
|
|
|
|
|
draftMsg.textContent = "불러오는 중…";
|
|
|
|
|
fetch("/cupang/api/box-calc/drafts/" + id)
|
|
|
|
|
.then(function (r) { if (!r.ok) throw new Error("http " + r.status); return r.json(); })
|
|
|
|
|
.then(function (data) {
|
|
|
|
|
var d = (data && data.draft) || {};
|
|
|
|
|
var snap = d.payload || {};
|
|
|
|
|
var items = snap.items || [];
|
|
|
|
|
tbody.innerHTML = "";
|
|
|
|
|
items.forEach(function (it) { addRow(it.product_code, it.quantity); });
|
|
|
|
|
if (!items.length) addRow("", 0);
|
|
|
|
|
draftName.value = d.title || "";
|
|
|
|
|
closeDraft();
|
|
|
|
|
// 박스 수는 서버에서 다시 계산한 뒤 분배 내역을 얹는다.
|
|
|
|
|
run(snap);
|
|
|
|
|
})
|
|
|
|
|
.catch(function () { draftMsg.textContent = "불러오기 실패 — 다시 시도하세요."; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function doDelete(id) {
|
|
|
|
|
fetch("/cupang/api/box-calc/drafts/" + id, { method: "DELETE" })
|
|
|
|
|
.then(function (r) { if (!r.ok) throw new Error("http " + r.status); return r.json(); })
|
|
|
|
|
.then(function () { draftMsg.textContent = "삭제됨"; loadDrafts(); })
|
|
|
|
|
.catch(function () { draftMsg.textContent = "삭제 실패 — 다시 시도하세요."; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (saveBtn) saveBtn.addEventListener("click", function () { openDraft("save"); });
|
|
|
|
|
if (loadBtn) loadBtn.addEventListener("click", function () { openDraft("load"); });
|
|
|
|
|
if (draftSave) draftSave.addEventListener("click", doSave);
|
|
|
|
|
if (draftName) {
|
|
|
|
|
draftName.addEventListener("keydown", function (e) {
|
|
|
|
|
if (e.key === "Enter") { e.preventDefault(); doSave(); }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (draftDlg) {
|
|
|
|
|
Array.prototype.forEach.call(draftDlg.querySelectorAll("[data-draft-close]"), function (el) {
|
|
|
|
|
el.addEventListener("click", closeDraft);
|
|
|
|
|
});
|
|
|
|
|
draftDlg.addEventListener("click", function (e) {
|
|
|
|
|
var l = e.target.closest("[data-draft-load]");
|
|
|
|
|
if (l) { doLoad(l.getAttribute("data-draft-load")); return; }
|
|
|
|
|
var x = e.target.closest("[data-draft-del]");
|
|
|
|
|
if (x) { doDelete(x.getAttribute("data-draft-del")); }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
document.addEventListener("keydown", function (e) {
|
|
|
|
|
if (e.key === "Escape" && draftDlg && !draftDlg.hidden) closeDraft();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
addRow("", 0);
|
|
|
|
|
render();
|
|
|
|
|
})();
|
|
|
|
|