- 양식에 unit_price.rows(수량은 원단위 줄 from_row · 코드는 work_item_code+variant_from 또는 ref_code) - 서버가 B09 단가표(PriceBook.resolve, 읽기만)로 줄마다 재료·노무·경비 · 금액란 0.1원 버림 · 계금 1원 버림 - 못 푼 줄은 막힘 + 까닭(0 아님), 원단위 줄이 안 선 장은 안 섬 · 단위 다름·하위 구조물(B-AX-ST)은 막힘 - 창구 /structure-sheets/unit-price 를 장 조회와 따로 둠(단가표 첫 조립 14초) - 찰쌓기: 돌쌓기 B-FP-13-04-05#갈래 · 기초잡석 B-FP-12-25 · 모르터는 공종 코드 미정 - 시험 4개 추가 · 전체 1539 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
146 lines
4.9 KiB
TypeScript
146 lines
4.9 KiB
TypeScript
/* =============================================================================
|
|
* B08_Quantity_UI_StructureSheet_UnitPrice.ts
|
|
* 구조물도 장 아래 **일위대가 표**(미리보기) — PLAN 3장 하단 ①②.
|
|
*
|
|
* ⚠ 값을 셈하지 않음 — 서버(`…/structure-sheets/unit-price`)가 B09 단가표로 낸 금액을 적기만.
|
|
* ⚠ 장 조회와 따로 받음 — 단가표 첫 조립이 십여 초라 표가 늦게 차도 위 수량표는 먼저 보임.
|
|
* ⚠ 막힌 줄은 0 이 아니라 까닭을 적고, 하나라도 있으면 합계 앞에 「미완」.
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import { el, num } from "./B08_Quantity_UI_StructureSheet_Formula";
|
|
|
|
interface UnitPriceRow {
|
|
seq: number;
|
|
name: string;
|
|
spec: string;
|
|
ref_code: string;
|
|
unit: string;
|
|
quantity: number | null;
|
|
skipped: boolean;
|
|
reason: string;
|
|
material?: number;
|
|
labor?: number;
|
|
expense?: number;
|
|
total?: number;
|
|
}
|
|
|
|
interface UnitPriceTable {
|
|
code: string;
|
|
name: string;
|
|
unit: string;
|
|
rows: UnitPriceRow[];
|
|
material: number;
|
|
labor: number;
|
|
expense: number;
|
|
total: number;
|
|
blocked: number;
|
|
complete: boolean;
|
|
}
|
|
|
|
/** 금액 칸 — 0.1원 자리까지(금액란 규칙). 못 푼 줄은 빈칸. */
|
|
function won(value: number | undefined): string {
|
|
return value === undefined ? "" : num(value, 1);
|
|
}
|
|
|
|
/** 장 아래 일위대가 칸 — 받는 동안 안내를 두고, 오면 표로 갈음. */
|
|
export function unitPriceSection(projectId: string, sheetKey: string): HTMLElement {
|
|
const wrap = el("div", "b08-grid");
|
|
wrap.append(
|
|
el("p", "b08-grid__caption", "일위대가(미리보기) 불러오는 중… 단가표 첫 조립은 십여 초"),
|
|
);
|
|
void (async () => {
|
|
try {
|
|
const response = await fetch(
|
|
`${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/structure-sheets/unit-price?sheet_key=${encodeURIComponent(sheetKey)}`,
|
|
{ credentials: "include" },
|
|
);
|
|
const payload = (await response.json().catch(() => ({}))) as {
|
|
unit_price?: UnitPriceTable | null;
|
|
message?: string;
|
|
};
|
|
if (!response.ok) throw new Error(payload.message ?? `HTTP ${response.status}`);
|
|
const table = payload.unit_price;
|
|
if (!table) {
|
|
wrap.replaceChildren(el("p", "b08-grid__caption", "이 양식에는 일위대가 줄이 아직 없음"));
|
|
return;
|
|
}
|
|
wrap.replaceChildren(...render(table));
|
|
} catch (error) {
|
|
wrap.replaceChildren(
|
|
el(
|
|
"p",
|
|
"b08-grid__caption b08-grid__caption--warn",
|
|
`일위대가를 불러오지 못함 — ${error instanceof Error ? error.message : ""}`,
|
|
),
|
|
);
|
|
}
|
|
})();
|
|
return wrap;
|
|
}
|
|
|
|
function render(table: UnitPriceTable): HTMLElement[] {
|
|
const total = table.complete
|
|
? `${num(table.total, 0)}원`
|
|
: `미완 — 막힌 줄 ${table.blocked} · 선 줄만 ${num(table.total, 0)}원`;
|
|
const head = el(
|
|
"p",
|
|
"b08-sheet__head",
|
|
`일위대가 ${table.code} · ${table.unit}당 ${total} (미리보기 — 내역 금액은 원가계산이 셈)`,
|
|
);
|
|
const scroller = el("div", "b08-grid__scroll");
|
|
const grid = el("table", "b08-grid__table b08-grid__table--summary");
|
|
const headRow = document.createElement("tr");
|
|
for (const label of [
|
|
"공종·자원",
|
|
"코드",
|
|
"수량",
|
|
"단위",
|
|
"재료비",
|
|
"노무비",
|
|
"경비",
|
|
"합계",
|
|
"비고",
|
|
]) {
|
|
headRow.append(el("th", "", label));
|
|
}
|
|
const thead = document.createElement("thead");
|
|
thead.append(headRow);
|
|
const tbody = document.createElement("tbody");
|
|
for (const row of table.rows) {
|
|
const tr = document.createElement("tr");
|
|
const note = row.skipped ? `안 섬 — ${row.reason}` : row.reason ? `⚠ ${row.reason}` : "";
|
|
// 단가표 이름에 갈래가 이미 들었거나 규격 칸이 코드 자체면 겹쳐 적지 않음.
|
|
const spec =
|
|
row.spec && !row.name.includes(row.spec) && !row.ref_code.includes(row.spec) ? row.spec : "";
|
|
tr.append(
|
|
el("td", "", spec ? `${row.name} (${spec})` : row.name),
|
|
el("td", "", row.ref_code),
|
|
el("td", "", row.quantity === null ? "" : num(row.quantity, 3)),
|
|
el("td", "", row.unit),
|
|
el("td", "", won(row.material)),
|
|
el("td", "", won(row.labor)),
|
|
el("td", "", won(row.expense)),
|
|
el("td", "", won(row.total)),
|
|
el("td", "", note),
|
|
);
|
|
tbody.append(tr);
|
|
}
|
|
const foot = document.createElement("tr");
|
|
foot.append(
|
|
el("td", "", "계"),
|
|
el("td", "", ""),
|
|
el("td", "", ""),
|
|
el("td", "", ""),
|
|
el("td", "", won(table.material)),
|
|
el("td", "", won(table.labor)),
|
|
el("td", "", won(table.expense)),
|
|
el("td", "", table.complete ? num(table.total, 0) : `미완 ${num(table.total, 0)}`),
|
|
el("td", "", ""),
|
|
);
|
|
tbody.append(foot);
|
|
grid.append(thead, tbody);
|
|
scroller.append(grid);
|
|
return [head, scroller];
|
|
}
|