125 lines
4.0 KiB
TypeScript
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;
|
|
}
|