Files
Aislo/B08_wf5_Quantity/B08_wf5_Quantity_UI_Tab_Final.ts
T

125 lines
4.0 KiB
TypeScript

import { B08Api } from "./B08_wf5_Quantity_Api";
import { el, formData, input, money, section, table } from "./B08_wf5_Quantity_UI_Dom";
import { TabContext } from "./B08_wf5_Quantity_Types";
const STAGES: Array<[string, string]> = [
["DIRECT_COST", "직접공사비"],
["NET_COST", "순공사원가"],
["GENERAL_ADMIN", "일반관리비"],
["PROFIT", "이윤"],
["TOTAL_COST", "총원가"],
["VAT", "부가가치세"],
["CONTRACT_COST", "도급공사비"],
["GOVERNMENT_MATERIAL", "관급자재대"],
["PROCUREMENT_FEE", "조달수수료"],
["TOTAL_PROJECT_COST", "총공사비"],
];
export function renderFinal(ctx: TabContext) {
const root = section(
"최종공사비·기준대조",
"직접공사비부터 총공사비까지 동일 계산 실행 ID로 확정합니다.",
);
const run = ctx.state.data.latest;
const button = el(
"button",
"b08-button b08-button--primary",
"전체 단계 계산 실행",
) as HTMLButtonElement;
button.onclick = async () => {
button.disabled = true;
try {
await ctx.calculate();
} finally {
button.disabled = false;
}
};
root.append(button);
if (!run) {
root.append(el("p", "b08-empty", "아직 계산 실행이 없습니다."));
return root;
}
const f = run.final;
root.append(
el("p", "b08-run-id", `계산 실행 ${run.run_id}`),
table(
["단계", "금액"],
[
["직접공사비", money(f.direct_cost)],
["순공사원가", money(f.net_cost)],
["일반관리비", money(f.general_admin)],
["이윤", money(f.profit)],
["총원가", money(f.total_cost)],
["부가가치세", money(f.vat)],
["도급공사비", money(f.contract_cost)],
["관급자재대", money(f.government_material)],
["조달수수료", money(f.procurement_fee)],
["총공사비", money(f.total_project_cost)],
],
),
reconciliationForm(ctx, run.run_id),
);
return root;
}
function reconciliationForm(ctx: TabContext, runId: string) {
const box = section(
"STmate/XLSX 기준금액 대조",
"원본 파일의 단계별 기준금액을 등록하면 최초 불일치 단계를 찾습니다.",
);
const form = el("form", "b08-form-grid") as HTMLFormElement;
form.append(
input("original_filename", "원본 파일명"),
input("sha256", "SHA-256"),
input("source_version", "STmate 버전"),
);
STAGES.forEach(([code, label]) => form.append(input(code, label, "number")));
const button = el("button", "b08-button", "기준 등록·대조") as HTMLButtonElement;
button.type = "submit";
form.append(button);
const result = el("div", "b08-reconcile-result");
form.onsubmit = async (event) => {
event.preventDefault();
const x = formData(form);
const values = STAGES.filter(([code]) => x[code] !== "").map(([stage_code]) => ({
stage_code,
reference_key: "TOTAL",
amount: Number(x[stage_code]),
metadata: {},
}));
const imported: any = await B08Api.importReference(ctx.state.projectId, {
source_type: "MANUAL",
original_filename: x.original_filename,
sha256: x.sha256,
source_version: x.source_version || null,
stored_path: `b08://reference/${x.original_filename}`,
values,
});
const compared: any = await B08Api.reconcile(
ctx.state.projectId,
runId,
imported.source_file_id,
);
result.replaceChildren(
el(
"strong",
compared.status === "MATCHED" ? "is-match" : "is-different",
compared.status === "MATCHED"
? "모든 등록 기준금액 일치"
: `최초 불일치: ${compared.first_difference_stage}`,
),
table(
["단계", "기준", "계산", "차이"],
compared.differences.map((d: any) => [
d.stage_code,
money(d.expected_amount),
money(d.actual_amount),
money(d.difference_amount),
]),
),
);
};
box.append(form, result);
return box;
}