feat(cupang): 채움률 낮은 혼합 상자 경고, 직접 입력 제거하고 출고일 목록으로 대체
- 혼합 상자 채움률 60% 이하면 "상자 변경 필요"(오렌지) 도장 + 업로드/날짜 전환 시 안내 팝업 - ① 직접 입력 표와 계산/제품 추가/초기화 버튼·이벤트 제거. 품목은 업로드(또는 임시 저장 불러오기)로만 - 상단 출고일 탭 바를 없애고 ① 카드 안 세로 목록으로 이동 (날짜·센터수·상자수·수량) - 임시 저장에 ship_date 를 함께 담아 불러올 때 출고일 유지 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=20260903g" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260903h" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
@@ -22,12 +22,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<!-- 업로드한 발주서의 출고일이 여러 개면 날짜별로 작업 영역을 나눈다 -->
|
|
||||||
<div class="cpg-date-tabs" id="cpg-date-tabs" hidden>
|
|
||||||
<span class="cpg-date-tabs-label">출고일</span>
|
|
||||||
<div class="cpg-date-tabs-list" id="cpg-date-tabs-list"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cpg-calc3">
|
<div class="cpg-calc3">
|
||||||
|
|
||||||
<!-- ① 상자 계산 — 제품명 + 수량만 입력 -->
|
<!-- ① 상자 계산 — 제품명 + 수량만 입력 -->
|
||||||
@@ -47,32 +41,11 @@
|
|||||||
<div class="cpg-po-result" id="cpg-po-result" hidden></div>
|
<div class="cpg-po-result" id="cpg-po-result" hidden></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="cpg-sum-h3">직접 입력 <span class="erp-muted">업로드 결과도 여기서 고칠 수 있습니다</span></h3>
|
<h3 class="cpg-sum-h3">출고일 <span class="erp-muted">업로드한 발주서 기준 · 날짜를 골라 확정합니다</span></h3>
|
||||||
|
|
||||||
<div class="erp-table-wrap">
|
<div class="cpg-date-list" id="cpg-date-list"></div>
|
||||||
<table class="erp-table cpg-calc-table">
|
<p class="erp-muted" id="cpg-date-empty">발주 파일을 업로드하면 출고일이 여기에 표시됩니다.</p>
|
||||||
<colgroup>
|
<p class="erp-muted cpg-calc-msg" id="cpg-calc-msg"></p>
|
||||||
<col class="cpg-col-name" />
|
|
||||||
<col class="cpg-col-qty" />
|
|
||||||
<col class="cpg-col-act" />
|
|
||||||
</colgroup>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>제품명</th>
|
|
||||||
<th>수량</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="cpg-calc-rows"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="erp-page-actions cpg-calc-actions">
|
|
||||||
<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>
|
|
||||||
<span class="erp-muted" id="cpg-calc-msg"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ② 상자 요약 — 센터에 배분하고 남은 수량이 실시간 반영 -->
|
<!-- ② 상자 요약 — 센터에 배분하고 남은 수량이 실시간 반영 -->
|
||||||
@@ -82,7 +55,7 @@
|
|||||||
<span class="erp-muted">카드를 ③ 센터로 끌어다 놓거나, 카드를 클릭해 담습니다.</span>
|
<span class="erp-muted">카드를 ③ 센터로 끌어다 놓거나, 카드를 클릭해 담습니다.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="cpg-sum-empty" class="erp-muted">왼쪽에서 제품과 수량을 넣고 계산하세요.</div>
|
<div id="cpg-sum-empty" class="erp-muted">① 에서 쿠팡 발주 파일을 업로드하면 상자가 계산됩니다.</div>
|
||||||
|
|
||||||
<div id="cpg-sum-body" hidden>
|
<div id="cpg-sum-body" hidden>
|
||||||
<div class="cpg-sum-kpis" id="cpg-sum-kpis"></div>
|
<div class="cpg-sum-kpis" id="cpg-sum-kpis"></div>
|
||||||
@@ -137,6 +110,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 채움률이 낮은 혼합 상자 안내 -->
|
||||||
|
<div class="cpg-modal" id="cpg-low-dlg" hidden>
|
||||||
|
<div class="cpg-modal-back" data-low-close></div>
|
||||||
|
<div class="cpg-modal-box cpg-low-box" role="dialog" aria-modal="true" aria-labelledby="cpg-low-title">
|
||||||
|
<h3 id="cpg-low-title">상자 변경 필요</h3>
|
||||||
|
<p class="cpg-low-lead">
|
||||||
|
아래 혼합 상자는 채움률이 60% 이하입니다. 더 작은 상자로 바꾸거나 내용물을 합치세요.
|
||||||
|
</p>
|
||||||
|
<ul class="cpg-low-list" id="cpg-low-list"></ul>
|
||||||
|
<div class="cpg-dlg-actions">
|
||||||
|
<button type="button" class="erp-btn erp-btn-primary" data-low-close>확인</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 저장 중 -->
|
<!-- 저장 중 -->
|
||||||
<div class="cpg-modal cpg-saving" id="cpg-saving" hidden>
|
<div class="cpg-modal cpg-saving" id="cpg-saving" hidden>
|
||||||
<div class="cpg-modal-back"></div>
|
<div class="cpg-modal-back"></div>
|
||||||
@@ -182,12 +170,8 @@
|
|||||||
// ③ 선택한 센터에만 분배. 담은 수량만큼 ② 의 잔여가 즉시 줄어든다.
|
// ③ 선택한 센터에만 분배. 담은 수량만큼 ② 의 잔여가 즉시 줄어든다.
|
||||||
// 분배 내역은 화면 안에서만 유지된다(DB 저장 없음).
|
// 분배 내역은 화면 안에서만 유지된다(DB 저장 없음).
|
||||||
(function () {
|
(function () {
|
||||||
var tbody = document.getElementById("cpg-calc-rows");
|
|
||||||
if (!tbody) return;
|
|
||||||
var runBtn = document.getElementById("cpg-calc-run");
|
|
||||||
var addBtn = document.getElementById("cpg-calc-add");
|
|
||||||
var resetBtn = document.getElementById("cpg-calc-reset");
|
|
||||||
var msg = document.getElementById("cpg-calc-msg");
|
var msg = document.getElementById("cpg-calc-msg");
|
||||||
|
if (!msg) return;
|
||||||
var sumEmpty = document.getElementById("cpg-sum-empty");
|
var sumEmpty = document.getElementById("cpg-sum-empty");
|
||||||
var sumBody = document.getElementById("cpg-sum-body");
|
var sumBody = document.getElementById("cpg-sum-body");
|
||||||
var sumKpis = document.getElementById("cpg-sum-kpis");
|
var sumKpis = document.getElementById("cpg-sum-kpis");
|
||||||
@@ -268,42 +252,22 @@
|
|||||||
return hit ? hit.name : cid;
|
return hit ? hit.name : cid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ① 입력 표 ─────────────────────────────────────
|
// ── 계산 ─────────────────────────────────────────
|
||||||
function addRow(code, qty) {
|
// 품목은 업로드(또는 임시 저장 불러오기)로만 들어온다. 직접 입력 표는 없앴다.
|
||||||
var tr = document.createElement("tr");
|
function currentItems() {
|
||||||
tr.className = "cpg-calc-row";
|
return calc.results.map(function (r) {
|
||||||
tr.innerHTML =
|
return { product_code: r.product_code, quantity: r.quantity };
|
||||||
'<td><select class="erp-select cpg-calc-name">' + optionsHtml + "</select></td>" +
|
|
||||||
'<td><input class="erp-input cpg-calc-qty" type="number" min="0" step="1" value="' + (qty || 0) + '" /></td>' +
|
|
||||||
'<td class="cpg-calc-act"><button type="button" class="erp-btn erp-btn-danger cpg-calc-del" title="행 삭제" aria-label="행 삭제">✕</button></td>';
|
|
||||||
tbody.appendChild(tr);
|
|
||||||
if (code) tr.querySelector(".cpg-calc-name").value = code;
|
|
||||||
return tr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rowsArr() {
|
|
||||||
return Array.prototype.slice.call(tbody.querySelectorAll(".cpg-calc-row"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function collect() {
|
|
||||||
var items = [];
|
|
||||||
rowsArr().forEach(function (tr) {
|
|
||||||
var code = tr.querySelector(".cpg-calc-name").value;
|
|
||||||
if (!code) return;
|
|
||||||
items.push({ product_code: code, quantity: parseInt(tr.querySelector(".cpg-calc-qty").value, 10) || 0 });
|
|
||||||
});
|
});
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(restore) {
|
function run(restore, items) {
|
||||||
var items = collect();
|
var list = items || currentItems();
|
||||||
if (!items.length) { msg.textContent = "제품명을 하나 이상 선택하세요."; return; }
|
if (!list.length) { msg.textContent = "먼저 쿠팡 발주 파일을 업로드하세요."; return; }
|
||||||
msg.textContent = "계산 중…";
|
msg.textContent = "계산 중…";
|
||||||
runBtn.disabled = true;
|
|
||||||
fetch("/cupang/api/box-calc", {
|
fetch("/cupang/api/box-calc", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ items: items })
|
body: JSON.stringify({ items: list })
|
||||||
})
|
})
|
||||||
.then(function (r) { if (!r.ok) throw new Error("http " + r.status); return r.json(); })
|
.then(function (r) { if (!r.ok) throw new Error("http " + r.status); return r.json(); })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
@@ -329,21 +293,20 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var had = Object.keys(alloc).some(function (k) { return alloc[k].length; });
|
|
||||||
alloc = {};
|
alloc = {};
|
||||||
stamped = {};
|
stamped = {};
|
||||||
allStamped = false; // 계산이 바뀌면 이전 배분은 근거가 사라지므로 비운다.
|
allStamped = false; // 계산이 바뀌면 이전 배분은 근거가 사라지므로 비운다.
|
||||||
var skipped = restore ? applyRestore(restore) : 0;
|
var skipped = restore ? applyRestore(restore) : 0;
|
||||||
render();
|
render();
|
||||||
if (restore) {
|
if (activeDate) { saveActive(); }
|
||||||
msg.textContent = "불러오기 완료 (" + calc.results.length + "건)" +
|
renderTabs();
|
||||||
(skipped ? " — 담을 수 없는 " + skipped + "건은 제외" : "");
|
msg.textContent = restore
|
||||||
} else {
|
? "불러오기 완료 (" + calc.results.length + "건)" +
|
||||||
msg.textContent = "계산 완료 (" + calc.results.length + "건)" + (had ? " — 센터 분배 초기화됨" : "");
|
(skipped ? " — 담을 수 없는 " + skipped + "건은 제외" : "")
|
||||||
}
|
: "계산 완료 (" + calc.results.length + "건)";
|
||||||
|
warnLowMixes();
|
||||||
})
|
})
|
||||||
.catch(function () { msg.textContent = "계산 실패 — 새로고침 후 다시 시도하세요."; })
|
.catch(function () { msg.textContent = "계산 실패 — 새로고침 후 다시 시도하세요."; });
|
||||||
.then(function () { runBtn.disabled = false; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 쿠팡 발주 엑셀 업로드 ─────────────────────────
|
// ── 쿠팡 발주 엑셀 업로드 ─────────────────────────
|
||||||
@@ -371,8 +334,8 @@
|
|||||||
var sets = {}; // "YYYY-MM-DD" -> 상태 스냅샷
|
var sets = {}; // "YYYY-MM-DD" -> 상태 스냅샷
|
||||||
var dateOrder = []; // 탭 순서
|
var dateOrder = []; // 탭 순서
|
||||||
var activeDate = ""; // "" = 업로드 전(수동 입력 모드)
|
var activeDate = ""; // "" = 업로드 전(수동 입력 모드)
|
||||||
var tabsBar = document.getElementById("cpg-date-tabs");
|
var tabsList = document.getElementById("cpg-date-list");
|
||||||
var tabsList = document.getElementById("cpg-date-tabs-list");
|
var tabsEmpty = document.getElementById("cpg-date-empty");
|
||||||
|
|
||||||
function centersOf(allocObj) {
|
function centersOf(allocObj) {
|
||||||
return Object.keys(allocObj || {}).filter(function (cid) {
|
return Object.keys(allocObj || {}).filter(function (cid) {
|
||||||
@@ -388,18 +351,29 @@
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DOW = ["일", "월", "화", "수", "목", "금", "토"];
|
||||||
|
|
||||||
|
function dateLabel(d) {
|
||||||
|
var p = (d || "").split("-");
|
||||||
|
if (p.length !== 3) return d || "날짜 미확인";
|
||||||
|
var dt = new Date(+p[0], +p[1] - 1, +p[2]);
|
||||||
|
return d + "(" + DOW[dt.getDay()] + ")";
|
||||||
|
}
|
||||||
|
|
||||||
function renderTabs() {
|
function renderTabs() {
|
||||||
if (!tabsBar) return;
|
if (!tabsList) return;
|
||||||
if (dateOrder.length < 1) { tabsBar.hidden = true; tabsList.innerHTML = ""; return; }
|
if (tabsEmpty) tabsEmpty.hidden = dateOrder.length > 0;
|
||||||
tabsBar.hidden = false;
|
if (dateOrder.length < 1) { tabsList.innerHTML = ""; return; }
|
||||||
tabsList.innerHTML = dateOrder.map(function (d) {
|
tabsList.innerHTML = dateOrder.map(function (d) {
|
||||||
var st = sets[d] || {};
|
var st = sets[d] || {};
|
||||||
var cn = centersOf(st.alloc).length;
|
var cn = centersOf(st.alloc).length;
|
||||||
var bx = boxesOf(st.alloc);
|
var bx = boxesOf(st.alloc);
|
||||||
|
var qty = 0;
|
||||||
|
(st.items || []).forEach(function (it) { qty += it.quantity; });
|
||||||
return '<button type="button" class="cpg-date-tab' + (d === activeDate ? " is-active" : "") + '"' +
|
return '<button type="button" class="cpg-date-tab' + (d === activeDate ? " is-active" : "") + '"' +
|
||||||
' data-date="' + esc(d) + '">' +
|
' data-date="' + esc(d) + '">' +
|
||||||
'<strong>' + esc(d || "날짜 미확인") + "</strong>" +
|
'<strong>' + esc(dateLabel(d)) + "</strong>" +
|
||||||
'<span>센터 ' + cn + "곳 · " + bx + "상자</span>" +
|
'<span>센터 ' + cn + "곳 · " + bx + "상자 · " + qty + "개</span>" +
|
||||||
"</button>";
|
"</button>";
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
@@ -408,7 +382,7 @@
|
|||||||
function saveActive() {
|
function saveActive() {
|
||||||
if (!activeDate) return;
|
if (!activeDate) return;
|
||||||
sets[activeDate] = {
|
sets[activeDate] = {
|
||||||
items: collect(),
|
items: currentItems(),
|
||||||
calc: { results: calc.results, mixes: calc.mixes },
|
calc: { results: calc.results, mixes: calc.mixes },
|
||||||
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,
|
||||||
@@ -431,12 +405,9 @@
|
|||||||
stamped = st.stamped; allStamped = st.allStamped;
|
stamped = st.stamped; allStamped = st.allStamped;
|
||||||
mixType = st.mixType || {}; mixOwner = st.mixOwner || {};
|
mixType = st.mixType || {}; mixOwner = st.mixOwner || {};
|
||||||
|
|
||||||
tbody.innerHTML = "";
|
|
||||||
(st.items || []).forEach(function (it) { addRow(it.product_code, it.quantity); });
|
|
||||||
if (!(st.items || []).length) addRow();
|
|
||||||
|
|
||||||
render();
|
render();
|
||||||
renderTabs();
|
renderTabs();
|
||||||
|
warnLowMixes();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tabsList) {
|
if (tabsList) {
|
||||||
@@ -838,6 +809,65 @@
|
|||||||
return '<span class="cpg-stamp' + (fresh ? " is-new" : "") + '" aria-label="배분 완료">배분 완료</span>';
|
return '<span class="cpg-stamp' + (fresh ? " is-new" : "") + '" aria-label="배분 완료">배분 완료</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 자투리 혼합 상자가 이 비율 이하로 차면 더 작은 상자로 바꾸는 편이 낫다.
|
||||||
|
var LOW_FILL = 60;
|
||||||
|
|
||||||
|
function fillPercentOf(key) { return Math.round(mixFill(key) * 1000) / 10; }
|
||||||
|
function isLowMix(key) { return fillPercentOf(key) <= LOW_FILL; }
|
||||||
|
|
||||||
|
// 배분 완료 대신 찍는 경고 도장(오렌지).
|
||||||
|
function stampLowHtml(key) {
|
||||||
|
var fresh = !stamped[key];
|
||||||
|
stamped[key] = true;
|
||||||
|
return '<span class="cpg-stamp cpg-stamp-warn' + (fresh ? " is-new" : "") + '"' +
|
||||||
|
' aria-label="상자 변경 필요">상자 변경 필요</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 채움률이 낮은 상자 안내 ───────────────────────
|
||||||
|
var lowDlg = document.getElementById("cpg-low-dlg");
|
||||||
|
var lowList = document.getElementById("cpg-low-list");
|
||||||
|
var lowSeen = ""; // 같은 상태로 팝업을 반복해서 띄우지 않도록
|
||||||
|
|
||||||
|
function lowMixes() {
|
||||||
|
var out = [];
|
||||||
|
calc.mixes.forEach(function (m) {
|
||||||
|
m.boxes.forEach(function (b, i) {
|
||||||
|
var key = mixKeyFor(b);
|
||||||
|
if (!isLowMix(key)) return;
|
||||||
|
out.push({
|
||||||
|
key: key,
|
||||||
|
name: mixTypeOf(key) + " 혼합 #" + (i + 1) + (mixOwner[key] ? " · " + mixOwner[key] : ""),
|
||||||
|
fill: fillPercentOf(key),
|
||||||
|
units: units[key] || 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function warnLowMixes() {
|
||||||
|
if (!lowDlg || !lowList) return;
|
||||||
|
var rows = lowMixes();
|
||||||
|
var sign = rows.map(function (r) { return r.key + ":" + r.fill; }).join("|");
|
||||||
|
if (!rows.length) { lowSeen = ""; return; }
|
||||||
|
if (sign === lowSeen) return; // 이미 안내한 그대로면 다시 띄우지 않는다
|
||||||
|
lowSeen = sign;
|
||||||
|
lowList.innerHTML = rows.map(function (r) {
|
||||||
|
return "<li><span class=\"cpg-low-name\">" + esc(r.name) + "</span>" +
|
||||||
|
"<span class=\"cpg-low-fill\">" + r.fill + "% · " + r.units + "개</span></li>";
|
||||||
|
}).join("");
|
||||||
|
lowDlg.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowDlg) {
|
||||||
|
Array.prototype.forEach.call(lowDlg.querySelectorAll("[data-low-close]"), function (el) {
|
||||||
|
el.addEventListener("click", function () { lowDlg.hidden = true; });
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape" && !lowDlg.hidden) lowDlg.hidden = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var allStamped = false; // 전체 배분 완료 도장이 이미 찍혀 있었는지
|
var allStamped = false; // 전체 배분 완료 도장이 이미 찍혀 있었는지
|
||||||
|
|
||||||
function kpi(label, value, hint, extra) {
|
function kpi(label, value, hint, extra) {
|
||||||
@@ -930,7 +960,8 @@
|
|||||||
'<div class="cpg-box-art">' + boxSvg(b.fill_percent, mixTypeOf(key)) + "</div>" +
|
'<div class="cpg-box-art">' + boxSvg(b.fill_percent, mixTypeOf(key)) + "</div>" +
|
||||||
'<div class="cpg-box-info">' +
|
'<div class="cpg-box-info">' +
|
||||||
'<div class="cpg-box-title">' + esc(mixTypeOf(key)) + " 혼합 #" + (i + 1) +
|
'<div class="cpg-box-title">' + esc(mixTypeOf(key)) + " 혼합 #" + (i + 1) +
|
||||||
(remain ? "" : stampHtml(key)) + "</div>" +
|
(isLowMix(key) ? stampLowHtml(key)
|
||||||
|
: (remain ? "" : stampHtml(key))) + "</div>" +
|
||||||
'<div class="cpg-box-typerow">' + mixTypeSelect(key) + "</div>" +
|
'<div class="cpg-box-typerow">' + mixTypeSelect(key) + "</div>" +
|
||||||
'<ul class="cpg-box-items">' + items + "</ul>" +
|
'<ul class="cpg-box-items">' + items + "</ul>" +
|
||||||
'<div class="cpg-box-bar"><span style="width:' + b.fill_percent + '%"></span></div>' +
|
'<div class="cpg-box-bar"><span style="width:' + b.fill_percent + '%"></span></div>' +
|
||||||
@@ -1056,7 +1087,7 @@
|
|||||||
function fitScrollAreas() {
|
function fitScrollAreas() {
|
||||||
var wide = window.innerWidth >= FRAME_MIN_WIDTH;
|
var wide = window.innerWidth >= FRAME_MIN_WIDTH;
|
||||||
var panes = [
|
var panes = [
|
||||||
[".cpg-calc-card .erp-table-wrap", document.querySelector(".cpg-calc-card .erp-table-wrap")],
|
[".cpg-date-list", document.getElementById("cpg-date-list")],
|
||||||
[".cpg-sum-scroll", document.querySelector(".cpg-calc-sum .cpg-sum-scroll")]
|
[".cpg-sum-scroll", document.querySelector(".cpg-calc-sum .cpg-sum-scroll")]
|
||||||
];
|
];
|
||||||
panes.forEach(function (pair) {
|
panes.forEach(function (pair) {
|
||||||
@@ -1433,48 +1464,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 입력 표 이벤트 ────────────────────────────────
|
|
||||||
tbody.addEventListener("click", function (e) {
|
|
||||||
if (!e.target.closest(".cpg-calc-del")) return;
|
|
||||||
if (rowsArr().length <= 1) addRow("", 0);
|
|
||||||
e.target.closest("tr").remove();
|
|
||||||
msg.textContent = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
tbody.addEventListener("input", function (e) {
|
|
||||||
if (e.target.classList.contains("cpg-calc-qty")) msg.textContent = "수량 변경됨 — 재계산하세요.";
|
|
||||||
});
|
|
||||||
tbody.addEventListener("change", function (e) {
|
|
||||||
if (e.target.classList.contains("cpg-calc-name")) msg.textContent = "제품 변경됨 — 재계산하세요.";
|
|
||||||
});
|
|
||||||
|
|
||||||
// 수량 칸: Tab = 다음 행 수량(마지막 행이면 새 행 추가), Enter = 계산
|
|
||||||
tbody.addEventListener("keydown", function (e) {
|
|
||||||
if (!e.target.classList.contains("cpg-calc-qty")) return;
|
|
||||||
if (e.key === "Enter") { e.preventDefault(); run(); return; }
|
|
||||||
if (e.key !== "Tab" || e.shiftKey) return;
|
|
||||||
e.preventDefault();
|
|
||||||
var rows = rowsArr();
|
|
||||||
var idx = rows.indexOf(e.target.closest("tr"));
|
|
||||||
var next = rows[idx + 1] || addRow("", 0);
|
|
||||||
var input = next.querySelector(".cpg-calc-qty");
|
|
||||||
input.focus();
|
|
||||||
input.select();
|
|
||||||
});
|
|
||||||
|
|
||||||
runBtn.addEventListener("click", run);
|
|
||||||
addBtn.addEventListener("click", function () {
|
|
||||||
addRow("", 0).querySelector(".cpg-calc-name").focus();
|
|
||||||
});
|
|
||||||
resetBtn.addEventListener("click", function () {
|
|
||||||
tbody.innerHTML = "";
|
|
||||||
addRow("", 0);
|
|
||||||
calc = { results: [], mixes: [] };
|
|
||||||
alloc = {};
|
|
||||||
render();
|
|
||||||
msg.textContent = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── 분배 확정 ─────────────────────────────────────
|
// ── 분배 확정 ─────────────────────────────────────
|
||||||
var confirmBtn = document.getElementById("cpg-confirm");
|
var confirmBtn = document.getElementById("cpg-confirm");
|
||||||
var savingDlg = document.getElementById("cpg-saving");
|
var savingDlg = document.getElementById("cpg-saving");
|
||||||
@@ -1696,7 +1685,8 @@
|
|||||||
|
|
||||||
function snapshot() {
|
function snapshot() {
|
||||||
return {
|
return {
|
||||||
items: collect(),
|
items: currentItems(),
|
||||||
|
ship_date: activeDate || uploadShipDate || "",
|
||||||
openCenters: openCenters.slice(),
|
openCenters: openCenters.slice(),
|
||||||
alloc: alloc,
|
alloc: alloc,
|
||||||
methods: methods,
|
methods: methods,
|
||||||
@@ -1814,13 +1804,14 @@
|
|||||||
var d = (data && data.draft) || {};
|
var d = (data && data.draft) || {};
|
||||||
var snap = d.payload || {};
|
var snap = d.payload || {};
|
||||||
var items = snap.items || [];
|
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 || "";
|
draftName.value = d.title || "";
|
||||||
closeDraft();
|
closeDraft();
|
||||||
|
// 저장해 둔 출고일을 그대로 이어 쓴다(없으면 확정 시 오늘 날짜).
|
||||||
|
sets = {}; dateOrder = []; activeDate = "";
|
||||||
|
uploadShipDate = snap.ship_date || "";
|
||||||
|
if (uploadShipDate) { activeDate = uploadShipDate; dateOrder = [uploadShipDate]; }
|
||||||
// 상자 수는 서버에서 다시 계산한 뒤 분배 내역을 얹는다.
|
// 상자 수는 서버에서 다시 계산한 뒤 분배 내역을 얹는다.
|
||||||
run(snap);
|
run(snap, items);
|
||||||
})
|
})
|
||||||
.catch(function () { draftMsg.textContent = "불러오기 실패 — 다시 시도하세요."; });
|
.catch(function () { draftMsg.textContent = "불러오기 실패 — 다시 시도하세요."; });
|
||||||
}
|
}
|
||||||
@@ -1855,8 +1846,8 @@
|
|||||||
if (e.key === "Escape" && draftDlg && !draftDlg.hidden) closeDraft();
|
if (e.key === "Escape" && draftDlg && !draftDlg.hidden) closeDraft();
|
||||||
});
|
});
|
||||||
|
|
||||||
addRow("", 0);
|
|
||||||
render();
|
render();
|
||||||
|
renderTabs();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260903g" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260903h" />{% 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=20260903g" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260903h" />{% 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=20260903g" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260903h" />{% 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=20260903g" />{% endblock %}
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260903h" />{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
|
{# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
|
||||||
|
|||||||
+34
-18
@@ -905,6 +905,34 @@ body.cpg-dragging { cursor: grabbing; user-select: none; }
|
|||||||
/* 도장은 카드가 흐려져도 또렷하게 유지 */
|
/* 도장은 카드가 흐려져도 또렷하게 유지 */
|
||||||
.cpg-sum-prod.is-done .cpg-stamp, .cpg-box-card.is-done .cpg-stamp { opacity: 1; }
|
.cpg-sum-prod.is-done .cpg-stamp, .cpg-box-card.is-done .cpg-stamp { opacity: 1; }
|
||||||
|
|
||||||
|
/* 상자 변경 필요 도장 — 채움률이 낮은 혼합 상자 */
|
||||||
|
.cpg-stamp-warn {
|
||||||
|
--stamp: #e8730c;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(232, 115, 12, .25);
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
}
|
||||||
|
.cpg-box-card.is-done .cpg-stamp-warn { opacity: 1; }
|
||||||
|
|
||||||
|
/* 채움률이 낮은 상자 안내 대화상자 */
|
||||||
|
.cpg-low-box { width: min(420px, calc(100vw - 32px)); }
|
||||||
|
.cpg-low-lead { margin: 0; font-size: 13px; line-height: 1.6; word-break: keep-all; }
|
||||||
|
.cpg-low-list {
|
||||||
|
list-style: none; margin: 0; padding: 0;
|
||||||
|
border: 1px solid var(--color-subtle-ash); border-radius: 8px;
|
||||||
|
max-height: 220px; overflow-y: auto;
|
||||||
|
}
|
||||||
|
.cpg-low-list > li {
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
padding: 7px 10px; font-size: 13px;
|
||||||
|
}
|
||||||
|
.cpg-low-list > li + li { border-top: 1px solid var(--color-ghost-gray); }
|
||||||
|
.cpg-low-name { flex: 1 1 auto; min-width: 0; font-weight: 600;
|
||||||
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.cpg-low-fill {
|
||||||
|
flex: 0 0 auto; color: #e8730c; font-weight: 700;
|
||||||
|
font-family: var(--font-geist-mono); font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 전체 배분 완료 도장 (센터 배분 KPI 칸) */
|
/* 전체 배분 완료 도장 (센터 배분 KPI 칸) */
|
||||||
.cpg-kpi { position: relative; overflow: hidden; }
|
.cpg-kpi { position: relative; overflow: hidden; }
|
||||||
.cpg-stamp-all {
|
.cpg-stamp-all {
|
||||||
@@ -1175,19 +1203,12 @@ body.cpg-dragging { cursor: grabbing; user-select: none; }
|
|||||||
}
|
}
|
||||||
.cpg-po-warn > li { padding: 2px 0; }
|
.cpg-po-warn > li { padding: 2px 0; }
|
||||||
|
|
||||||
/* ── 출고일 탭 (업로드한 발주서의 출고일이 여러 개일 때) ── */
|
/* ── 출고일 목록 (① 카드 안, 업로드한 발주서 기준) ── */
|
||||||
.cpg-date-tabs {
|
.cpg-date-list { display: flex; flex-direction: column; gap: 6px; }
|
||||||
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.cpg-date-tabs[hidden] { display: none; }
|
|
||||||
.cpg-date-tabs-label {
|
|
||||||
font-size: 12px; color: var(--color-midtone-gray); font-weight: 600;
|
|
||||||
}
|
|
||||||
.cpg-date-tabs-list { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
||||||
.cpg-date-tab {
|
.cpg-date-tab {
|
||||||
display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
|
display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
|
||||||
padding: 6px 14px;
|
width: 100%; box-sizing: border-box;
|
||||||
|
padding: 8px 12px;
|
||||||
border: 1px solid var(--color-subtle-ash); border-radius: 10px;
|
border: 1px solid var(--color-subtle-ash); border-radius: 10px;
|
||||||
background: #fff; cursor: pointer; font: inherit; text-align: left;
|
background: #fff; cursor: pointer; font: inherit; text-align: left;
|
||||||
transition: background .12s, border-color .12s;
|
transition: background .12s, border-color .12s;
|
||||||
@@ -1260,13 +1281,8 @@ body.cpg-dragging { cursor: grabbing; user-select: none; }
|
|||||||
}
|
}
|
||||||
.cpg-po-row { align-items: center; }
|
.cpg-po-row { align-items: center; }
|
||||||
.cpg-po-msg { margin: 0; font-size: 12px; }
|
.cpg-po-msg { margin: 0; font-size: 12px; }
|
||||||
/* 상단 액션/출고일 탭은 줄어들지 않게 (3열 프레임만 남은 높이를 차지) */
|
/* 상단 액션은 줄어들지 않게 (3열 프레임만 남은 높이를 차지) */
|
||||||
.cpg > .erp-page-actions,
|
.cpg > .erp-page-actions { flex-shrink: 0; }
|
||||||
.cpg > .cpg-date-tabs { flex-shrink: 0; }
|
|
||||||
|
|
||||||
/* ① 직접 입력 — 수량 입력란 70px 고정 */
|
|
||||||
.cpg-calc-card .cpg-calc-table col.cpg-col-qty { width: 70px; }
|
|
||||||
.cpg-calc-card .cpg-calc-table .cpg-calc-qty { width: 70px; max-width: 70px; margin-left: auto; }
|
|
||||||
|
|
||||||
/* 출고 보기(읽기 전용) — ③ 하단 메타 정보 */
|
/* 출고 보기(읽기 전용) — ③ 하단 메타 정보 */
|
||||||
/* ── 출고 보기 ② 상자 목록 — 상자번호 · 제품명 · 제품코드 · 수량 ── */
|
/* ── 출고 보기 ② 상자 목록 — 상자번호 · 제품명 · 제품코드 · 수량 ── */
|
||||||
|
|||||||
Reference in New Issue
Block a user