- 일위대가 격자(ui_template_hopyo_grid onPaste) 누르고 Ctrl+V → 서버 /hopyo-paste · 열 짝 = 처음 한 번 묻고 이 브라우저에 기억 · 엑셀 머리 글이면 묻지 않음 - 호표 머리 없음 = 열린 문서에 고정 줄(알림 누름 = 다시 잇기) · 머리 있음 = 호표마다 사전조합 + 결과 창 [다음 — 이 공사 단가로 다시 잇기] - 다시 잇기 창(EST 신 · 구 단가 비교 꼴) — 붙인 단가 · 최신 판 단가 · 후보(705-3 이음 짝) · 금액 차이 · 줄마다 · 모두 고름 · [바꿈] · [되돌리기] - 서버 M02_Hopyo_Relink — 후보 표 · 이음 칸만 고쳐 씀(숫자 안 받음) · 전후 1 단위 값 · 되돌림 - 실무자료 [모두 고정으로 들여오기] 결과에도 다음 걸음 단추 - M02 상세가 고정 줄을 읽을 때 떨구던 흠 — normalizeLabor 가 고정 칸 지킴 · 로직 표 · 격자에 고정 줄 이름 · 규격 · 단위 · 「고정」 꼬리표 · 이음 / 손 단가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
5.2 KiB
TypeScript
125 lines
5.2 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Labor_Grid.ts
|
|
* 일위대가 엑셀 꼴 격자(704-3) — 문서 · 서버 calc 답 → 공용 격자(`ui_template_hopyo_grid` · B09 일위대가표와 같은 꼴).
|
|
* 일위대가 카드 · 상세(`Labor_Detail`)가 위에 붙임. 값은 calc 답 그대로(여기서 셈하지 않음):
|
|
* 머리 = 일위대가번 · 단위 · 1 단위 합계(`반영합`)
|
|
* 로직 줄 = 이름(별칭 앞섬) · 규격(옵션 값 · 켜쌓기) · 수량 · 분모 · 단가 = 기준 ÷ 배 · 금액 = 반영 · 꼬리표 품셈
|
|
* 자재 줄 = 이름 · 규격 · 수량 · 단위 · 재료비 단가 = 기준단가 · 금액 = 반영 · 별도계상은 비고 「합계 밖」
|
|
* 빈 줄(금액 모름 · 수량 없음) = 합계 줄 「빈 줄 n」 붉게 — 701-4 경고와 같은 줄
|
|
* 규격 열(704-4 · `Labor_Spec`) = 수량 칸 열마다 · 고칠 때 격자 위 규격 줄 + 수량 칸 고침
|
|
* ========================================================================== */
|
|
|
|
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";
|
|
import { specBar, specCells, withSpecs, type SpecEdit } from "./M02_MasterTemplete_Labor_Spec";
|
|
import { pasteInto } from "./M02_MasterTemplete_Labor_Paste";
|
|
|
|
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<string, ItemLogic>,
|
|
): 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;
|
|
const fx = row.고정; // 705-1 고정 줄 — 이름 · 규격 · 단위 = 줄 것 · 비고 = 이음 · 손 단가
|
|
if (fx)
|
|
return {
|
|
name: fx.이름,
|
|
spec: fx.규격 ?? "",
|
|
qty,
|
|
unit: fx.비율 ? "%" : (fx.단위 ?? ""),
|
|
prices: money(line?.기준, line?.배 || 1),
|
|
amounts: money(line?.반영),
|
|
note:
|
|
line?.까닭 ??
|
|
(Object.values(fx.이음 ?? {}).some(Boolean) ? tc("Grid_Linked") : tc("Grid_Hand")),
|
|
tag: HOPYO_TAG.fix,
|
|
};
|
|
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];
|
|
const block: HopyoBlock = {
|
|
key: doc.열,
|
|
name: doc.일위대가번 || doc.열,
|
|
unit: doc.단위 ?? "",
|
|
total: money(calc?.반영합),
|
|
lines,
|
|
...(calc ? {} : { blank: 0 }),
|
|
};
|
|
return withSpecs(block, doc, calc);
|
|
}
|
|
|
|
/** 격자 하나 — `edit` 있으면 Ctrl+V 붙여넣기(705-2 · `Labor_Paste`) · `spec` 이면 위에 규격 줄 · 규격 수량 칸 고침(704-4) */
|
|
export const laborGrid = (
|
|
doc: LaborDoc,
|
|
calc: LaborCalc | null,
|
|
logics: ReadonlyMap<string, ItemLogic>,
|
|
edit?: SpecEdit | false,
|
|
spec = true,
|
|
): HTMLElement => {
|
|
const block = laborBlock(doc, calc, logics);
|
|
if (!edit) return createHopyoGrid([block]).root;
|
|
const paste = (text: string): void => void pasteInto(doc, text, () => edit(false));
|
|
if (!spec) return createHopyoGrid([block], {}, paste).root;
|
|
const grid = createHopyoGrid([block], specCells(block, doc, edit), paste).root;
|
|
const box = document.createElement("div");
|
|
box.append(specBar(doc, calc, edit), grid);
|
|
return box;
|
|
};
|