|
|
@@ -1,6 +1,6 @@
|
|
|
|
{% extends "erp_base.html" %}
|
|
|
|
{% extends "erp_base.html" %}
|
|
|
|
|
|
|
|
|
|
|
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829a" />{% endblock %}
|
|
|
|
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260829b" />{% endblock %}
|
|
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
{% block content %}
|
|
|
|
<section class="cpg">
|
|
|
|
<section class="cpg">
|
|
|
@@ -8,6 +8,7 @@
|
|
|
|
<div class="erp-page-actions">
|
|
|
|
<div class="erp-page-actions">
|
|
|
|
<a class="erp-btn erp-btn-primary" href="/cupang/">◀◀ 달력</a>
|
|
|
|
<a class="erp-btn erp-btn-primary" href="/cupang/">◀◀ 달력</a>
|
|
|
|
<a class="erp-btn erp-btn-outline" href="/cupang/box-rules">박스 입수량 설정</a>
|
|
|
|
<a class="erp-btn erp-btn-outline" href="/cupang/box-rules">박스 입수량 설정</a>
|
|
|
|
|
|
|
|
<a class="erp-btn erp-btn-outline" href="/cupang/centers">입고센터 관리</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{% if not box_rules %}
|
|
|
|
{% if not box_rules %}
|
|
|
@@ -19,12 +20,13 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{% else %}
|
|
|
|
{% else %}
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cpg-calc-layout">
|
|
|
|
<div class="cpg-calc3">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ① 박스 계산 — 제품명 + 수량만 입력 -->
|
|
|
|
<div class="erp-card cpg-form-card cpg-calc-card">
|
|
|
|
<div class="erp-card cpg-form-card cpg-calc-card">
|
|
|
|
<div class="cpg-card-head">
|
|
|
|
<div class="cpg-card-head">
|
|
|
|
<h2>박스 계산</h2>
|
|
|
|
<h2>① 박스 계산</h2>
|
|
|
|
<span class="erp-muted">수량을 고치고 다시 계산하면 결과가 갱신됩니다.</span>
|
|
|
|
<span class="erp-muted">수량 칸에서 Tab = 다음 상품 수량, Enter = 계산</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="erp-table-wrap">
|
|
|
|
<div class="erp-table-wrap">
|
|
|
@@ -32,20 +34,12 @@
|
|
|
|
<colgroup>
|
|
|
|
<colgroup>
|
|
|
|
<col class="cpg-col-name" />
|
|
|
|
<col class="cpg-col-name" />
|
|
|
|
<col class="cpg-col-qty" />
|
|
|
|
<col class="cpg-col-qty" />
|
|
|
|
<col class="cpg-col-box" />
|
|
|
|
|
|
|
|
<col class="cpg-col-n" />
|
|
|
|
|
|
|
|
<col class="cpg-col-n" />
|
|
|
|
|
|
|
|
<col class="cpg-col-n" />
|
|
|
|
|
|
|
|
<col class="cpg-col-act" />
|
|
|
|
<col class="cpg-col-act" />
|
|
|
|
</colgroup>
|
|
|
|
</colgroup>
|
|
|
|
<thead>
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<tr>
|
|
|
|
<th>제품명</th>
|
|
|
|
<th>제품명</th>
|
|
|
|
<th class="cpg-calc-num">수량</th>
|
|
|
|
<th>수량</th>
|
|
|
|
<th>박스 / 입수량</th>
|
|
|
|
|
|
|
|
<th class="cpg-calc-num">박스</th>
|
|
|
|
|
|
|
|
<th class="cpg-calc-num">낱개</th>
|
|
|
|
|
|
|
|
<th class="cpg-calc-num">필요</th>
|
|
|
|
|
|
|
|
<th></th>
|
|
|
|
<th></th>
|
|
|
|
</tr>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
</thead>
|
|
|
@@ -61,27 +55,64 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="erp-card cpg-form-card cpg-calc-sum" id="cpg-calc-sum-card" hidden>
|
|
|
|
<!-- ② 박스 요약 — 센터에 배분하고 남은 수량이 실시간 반영 -->
|
|
|
|
|
|
|
|
<div class="erp-card cpg-form-card cpg-calc-sum">
|
|
|
|
<div class="cpg-card-head">
|
|
|
|
<div class="cpg-card-head">
|
|
|
|
<h2>박스 요약</h2>
|
|
|
|
<h2>② 박스 요약</h2>
|
|
|
|
<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-body" hidden>
|
|
|
|
<div class="cpg-sum-kpis" id="cpg-sum-kpis"></div>
|
|
|
|
<div class="cpg-sum-kpis" id="cpg-sum-kpis"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<h3 class="cpg-sum-h3">제품별 박스</h3>
|
|
|
|
<h3 class="cpg-sum-h3">제품별 박스 <span class="erp-muted">잔여 기준</span></h3>
|
|
|
|
<div class="cpg-sum-prods" id="cpg-sum-prods"></div>
|
|
|
|
<div class="cpg-sum-prods" id="cpg-sum-prods"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<h3 class="cpg-sum-h3">자투리 혼합 박스 <span class="erp-muted" id="cpg-sum-mix-note"></span></h3>
|
|
|
|
<h3 class="cpg-sum-h3">자투리 혼합 박스 <span class="erp-muted" id="cpg-sum-mix-note"></span></h3>
|
|
|
|
<div class="cpg-sum-boxes" id="cpg-sum-boxes"></div>
|
|
|
|
<div class="cpg-sum-boxes" id="cpg-sum-boxes"></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</div><!-- /cpg-calc-layout -->
|
|
|
|
<!-- ③ 센터 분배 — 드래그해서 담고 수량 입력 -->
|
|
|
|
|
|
|
|
<div class="erp-card cpg-form-card cpg-dist-card">
|
|
|
|
|
|
|
|
<div class="cpg-card-head">
|
|
|
|
|
|
|
|
<h2>③ 센터 분배</h2>
|
|
|
|
|
|
|
|
<span class="erp-muted">입고센터 관리에 등록한 센터만 표시됩니다.</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{% if not centers %}
|
|
|
|
|
|
|
|
<p class="erp-muted">
|
|
|
|
|
|
|
|
등록된 입고센터가 없습니다. <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>
|
|
|
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div><!-- /cpg-calc3 -->
|
|
|
|
|
|
|
|
|
|
|
|
<script type="application/json" id="cpg-calc-rules">{{ box_rules | tojson }}</script>
|
|
|
|
<script type="application/json" id="cpg-calc-rules">{{ box_rules | tojson }}</script>
|
|
|
|
<script>
|
|
|
|
<script>
|
|
|
|
// 박스 계산기 — 행 추가/삭제, 수량 수정 후 재계산.
|
|
|
|
// 박스 계산 3열 화면.
|
|
|
|
// 계산 자체는 서버(POST /cupang/api/box-calc, store.compute_boxes)에서 수행한다.
|
|
|
|
// ① 제품명·수량 입력 → 서버(POST /cupang/api/box-calc)에서 계산
|
|
|
|
|
|
|
|
// ② 계산 결과 요약(제품별 박스 / 자투리 혼합 박스) — 드래그 소스
|
|
|
|
|
|
|
|
// ③ 센터별 분배 — 드롭 대상. 배분한 수량만큼 ② 의 잔여가 줄어든다.
|
|
|
|
|
|
|
|
// 분배 내역은 화면 안에서만 유지된다(DB 저장 없음).
|
|
|
|
(function () {
|
|
|
|
(function () {
|
|
|
|
var tbody = document.getElementById("cpg-calc-rows");
|
|
|
|
var tbody = document.getElementById("cpg-calc-rows");
|
|
|
|
if (!tbody) return;
|
|
|
|
if (!tbody) return;
|
|
|
@@ -89,144 +120,168 @@
|
|
|
|
var addBtn = document.getElementById("cpg-calc-add");
|
|
|
|
var addBtn = document.getElementById("cpg-calc-add");
|
|
|
|
var resetBtn = document.getElementById("cpg-calc-reset");
|
|
|
|
var resetBtn = document.getElementById("cpg-calc-reset");
|
|
|
|
var msg = document.getElementById("cpg-calc-msg");
|
|
|
|
var msg = document.getElementById("cpg-calc-msg");
|
|
|
|
var sumCard = document.getElementById("cpg-calc-sum-card");
|
|
|
|
var sumEmpty = document.getElementById("cpg-sum-empty");
|
|
|
|
|
|
|
|
var sumBody = document.getElementById("cpg-sum-body");
|
|
|
|
var sumKpis = document.getElementById("cpg-sum-kpis");
|
|
|
|
var sumKpis = document.getElementById("cpg-sum-kpis");
|
|
|
|
var sumProds = document.getElementById("cpg-sum-prods");
|
|
|
|
var sumProds = document.getElementById("cpg-sum-prods");
|
|
|
|
var sumBoxes = document.getElementById("cpg-sum-boxes");
|
|
|
|
var sumBoxes = document.getElementById("cpg-sum-boxes");
|
|
|
|
var sumMixNote = document.getElementById("cpg-sum-mix-note");
|
|
|
|
var sumMixNote = document.getElementById("cpg-sum-mix-note");
|
|
|
|
|
|
|
|
var distList = document.getElementById("cpg-dist-list");
|
|
|
|
|
|
|
|
|
|
|
|
var rules = [];
|
|
|
|
var rules = [];
|
|
|
|
try { rules = JSON.parse(document.getElementById("cpg-calc-rules").textContent || "[]"); } catch (e) {}
|
|
|
|
try { rules = JSON.parse(document.getElementById("cpg-calc-rules").textContent || "[]"); } catch (e) {}
|
|
|
|
|
|
|
|
|
|
|
|
var optionsHtml = '<option value="">— 제품명 선택 —</option>';
|
|
|
|
|
|
|
|
rules.forEach(function (r) {
|
|
|
|
|
|
|
|
var label = r.product_name_snapshot || r.product_code;
|
|
|
|
|
|
|
|
optionsHtml += '<option value="' + esc(r.product_code) + '">' + esc(label) + "</option>";
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function esc(s) { var d = document.createElement("div"); d.textContent = s == null ? "" : s; return d.innerHTML; }
|
|
|
|
function esc(s) { var d = document.createElement("div"); d.textContent = s == null ? "" : s; return d.innerHTML; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var optionsHtml = '<option value="">— 제품명 선택 —</option>';
|
|
|
|
|
|
|
|
rules.forEach(function (r) {
|
|
|
|
|
|
|
|
optionsHtml += '<option value="' + esc(r.product_code) + '">' +
|
|
|
|
|
|
|
|
esc(r.product_name_snapshot || r.product_code) + "</option>";
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 계산 결과 + 센터 배분 상태
|
|
|
|
|
|
|
|
var calc = { results: [], mixes: [] };
|
|
|
|
|
|
|
|
var alloc = {}; // centerId -> [{id, key, kind, label, count}]
|
|
|
|
|
|
|
|
var seq = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── ① 입력 표 ─────────────────────────────────────
|
|
|
|
function addRow(code, qty) {
|
|
|
|
function addRow(code, qty) {
|
|
|
|
var tr = document.createElement("tr");
|
|
|
|
var tr = document.createElement("tr");
|
|
|
|
tr.className = "cpg-calc-row";
|
|
|
|
tr.className = "cpg-calc-row";
|
|
|
|
tr.innerHTML =
|
|
|
|
tr.innerHTML =
|
|
|
|
'<td><select class="erp-select cpg-calc-name">' + optionsHtml + "</select></td>" +
|
|
|
|
'<td><select class="erp-select cpg-calc-name">' + optionsHtml + "</select></td>" +
|
|
|
|
'<td class="cpg-calc-num"><input class="erp-input cpg-calc-qty" type="number" min="0" step="1" value="' + (qty || 0) + '" /></td>' +
|
|
|
|
'<td><input class="erp-input cpg-calc-qty" type="number" min="0" step="1" value="' + (qty || 0) + '" /></td>' +
|
|
|
|
'<td class="cpg-calc-box">—</td>' +
|
|
|
|
|
|
|
|
'<td class="cpg-calc-num cpg-calc-boxes">—</td>' +
|
|
|
|
|
|
|
|
'<td class="cpg-calc-num cpg-calc-rem">—</td>' +
|
|
|
|
|
|
|
|
'<td class="cpg-calc-num cpg-calc-req">—</td>' +
|
|
|
|
|
|
|
|
'<td class="cpg-calc-act"><button type="button" class="erp-btn erp-btn-danger cpg-calc-del" title="행 삭제" aria-label="행 삭제">✕</button></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);
|
|
|
|
tbody.appendChild(tr);
|
|
|
|
if (code) tr.querySelector(".cpg-calc-name").value = code;
|
|
|
|
if (code) tr.querySelector(".cpg-calc-name").value = code;
|
|
|
|
return tr;
|
|
|
|
return tr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearResults() {
|
|
|
|
function rowsArr() {
|
|
|
|
Array.prototype.forEach.call(tbody.querySelectorAll(".cpg-calc-row"), function (tr) {
|
|
|
|
return Array.prototype.slice.call(tbody.querySelectorAll(".cpg-calc-row"));
|
|
|
|
tr.querySelector(".cpg-calc-box").textContent = "—";
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-boxes").textContent = "—";
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-rem").textContent = "—";
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-req").textContent = "—";
|
|
|
|
|
|
|
|
tr.classList.remove("cpg-calc-warn");
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
sumCard.hidden = true;
|
|
|
|
|
|
|
|
sumKpis.innerHTML = "";
|
|
|
|
|
|
|
|
sumProds.innerHTML = "";
|
|
|
|
|
|
|
|
sumBoxes.innerHTML = "";
|
|
|
|
|
|
|
|
sumMixNote.textContent = "";
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function collect() {
|
|
|
|
function collect() {
|
|
|
|
var items = [];
|
|
|
|
var items = [];
|
|
|
|
Array.prototype.forEach.call(tbody.querySelectorAll(".cpg-calc-row"), function (tr) {
|
|
|
|
rowsArr().forEach(function (tr) {
|
|
|
|
var code = tr.querySelector(".cpg-calc-name").value;
|
|
|
|
var code = tr.querySelector(".cpg-calc-name").value;
|
|
|
|
if (!code) return;
|
|
|
|
if (!code) return;
|
|
|
|
items.push({ product_code: code, quantity: parseInt(tr.querySelector(".cpg-calc-qty").value, 10) || 0, _tr: tr });
|
|
|
|
items.push({ product_code: code, quantity: parseInt(tr.querySelector(".cpg-calc-qty").value, 10) || 0 });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return items;
|
|
|
|
return items;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function run() {
|
|
|
|
function run() {
|
|
|
|
var items = collect();
|
|
|
|
var items = collect();
|
|
|
|
if (!items.length) { clearResults(); msg.textContent = "제품명을 하나 이상 선택하세요."; return; }
|
|
|
|
if (!items.length) { msg.textContent = "제품명을 하나 이상 선택하세요."; return; }
|
|
|
|
msg.textContent = "계산 중…";
|
|
|
|
msg.textContent = "계산 중…";
|
|
|
|
runBtn.disabled = true;
|
|
|
|
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.map(function (it) { return { product_code: it.product_code, quantity: it.quantity }; }) })
|
|
|
|
body: JSON.stringify({ items: items })
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.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) {
|
|
|
|
var results = (data && data.results) || [];
|
|
|
|
calc.results = ((data && data.results) || []).filter(function (r) { return r.configured; });
|
|
|
|
results.forEach(function (res, i) {
|
|
|
|
calc.mixes = (data && data.mixes) || [];
|
|
|
|
var tr = items[i] && items[i]._tr;
|
|
|
|
var had = Object.keys(alloc).some(function (k) { return alloc[k].length; });
|
|
|
|
if (!tr) return;
|
|
|
|
alloc = {}; // 계산이 바뀌면 이전 배분은 근거가 사라지므로 비운다.
|
|
|
|
tr.querySelector(".cpg-calc-box").textContent = res.units_per_box
|
|
|
|
render();
|
|
|
|
? (res.box_name || "-") + " · " + res.units_per_box + "개"
|
|
|
|
msg.textContent = "계산 완료 (" + calc.results.length + "건)" + (had ? " — 센터 분배 초기화됨" : "");
|
|
|
|
: "미설정";
|
|
|
|
|
|
|
|
if (res.configured) {
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-boxes").textContent = res.full_boxes;
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-rem").textContent = res.remainder_units;
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-req").textContent = res.required_boxes;
|
|
|
|
|
|
|
|
tr.classList.remove("cpg-calc-warn");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-boxes").textContent = "—";
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-rem").textContent = "—";
|
|
|
|
|
|
|
|
tr.querySelector(".cpg-calc-req").textContent = "—";
|
|
|
|
|
|
|
|
tr.classList.add("cpg-calc-warn");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
renderSummary(results, (data && data.mixes) || [], (data && data.grand_total_boxes) || 0);
|
|
|
|
|
|
|
|
msg.textContent = "계산 완료 (" + results.length + "건)";
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.catch(function () { msg.textContent = "계산 실패 — 새로고침 후 다시 시도하세요."; })
|
|
|
|
.catch(function () { msg.textContent = "계산 실패 — 새로고침 후 다시 시도하세요."; })
|
|
|
|
.then(function () { runBtn.disabled = false; });
|
|
|
|
.then(function () { runBtn.disabled = false; });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── 요약 렌더 ─────────────────────────────────────────
|
|
|
|
// ── 배분 집계 ─────────────────────────────────────
|
|
|
|
// 상자 그림: fill(0~100) 만큼 안쪽이 차오르고, 뚜껑이 살짝 열린 채 표시.
|
|
|
|
function allocatedFor(key) {
|
|
|
|
|
|
|
|
var n = 0;
|
|
|
|
|
|
|
|
Object.keys(alloc).forEach(function (cid) {
|
|
|
|
|
|
|
|
alloc[cid].forEach(function (a) { if (a.key === key) n += a.count; });
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
var hit = calc.results.filter(function (r) { return r.product_code === code; })[0];
|
|
|
|
|
|
|
|
return hit ? hit.full_boxes : 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1; // 혼합 박스는 1장 = 1박스
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function remainFor(key) { return Math.max(0, totalFor(key) - allocatedFor(key)); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── ② 요약 렌더 ───────────────────────────────────
|
|
|
|
function boxSvg(fill, label) {
|
|
|
|
function boxSvg(fill, label) {
|
|
|
|
var f = Math.max(0, Math.min(100, fill || 0));
|
|
|
|
var f = Math.max(0, Math.min(100, fill || 0));
|
|
|
|
var h = 40 * f / 100;
|
|
|
|
var h = 40 * f / 100;
|
|
|
|
return '' +
|
|
|
|
return '' +
|
|
|
|
'<svg class="cpg-box-svg" viewBox="0 0 72 64" role="img" aria-label="' + esc(label) + ' 박스">' +
|
|
|
|
'<svg class="cpg-box-svg" viewBox="0 0 72 64" role="img" aria-label="' + esc(label) + '">' +
|
|
|
|
'<rect class="cpg-box-fill" x="10" y="' + (54 - h) + '" width="52" height="' + h + '" rx="2" />' +
|
|
|
|
'<rect class="cpg-box-fill" x="10" y="' + (54 - h) + '" width="52" height="' + h + '" rx="2" />' +
|
|
|
|
'<path class="cpg-box-body" d="M10 20 H62 V54 H10 Z" />' +
|
|
|
|
'<path class="cpg-box-body" d="M10 20 H62 V54 H10 Z" />' +
|
|
|
|
'<path class="cpg-box-flapL" d="M10 20 L2 8 H30 L36 20 Z" />' +
|
|
|
|
'<path class="cpg-box-flapL" d="M10 20 L2 8 H30 L36 20 Z" />' +
|
|
|
|
'<path class="cpg-box-flapR" d="M62 20 L70 8 H42 L36 20 Z" />' +
|
|
|
|
'<path class="cpg-box-flapR" d="M62 20 L70 8 H42 L36 20 Z" />' +
|
|
|
|
'<line class="cpg-box-seam" x1="36" y1="20" x2="36" y2="54" />' +
|
|
|
|
'<line class="cpg-box-seam" x1="36" y1="20" x2="36" y2="54" />' +
|
|
|
|
'<text class="cpg-box-label" x="36" y="42" text-anchor="middle">' + esc(label) + "</text>" +
|
|
|
|
'<text class="cpg-box-label" x="36" y="42" text-anchor="middle">쿠팡박스</text>' +
|
|
|
|
"</svg>";
|
|
|
|
"</svg>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function kpi(label, value, hint) {
|
|
|
|
|
|
|
|
return '<div class="cpg-kpi"><span class="cpg-kpi-label">' + esc(label) + "</span>" +
|
|
|
|
|
|
|
|
'<strong class="cpg-kpi-value">' + esc(value) + "</strong>" +
|
|
|
|
|
|
|
|
'<span class="cpg-kpi-hint">' + esc(hint) + "</span></div>";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderSummary() {
|
|
|
|
|
|
|
|
if (!calc.results.length) {
|
|
|
|
|
|
|
|
sumEmpty.hidden = false;
|
|
|
|
|
|
|
|
sumBody.hidden = true;
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sumEmpty.hidden = true;
|
|
|
|
|
|
|
|
sumBody.hidden = false;
|
|
|
|
|
|
|
|
|
|
|
|
function renderSummary(results, mixes, grandTotal) {
|
|
|
|
var totalProd = 0, remainProd = 0, leftoverUnits = 0;
|
|
|
|
var ok = results.filter(function (r) { return r.configured; });
|
|
|
|
calc.results.forEach(function (r) {
|
|
|
|
if (!ok.length) { sumCard.hidden = true; return; }
|
|
|
|
totalProd += r.full_boxes;
|
|
|
|
|
|
|
|
remainProd += remainFor(prodKey(r.product_code));
|
|
|
|
var fullSum = 0, mixSum = 0, leftoverSum = 0;
|
|
|
|
leftoverUnits += r.remainder_units;
|
|
|
|
ok.forEach(function (r) { fullSum += r.full_boxes; leftoverSum += r.remainder_units; });
|
|
|
|
});
|
|
|
|
mixes.forEach(function (m) { mixSum += m.box_count; });
|
|
|
|
var totalMix = 0, remainMix = 0;
|
|
|
|
|
|
|
|
calc.mixes.forEach(function (m) {
|
|
|
|
|
|
|
|
m.boxes.forEach(function (b, i) {
|
|
|
|
|
|
|
|
totalMix += 1;
|
|
|
|
|
|
|
|
remainMix += remainFor(mixKey(m.box_name, i));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
var assigned = (totalProd + totalMix) - (remainProd + remainMix);
|
|
|
|
|
|
|
|
|
|
|
|
sumKpis.innerHTML =
|
|
|
|
sumKpis.innerHTML =
|
|
|
|
kpi("총 박스", (grandTotal || (fullSum + mixSum)) + "박스", "제품별 박스 + 혼합 박스") +
|
|
|
|
kpi("총 박스", (totalProd + totalMix) + "박스", "제품별 " + totalProd + " + 혼합 " + totalMix) +
|
|
|
|
kpi("제품별 박스", fullSum + "박스", "한 제품만 가득 채운 박스") +
|
|
|
|
kpi("센터 배분", assigned + "박스", "③ 에 담긴 수량") +
|
|
|
|
kpi("혼합 박스", mixSum + "박스", "자투리 " + leftoverSum + "개를 모아 담음");
|
|
|
|
kpi("미배분 잔여", (remainProd + remainMix) + "박스", "자투리 " + leftoverUnits + "개 포함");
|
|
|
|
|
|
|
|
|
|
|
|
// 제품별
|
|
|
|
|
|
|
|
var ph = "";
|
|
|
|
var ph = "";
|
|
|
|
ok.forEach(function (r) {
|
|
|
|
calc.results.forEach(function (r) {
|
|
|
|
ph += '<div class="cpg-sum-prod">' +
|
|
|
|
var key = prodKey(r.product_code);
|
|
|
|
|
|
|
|
var remain = remainFor(key);
|
|
|
|
|
|
|
|
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) + '">' +
|
|
|
|
'<div class="cpg-sum-prod-head">' +
|
|
|
|
'<div class="cpg-sum-prod-head">' +
|
|
|
|
'<span class="cpg-sum-prod-name">' + esc(r.product_name) + "</span>" +
|
|
|
|
'<span class="cpg-sum-prod-name">' + esc(r.product_name) + "</span>" +
|
|
|
|
'<span class="erp-badge erp-badge-neutral">' + esc(r.box_name) + "</span>" +
|
|
|
|
'<span class="erp-badge erp-badge-neutral">' + esc(r.box_name) + "</span>" +
|
|
|
|
"</div>" +
|
|
|
|
"</div>" +
|
|
|
|
'<div class="cpg-sum-prod-nums">' +
|
|
|
|
'<div class="cpg-sum-prod-nums">' +
|
|
|
|
'<strong>' + r.full_boxes + "박스</strong>" +
|
|
|
|
"<strong>" + remain + "박스</strong>" +
|
|
|
|
'<span class="erp-muted">' + r.quantity + "개 ÷ " + r.units_per_box + "개</span>" +
|
|
|
|
'<span class="erp-muted">총 ' + r.full_boxes + "박스" + (used ? " · 배분 " + used : "") + "</span>" +
|
|
|
|
(r.remainder_units
|
|
|
|
(r.remainder_units
|
|
|
|
? '<span class="cpg-sum-left">자투리 ' + r.remainder_units + "개</span>"
|
|
|
|
? '<span class="cpg-sum-left">자투리 ' + r.remainder_units + "개</span>"
|
|
|
|
: '<span class="cpg-sum-exact">딱 맞음</span>') +
|
|
|
|
: '<span class="cpg-sum-exact">딱 맞음</span>') +
|
|
|
@@ -235,22 +290,28 @@
|
|
|
|
});
|
|
|
|
});
|
|
|
|
sumProds.innerHTML = ph;
|
|
|
|
sumProds.innerHTML = ph;
|
|
|
|
|
|
|
|
|
|
|
|
// 자투리 혼합 박스
|
|
|
|
if (!calc.mixes.length) {
|
|
|
|
if (!mixes.length) {
|
|
|
|
|
|
|
|
sumMixNote.textContent = "— 자투리 없음";
|
|
|
|
sumMixNote.textContent = "— 자투리 없음";
|
|
|
|
sumBoxes.innerHTML = '<p class="erp-muted">남는 낱개가 없습니다. 모든 박스가 딱 맞습니다.</p>';
|
|
|
|
sumBoxes.innerHTML = '<p class="erp-muted">남는 낱개가 없습니다.</p>';
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
sumMixNote.textContent = "— 같은 박스명끼리만 섞습니다";
|
|
|
|
sumMixNote.textContent = "— 1장 = 1박스";
|
|
|
|
var bh = "", n = 0;
|
|
|
|
var bh = "", n = 0;
|
|
|
|
mixes.forEach(function (m) {
|
|
|
|
calc.mixes.forEach(function (m) {
|
|
|
|
m.boxes.forEach(function (b, i) {
|
|
|
|
m.boxes.forEach(function (b, i) {
|
|
|
|
|
|
|
|
var key = mixKey(m.box_name, i);
|
|
|
|
|
|
|
|
var remain = remainFor(key);
|
|
|
|
var items = b.items.map(function (it) {
|
|
|
|
var items = b.items.map(function (it) {
|
|
|
|
return '<li><span>' + esc(it.product_name) + "</span><b>" + it.quantity + "개</b></li>";
|
|
|
|
return "<li><span>" + esc(it.product_name) + "</span><b>" + it.quantity + "개</b></li>";
|
|
|
|
}).join("");
|
|
|
|
}).join("");
|
|
|
|
bh += '<div class="cpg-box-card" style="animation-delay:' + (n * 70) + 'ms">' +
|
|
|
|
bh += '<div class="cpg-box-card' + (remain ? "" : " is-done") + '"' +
|
|
|
|
'<div class="cpg-box-art">' + boxSvg(b.fill_percent, "쿠팡박스") + "</div>" +
|
|
|
|
(remain ? ' draggable="true"' : "") +
|
|
|
|
|
|
|
|
' data-key="' + esc(key) + '" data-kind="mix"' +
|
|
|
|
|
|
|
|
' data-label="' + esc(m.box_name + " 혼합 #" + (i + 1)) + '"' +
|
|
|
|
|
|
|
|
' style="animation-delay:' + (n * 60) + 'ms">' +
|
|
|
|
|
|
|
|
'<div class="cpg-box-art">' + boxSvg(b.fill_percent, m.box_name) + "</div>" +
|
|
|
|
'<div class="cpg-box-info">' +
|
|
|
|
'<div class="cpg-box-info">' +
|
|
|
|
'<div class="cpg-box-title">' + esc(m.box_name) + " 혼합 #" + (i + 1) + "</div>" +
|
|
|
|
'<div class="cpg-box-title">' + esc(m.box_name) + " 혼합 #" + (i + 1) +
|
|
|
|
|
|
|
|
(remain ? "" : ' <span class="erp-badge erp-badge-success">배분됨</span>') + "</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>' +
|
|
|
|
'<div class="cpg-box-fillnum">' + b.fill_percent + "% 채움</div>" +
|
|
|
|
'<div class="cpg-box-fillnum">' + b.fill_percent + "% 채움</div>" +
|
|
|
@@ -261,45 +322,159 @@
|
|
|
|
});
|
|
|
|
});
|
|
|
|
sumBoxes.innerHTML = bh;
|
|
|
|
sumBoxes.innerHTML = bh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sumCard.hidden = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function kpi(label, value, hint) {
|
|
|
|
// ── ③ 센터 분배 렌더 ──────────────────────────────
|
|
|
|
return '<div class="cpg-kpi"><span class="cpg-kpi-label">' + esc(label) + "</span>" +
|
|
|
|
function renderDist() {
|
|
|
|
'<strong class="cpg-kpi-value">' + esc(value) + "</strong>" +
|
|
|
|
if (!distList) return;
|
|
|
|
'<span class="cpg-kpi-hint">' + esc(hint) + "</span></div>";
|
|
|
|
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]");
|
|
|
|
|
|
|
|
var rows = alloc[cid] || [];
|
|
|
|
|
|
|
|
var total = 0;
|
|
|
|
|
|
|
|
var html = "";
|
|
|
|
|
|
|
|
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>";
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
list.innerHTML = html;
|
|
|
|
|
|
|
|
box.classList.toggle("has-items", rows.length > 0);
|
|
|
|
|
|
|
|
totalEl.textContent = total + "박스";
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function render() { renderSummary(); renderDist(); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── 드래그 앤 드롭 ────────────────────────────────
|
|
|
|
|
|
|
|
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.effectAllowed = "copy";
|
|
|
|
|
|
|
|
card.classList.add("is-dragging");
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
sumBody.addEventListener("dragend", function (e) {
|
|
|
|
|
|
|
|
var card = e.target.closest("[data-key]");
|
|
|
|
|
|
|
|
if (card) card.classList.remove("is-dragging");
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (distList) {
|
|
|
|
|
|
|
|
distList.addEventListener("dragover", function (e) {
|
|
|
|
|
|
|
|
var zone = e.target.closest("[data-drop-for]");
|
|
|
|
|
|
|
|
if (!zone) return;
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
e.dataTransfer.dropEffect = "copy";
|
|
|
|
|
|
|
|
zone.classList.add("is-over");
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
distList.addEventListener("dragleave", function (e) {
|
|
|
|
|
|
|
|
var zone = e.target.closest("[data-drop-for]");
|
|
|
|
|
|
|
|
if (zone && !zone.contains(e.relatedTarget)) zone.classList.remove("is-over");
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
distList.addEventListener("drop", function (e) {
|
|
|
|
|
|
|
|
var zone = e.target.closest("[data-drop-for]");
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 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);
|
|
|
|
|
|
|
|
if (isNaN(want) || want < 1) want = 1;
|
|
|
|
|
|
|
|
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 + "박스";
|
|
|
|
|
|
|
|
renderSummary();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── 입력 표 이벤트 ────────────────────────────────
|
|
|
|
tbody.addEventListener("click", function (e) {
|
|
|
|
tbody.addEventListener("click", function (e) {
|
|
|
|
var del = e.target.closest(".cpg-calc-del");
|
|
|
|
if (!e.target.closest(".cpg-calc-del")) return;
|
|
|
|
if (!del) return;
|
|
|
|
if (rowsArr().length <= 1) addRow("", 0);
|
|
|
|
var rows = tbody.querySelectorAll(".cpg-calc-row");
|
|
|
|
e.target.closest("tr").remove();
|
|
|
|
if (rows.length <= 1) { addRow("", 0); }
|
|
|
|
|
|
|
|
del.closest("tr").remove();
|
|
|
|
|
|
|
|
msg.textContent = "";
|
|
|
|
msg.textContent = "";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 수량/제품을 바꾸면 이전 결과는 지운다 — 낡은 숫자 오독 방지.
|
|
|
|
|
|
|
|
tbody.addEventListener("input", function (e) {
|
|
|
|
tbody.addEventListener("input", function (e) {
|
|
|
|
if (e.target.classList.contains("cpg-calc-qty")) { msg.textContent = "수량 변경됨 — 재계산하세요."; }
|
|
|
|
if (e.target.classList.contains("cpg-calc-qty")) msg.textContent = "수량 변경됨 — 재계산하세요.";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
tbody.addEventListener("change", function (e) {
|
|
|
|
tbody.addEventListener("change", function (e) {
|
|
|
|
if (e.target.classList.contains("cpg-calc-name")) { msg.textContent = "제품 변경됨 — 재계산하세요."; }
|
|
|
|
if (e.target.classList.contains("cpg-calc-name")) msg.textContent = "제품 변경됨 — 재계산하세요.";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 수량 칸: Tab = 다음 행 수량(마지막 행이면 새 행 추가), Enter = 계산
|
|
|
|
tbody.addEventListener("keydown", function (e) {
|
|
|
|
tbody.addEventListener("keydown", function (e) {
|
|
|
|
if (e.key === "Enter" && e.target.classList.contains("cpg-calc-qty")) { e.preventDefault(); run(); }
|
|
|
|
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);
|
|
|
|
runBtn.addEventListener("click", run);
|
|
|
|
addBtn.addEventListener("click", function () { addRow("", 0); });
|
|
|
|
addBtn.addEventListener("click", function () {
|
|
|
|
|
|
|
|
addRow("", 0).querySelector(".cpg-calc-name").focus();
|
|
|
|
|
|
|
|
});
|
|
|
|
resetBtn.addEventListener("click", function () {
|
|
|
|
resetBtn.addEventListener("click", function () {
|
|
|
|
tbody.innerHTML = "";
|
|
|
|
tbody.innerHTML = "";
|
|
|
|
addRow("", 0);
|
|
|
|
addRow("", 0);
|
|
|
|
clearResults();
|
|
|
|
calc = { results: [], mixes: [] };
|
|
|
|
|
|
|
|
alloc = {};
|
|
|
|
|
|
|
|
render();
|
|
|
|
msg.textContent = "";
|
|
|
|
msg.textContent = "";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
addRow("", 0);
|
|
|
|
addRow("", 0);
|
|
|
|
|
|
|
|
render();
|
|
|
|
})();
|
|
|
|
})();
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
{% endif %}
|
|
|
|
{% endif %}
|
|
|
|