/* ============================================================================= * B09_Estimation_UI_Tabs_Bill.ts * B09 설계내역서 탭(PLAN 145-3) — 실무 엑셀 꼴(공종 · 명칭 · 규격 · 수량 · 단위 · 합계 · 노무 · 재료 · 경비 · 비고). * * 값 = `B09_Estimation_Calc_Bill`(조작 중 브라우저 · [저장] 때 서버 정본) * 고칠 칸 = 관급자재대 조달수수료 율(%) · 항목 수량(166-2 · 덮어쓰기 · 빈칸 = 자동) · * 손 줄 명칭 · 규격 · 단위 · 수량 · 빈칸 = 「연결 없음」(수량 · 단가 원천 없음) * 줄 누름 = 단가를 낸 탭 줄(자재 → 재료비목록표 · 구조물 → 일위대가목록표) * 우클릭 = 줄 추가 · 지우기 · 연결 · 레벨(`…_UI_Tabs_Bill_Edit`) * 레벨(182) = 줄 `level`(셈이 냄) → class is-lvN · 글자 크기 · 첫 칸 들여쓰기(CSS) · 묶음 줄 = 이름만 고침 * 「연결 ▾」 칸(182) = 손 항목 줄만 · 누름 = 우클릭 [연결] 과 같은 메뉴(연결됨 = 호표 번호) * ========================================================================== */ import type { DataRow } from "@ui/ui_template_data_table"; import type { Row } from "./B09_Estimation_Calc_Base"; import { money, parseNumber, qty, text, type TabBuild, type TabId } from "./B09_Estimation_UI_Tabs"; import { billMenu, editQty, editText, handEditable, openLinkMenu, type BillTable, } from "./B09_Estimation_UI_Tabs_Bill_Edit"; export const buildBill: TabBuild = (ctx): BillTable => { const { sheets, user, setUser } = ctx; const bill: Row = sheets.bill || { rows: [], totals: null }; const rows: DataRow[] = bill.rows.map((r: Row) => ({ ...r, connect: r.hand && r.kind === "item" ? `${r.note || "연결"} ▾` : "", })); const direct: Row = bill.totals?.direct || {}; const pair = (key: string, label: string) => [ money(`price_${key}`, "단가", [label]), money(key, "금액", [label]), ]; return { columns: [ text("name", "공종 · 명칭", 220), text("spec", "규격", 160), qty("qty", "수량", 3), text("unit", "단위", 56), ...pair("sum", "합계"), ...pair("labor", "노무비"), ...pair("material", "재료비"), ...pair("expense", "경비"), text("connect", "연결", 96), text("note", "비고", 200), ], rows, total: { name: "순공사비 계(관급 제외)", ...direct }, frozen: 1, hint: "공종 나무 = 엑셀 꼴 고정 · 금액 = ROUNDDOWN(단가 × 수량, 0) 비목마다 · 4 자재대 = 대비표 「별도」 · 관급자재대 = 「관급」 표시 자재 · 조달수수료 율(%)은 수량 칸에 넣음 · " + "수량 칸 = 덮어쓰기(빈칸 = 자동) · 우클릭 · 「연결 ▾」 = 줄 추가 · 지우기 · 일위대가 · 단가산출 연결(새 산근 단가 = [저장] 뒤) · " + "레벨 = 우클릭 묶음 줄로 · 하위 줄 추가 · 들여쓰기 · 내어쓰기(묶음 금액 = 아래 항목 합 · 공종 합은 항목만) · " + `빈 줄 ${direct.blank ?? 0} — 계는 아는 금액만 · 원가계산서는 빈 줄이 없을 때만 받음`, editable: (i, key) => { const r = rows[i]; if (r?.kind === "group") return key === "name"; if (!r || r.kind !== "item") return false; return r.hand ? handEditable(key) : key === "qty"; }, onEdit: (i, key, value) => { const r = rows[i]; if (!r) return; if (r.hand && key !== "qty") return editText(ctx, String(r.key), key, value); if (key !== "qty") return; const v = parseNumber(value); if (v === undefined) return; if (r.key !== "fee") return editQty(ctx, r, v); setUser({ ...user, estimate: { ...(user.estimate || {}), procurement_fee_pct: v } }); }, tip: (i, key) => { const r = rows[i]; if (key !== "qty" || !r || !("qty_auto" in r)) return null; return `자동 값 ${r.qty_auto ?? "빈칸"} — 우클릭 [수량 자동으로 되돌리기]`; }, // 182 레벨 — 글자 크기 · 들여쓰기(is-lvN) · 제목 · 묶음 줄 굵게 rowClass: (i) => { const r = rows[i]; if (!r) return null; const title = r.kind === "title" ? "is-title" : r.kind === "group" ? "is-group" : ""; return [`is-lv${r.level ?? 1}`, title, r.hand ? "is-hand" : ""].filter((c) => c).join(" "); }, menu: (i) => billMenu(ctx, rows, i), onCell: (i, key) => { if (key === "connect") openLinkMenu(ctx, rows, i); }, link: (i, key) => { if (key === "connect") return null; // 「연결 ▾」 = 메뉴(이동 안 함) const ref = rows[i]?.ref as Row | null | undefined; return ref?.key ? { tab: ref.tab as TabId, key: String(ref.key) } : null; }, }; };