Files
Aislo/B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts
T
eomsangdonandClaude Opus 5.5 9633e97c3c feat(723-4): 723-1 뒤 B09 일위대가 수량 연결 마무리 — 알약 값 쓴 SC 만 덮개 나눔 · SK 속 SC · 공사 불러오기 수량 · 비고 경고
- Labor_Source = 덮개 열쇠 `C:참조|S:사본` 은 알약 값 쓴 SC 블록만(빈 수량 · 옛 손 = `C:참조` 그대로 = 703-1 지난 공사 가져옴 꼴) ·
  SK 속 SC 안 줄 수량 없음 = 엔진엔 0 값묶음(`_Loose`) · 그 줄 = 사전조합 블록(이 공사 입력) — SK 통째 빈칸 안 됨
- Bill_Hopyo(707-7 공사 통째 불러오기) = 이 공사 엑셀 수량을 굳힌 손으로 남김(723-1 `with_pills` 수량 걷기는 M02 저장용)
- Daega_Personal 원천 = 블록 origin 참조(덮개 열쇠 꼬리 무관) · 내 자료집 저장 알림 글 = 줄 수량 저장 안 함
- 비고(펼침 · 일위대가표) = 수량 없는 SC 안 줄은 「산근 n호표 · 수량 없음 — 이 공사에서 입력」(`noteOf`)
- 낡은 시험 고침(사유 = 723-1 SC 안 줄 수량 저장 안 함 · 이 공사 입력 · 알약으로 이음):
  test_204_2(서고 SC 수량 없음 · ask) · test_703(자료집 저장 손 걷힘) · test_719_1(서고 사본 = 알약 값으로 같은 값 · 없으면 수량 없음) ·
  test_719_2(SK 속 SC = 빈 줄 → 이 공사 입력 1 = M02 알약 1 금액) · test_702_3(서고 SC 빈 줄 3 → 입력 뒤 금액)
- test_723_4 = 구조물 사본 면적 알약 굳힘 · 설계 연동 · 알약 안 이음 = 빈 줄 시험 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:25:30 +09:00

132 lines
5.0 KiB
TypeScript

/* =============================================================================
* B09_Estimation_UI_Tabs_Labor.ts
* B09 대가 탭 둘(PLAN 145-2) — 일위대가목록표 · 일위대가표(실무 엑셀 꼴 · 값 = `B09_Estimation_Calc_Labor`).
*
* 목록표 = 호표 한 줄(합계 · 노무비 · 재료비 · 경비) · 줄 누름 = 그 블록으로
* 일위대가표 = 공용 일위대가 격자(704-3 · `ui_template_hopyo_grid` · 머리 · [품셈 인용] · 줄 · 합계 · 줄 꼬리표) ·
* 줄 누름 = 단가를 낸 탭 · 줄(대가 줄 = 그 블록)
* 줄 꼴(155-2) = 머리 · 합계 굵게(`is-title`) · 인용 · 줄 = 들여쓰기 「ㄴ」(2단 · 겹 대가는 안 펼침)
* 연결된 구조물 조합 사본이 없으면 빈 표 「연결 없음」
* 빈 줄(166-6) = 합계 = 아는 줄 합 · 옆 「빈 줄 n」 · 풍선 = 빈 줄 이름 · 내역 단가엔 안 넘김
* ========================================================================== */
import type { DataRow } from "@ui/ui_template_data_table";
import type { Row } from "./B09_Estimation_Calc_Base";
import {
blockLevel,
money,
noteOf,
text,
type Link,
type TabBuild,
type TabId,
type TabTable,
} from "./B09_Estimation_UI_Tabs";
import { raw } from "./B09_Estimation_UI_Tabs_Sangeun";
import { HOPYO_TAG, hopyoCellClass, hopyoGrid } from "@ui/ui_template_hopyo_grid";
const EMPTY =
"일위대가 없음 — 이 공사에 고른 구조물 조합이 없음 · B05 · B06 에서 구조물 조합을 고르고 [저장] · 또는 설계내역서 오른쪽 재료 창고";
const BLANK = "빈 줄은 금액을 모르는 줄(합계는 아는 줄만)";
/** 「빈 줄 n」 작은 글 · 풍선 = 빈 줄 이름 */
const blankNote = (b: Row): string => (b.blank ? `빈 줄 ${b.blank}` : "");
const blankTip = (b: Row | undefined): string | null =>
b?.blank ? `빈 줄 — ${(b.blank_names as string[]).join(" · ")}` : null;
/** 블록 줄 → 단가를 낸 탭 */
const LINE_TAB: Record<string, TabId> = {
labor: "labor",
material: "material",
machine: "machine",
daega: "dg",
sangeun: "sg",
};
export const buildDaegaList: TabBuild = ({ sheets }): TabTable | string => {
const rows: DataRow[] = (sheets.daega || []).map((r: Row) => ({
...r,
no: `대가${r.no}`,
blank_note: blankNote(r),
}));
if (!rows.length) return EMPTY;
return {
columns: [
text("no", "호표", 80),
text("kind", "꼴", 70),
text("name", "명칭", 240),
text("spec", "규격", 200),
text("unit", "단위", 56),
money("sum", "합계"),
text("blank_note", "", 64),
money("labor", "노무비"),
money("material", "재료비"),
money("expense", "경비"),
],
rows,
frozen: 3,
hint: `줄을 누르면 그 블록으로 이동 · ${BLANK}`,
link: (i: number): Link | null => (rows[i] ? { tab: "dg", key: String(rows[i].key) } : null),
tip: (i, key) =>
key === "spec"
? raw(rows[i]?.spec, rows[i]?.spec_raw)
: key === "blank_note" || key === "sum"
? blankTip(rows[i])
: null,
};
};
export const buildDaegaBlocks: TabBuild = ({ sheets }): TabTable | string => {
const blocks: Row[] = sheets.daega_blocks || [];
if (!blocks.length) return EMPTY;
// 704-3 — 공용 일위대가 격자(M02 카드 · 상세와 같은 열 · 줄 꼴) · 값 = 서버 `Calc_Labor` 그대로
const grid = hopyoGrid(
blocks.map((b) => ({
key: b.id,
no: b.no,
name: b.name,
spec: b.spec,
spec_raw: b.spec_raw,
unit: b.unit,
kind: b.kind,
cite: b.cite,
total: { sum: b.sum, labor: b.labor, material: b.material, expense: b.expense },
blank: b.blank ?? 0,
blank_names: b.blank_names ?? [],
lines: b.lines.map((ln: Row) => ({
name: ln.name || "",
spec: ln.spec || "",
spec_raw: ln.spec_raw,
qty: ln.qty,
unit: ln.unit || "",
prices: { sum: ln.price, ...ln.prices },
amounts: { sum: ln.amount, ...ln.amounts },
note: noteOf(ln),
tag: ln.kind === "fixed" ? HOPYO_TAG.fix : HOPYO_TAG.pum, // 705-1 고정 줄
})),
})),
);
const { rows, titles } = grid;
const links: Array<Link | null> = rows.map((_, i) => {
const at = grid.at.get(i);
const ln: Row | undefined = at && blocks[at[0]].lines[at[1]];
const tab = ln && LINE_TAB[ln.kind];
return tab && ln.key ? { tab, key: String(ln.key) } : null;
});
return {
columns: grid.columns,
rows,
frozen: 1,
hint: `줄을 누르면 단가를 낸 탭으로 이동 · ${BLANK}`,
link: (i: number) => links[i] ?? null,
rowClass: (i: number) => (titles.has(i) ? "is-title" : null),
cellClass: (i: number, key: string) => hopyoCellClass(rows, i, key),
level: blockLevel("name", (i) => titles.has(i)),
tip: (i: number, key: string) =>
key === "spec"
? raw(rows[i]?.spec, rows[i]?.spec_raw)
: key === "note" || key === "sum"
? blankTip(rows[i])
: null,
};
};