fix(b08): 자재 콘크리트소요 5줄 늘 보임 · 콘크리트 전용 빈 까닭 한 줄(163-5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
2026-10-09 23:58:18 +09:00
co-authored by Claude Opus 5.5
parent 35d6b68c41
commit 4c0ea30c7b
2 changed files with 38 additions and 5 deletions
+35 -3
View File
@@ -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] }));
@@ -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 (