/* ============================================================================= * M02_MasterTemplete_Labor_Grid.ts * 일위대가 엑셀 꼴 격자(704-3) — 문서 · 서버 calc 답 → 공용 격자(`ui_template_hopyo_grid` · B09 일위대가표와 같은 꼴). * 일위대가 카드 · 상세(`Labor_Detail`)가 위에 붙임. 값은 calc 답 그대로(여기서 셈하지 않음): * 머리 = 일위대가번 · 단위 · 1 단위 합계(`반영합`) * 로직 줄 = 이름(별칭 앞섬) · 규격(옵션 값 · 켜쌓기) · 수량 · 분모 · 단가 = 기준 ÷ 배 · 금액 = 반영 · 꼬리표 품셈 * 자재 줄 = 이름 · 규격 · 수량 · 단위 · 재료비 단가 = 기준단가 · 금액 = 반영 · 별도계상은 비고 「합계 밖」 * 빈 줄(금액 모름 · 수량 없음) = 합계 줄 「빈 줄 n」 붉게 — 701-4 경고와 같은 줄 * ========================================================================== */ import { createHopyoGrid, HOPYO_TAG, type HopyoBlock, type HopyoLine, type HopyoMoney, } from "@ui/ui_template_hopyo_grid"; import type { ItemLogic } from "./M02_MasterTemplete_Api_Fetch"; import type { LaborCalc, LaborCost, LaborDoc } from "./M02_MasterTemplete_Labor_Api"; import { lineId } from "./M02_MasterTemplete_Labor_Calc"; import { tc } from "./M02_MasterTemplete_Labor_Text"; import { valueLine } from "./M02_Labor_Values_Model"; const money = (m: LaborCost | null | undefined, times = 1): HopyoMoney => m ? { sum: m.계 / times, labor: m.노무비 / times, material: m.재료비 / times, expense: m.경비 / times, } : {}; /** 문서 + calc 답 → 호표 하나 — calc 없으면(계산 중) 금액 빈 칸 · 빈 줄 0 */ export function laborBlock( doc: LaborDoc, calc: LaborCalc | null, logics: ReadonlyMap, ): HopyoBlock { const logicLines = doc.담은로직.map((row): HopyoLine => { const id = lineId(row) ?? ""; const line = calc?.줄.find((l) => lineId(l) === id); const logic = logics.get(row.로직); const layer = row.켜쌓기율 != null ? tc("Labor_Layer").replace("{n}", String(row.켜쌓기율)) : ""; const qty = line?.수량 ?? null; return { name: row.별칭 || ((row.참조 ? line?.이름 : logic?.이름) ?? row.참조?.키 ?? row.로직), spec: [valueLine(logic?.입력 ?? [], row.입력값).text, layer].filter(Boolean).join(" · "), qty, unit: line?.분모 ?? (row.참조 ? "" : (logic?.결과단위 ?? "")), prices: money(line?.기준, line?.배 || 1), amounts: money(line?.반영), note: line?.까닭 ?? (calc && qty === null ? tc("Grid_NoQty") : (row.참조?.키 ?? row.로직)), tag: HOPYO_TAG.pum, }; }); const matLines = doc.자재.map((mat): HopyoLine => { const line = calc?.자재.find((m) => m.코드 === mat.코드); const qty = line?.수량 ?? null; const price = line?.기준단가 ?? null; const cost = line?.반영 ?? null; return { name: mat.이름 ?? mat.코드, spec: mat.규격 ?? "", qty, unit: mat.단위 ?? "", prices: { sum: price, material: price }, amounts: { sum: cost, material: cost }, note: line?.까닭 ?? (calc && qty === null ? tc("Grid_NoQty") : mat.별도계상 !== "직접계산" ? tc("Grid_Apart") // 짝 `settle` 의 aside 와 같은 규칙 : mat.코드), }; }); const lines = [...logicLines, ...matLines]; return { key: doc.열, name: doc.일위대가번 || doc.열, unit: doc.단위 ?? "", total: money(calc?.반영합), lines, ...(calc ? {} : { blank: 0 }), }; } /** 격자 하나(읽기 전용) */ export const laborGrid = ( doc: LaborDoc, calc: LaborCalc | null, logics: ReadonlyMap, ): HTMLElement => createHopyoGrid([laborBlock(doc, calc, logics)]).root;