feat(B09 원가계산서 · 708-2): 현행 비목 다섯 셈 — 퇴직공제 · 임금채권 · 석면 · 하도급 요율 셈 · 공사이행보증 금액 손입력

- 셈 짝(Calc_Total py ↔ ts) — STmate 일반형식 식 · 자리(지식DB 12 · 35 분석) · 경비 묶음 안 · 나.소계에 듦
  퇴직공제부금비 = 직접노무비 × 율 · 임금채권 · 석면 = (직노 + 간노) × 율 · 하도급 = 순공사비 × 율 ·
  공사이행보증 = 금액 직접 입력(performance_fee) · 자동 요율 없음 · 요율 빈칸 = 해당 없음(0)
- 요율 출처 「불러옴」 = rates_imported(707-2 불러오기가 실음 · 요율 고치면 사용자)
- 707-2 불러오기 — 「셈 없음」 알림 뺌 · 공사이행보증 줄 금액 = 손입력 칸
- 총괄 · 원가계산서 탭 · 인쇄 미리보기 = 같은 줄 짜기
- 낡은 시험 고침 — 현행 비목 기대값 빈칸 → 0 · 퇴직공제 요율 칸 고침 가능 · 거창 총공사비 = 엑셀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 21:27:40 +09:00
co-authored by Claude Opus 5.5
parent a5179e4dfe
commit 472bf98ba2
9 changed files with 161 additions and 79 deletions
+25 -19
View File
@@ -9,13 +9,14 @@
* 입력 sums = 설계내역서 비목 합(145-3 `sheets.bill.sums`) · estimate = 사용자 값(`user.estimate`) ·
* table = M01 요율 압축 표(payload `rate_table`) · year = 연금 해 — 자동 고르기(166-1) = 파이썬 짝 머리 글
* 식 · 끝수 = 엑셀 총괄 49~67 그대로 · 빈칸 규칙 = 파이썬 짝 머리 글 그대로.
* 현행 비목 다섯(708-2) = STmate 일반형식 식 · 자리(경비 묶음 안 · 나.소계에 듦) — 근거 = 파이썬 짝 머리 글.
* ========================================================================== */
import { num, rounddown, roundup, total, type Row } from "./B09_Estimation_Calc_Base";
type Num = number | null;
/** 제비율 줄 [키, 이름, M01 요율 후보 키, 산식 글] — 엑셀 총괄 49~58 차례 · 나.소계에 듦 */
/** 제비율 줄 [키, 이름, M01 요율 후보 키, 산식 글] — 엑셀 총괄 49~58 차례 + 현행 비목 · 나.소계에 듦 */
export const RATE_LINES: Array<[string, string, string, string]> = [
["indirect_labor", "간접노무비", "RT000008", "직접노무비 × 율"],
["accident", "산재보험료", "RT000001", "(직접노무비 + 간접노무비) × 율"],
@@ -23,19 +24,18 @@ export const RATE_LINES: Array<[string, string, string, string]> = [
["health", "건강보험료", "RT000003", "직접노무비 × 율"],
["care", "노인장기요양보험료", "RT000005", "건강보험료 × 율"],
["pension", "연금보험료", "RT000004", "직접노무비 × 율"],
["retirement", "퇴직공제부금비", "RT000011", "직접노무비 × 율"],
["safety", "산업안전보건관리비", "RT000006", "A · B 중 작은 쪽 + 기초액"],
["other", "기타경비", "RT000009", "(직접노무비 + 간접노무비 + 재료비) × 율"],
["performance", "공사이행보증수수료", "RT000016", "금액 직접 입력"],
["subcontract", "하도급대금지급보증수수료", "RT000014", "순공사비 × 율"],
["environment", "환경보전비", "RT000010", "순공사비 × 율"],
["machine", "건설기계대여금지급보증", "RT000015", "순공사비 × 율"],
["asbestos", "석면피해구제분담금", "RT000013", "(직접노무비 + 간접노무비) × 율"],
["wage_claim", "임금채권보장분담금", "RT000012", "(직접노무비 + 간접노무비) × 율"],
];
/** 엑셀에 없는 현행 비목 — 줄만 두고 빈칸 */
export const CURRENT_ONLY: Array<[string, string, string]> = [
["retirement", "퇴직공제부금비", "RT000011"],
["wage_claim", "임금채권보장분담금", "RT000012"],
["asbestos", "석면피해구제분담금", "RT000013"],
["subcontract", "하도급대금지급보증수수료", "RT000014"],
["performance", "공사이행보증수수료", "RT000016"],
];
/** 현행 비목 — 요율 빈칸 = 해당 없음(0) · 공사이행보증 = 금액 손입력(율 칸 없음) */
export const CURRENT_ONLY = ["retirement", "subcontract", "asbestos", "wage_claim", "performance"];
/** 소계 아래 줄 */
export const TAIL_LINES: Array<[string, string, string, string]> = [
["overhead", "일반관리비", "RT000017", "나.소계 × 율"],
@@ -113,6 +113,7 @@ export function computeTotal(
const s: Row = sums && typeof sums === "object" ? sums : {};
const est: Row = estimate && typeof estimate === "object" ? estimate : {};
const ratesIn: Row = est.rates && typeof est.rates === "object" ? est.rates : {};
const imported: unknown[] = Array.isArray(est.rates_imported) ? est.rates_imported : [];
const hand = (key: string): number => num(est[key]) || 0;
const material = num(s.material);
@@ -138,7 +139,14 @@ export function computeTotal(
const autoRate: Num = got.rate ?? null;
rates[key] = {
rate: mine === null ? autoRate : mine,
from: mine !== null ? "user" : autoRate !== null ? "auto" : null,
from:
mine !== null
? imported.includes(key)
? "import"
: "user"
: autoRate !== null
? "auto"
: null,
auto: autoRate,
why: got.why || "",
};
@@ -168,6 +176,12 @@ export function computeTotal(
line.other = pct(total([labor, line.indirect_labor, material]), rate.other);
line.environment = pct(direct, rate.environment);
line.machine = pct(direct, rate.machine);
line.retirement = pct(labor, rate.retirement);
line.subcontract = pct(direct, rate.subcontract);
line.asbestos = pct(totalLabor, rate.asbestos);
line.wage_claim = pct(totalLabor, rate.wage_claim);
for (const key of CURRENT_ONLY) if (rate[key] === null) line[key] = 0; // 요율 빈칸 = 해당 없음
line.performance = hand("performance_fee");
const netCost = total([direct, ...RATE_LINES.map(([key]) => line[key])]);
line.overhead = pct(netCost, rate.overhead);
const da = total([netCost, line.overhead]);
@@ -193,15 +207,7 @@ export function computeTotal(
indirect_labor: line.indirect_labor,
labor: totalLabor,
expense,
accident: line.accident,
employment: line.employment,
health: line.health,
care: line.care,
pension: line.pension,
safety: line.safety,
other: line.other,
environment: line.environment,
machine: line.machine,
...Object.fromEntries(RATE_LINES.slice(1).map(([key]) => [key, line[key]])),
expense_sum: total([
expense,
...RATE_LINES.filter(([key]) => key !== "indirect_labor").map(([key]) => line[key]),