- 서버: 내역 응답에 집계표 넷(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
88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
/* =============================================================================
|
|
* B09_Estimation_UI_Tab_PriceCompare.ts
|
|
* B09 자재단가대비표 탭 — 원천 슬롯 여섯 · 채택 · 최소단가 (실무 `자재단가대비표` · STmate `wM_Boxa` 보기)
|
|
*
|
|
* - 칸: 호표 · 명칭 · 규격 · 단위 · 슬롯 1~5(단가 · 페이지) · 적용 단가(6번) · 비고.
|
|
* - 채택 슬롯 = 굵게·색 · 최소단가 = 「최소」 표시(서버 `min_slot`). 값 없는 슬롯은 빈칸(0 아님).
|
|
* - 채택 바꾸기(「변동없음 / 1 단가~5 단가 / 최소단가」 일괄)는 2차 — 저장 모양 판정 뒤.
|
|
* ========================================================================== */
|
|
|
|
import type { B09Tab, B09TabContext } from "./B09_Estimation_UI_Shell_Types";
|
|
import { L, el, hint, numberCell, sheetTable, won } from "./B09_Estimation_UI_Sheet";
|
|
import { loadPriceCompare, type PriceCompareDto } from "./B09_Estimation_UI_Store";
|
|
|
|
function head(slotNames: string[]): HTMLElement {
|
|
const thead = el("thead");
|
|
const top = el("tr");
|
|
const bottom = el("tr");
|
|
for (const label of [
|
|
L("B09_Sheet_Col_Sheet"),
|
|
L("B09_Sheet_Col_Name"),
|
|
L("B09_Sheet_Col_Spec"),
|
|
L("B09_Sheet_Col_Unit"),
|
|
]) {
|
|
const th = el("th", "", label);
|
|
th.rowSpan = 2;
|
|
top.append(th);
|
|
}
|
|
slotNames.forEach((name, index) => {
|
|
const th = el("th", "", `${index + 1} ${name}`);
|
|
th.colSpan = 2;
|
|
top.append(th);
|
|
bottom.append(
|
|
el("th", "", L("B09_Sheet_Col_UnitPrice")),
|
|
el("th", "", L("B09_Sheet_Col_Page")),
|
|
);
|
|
});
|
|
const note = el("th", "", L("B09_Sheet_Col_Note"));
|
|
note.rowSpan = 2;
|
|
top.append(note);
|
|
thead.append(top, bottom);
|
|
return thead;
|
|
}
|
|
|
|
function draw(ctx: B09TabContext, data: PriceCompareDto): void {
|
|
const table = data.material_comparison;
|
|
const { wrap, tbody } = sheetTable(head(table.slot_names));
|
|
table.rows.forEach((row, index) => {
|
|
const tr = el("tr");
|
|
tr.append(
|
|
el("td", "", String(index + 1)),
|
|
el("td", "", row.name),
|
|
el("td", "", row.spec),
|
|
el("td", "", row.unit),
|
|
);
|
|
row.slots.forEach((slot, slotIndex) => {
|
|
const price = numberCell(won(slot.price_krw));
|
|
if (slot.adopted) price.classList.add("b09s-adopted");
|
|
if (row.min_slot === slotIndex + 1) price.append(el("span", "b09s-min", L("B09_Sheet_Min")));
|
|
tr.append(price, el("td", "", slot.source_note));
|
|
});
|
|
tr.append(el("td", "b09s-note", row.note));
|
|
tbody.append(tr);
|
|
});
|
|
ctx.body.append(el("div", "b09s-title", L("B09_Sheet_Tab_PriceCompare")), wrap);
|
|
if (table.rows.length === 0) ctx.body.append(hint(L("B09_Sheet_EmptyGroup")));
|
|
for (const note of table.notes) ctx.body.append(hint(note));
|
|
}
|
|
|
|
export const priceCompareTab: B09Tab = {
|
|
key: "price_compare",
|
|
label: () => L("B09_Sheet_Tab_PriceCompare"),
|
|
render(ctx) {
|
|
if (!ctx.projectId) {
|
|
ctx.body.append(hint(L("B09_Sheet_NoProject")));
|
|
return;
|
|
}
|
|
ctx.body.append(hint(L("B09_Sheet_Loading")));
|
|
loadPriceCompare(ctx.projectId)
|
|
.then((data) => {
|
|
ctx.body.replaceChildren();
|
|
draw(ctx, data);
|
|
})
|
|
.catch((error: Error) => {
|
|
ctx.body.replaceChildren(hint(`${L("B09_Sheet_LoadFailed")} ${error.message}`, true));
|
|
});
|
|
},
|
|
};
|