feat(714-2): 공용 단가산출 계산서 묶음 꼴 — 인력 · 재료 · 경비(장비) · 하위(714-1 줄 덧칸 묶음) · 장비 · 하위 로직 = 한 줄 + 노 · 재 · 경 칸 · 누르면 몫 식 석 줄 펼침 · 묶음 머리 기준단위 · 환산 근거 · 쓰는 입력 칸 · 맨 아래 비목 소계 셋 + 계

- calc_sheet.ts bundle 꼴 · bundleLines(순수 · 같은 이름 k 번째끼리 짝) · 새 calc_sheet.css(장비 줄 누름 · 몫 들여씀)
- M01 「인력·재료·경비」 = bundle 꼴 · 변수 칸 = 서버 쓰는입력
- 714-3 텍스트 수식 단추도 서버 쓰는입력 먼저(조건 · 재료 고르기 입력까지) · 없으면 조각
- 704-2 M02 계산서 보기 = 비목 꼴 그대로(DOM 전후 같음)
- 장비 펼침 넉 줄(손료 · 주연료 · 잡품 · 운전사) = sub3 「펼침」 덧칸 뒤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 16:30:33 +09:00
co-authored by Claude Opus 5.5
parent 301a434402
commit 702ab1daa8
6 changed files with 246 additions and 43 deletions
@@ -1,7 +1,7 @@
/* =============================================================================
* M01_MasterData_UI_LogicLab_Est.ts
* 「인력·재료·경비」 보기(PLAN 714-2) — 공용 단가산출 계산서(`A00_Common/calc_sheet.ts`)에 시험 계산 읽는 식(`/text`)을 그대로.
* 묶음 머리 변수 칸 = 그 묶음 줄들이 쓰는 시험 입력(714-3 `usedInputs`) · 바꾸면 시험 계산 입력 고침 + 다시 셈(정본 안 바뀜)
* 인력 · 재료 · 경비(장비) 묶음(714-1 줄 덧칸 `묶음`) · 묶음 머리 변수 칸 = 줄들의 `쓰는입력` · 바꾸면 시험 계산 입력 고침 + 다시 셈(정본 안 바뀜)
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
@@ -9,7 +9,7 @@ import { createText } from "@ui/ui_template_text";
import { calcSheetRows } from "../A00_Common/calc_sheet";
import type { LogicInput } from "./M01_MasterData_UI_Logic_Api";
import { safeReason } from "./M01_MasterData_UI_Logic_Text";
import { usedInputs, type PreviewTextAnswer } from "./M01_MasterData_UI_LogicLab_Formula";
import type { PreviewTextAnswer } from "./M01_MasterData_UI_LogicLab_Formula";
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
export interface EstContext {
@@ -31,18 +31,15 @@ export function buildEst(ctx: EstContext): HTMLElement {
host.append(createText(safeReason(text.reason), "error"));
return host;
}
const names = ctx.inputs.map((x) => x.이름);
host.append(
...calcSheetRows({
묶음: text.groups,
계: text.계,
sumLabel: `${tl("Total")} (${ctx.unit || "원"})`,
bundle: true,
inputs: ctx.inputs,
values: ctx.values,
onInput: ctx.onInput,
uses: (_, gi) => [
...new Set(text.groups[gi].줄.flatMap((line) => usedInputs(line, names).map(([n]) => n))),
],
}),
);
return host;
@@ -3,7 +3,7 @@
* 「텍스트 수식」 컨테이너 — 서버(`POST /text`)가 준 줄을 그대로 찍음(화면에서 다시 계산하지 않음)
* 두 칸 — 왼쪽 = 변수 이름 식(`이름글`) · 오른쪽 = 값이 들어간 식(`글`) · 줄은 `짝` 번호로 같은 높이
* 한쪽에 올리거나 누르면 같은 줄이 밝아짐 · 비목 제목 · 소계 · 끝수 한 마디 · 까닭 있는 줄은 옅게
* 714-3 — 줄이 쓰는 입력(표 찾기 행 값 · 이름 조각)을 오른쪽 칸 아래 작은 단추로 · 누르면 그 자리에 고르기/글 칸 →
* 714-3 — 줄이 쓰는 입력(서버 `쓰는입력` · 없으면 표 찾기 행 값 · 이름 조각)을 오른쪽 칸 아래 작은 단추로 · 누르면 그 자리에 고르기/글 칸 →
* 바꾸면 `onInput`(시험 계산 입력 고침 + 다시 셈) · 정본은 안 바뀜(요구서 4-1 확정 8)
* ========================================================================== */
@@ -123,10 +123,12 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void {
const rows: HTMLElement[] = [];
const specs = ctx.onInput ? (ctx.inputs ?? []) : [];
const vars = (line: TextLine): HTMLElement[] => {
const used = usedInputs(
line,
specs.map((x) => x.이름),
);
const names = specs.map((x) => x.이름);
const seen = new Map(usedInputs(line, names));
// 서버 「쓰는입력」(714-1 · 조건 · 재료 고르기 · 중간값까지) 이 있으면 그것 · 없으면 조각에서
const used: [string, string][] = line.쓰는입력
? line.쓰는입력.filter((n) => names.includes(n)).map((n) => [n, seen.get(n) ?? ""])
: [...seen];
if (!used.length || !ctx.onInput) return [];
const onInput = ctx.onInput;
return [
@@ -148,6 +148,12 @@ export interface TextLine {
짝?: number;
금액: number;
까닭?: string;
/** 714-1 계산서 줄 덧칸 — 쓰는 로직 입력 · 묶음(인력 · 재료 · 경비 · 하위) · 장비 · 찾는 표 기준 · 환산 근거 */
쓰는입력?: string[];
묶음?: string | null;
장비?: boolean | null;
기준단위?: string | null;
환산근거?: string;
}
/** `POST /text` 답 — 서버가 만든 읽는 식 줄(화면은 그대로 찍음) */
export type TextAnswer =