Files
Aislo/B09_Estimation/B09_Estimation_UI_Tab_Summary.ts
T
eomsangdonandClaude Opus 5 3ce997fa69 feat(b09): 표 화면 넷 — 중기(목록표·중기사용료) · 자재단가대비표 · 집계표 넷 · 목록표 일곱
- 서버: 내역 응답에 집계표 넷(resources)·목록표(lists) — 내역이 쓴 자원을 처음 쓰인 차례로 되모음
  (묶음 줄·구조물도 호표 줄도 구성까지 풀어 셈) · 재료비 집계표에 자재대(사급·관급) 줄 이어 붙임
- 중기시간금액집계표: 단가 열 없이 합계·노무·재료·경비 — 성분마다 반올림한 뒤 합(골든셋 131/144, 합계 한 번 반올림 107/144)
- 재료·노무·경비 집계표: 반올림(수량 × 단가) 골든셋 279/279 — 시험 한 벌 더함
- 중기사용료 호표: 잡품 줄은 수량 칸 율(%) · 단가 칸 밑수(주연료비) · 금액 — 실무 표 그대로. 제잡비·공구손료 줄도 밑수를 단가 칸에
- 자재단가대비표: 슬롯 1~6 단가·페이지 · 채택 칸 굵게 · 최소단가 표시(서버 min_slot)
- 목록표 일곱(일위대가·단가산출근거·중기·재료비·노무비·경비·일식견적): 색인만 — 서버 번호·단가 그대로, 일위대가·산근·중기 줄은 누르면 들어감
- 옛 중기 탭 줄을 새 탭으로 바꿔 끼움(중기경비계산서는 옛 표를 아래에 이어 보임)
- 검증: ORCA — 중기 목록 9 · 굴착기 0.7 호표 합계 109,704 · 잡품 22 % × 21,418.1 = 4,711.9 · 노무 집계 벌목부 9,686,529 · 중기 집계 성분 합 · 시험 1638 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-14 01:56:02 +09:00

119 lines
3.9 KiB
TypeScript

/* =============================================================================
* B09_Estimation_UI_Tab_Summary.ts
* B09 집계표 탭 — 재료비·노무비·경비 수량금액집계표 + 중기시간금액집계표 (실무 시트 · PLAN 12장)
*
* - 칸은 실무 시트 그대로:
* 재료비·노무비·경비 호표 · 명칭 · 규격 · 수량 · 단위 · 단가 · 금액 · 비고
* 중기 호표 · 명칭 · 규격 · 수량 · 단위 · 합계 · 노무비 · 재료비 · 경비 · 비고 (⚠ 단가 열 없음)
* - 값은 서버가 내역에서 되모은 것(`resources`) — 집계표는 **반올림**이라 내역서 본체(절사)와 원 단위로
* 어긋나는 것이 정상(그 문구를 표 아래에 함께 보임).
* - 줄을 누르면 중기는 그 기종의 중기사용료로 들어감.
* ========================================================================== */
import type { B09Tab, B09TabContext } from "./B09_Estimation_UI_Shell_Types";
import {
L,
el,
hint,
numberCell,
plainTable,
quantity,
segmented,
won,
} from "./B09_Estimation_UI_Sheet";
import { loadBill, type BillDto, type ResourceGroup } from "./B09_Estimation_UI_Store";
let group: ResourceGroup = "material";
const GROUPS: Array<[ResourceGroup, Parameters<typeof L>[0]]> = [
["material", "B09_Sheet_Sum_Material"],
["labor", "B09_Sheet_Sum_Labor"],
["expense", "B09_Sheet_Sum_Expense"],
["machine", "B09_Sheet_Sum_Machine"],
];
function draw(ctx: B09TabContext, bill: BillDto): void {
ctx.body.append(
segmented(
GROUPS.map(([key, label]) => [key, L(label)]),
group,
(key) => {
group = key as ResourceGroup;
ctx.body.replaceChildren();
draw(ctx, bill);
},
),
);
const machine = group === "machine";
const front = [
L("B09_Sheet_Col_Sheet"),
L("B09_Sheet_Col_Name"),
L("B09_Sheet_Col_Spec"),
L("B09_Sheet_Col_Quantity"),
L("B09_Sheet_Col_Unit"),
];
const money = machine
? [
L("B09_Sheet_Col_Total"),
L("B09_Sheet_Col_Labor"),
L("B09_Sheet_Col_Material"),
L("B09_Sheet_Col_Expense"),
]
: [L("B09_Sheet_Col_UnitPrice"), L("B09_Sheet_Col_Amount")];
const { wrap, tbody } = plainTable([...front, ...money, L("B09_Sheet_Col_Note")]);
const rows = bill.resources.groups[group];
for (const row of rows) {
const tr = el("tr");
tr.append(
el("td", "", String(row.number)),
el("td", "", row.name),
el("td", "", row.spec),
numberCell(quantity(row.quantity, null)),
el("td", "", row.unit),
);
if (machine) {
tr.append(
numberCell(won(row.amount_krw)),
numberCell(won(row.labor_krw)),
numberCell(won(row.material_krw)),
numberCell(won(row.expense_krw)),
);
tr.classList.add("is-clickable");
tr.title = L("B09_Sheet_Drill");
tr.addEventListener("click", () => ctx.open("machine", row.code));
} else {
tr.append(numberCell(won(row.unit_price_krw)), numberCell(won(row.amount_krw)));
}
tr.append(el("td", "b09s-note", row.note));
tbody.append(tr);
}
ctx.body.append(wrap);
if (rows.length === 0) ctx.body.append(hint(L("B09_Sheet_EmptyGroup")));
ctx.body.append(hint(bill.resources.note));
if (bill.resources.missing.length > 0) {
ctx.body.append(
hint(`${L("B09_Sheet_SumMissing")} ${bill.resources.missing.join(", ")}`, true),
);
}
}
export const summaryTab: B09Tab = {
key: "summary",
label: () => L("B09_Sheet_Tab_Summary"),
render(ctx) {
if (!ctx.projectId) {
ctx.body.append(hint(L("B09_Sheet_NoProject")));
return;
}
ctx.body.append(hint(L("B09_Sheet_Loading")));
loadBill(ctx.projectId)
.then((bill) => {
ctx.body.replaceChildren();
draw(ctx, bill);
})
.catch((error: Error) => {
ctx.body.replaceChildren(hint(`${L("B09_Sheet_LoadFailed")} ${error.message}`, true));
});
},
};