feat(malaysia): 재고조사 랙(rack) 박스 단위 입력
낱개/콤보 평면 수량 그리드를 창고 랙 그림(A/B 36칸) 입력으로 대체.
각 칸에 아이템·박스당 입수량·박스 수를 넣고 + 로 한 칸 여러 아이템 추가.
저장 시 SKU별 qty=SUM(입수량×박스수) 집계로 daily_stocktake_line 재생성.
이후 분해/확정 로직은 라인 기준 그대로 동작.
- DB: daily_stocktake_rack 테이블 + grant (멱등)
- store: rack_cell_codes/rack_layout/aggregate_rack 순수함수
- db: list_rack_entries/replace_rack(1 트랜잭션, draft만)
- router: rack 컨텍스트 + POST /stocktakes/{id}/rack/bulk
- UI: 랙 보드 템플릿 + malaysia_rack.js(행 추가/삭제·실시간 소계)
- 테스트 4건 추가(18 전부 통과)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/* 말레이시아 재고조사 — 랙(rack) 입력 보조 스크립트.
|
||||
*
|
||||
* - 칸(.rack-cell) 헤더의 + 버튼: 빈 입력행을 칸에 추가(템플릿 #rk-row-tpl 클론).
|
||||
* 클론 시 숨김필드 rk_cell 에 해당 칸 코드를 채워 서버 병렬배열과 정렬.
|
||||
* - 행의 × 버튼: 그 행 제거(칸에 행이 0개여도 무방 — 서버가 빈 칸 스킵).
|
||||
* - 입수량/박스수 입력 시 행 소계(rk-sub)와 칸 합계(rk-cell-total-v) 실시간 갱신.
|
||||
* 저장은 폼 제출(서버에서 SKU별 집계 → 재고조사 라인 재생성).
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function num(el) {
|
||||
var v = parseInt((el && el.value) || "0", 10);
|
||||
return isNaN(v) || v < 0 ? 0 : v;
|
||||
}
|
||||
|
||||
function recalcRow(row) {
|
||||
var upb = num(row.querySelector(".rk-upb"));
|
||||
var box = num(row.querySelector(".rk-box"));
|
||||
var sub = row.querySelector(".rk-sub");
|
||||
if (sub) sub.textContent = String(upb * box);
|
||||
}
|
||||
|
||||
function recalcCell(cell) {
|
||||
var total = 0;
|
||||
cell.querySelectorAll(".rk-row").forEach(function (row) {
|
||||
total += num(row.querySelector(".rk-upb")) * num(row.querySelector(".rk-box"));
|
||||
});
|
||||
var out = cell.querySelector(".rk-cell-total-v");
|
||||
if (out) out.textContent = String(total);
|
||||
}
|
||||
|
||||
function wireRow(row) {
|
||||
row.querySelectorAll(".rk-upb, .rk-box").forEach(function (inp) {
|
||||
inp.addEventListener("input", function () {
|
||||
recalcRow(row);
|
||||
var cell = row.closest(".rack-cell");
|
||||
if (cell) recalcCell(cell);
|
||||
});
|
||||
});
|
||||
var del = row.querySelector(".rk-del");
|
||||
if (del) {
|
||||
del.addEventListener("click", function () {
|
||||
var cell = row.closest(".rack-cell");
|
||||
row.remove();
|
||||
if (cell) recalcCell(cell);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRow(cell) {
|
||||
var tpl = document.getElementById("rk-row-tpl");
|
||||
if (!tpl) return;
|
||||
var frag = tpl.content.cloneNode(true);
|
||||
var row = frag.querySelector(".rk-row");
|
||||
if (!row) return;
|
||||
var hidden = row.querySelector('input[name="rk_cell"]');
|
||||
if (hidden) hidden.value = cell.getAttribute("data-cell") || "";
|
||||
cell.querySelector(".rk-rows").appendChild(row);
|
||||
wireRow(row);
|
||||
var sku = row.querySelector(".rk-sku");
|
||||
if (sku) sku.focus();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.querySelectorAll(".rack-cell .rk-row").forEach(wireRow);
|
||||
document.querySelectorAll(".rack-cell").forEach(recalcCell);
|
||||
document.querySelectorAll(".rack-cell .rk-add").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
var cell = btn.closest(".rack-cell");
|
||||
if (cell) addRow(cell);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user