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:
@@ -7,7 +7,8 @@
|
||||
* 맨 위 = 요율 고르는 값(추정가격 · 공사기간 · 환경보전비 공종 — 누르면 도로 ↔ 그 밖)(166-1)
|
||||
* 요율 = 자동(M01 조달청 제비율 · 짝 셈이 고름) · 사용자가 적으면 앞섬 · 출처 칸 누름 = 자동으로
|
||||
* 고칠 칸 = 요율 · 손입력(기초액 · 이윤 보정 · 부가세 조정 · 조달수수료율) ·
|
||||
* 엑셀에 없는 현행 비목 = 줄만(빈칸)
|
||||
* 현행 비목(708-2) = 경비 묶음 안 · 요율 빈칸 = 해당 없음(0) · 공사이행보증 = 금액 손입력 ·
|
||||
* 요율 출처 = 사용자 · 불러옴(707-2 `rates_imported`) — 요율을 고치면 사용자
|
||||
* 원가계산서 = 총괄 값 거울 + 구성비 · 고칠 칸 = 간접재료비 · 작업설 · 줄 누름 = 총괄 그 줄로
|
||||
* 레벨(185-1 · 201-1) = 줄 `level`(화면이 정함 · 줄 짜임이 이 파일에 있음 · 셈 짝은 값만) →
|
||||
* 공용 표 level 옵션(명칭 칸 들여쓰기 · 보기 전용) · 1단 = 굵게(is-title)
|
||||
@@ -42,8 +43,22 @@ const HAND: Record<string, [string, string]> = {
|
||||
supply: ["procurement_fee_pct", "조달수수료율(%)"],
|
||||
indirect_material: ["indirect_material", "간접재료비(원)"],
|
||||
scrap: ["scrap", "작업설 부산물(원)"],
|
||||
performance: ["performance_fee", "공사이행보증수수료(원)"],
|
||||
};
|
||||
const RATE_KEYS = new Set([...RATE_LINES, ...TAIL_LINES].map(([key]) => key));
|
||||
/** 요율 칸을 고치는 줄 — 공사이행보증 = 금액 손입력(율 칸 없음) */
|
||||
const RATE_KEYS = new Set(
|
||||
[...RATE_LINES, ...TAIL_LINES].map(([key]) => key).filter((key) => key !== "performance"),
|
||||
);
|
||||
|
||||
/** 요율 줄 사용자 값 고침 · 지움 — 불러옴 표시도 뗌 */
|
||||
function setRate(estimate: Row, key: string, v: number | null): void {
|
||||
const rates: Row = { ...(estimate.rates || {}) };
|
||||
if (v === null) delete rates[key];
|
||||
else rates[key] = v;
|
||||
estimate.rates = rates;
|
||||
if (Array.isArray(estimate.rates_imported))
|
||||
estimate.rates_imported = estimate.rates_imported.filter((k: string) => k !== key);
|
||||
}
|
||||
|
||||
const ENV_NAME: Record<string, string> = { road: "도로", other: "그 밖의 토목" };
|
||||
|
||||
@@ -52,12 +67,8 @@ function editEstimate(ctx: TabContext, rowKey: string, column: string, value: st
|
||||
const v = parseNumber(value);
|
||||
if (v === undefined) return;
|
||||
const estimate: Row = { ...(ctx.user.estimate || {}) };
|
||||
if (column === "rate" && RATE_KEYS.has(rowKey)) {
|
||||
const rates: Row = { ...(estimate.rates || {}) };
|
||||
if (v === null) delete rates[rowKey];
|
||||
else rates[rowKey] = v;
|
||||
estimate.rates = rates;
|
||||
} else if (HAND[rowKey]) {
|
||||
if (column === "rate" && RATE_KEYS.has(rowKey)) setRate(estimate, rowKey, v);
|
||||
else if (HAND[rowKey]) {
|
||||
const field = HAND[rowKey][0];
|
||||
if (v === null) delete estimate[field];
|
||||
else estimate[field] = v;
|
||||
@@ -71,11 +82,9 @@ function clickEstimate(ctx: TabContext, rowKey: string, column: string, from: un
|
||||
if (rowKey === "in_env") {
|
||||
if (estimate.environment_kind === "road") delete estimate.environment_kind;
|
||||
else estimate.environment_kind = "road";
|
||||
} else if (column === "candidate" && from === "user") {
|
||||
const rates: Row = { ...(estimate.rates || {}) };
|
||||
delete rates[rowKey];
|
||||
estimate.rates = rates;
|
||||
} else return;
|
||||
} else if (column === "candidate" && (from === "user" || from === "import"))
|
||||
setRate(estimate, rowKey, null);
|
||||
else return;
|
||||
ctx.setUser({ ...ctx.user, estimate });
|
||||
}
|
||||
|
||||
@@ -122,8 +131,13 @@ function billRows(bill: Row | undefined): DataRow[] {
|
||||
];
|
||||
}
|
||||
|
||||
function rateText(formula: string, rate: Num): string {
|
||||
return rate === null ? `${formula}(요율 빈칸)` : formula.replace("율", `${rate}%`);
|
||||
function rateText(formula: string, rate: Num, key = ""): string {
|
||||
if (key === "performance") return formula;
|
||||
if (rate === null)
|
||||
return CURRENT_ONLY.includes(key)
|
||||
? `해당 없음(요율 빈칸) — ${formula}`
|
||||
: `${formula}(요율 빈칸)`;
|
||||
return formula.replace("율", `${rate}%`);
|
||||
}
|
||||
|
||||
export const buildTotal: TabBuild = (ctx): TabTable => {
|
||||
@@ -137,8 +151,10 @@ export const buildTotal: TabBuild = (ctx): TabTable => {
|
||||
/** 출처 칸 글 — 사용자 · 자동 까닭 · 자동 없음 */
|
||||
const source = (key: string): string => {
|
||||
const r: Row = picked[key] || {};
|
||||
if (r.from === "user")
|
||||
return `사용자 값 · 누르면 자동${r.auto === null || r.auto === undefined ? "(없음)" : ` ${r.auto}%`}으로`;
|
||||
if (key === "performance") return "금액 직접 입력(율 셈 없음)";
|
||||
if (r.from === "user" || r.from === "import")
|
||||
return `${r.from === "user" ? "사용자" : "불러옴"} 값 · 누르면 자동${r.auto === null || r.auto === undefined ? "(없음)" : ` ${r.auto}%`}으로`;
|
||||
if (CURRENT_ONLY.includes(key)) return "사용자 · 불러옴만(자동 없음)";
|
||||
return r.from === "auto" ? `자동 — ${r.why}` : `자동 없음${r.why ? ` — ${r.why}` : ""}`;
|
||||
};
|
||||
const hand = (key: string): Num => {
|
||||
@@ -165,7 +181,7 @@ export const buildTotal: TabBuild = (ctx): TabTable => {
|
||||
...extra,
|
||||
});
|
||||
const rateRow = ([key, name, rt, formula]: [string, string, string, string], no: number) =>
|
||||
row(key, "", `${no}. ${name}`, line[key] ?? null, rateText(formula, rate(key)), {
|
||||
row(key, "", `${no}. ${name}`, line[key] ?? null, rateText(formula, rate(key), key), {
|
||||
rate: rate(key),
|
||||
candidate: source(key),
|
||||
from: picked[key]?.from ?? null,
|
||||
@@ -204,11 +220,6 @@ export const buildTotal: TabBuild = (ctx): TabTable => {
|
||||
expense: sums.expense ?? null,
|
||||
}),
|
||||
...RATE_LINES.map((spec, i) => rateRow(spec, i + 1)),
|
||||
...CURRENT_ONLY.map(([key, name, rt]) =>
|
||||
row(key, "", name, null, "현행 비목 · 엑셀에 없음 — 밑수 · 고르는 값 미정(줄만)", {
|
||||
candidate: cand[rt] ?? rt,
|
||||
}),
|
||||
),
|
||||
row("net_cost", "나.", "소계(순공사원가)", sub.net_cost ?? null),
|
||||
rateRow(TAIL_LINES[0], RATE_LINES.length + 1),
|
||||
row("da", "다.", "소계", sub.da ?? null),
|
||||
@@ -302,7 +313,7 @@ export const buildCost: TabBuild = (ctx): TabTable => {
|
||||
const formula: Record<string, string> = Object.fromEntries(
|
||||
[...RATE_LINES, ...TAIL_LINES].map(([key, , , f]) => [
|
||||
key,
|
||||
rateText(f, picked[key]?.rate ?? null),
|
||||
rateText(f, picked[key]?.rate ?? null, key),
|
||||
]),
|
||||
);
|
||||
const rows: DataRow[] = COST_ROWS.map(([key, group, part, name, link]) => ({
|
||||
@@ -339,7 +350,7 @@ export const buildCost: TabBuild = (ctx): TabTable => {
|
||||
level: nameLevel(rows),
|
||||
mergeSame: ["group", "part"],
|
||||
banner: unfinished(ctx.sheets) ?? undefined,
|
||||
hint: "고칠 칸은 간접재료비 · 줄을 누르면 총괄 그 줄로 이동",
|
||||
hint: "고칠 칸은 간접재료비 · 작업설 · 공사이행보증수수료 · 줄을 누르면 총괄 그 줄로 이동",
|
||||
editable: (i, key) => key === "amount" && String(rows[i]?.key) in HAND,
|
||||
onEdit: (i, key, value) => editEstimate(ctx, String(rows[i]?.key), key, value),
|
||||
link: (i) => {
|
||||
|
||||
Reference in New Issue
Block a user