Files
Aislo/A00_Common/calc_sheet.ts
T
eomsangdonandClaude Opus 5.5 cddb28b63a feat(714-2): 공용 단가산출 계산서 틀 — A00_Common/calc_sheet.ts(704-2 계산서 줄 올림 · 묶음 머리 변수 칸 · 바꾸면 onInput) · M02 계산서 보기 · M01 「인력·재료·경비」 보기가 한 벌로 씀(장비 한 줄 · 기준단위 · 환산 근거는 714-1 뒤)
- 704-2 Labor_Sheet 줄 · 소계 · 계 = calcSheetRows(표본 셋 DOM 전후 같음)
- 새 M01_MasterData_UI_LogicLab_Est.ts — /text 묶음 그대로 · 묶음이 쓰는 시험 입력(714-3 usedInputs) = 머리 변수 칸 · 「준비 중」 자리 걷음
- 낡은 시험 test_704_logic_standard 고침 — 계산서가 계산 줄 부품을 공용 계산서 안에서 씀(import 자리만 바뀜)
- 새 지킴 시험 test_714_2_calc_sheet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:03:36 +09:00

107 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* calc_sheet.ts
* 공용 「단가산출 계산서」(PLAN 714-2 · 요구서 내역12 4장 5) — 704-2 M02 계산서 보기를 올린 것.
* 쓰는 곳 = M01 「인력·재료·경비」 보기 · M02 기준 목록 계산서 보기(704-2) · (뒤에) 일위대가 모달 · B09 단가산출근거
* 꼴 = 묶음마다 [머리(변수 칸이 있을 때만) · 쓰는 입력 값 줄] · 식 줄(이름 | 단가 × 품 | 금액) · 소계 · 맨 아래 계
* 값 = 서버 셈 그대로 찍음(화면에서 다시 셈 안 함) · 변수 칸 바꾸면 `onInput(이름, 값)` — 부르는 쪽이 다시 셈
* 부품 = `ui_template_calc_row`(값 줄 · 식 줄) · 입력 · 고르기 · 글 — 날 요소 · 페이지 CSS 없음
* ========================================================================== */
import { createCalcFormula, createCalcFormulaRow, createCalcRow } from "@ui/ui_template_calc_row";
import { el } from "@ui/ui_template_elements";
import { createInputField, createSelectField } from "@ui/ui_template_input";
import { createText } from "@ui/ui_template_text";
export interface SheetLine {
이름: string;
/** 서버 값 식 「단가 * 품 = 금액」 — 「= 금액」 은 떼고 「*」 → 「×」 */
글: string;
금액: number | null;
}
export interface SheetGroup {
비목: string;
소계: number | null;
끝수?: string;
줄: SheetLine[];
}
export interface SheetInput {
이름: string;
단위?: string;
고르기?: (string | number)[];
}
export interface SheetOptions {
묶음: SheetGroup[];
계: number | null;
/** 맨 아래 계 줄 이름(「계 (원/㎡)」) */
sumLabel: string;
/** 변수 칸 — 입력 꼴 · 지금 값 · 묶음이 쓰는 입력 이름 · 바꾼 뒤 할 일(없으면 변수 칸 없음) */
inputs?: SheetInput[];
values?: Record<string, string>;
uses?: (group: SheetGroup, index: number) => string[];
onInput?: (name: string, value: string) => void;
}
const won = (v: number | null, digits = 0): string =>
v === null ? "-" : v.toLocaleString("ko-KR", { maximumFractionDigits: digits });
const out = (v: number | null, key = false, digits = 0): HTMLElement =>
el("span", { className: `ui-calc-row__out${key ? " is-key" : ""}`, text: won(v, digits) });
/** 값 식 → 식 칸 글 — 「268,908 * 0.12 = 32,269」 → 「268,908 × 0.12」 */
export const sheetFormula = (text: string): string =>
text.replace(/ = [^=]*$/, "").replaceAll(" * ", " × ");
/** 변수 칸 하나 — 고르기 있으면 고르기 · 아니면 글 칸(Enter · 떠남 때 넘김) */
function varRow(spec: SheetInput, value: string, onInput: (v: string) => void): HTMLElement {
let control: HTMLElement;
if (spec.고르기?.length) {
control = createSelectField({
options: spec.고르기.map((o) => ({ value: String(o), text: String(o) })),
value,
compact: true,
ariaLabel: spec.이름,
onChange: onInput,
}).root;
} else {
const box = createInputField({ value, compact: true });
box.input.setAttribute("aria-label", spec.이름);
box.input.addEventListener("change", () => {
if (box.input.value.trim() && box.input.value !== value) onInput(box.input.value.trim());
});
control = box.root;
}
const row = createCalcRow(spec.단위 ? `${spec.이름} (${spec.단위})` : spec.이름, control);
row.dataset.sheetVar = spec.이름;
return row;
}
/** 계산서 줄들 — 부르는 쪽 세로 칸(flex column)에 그대로 붙임 */
export function calcSheetRows(o: SheetOptions): HTMLElement[] {
const rows: HTMLElement[] = [];
const specs = new Map((o.onInput ? (o.inputs ?? []) : []).map((s) => [s.이름, s]));
o.묶음.forEach((g, gi) => {
const vars = (o.uses?.(g, gi) ?? []).filter((n) => specs.has(n));
if (vars.length && o.onInput) {
const onInput = o.onInput;
const head = createText(g.비목, "muted", 14);
head.dataset.sheetHead = g.비목;
rows.push(head);
for (const n of vars)
rows.push(varRow(specs.get(n)!, o.values?.[n] ?? "", (v) => onInput(n, v)));
}
for (const line of g.줄)
rows.push(
createCalcFormulaRow(
line.이름,
createCalcFormula(sheetFormula(line.글)),
out(line.금액, false, 1),
),
);
rows.push(
createCalcFormulaRow(`${g.비목}${g.끝수 ? ` (${g.끝수})` : ""}`, null, out(g.소계, true)),
);
});
rows.push(createCalcFormulaRow(o.sumLabel, null, out(o.계, true)));
return rows;
}