Merge remote-tracking branch 'origin/sub_laptop_3' into sub_laptop_1

This commit is contained in:
2026-10-11 14:06:10 +09:00
11 changed files with 123 additions and 21 deletions
+3 -4
View File
@@ -490,11 +490,10 @@ def compute_bill(
gaps.extend(row.pop("_gaps", []))
row.pop("_home", None)
row["no"] = ""
if row["kind"] == "title":
empty = f" 빈 줄 {row['blank']}" if row["blank"] else ""
row["note"] = ("(제외금액)" if row["key"] == "sec:G" else "") + empty
if row["kind"] == "title": # 257-2 비고 「빈 줄 n」 없음(blank 수는 줄 값 그대로)
row["note"] = "(제외금액)" if row["key"] == "sec:G" else ""
elif row["kind"] == "group":
row["note"] = f"빈 줄 {row['blank']}" if row["blank"] else ""
row["note"] = ""
if row["kind"] != "item" and row["cost"] == "direct" and row["level"] <= 2:
up = row["parent"] # 번호 = 레벨 1 · 2 묶음 차례(1 · 1.1 · 관급 없음)
seq[up] = seq.get(up, 0) + 1
+3 -4
View File
@@ -458,10 +458,9 @@ export function computeBill(
delete row._gaps;
delete row._home;
row.no = "";
if (row.kind === "title")
row.note =
(row.key === "sec:G" ? "(제외금액)" : "") + (row.blank ? ` 빈 줄 ${row.blank}` : "");
else if (row.kind === "group") row.note = row.blank ? `빈 줄 ${row.blank}` : "";
// 257-2 비고 「빈 줄 n」 없음(blank 수는 줄 값 그대로)
if (row.kind === "title") row.note = row.key === "sec:G" ? "(제외금액)" : "";
else if (row.kind === "group") row.note = "";
if (row.kind !== "item" && row.cost === "direct" && row.level <= 2) {
// 번호 = 레벨 1 · 2 묶음 차례(1 · 1.1 · 관급 없음)
const up = row.parent;
+13 -3
View File
@@ -62,7 +62,13 @@ import {
} from "./B09_Estimation_UI_Tabs_Summary";
import { buildCost, buildTotal } from "./B09_Estimation_UI_Tabs_Total";
import { buildSg, buildSgList } from "./B09_Estimation_UI_Tabs_Sangeun";
import { isGroupTab, lineHint, pickClass, renderGroupPanel } from "./B09_Estimation_UI_Tabs_Groups";
import {
isGroupTab,
lineHint,
pickClass,
renderGroupPanel,
unpickOnRelease,
} from "./B09_Estimation_UI_Tabs_Groups";
// 252 초안 탭(공기 등) — draft 폴더가 있으면 등록 · 지우면 빈 목록(오류 없음)
import.meta.glob("./draft/*_UI.ts", { eager: true });
@@ -274,13 +280,17 @@ export function renderB09Estimation(root: HTMLElement): void {
const bill = (spec as BillTable).menu ? (spec as BillTable) : null;
const at = focus === undefined ? -1 : rowIndex(spec.rows, focus);
if (bill) jumpTo(bill.rows, at);
// 257-2 설계내역서 밖 탭도 이동해 온 줄 = 같은 빨강 상자(그 줄 하나 · 줄 누름 · Esc 로 풂)
const range: [number, number] | null = !bill && at >= 0 ? [at, at] : null;
const rangeClass = range ? (i: number) => pickClass(range, i) : undefined;
const table = makeTable(
bill ? (i) => pickedClass(bill.rows[i]) ?? pickClass(jumpRange(), i) : undefined,
bill ? (i) => pickedClass(bill.rows[i]) ?? pickClass(jumpRange(), i) : rangeClass,
);
if (range) unpickOnRelease(table.root);
const level = bill ? levelBar(table.root, bill, (i) => table.edit(i)) : null;
if (level) bars.setTools(level.tools);
panel.replaceChildren(table.root, ...(level ? [level.guide] : []), ...(hint ? [hint] : []));
if (at >= 0) requestAnimationFrame(() => table.focusRow(at, !bill)); // 설계내역서 = 스크롤만(표시 = 빨강)
if (at >= 0) requestAnimationFrame(() => table.focusRow(at, false)); // 스크롤만(표시 = 빨강)
};
const apply = (next: Row, fromServer: boolean): void => {
@@ -203,6 +203,17 @@ function listenKeys(): void {
});
}
/** 257-2 이동해 온 줄 빨강(묶음 아닌 표 · 설계내역서 빼고) — 줄 누름 · 방향키 · Esc 로 풂(다시 그리지 않음) */
export function unpickOnRelease(root: HTMLElement): void {
const clear = (): void => {
for (const tr of root.querySelectorAll("tr.is-pick"))
tr.classList.remove("is-pick", "is-pick-top", "is-pick-end");
};
root.addEventListener("click", clear);
live = { root, unpick: clear };
listenKeys();
}
/** 이 화면 안 보기(기억이 안 돼도 바꾼 보기 유지) */
const chosen = new Map<TabId, GroupView>();
+12 -3
View File
@@ -223,8 +223,9 @@ export const buildTotal: TabBuild = (ctx): TabTable => {
`순자재대 ${won(supply.net ?? null)} + 조달수수료 ${won(supply.fee ?? null)} → 천원 올림`,
{ hand: hand("supply") },
),
row("grand", "바.", "총공사비", sub.grand ?? null),
];
// 257-2 총공사비 = 표 아래 고정 줄(템플릿 total · 몸 줄 아님 · 줄 이동 누름 없음)
const grand = row("grand", "바.", "총공사비", sub.grand ?? null);
const safetyRow = rows.find((r) => r.key === "safety");
if (safetyRow && safety.a !== null && safety.a !== undefined)
safetyRow.note = `A(관급 ÷ 1.1 포함) ${won(safety.a)} · B(× 1.2) ${won(safety.b)} → 작은 쪽`;
@@ -245,6 +246,7 @@ export const buildTotal: TabBuild = (ctx): TabTable => {
text("candidate", "요율 출처", 260),
],
rows,
total: grand,
frozen: 2,
rowClass: (i) => levelClass(rows[i]),
level: nameLevel(rows),
@@ -288,8 +290,7 @@ const COST_ROWS: Array<[string, string, string, string, string?]> = [
["vat", "부가가치세", "", "", "vat"],
["contract", "도급공사비", "", "", "contract"],
["supply", "관급자재대", "", "", "supply"],
["grand", "총공사비", "", "", "grand"],
];
]; // 총공사비(grand) = 표 아래 고정 줄(257-2 · buildCost total)
export const buildCost: TabBuild = (ctx): TabTable => {
const t: Row = ctx.sheets.total || {};
@@ -314,6 +315,14 @@ export const buildCost: TabBuild = (ctx): TabTable => {
level: !part ? 1 : name === "소계" ? 2 : 3,
}));
return {
// 257-2 총공사비 = 표 아래 고정 줄(템플릿 total · 줄 이동 누름 없음)
total: {
key: "grand",
group: "총공사비",
amount: cost.grand ?? null,
share: share.grand ?? null,
note: formula.grand ?? "",
},
columns: [
text("group", "구분", 96),
text("part", "비목", 72),