diff --git a/B08_Quantity/B08_Quantity_UI_Material.ts b/B08_Quantity/B08_Quantity_UI_Material.ts index f6ef3f61d..0d4ca77f4 100644 --- a/B08_Quantity/B08_Quantity_UI_Material.ts +++ b/B08_Quantity/B08_Quantity_UI_Material.ts @@ -7,7 +7,8 @@ * 고칠 칸 = 단위수량 · ㎥당 배합 · 할증 % — 조작 = 캐시(`b08-materials`) + 짝 계산(`Material_Calc.ts`)으로 바로 * 총괄 모래 · 자갈 · 시멘트 할증 = 좌측 패널 값(PLAN 124 · 표에서 안 고침 · 패널 조작에 바로 다시 셈) * [저장] = PUT …/quantity/materials → 서버가 정본으로 다시 셈 · 캐시 비움 · 자동 저장 없음 - * 값 없는 줄은 숨김(화면만 · 셈 · 열 · 단위수량은 그대로) · 표는 공용 카드 안(PLAN 143-3) + * 값 없는 줄은 숨김(화면만 · 셈 · 열 · 단위수량은 그대로) · 표는 공용 카드 안(PLAN 143-3) · + * 콘크리트소요는 5줄 늘 보임(수량 빈칸 · 배합 늘 고침) + 콘크리트 전용 빈 까닭 한 줄(163-5) * 콘크리트(기계) · 모르터 1:3 열 = 늘 보임 · 손 단위수량 → 콘크리트소요(160-4) · 엑셀 손값과 다른 * 배합 · 할증은 표 아래 알림 한 줄만 · 맞춤 없는 사본 자재 · 채집석 공제 = 총괄 끝 줄(160-3 · 5) * ========================================================================== */ @@ -26,6 +27,7 @@ import { blankUnitRows, computeMaterialSheets, DEDUCT_KEY, + matched, MIX_FIELDS, type Cells, type MaterialOverrides, @@ -189,9 +191,31 @@ function excelDiffs(s: MaterialSheets): string { return out.length ? `2공구 엑셀 손값과 다름 — ${out.join(" · ")} · 지금 값 = 품셈 근거` : ""; } +/** 콘크리트소요 원천 = 콘크리트(기계) · 모르터 1:3 열 + 그 둘에 맞는 사본 자재 열(짝 계산 `matched` 그대로) */ +const CONCRETE_SOURCES = ["concrete_machine", "mortar_1_3"]; + +/** 콘크리트소요가 다 빈칸일 때 까닭 한 줄(163-5) — 원천 단위수량이 빈 집계표 줄을 사본 자재 유무로 가름 */ +function concreteWhy(s: MaterialSheets, source: SummarySource): string { + if (s.concrete.rows.some((row) => row.qty != null)) return ""; + const keys = new Set( + s.quantity.materials + .filter( + (m) => + CONCRETE_SOURCES.includes(m.key) || + CONCRETE_SOURCES.includes(matched(m.name, m.spec, m.unit) ?? ""), + ) + .map((m) => m.key), + ); + const blank = s.quantity.rows.filter((row) => [...keys].every((k) => row.units[k] == null)); + const copied = blank.filter((row) => source.snapshot?.units?.[row.id]).length; + return blank.length + ? `콘크리트소요 빈 까닭 — 콘크리트(기계) · 모르터 1:3 단위수량 비었음 ${blank.length}줄(사본 자재에 콘크리트 · 모르터 없음 ${copied} · 사본 자재 없음 · 미연결 ${blank.length - copied}) — 자재수량 탭 그 열에 넣음` + : ""; +} + function concreteTable(s: MaterialSheets, o: MaterialOverrides): Table { const { rows, total } = s.concrete; - const keep = shown(rows, (row) => row.qty != null || anyValue(row.amounts)); + const keep = rows.map((_row, i) => i); // 5줄 늘 보임(163-5) — 수량 없으면 빈칸 · 배합은 늘 고침 const rowsOut: DataRow[] = keep.map((at, i) => { const row = rows[at]; const out: DataRow = { @@ -310,7 +334,10 @@ export async function renderMaterialSheet( const toolbar = document.createElement("div"); toolbar.className = "b08-material__toolbar"; const notes = document.createElement("div"); - notes.append(note(BASIS), note(`${HINTS[sheet]} · ${HIDDEN}`)); + notes.append( + note(BASIS), + note(sheet === "concrete" ? HINTS[sheet] : `${HINTS[sheet]} · ${HIDDEN}`), + ); if (sheet !== "quantity") { // 빈 까닭 한 줄(131) — 단위수량 빈 줄 · 서버 알림 · 빈 칸은 빈칸 그대로 const blank = blankUnitRows(sheets); @@ -324,6 +351,9 @@ export async function renderMaterialSheet( notes.append(reason); } } + const concreteNote = note(""); // 콘크리트 전용 빈 까닭(163-5) — 조작마다 다시 + concreteNote.dataset.b08MaterialConcreteWhy = ""; + if (sheet === "concrete") notes.append(concreteNote); toolbar.append(notes, save); let built = BUILD[sheet](sheets, overrides); @@ -358,6 +388,8 @@ export async function renderMaterialSheet( built = BUILD[sheet](sheets, overrides); table.setRows(built.rows, built.total); excel.textContent = sheet === "quantity" ? "" : excelDiffs(sheets); + concreteNote.textContent = concreteWhy(sheets, payload.source); + concreteNote.hidden = !concreteNote.textContent; }; redraw(); host.replaceChildren(createCard({ body: [toolbar, table.root, excel] })); diff --git a/resources/tester/test_143_3_b08_material_cards.py b/resources/tester/test_143_3_b08_material_cards.py index 6411fe75b..b21cffa34 100644 --- a/resources/tester/test_143_3_b08_material_cards.py +++ b/resources/tester/test_143_3_b08_material_cards.py @@ -12,9 +12,10 @@ SUMMARY = (ROOT / "B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding= def test_자재_셋_값_없는_줄_숨김(): - assert MATERIAL.count("const keep = shown(rows, ") == 3 # 총괄 · 콘크리트 · 자재수량 + assert ( + MATERIAL.count("const keep = shown(rows, ") == 2 + ) # 총괄 · 자재수량(콘크리트 = 늘 · 163-5) assert "row.qty != null || anyValue(row.units)" in MATERIAL # 손 단위수량 있으면 보임 - assert "row.qty != null || anyValue(row.amounts)" in MATERIAL assert "row.qty != null || row.amount != null" in MATERIAL # 고친 칸 → 원래 줄(숨긴 줄 건너뜀) for target in (