feat(b08): 자재총괄 할증률 「이내」 칸 — 표값 상한 안에서만 고름 · 비우면 표값(초안 연결은 Page 한 줄 대기)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
2026-09-14 10:52:52 +09:00
co-authored by Claude Opus 5
parent e6e6f9b7d5
commit 87083c3341
+48 -1
View File
@@ -28,6 +28,8 @@ export interface MaterialRow {
unit: string;
net_amount: number;
surcharge_pct: number | null;
/** 할증률표 값 = 고를 수 있는 천장(품셈 1-3-1 「표의 값 이내」). `null` 이면 칸이 안 섬. */
surcharge_cap?: number | null;
total_amount: number;
supply: string;
supply_label: string;
@@ -241,9 +243,49 @@ export interface SupplyChoice {
export interface MaterialGridOptions {
/** 저장 전 변경분 — 고른 값은 여기 쌓이고 [저장]에서만 정본으로 간다. */
choices: Record<string, SupplyChoice>;
/** 할증률 고른 값 `{이름 규격: % | null}` — 산출 조건 `material_surcharge`. 안 주면 칸 없이 글자만.
* `null` = 표값으로 되돌림. 0 ~ 표값만 받음(서버도 한 번 더 거름). */
surcharge?: Record<string, number | null>;
onChange: () => void;
}
/** 할증률 칸 — 표값(상한) 안에서만. 범위를 넘으면 칸이 빨갛게 되고 값을 안 받음. */
function surchargeCell(
row: MaterialRow,
key: string,
picks: Record<string, number | null>,
onChange: () => void,
): HTMLTableCellElement {
const cap = row.surcharge_cap as number;
const td = document.createElement("td");
const input = document.createElement("input");
input.type = "number";
input.min = "0";
input.max = String(cap);
input.step = "0.1";
input.className = "b08-grid__select";
input.style.width = "4.5em";
const picked = picks[key];
input.value = String(picked ?? row.surcharge_pct ?? cap);
input.title = `표값 ${cap}% 이내(품셈 1-3-1) · 비우면 표값`;
input.addEventListener("change", () => {
const text = input.value.trim();
const value = Number(text);
if (text !== "" && !(Number.isFinite(value) && value >= 0 && value <= cap)) {
input.setCustomValidity(`0 ~ ${cap}% 만 받음`);
input.reportValidity();
return;
}
input.setCustomValidity("");
picks[key] = text === "" ? null : value;
if (text === "") input.value = String(cap);
td.classList.add("is-changed");
onChange();
});
td.append(input);
return td;
}
/** 표 안의 고르는 칸. 바꾼 줄은 **표시가 남는다** — 무엇을 만졌는지 보여야 한다. */
function choiceCell(
value: string,
@@ -337,7 +379,12 @@ export function renderMaterialGrid(
tr.append(textCell(row.unit, "b08-grid__unit"));
tr.append(textCell(num(row.net_amount, 2)));
// 미확보는 빈칸이 아니라 「-」 — 빈칸이면 0 % 로 오해된다.
tr.append(textCell(row.surcharge_pct === null ? "" : num(row.surcharge_pct, 0)));
// 표값이 있는 줄은 「이내」로 고를 칸(초안 `surcharge` 가 이어졌을 때만).
tr.append(
options?.surcharge && typeof row.surcharge_cap === "number"
? surchargeCell(row, key, options.surcharge, options.onChange)
: textCell(row.surcharge_pct === null ? "" : num(row.surcharge_pct, 0)),
);
tr.append(textCell(num(row.total_amount, 2)));
if (options) {
// 관급/사급은 **자재마다 갈리는 발주 결정**이라 줄에서 고른다(2026-09-07 확정).