change(cupang): 화면 문구 "박스" → "상자" 통일

템플릿·라우터 메시지·페이지 제목과 seed/DDL 기본값(쿠팡박스→쿠팡상자)까지
표기를 맞췄다. URL(/box-calc)과 코드 식별자(box_name 등)는 그대로.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 16:00:09 +09:00
parent 1c1ca69feb
commit 4a260cb161
14 changed files with 140 additions and 140 deletions
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901n" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901o" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -16,8 +16,8 @@
{% if not box_rules %}
<div class="erp-card cpg-form-card">
<p class="erp-muted">
등록된 박스 입수량 규칙이 없습니다.
<a href="/cupang/box-rules">박스 입수량 설정</a>에서 먼저 제품별 입수량을 등록하세요.
등록된 상자 입수량 규칙이 없습니다.
<a href="/cupang/box-rules">상자 입수량 설정</a>에서 먼저 제품별 입수량을 등록하세요.
</p>
</div>
{% else %}
@@ -30,7 +30,7 @@
<div class="cpg-calc3">
<!--박스 계산 — 제품명 + 수량만 입력 -->
<!--상자 계산 — 제품명 + 수량만 입력 -->
<div class="erp-card cpg-form-card cpg-calc-card">
<div class="cpg-card-head">
<h2>① 쿠팡 발주 업로드</h2>
@@ -75,10 +75,10 @@
</div>
</div>
<!--박스 요약 — 센터에 배분하고 남은 수량이 실시간 반영 -->
<!--상자 요약 — 센터에 배분하고 남은 수량이 실시간 반영 -->
<div class="erp-card cpg-form-card cpg-calc-sum">
<div class="cpg-card-head">
<h2>박스 요약</h2>
<h2>상자 요약</h2>
<span class="erp-muted">카드를 ③ 센터로 끌어다 놓거나, 카드를 클릭해 담습니다.</span>
</div>
@@ -88,10 +88,10 @@
<div class="cpg-sum-kpis" id="cpg-sum-kpis"></div>
<div class="cpg-sum-scroll">
<h3 class="cpg-sum-h3">제품별 박스 <span class="erp-muted">잔여 기준</span></h3>
<h3 class="cpg-sum-h3">제품별 상자 <span class="erp-muted">잔여 기준</span></h3>
<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>
</div>
@@ -103,7 +103,7 @@
<h2>③ 센터 분배</h2>
<span class="erp-muted">담을 센터를 골라 추가하세요.</span>
<button type="button" class="erp-btn erp-btn-primary cpg-confirm-btn" id="cpg-confirm" disabled
title="모든 박스를 배분하고 센터마다 출고방식을 고르면 활성화됩니다.">분배 확정</button>
title="모든 상자를 배분하고 센터마다 출고방식을 고르면 활성화됩니다.">분배 확정</button>
</div>
{% if not centers %}
@@ -118,7 +118,7 @@
</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">
@@ -126,7 +126,7 @@
<p class="cpg-dlg-item" id="cpg-dlg-item"></p>
<div class="erp-field"><span>센터</span>
<div class="cpg-dlg-centers" id="cpg-dlg-centers" role="group" aria-label="담을 센터"></div></div>
<label class="erp-field"><span>박스 수량</span>
<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">
@@ -195,7 +195,7 @@
<script type="application/json" id="cpg-calc-rules">{{ box_rules | tojson }}</script>
<script type="application/json" id="cpg-centers">{{ centers | tojson }}</script>
<script>
// 박스 계산 3열 화면.
// 상자 계산 3열 화면.
// ① 제품명·수량 입력 → 서버(POST /cupang/api/box-calc)에서 계산
// ② 계산 결과 요약 — 드래그(또는 클릭) 소스
// ③ 선택한 센터에만 분배. 담은 수량만큼 ② 의 잔여가 즉시 줄어든다.
@@ -243,8 +243,8 @@
// ── 상태 ─────────────────────────────────────────
var calc = { results: [], mixes: [] }; // 서버 계산 결과
var units = {}; // key -> 박스 1개당 상품 수
var contents = {}; // key -> 혼합 박스 내용물 [{product_name, quantity}]
var units = {}; // key -> 상자 1개당 상품 수
var contents = {}; // key -> 혼합 상자 내용물 [{product_name, quantity}]
var labels = {}; // key -> 표시 이름
var openCenters = []; // ③ 에 추가한 센터 id (문자열)
var alloc = {}; // centerId -> [{id, key, count}]
@@ -255,7 +255,7 @@
function prodKey(code) { return "p:" + code; }
function mixKey(boxName, idx) { return "m:" + boxName + "#" + idx; }
// 자투리 혼합 박스에 쓸 상자 종류 — 요약 카드에서 고른다(표시용).
// 자투리 혼합 상자에 쓸 상자 종류 — 요약 카드에서 고른다(표시용).
var MIX_TYPES = ["쿠팡상자", "3호상자", "6호상자"];
var mixType = {}; // mixKey -> 고른 상자 종류
var mixOwner = {}; // mixKey -> 센터명(업로드로 만들어진 경우)
@@ -360,8 +360,8 @@
}
// ── 쿠팡 발주 엑셀 업로드 ─────────────────────────
// 업로드 → 센터별 수량 합산(서버) → 센터마다 박스 계산 → ②③ 자동 채움.
// 박스는 센터별로 포장하므로 계산도 센터 단위로 한다(자투리 혼합 박스 포함).
// 업로드 → 센터별 수량 합산(서버) → 센터마다 상자 계산 → ②③ 자동 채움.
// 상자는 센터별로 포장하므로 계산도 센터 단위로 한다(자투리 혼합 상자 포함).
var poFile = document.getElementById("cpg-po-file");
var poMsg = document.getElementById("cpg-po-msg");
var poResult = document.getElementById("cpg-po-result");
@@ -412,7 +412,7 @@
return '<button type="button" class="cpg-date-tab' + (d === activeDate ? " is-active" : "") + '"' +
' data-date="' + esc(d) + '">' +
'<strong>' + esc(d || "날짜 미확인") + "</strong>" +
'<span>센터 ' + cn + "곳 · " + bx + "박스</span>" +
'<span>센터 ' + cn + "곳 · " + bx + "상자</span>" +
"</button>";
}).join("");
}
@@ -464,8 +464,8 @@
});
}
// 센터별 계산 결과를 하나의 ② 요약으로 합치고, 각 박스를 그 센터에 자동 배분한다.
// (한 출고일 안에서만 합친다 — 박스는 센터 단위로 포장)
// 센터별 계산 결과를 하나의 ② 요약으로 합치고, 각 상자를 그 센터에 자동 배분한다.
// (한 출고일 안에서만 합친다 — 상자는 센터 단위로 포장)
function buildDataset(groups, perCenter) {
var mergedProds = {}; // code -> 합산 결과
var mergedMixes = {}; // box_name -> {box_name, boxes:[]}
@@ -603,7 +603,7 @@
return;
}
// 날짜 × 센터 단위로 박스 계산 (박스는 센터별로 포장되므로)
// 날짜 × 센터 단위로 상자 계산 (상자는 센터별로 포장되므로)
var jobs = [];
dateGroups.forEach(function (g) {
g.centers.forEach(function (c) {
@@ -663,7 +663,7 @@
var hit = calc.results.filter(function (r) { return r.product_code === code; })[0];
return hit ? hit.full_boxes : 0;
}
return 1; // 혼합 박스는 1장 = 1박스
return 1; // 혼합 상자는 1장 = 1상자
}
function remainFor(key) { return Math.max(0, totalFor(key) - allocatedFor(key)); }
@@ -736,7 +736,7 @@
});
var assigned = (totalProd + totalMix) - (remainProd + remainMix);
// 모든 박스를 센터에 배분하면 "센터 배분" 칸에 전체 완료 도장을 찍는다.
// 모든 상자를 센터에 배분하면 "센터 배분" 칸에 전체 완료 도장을 찍는다.
var totalBoxes = totalProd + totalMix;
var allDone = totalBoxes > 0 && (remainProd + remainMix) === 0;
var allStamp = "";
@@ -747,9 +747,9 @@
allStamped = allDone;
sumKpis.innerHTML =
kpi("총 박스", totalBoxes + "박스", "제품별 " + totalProd + " + 혼합 " + totalMix) +
kpi("센터 배분", assigned + "박스", "③ 에 담긴 수량", allStamp) +
kpi("미배분 잔여", (remainProd + remainMix) + "박스", "자투리 " + leftoverUnits + "개 포함");
kpi("총 상자", totalBoxes + "상자", "제품별 " + totalProd + " + 혼합 " + totalMix) +
kpi("센터 배분", assigned + "상자", "③ 에 담긴 수량", allStamp) +
kpi("미배분 잔여", (remainProd + remainMix) + "상자", "자투리 " + leftoverUnits + "개 포함");
var ph = "";
calc.results.forEach(function (r) {
@@ -764,8 +764,8 @@
(remain ? "" : stampHtml(key)) +
"</div>" +
'<div class="cpg-sum-prod-nums">' +
"<strong>" + remain + "박스</strong>" +
'<span class="erp-muted">' + (remain * r.units_per_box) + "개 · 총 " + r.full_boxes + "박스" +
"<strong>" + remain + "상자</strong>" +
'<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>"
@@ -779,7 +779,7 @@
sumMixNote.textContent = "— 자투리 없음";
sumBoxes.innerHTML = '<p class="erp-muted">남는 낱개가 없습니다.</p>';
} else {
sumMixNote.textContent = "— 1장 = 1박스";
sumMixNote.textContent = "— 1장 = 1상자";
var bh = "", n = 0;
calc.mixes.forEach(function (m) {
m.boxes.forEach(function (b, i) {
@@ -809,14 +809,14 @@
}
}
// 혼합 박스는 안에 무엇이 몇 개 들어가는지 트리로 펼쳐 보여준다.
// 혼합 상자는 안에 무엇이 몇 개 들어가는지 트리로 펼쳐 보여준다.
function treeHtml(entry) {
var list = contents[entry.key];
if (!list || !list.length) return "";
return '<ul class="cpg-dist-tree">' + list.map(function (it) {
return '<li><span class="cpg-tree-name">' + esc(it.product_name) + "</span>" +
'<span class="cpg-tree-qty">' + (it.quantity * entry.count) + "개</span>" +
(entry.count > 1 ? '<span class="cpg-tree-per">(' + it.quantity + "개 × " + entry.count + "박스)</span>" : "") +
(entry.count > 1 ? '<span class="cpg-tree-per">(' + it.quantity + "개 × " + entry.count + "상자)</span>" : "") +
"</li>";
}).join("") + "</ul>";
}
@@ -838,7 +838,7 @@
'<div class="cpg-dist-line">' +
'<span class="cpg-dist-name">' + esc(labels[a.key] || a.key) + "</span>" +
'<input class="erp-input cpg-dist-qty" type="number" min="1" max="999" step="1" inputmode="numeric" value="' + a.count + '" />' +
'<span class="cpg-dist-unit">박스 · ' + (a.count * per) + "개</span>" +
'<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>' +
"</div>" +
treeHtml(a) +
@@ -855,14 +855,14 @@
'<option value="파렛트"' + (method === "파렛트" ? " selected" : "") + '>파렛트</option>' +
"</select>" +
'<span class="cpg-dist-sum">' +
'<span class="erp-badge erp-badge-neutral">' + boxes + "박스</span>" +
'<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>') +
: '<p class="cpg-dist-hint">여기로 상자를 끌어다 놓기</p>') +
"</div>";
});
distList.innerHTML = html;
@@ -875,15 +875,15 @@
function openDialog(key, centerId) {
if (!openCenters.length) { msg.textContent = "먼저 ③ 에서 센터를 추가하세요."; return; }
var remain = remainFor(key);
if (remain <= 0) { msg.textContent = "남은 박스가 없습니다."; return; }
if (remain <= 0) { msg.textContent = "남은 상자가 없습니다."; return; }
pending = { key: key };
dlgItem.textContent = (labels[key] || key) + " — 잔여 " + remain + "박스 (" + (remain * (units[key] || 0)) + "개)";
dlgItem.textContent = (labels[key] || key) + " — 잔여 " + remain + "상자 (" + (remain * (units[key] || 0)) + "개)";
dlgCenterId = String(centerId != null ? centerId : openCenters[0]);
renderDialogCenters();
dlgQty.max = remain;
dlgQty.value = key.indexOf("m:") === 0 ? 1 : remain;
dlgHint.textContent = "1박스 = " + (units[key] || 0) + "개 · 최대 " + remain + "박스";
dlgHint.textContent = "1상자 = " + (units[key] || 0) + "개 · 최대 " + remain + "상자";
dlg.hidden = false;
dlgQty.focus();
dlgQty.select();
@@ -916,7 +916,7 @@
closeDialog();
render();
msg.textContent = got
? centerName(cid) + " ← " + (labels[key] || key) + " " + got + "박스"
? centerName(cid) + " ← " + (labels[key] || key) + " " + got + "상자"
: "담을 수량이 없습니다.";
}
@@ -1069,7 +1069,7 @@
var max = entry.count + remainFor(entry.key);
if (want > max) { want = max; e.target.value = max; }
entry.count = want;
li.querySelector(".cpg-dist-unit").textContent = "박스 · " + (want * (units[entry.key] || 0)) + "개";
li.querySelector(".cpg-dist-unit").textContent = "상자 · " + (want * (units[entry.key] || 0)) + "개";
var tree = li.querySelector(".cpg-dist-tree");
if (tree) tree.outerHTML = treeHtml(entry);
renderSummary();
@@ -1105,7 +1105,7 @@
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[0]) badges[0].textContent = boxes + "상자";
if (badges[1]) badges[1].textContent = pieces + "개";
}
@@ -1203,9 +1203,9 @@
function confirmBlockReason() {
var t = boxTotals();
if (!t.total) return "먼저 ① 에서 계산하세요.";
if (t.remain) return "아직 " + t.remain + "박스가 남았습니다.";
if (t.remain) return "아직 " + t.remain + "상자가 남았습니다.";
var cids = filledCenters();
if (!cids.length) return "센터에 담긴 박스가 없습니다.";
if (!cids.length) return "센터에 담긴 상자가 없습니다.";
var missing = cids.filter(function (cid) { return !methods[String(cid)]; });
if (missing.length) return "출고방식 미선택: " + missing.map(centerName).join(", ");
return "";
@@ -1218,7 +1218,7 @@
confirmBtn.title = why || "출고일자를 골라 출고 묶음을 만듭니다.";
}
// 센터별 전송 payload — 혼합 박스는 내용물 제품으로 풀어서 담는다.
// 센터별 전송 payload — 혼합 상자는 내용물 제품으로 풀어서 담는다.
function buildPlans() {
return filledCenters().map(function (cid) {
var rows = alloc[cid] || [];
@@ -1234,7 +1234,7 @@
});
}
});
// 출고 엑셀 "출고" 칸 — 상자 종류별 박스 수 (예: "쿠팡상자, 41개")
// 출고 엑셀 "출고" 칸 — 상자 종류별 상자 수 (예: "쿠팡상자, 41개")
// 종류가 여럿이면 " / " 로 잇는다 (예: "쿠팡상자, 41개 / 3호상자, 2개")
var byType = {};
var order = [];
@@ -1329,7 +1329,7 @@
var pieces = pl.items.reduce(function (n, it) { return n + it.quantity; }, 0);
return '<div class="cpg-cfm-row">' +
'<span class="cpg-cfm-cname">' + esc(centerName(pl.center_id)) + "</span>" +
'<span class="cpg-cfm-cmeta">' + esc(pl.ship_method) + " · " + pl.boxes + "박스 · " + pieces + "개</span>" +
'<span class="cpg-cfm-cmeta">' + esc(pl.ship_method) + " · " + pl.boxes + "상자 · " + pieces + "개</span>" +
"</div>";
}).join("");
renderCal();
@@ -1565,7 +1565,7 @@
if (!items.length) addRow("", 0);
draftName.value = d.title || "";
closeDraft();
// 박스 수는 서버에서 다시 계산한 뒤 분배 내역을 얹는다.
// 상자 수는 서버에서 다시 계산한 뒤 분배 내역을 얹는다.
run(snap);
})
.catch(function () { draftMsg.textContent = "불러오기 실패 — 다시 시도하세요."; });
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901n" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901o" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -14,7 +14,7 @@
<!-- 왼쪽: 추가/수정 (product_code UNIQUE → upsert) -->
<div class="erp-card cpg-form-card cpg-brule-add">
<div class="cpg-card-head">
<h2>박스 입수량 추가 / 수정</h2>
<h2>상자 입수량 추가 / 수정</h2>
<span class="erp-muted">같은 제품코드는 덮어씁니다.</span>
</div>
<form method="post" action="/cupang/box-rules">
@@ -32,9 +32,9 @@
<input class="erp-input cpg-brule-code" type="text" name="product_code" id="brule-code" required placeholder="자동" /></label>
</div>
<div class="cpg-brule-row">
<label class="erp-field"><span>박스이름</span>
<input class="erp-input cpg-brule-box" type="text" name="box_name" value="쿠팡박스" /></label>
<label class="erp-field"><span>박스당 입수량 *</span>
<label class="erp-field"><span>상자이름</span>
<input class="erp-input cpg-brule-box" type="text" name="box_name" value="쿠팡상자" /></label>
<label class="erp-field"><span>상자당 입수량 *</span>
<span class="cpg-upb-wrap">
<input class="erp-input cpg-brule-upb" type="number" name="units_per_box" min="1" required />
<span class="cpg-upb-unit"></span>
@@ -74,7 +74,7 @@
<div class="erp-table-wrap">
<table class="erp-table">
<thead>
<tr><th>제품명</th><th>제품코드</th><th>박스</th><th>입수량</th><th>메모</th><th>동작</th></tr>
<tr><th>제품명</th><th>제품코드</th><th>상자</th><th>입수량</th><th>메모</th><th>동작</th></tr>
</thead>
<tbody>
{% for r in box_rules %}
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901n" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901o" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901n" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901o" />{% endblock %}
{% block content %}
<section class="cpg">
@@ -12,7 +12,7 @@
<span class="cpg-settings-btns">
<a class="erp-btn erp-btn-outline" href="/cupang/products">제품명 설정</a>
<a class="erp-btn erp-btn-outline" href="/cupang/centers">입고센터 관리</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>
</span>
</div>
<div class="cpg-actions-spacer"></div>
@@ -97,7 +97,7 @@
<a class="cpg-ship-name" href="/cupang/{{ s.id }}">{{ s.center_name_snapshot or '센터 미지정' }}</a>
<span class="erp-badge erp-badge-neutral">{{ s.ship_method }}</span>
<span class="cpg-dist-sum">
<span class="erp-badge erp-badge-neutral">{{ s.total_boxes }}박스</span>
<span class="erp-badge erp-badge-neutral">{{ s.total_boxes }}상자</span>
<span class="erp-badge erp-badge-neutral">{{ s.total_qty }}개</span>
<button type="button" class="cpg-icon-btn is-danger" title="이 출고 삭제"
data-del="{{ s.id }}" data-name="{{ s.center_name_snapshot }}">삭제</button>
@@ -110,7 +110,7 @@
<li class="cpg-dist-item{% if e.kind == 'mix' %} is-mix{% endif %}">
<div class="cpg-dist-line">
<span class="cpg-dist-name">{{ e.name }}</span>
<span class="cpg-dist-unit">{{ e.count }}박스 · {{ e.quantity }}개</span>
<span class="cpg-dist-unit">{{ e.count }}상자 · {{ e.quantity }}개</span>
</div>
{% if e.kind == 'mix' and e['items'] %}
<ul class="cpg-dist-tree">
@@ -123,12 +123,12 @@
</li>
{% endfor %}
{% else %}
{# 예전에 만든 출고(박스 구성 미저장) — 제품 합계만 표시 #}
{# 예전에 만든 출고(상자 구성 미저장) — 제품 합계만 표시 #}
{% for it in s['items'] %}
<li class="cpg-dist-item">
<div class="cpg-dist-line">
<span class="cpg-dist-name">{{ it.name }}</span>
<span class="cpg-dist-unit">{{ it.boxes }}박스 · {{ it.qty }}개</span>
<span class="cpg-dist-unit">{{ it.boxes }}상자 · {{ it.qty }}개</span>
</div>
</li>
{% endfor %}
@@ -141,7 +141,7 @@
</ul>
{% else %}
<p class="erp-muted cpg-empty">선택한 날짜의 출고 묶음이 없습니다.
<a href="/cupang/box-calc">박스 계산에서 등록</a></p>
<a href="/cupang/box-calc">상자 계산에서 등록</a></p>
{% endif %}
</div>
</div>
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901n" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901o" />{% endblock %}
{% block content %}
<section class="cpg">
+13 -13
View File
@@ -1,9 +1,9 @@
{% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901n" />{% endblock %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260901o" />{% endblock %}
{% block content %}
{# 출고 묶음 보기 — 박스 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
{# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #}
<section class="cpg">
<div class="erp-page-actions">
@@ -44,18 +44,18 @@
</div>
</div>
<!--박스 요약 -->
<!--상자 요약 -->
<div class="erp-card cpg-form-card cpg-calc-sum">
<div class="cpg-card-head">
<h2>박스 요약</h2>
<h2>상자 요약</h2>
<span class="erp-muted">확정 당시 수량 기준으로 다시 계산한 값</span>
</div>
<div id="cpg-sum-body">
<div class="cpg-sum-kpis">
<div class="cpg-kpi">
<span class="cpg-kpi-label">박스</span>
<strong class="cpg-kpi-value">{{ calc.grand_total_boxes }}박스</strong>
<span class="cpg-kpi-label">상자</span>
<strong class="cpg-kpi-value">{{ calc.grand_total_boxes }}상자</strong>
<span class="cpg-kpi-hint">제품별 + 혼합</span>
</div>
<div class="cpg-kpi">
@@ -71,7 +71,7 @@
</div>
<div class="cpg-sum-scroll">
<h3 class="cpg-sum-h3">제품별 박스</h3>
<h3 class="cpg-sum-h3">제품별 상자</h3>
<div class="cpg-sum-prods">
{% for r in calc.results if r.configured %}
<div class="cpg-sum-prod is-done">
@@ -79,7 +79,7 @@
<span class="cpg-sum-prod-name">{{ r.product_name }}</span>
</div>
<div class="cpg-sum-prod-nums">
<strong>{{ r.full_boxes }}박스</strong>
<strong>{{ r.full_boxes }}상자</strong>
<span class="erp-muted">{{ r.quantity }}개 · 입수량 {{ r.units_per_box }}</span>
{% if r.remainder_units %}
<span class="cpg-sum-left">자투리 {{ r.remainder_units }}개</span>
@@ -91,14 +91,14 @@
{% endfor %}
{% set unconfigured = calc.results | rejectattr('configured') | list %}
{% if unconfigured %}
<p class="erp-muted">박스 입수량 미설정:
<p class="erp-muted">상자 입수량 미설정:
{% for r in unconfigured %}{{ r.product_name }}{% if not loop.last %}, {% endif %}{% endfor %}
</p>
{% endif %}
</div>
<h3 class="cpg-sum-h3">자투리 혼합 박스
<span class="erp-muted">{% if not calc.mixes %}— 자투리 없음{% else %}— 1장 = 1박스{% endif %}</span>
<h3 class="cpg-sum-h3">자투리 혼합 상자
<span class="erp-muted">{% if not calc.mixes %}— 자투리 없음{% else %}— 1장 = 1상자{% endif %}</span>
</h3>
<div class="cpg-sum-boxes">
{% for m in calc.mixes %}
@@ -135,7 +135,7 @@
<strong>{{ shipment.center_name_snapshot or '센터 미지정' }}</strong>
<span class="erp-badge erp-badge-neutral">{{ shipment.ship_method }}</span>
<span class="cpg-dist-sum">
<span class="erp-badge erp-badge-neutral">{{ calc.grand_total_boxes }}박스</span>
<span class="erp-badge erp-badge-neutral">{{ calc.grand_total_boxes }}상자</span>
<span class="erp-badge erp-badge-neutral">{{ total_qty }}개</span>
</span>
</div>
@@ -145,7 +145,7 @@
<li class="cpg-dist-item{% if e.kind == 'mix' %} is-mix{% endif %}">
<div class="cpg-dist-line">
<span class="cpg-dist-name">{{ e.name }}</span>
<span class="cpg-dist-unit">{{ e.count }}박스 · {{ e.quantity }}개</span>
<span class="cpg-dist-unit">{{ e.count }}상자 · {{ e.quantity }}개</span>
</div>
{% if e.kind == 'mix' and e['items'] %}
<ul class="cpg-dist-tree">