- 서버: 내역 응답에 집계표 넷(resources)·목록표(lists) — 내역이 쓴 자원을 처음 쓰인 차례로 되모음 (묶음 줄·구조물도 호표 줄도 구성까지 풀어 셈) · 재료비 집계표에 자재대(사급·관급) 줄 이어 붙임 - 중기시간금액집계표: 단가 열 없이 합계·노무·재료·경비 — 성분마다 반올림한 뒤 합(골든셋 131/144, 합계 한 번 반올림 107/144) - 재료·노무·경비 집계표: 반올림(수량 × 단가) 골든셋 279/279 — 시험 한 벌 더함 - 중기사용료 호표: 잡품 줄은 수량 칸 율(%) · 단가 칸 밑수(주연료비) · 금액 — 실무 표 그대로. 제잡비·공구손료 줄도 밑수를 단가 칸에 - 자재단가대비표: 슬롯 1~6 단가·페이지 · 채택 칸 굵게 · 최소단가 표시(서버 min_slot) - 목록표 일곱(일위대가·단가산출근거·중기·재료비·노무비·경비·일식견적): 색인만 — 서버 번호·단가 그대로, 일위대가·산근·중기 줄은 누르면 들어감 - 옛 중기 탭 줄을 새 탭으로 바꿔 끼움(중기경비계산서는 옛 표를 아래에 이어 보임) - 검증: ORCA — 중기 목록 9 · 굴착기 0.7 호표 합계 109,704 · 잡품 22 % × 21,418.1 = 4,711.9 · 노무 집계 벌목부 9,686,529 · 중기 집계 성분 합 · 시험 1638 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
153 lines
5.0 KiB
TypeScript
153 lines
5.0 KiB
TypeScript
/* =============================================================================
|
|
* B09_Estimation_UI_Tab_Lists.ts
|
|
* B09 목록표 탭 — 일곱 목록표를 한 자리에 (실무 `…목록표` 시트 · PLAN 12장)
|
|
*
|
|
* 일위대가 · 단가산출근거 · 중기 호표 · 명칭 · 규격 · 단위 · 합계 · 노무비 · 재료비 · 경비 · 비고
|
|
* 재료비 · 노무비 · 경비 · 일식견적 호표 · 명칭 · 규격 · 단위 · 단가 · 비고
|
|
*
|
|
* - ⚠ 목록표는 **색인**일 뿐 — 계산 쌍이 없음. 서버가 실은 번호·코드·단가를 그대로 늘어놓음.
|
|
* 번호는 내역에 처음 쓰인 차례(서버가 낼 때마다 매김).
|
|
* - 일위대가·산근·중기 줄을 누르면 그 호표로 들어감.
|
|
* ========================================================================== */
|
|
|
|
import type { B09Tab, B09TabContext } from "./B09_Estimation_UI_Shell_Types";
|
|
import { L, el, hint, numberCell, plainTable, segmented, won } from "./B09_Estimation_UI_Sheet";
|
|
import { loadBill, type BillDto, type ResourceGroup } from "./B09_Estimation_UI_Store";
|
|
|
|
type ListKey = "unit_price" | "price_basis" | ResourceGroup;
|
|
let active: ListKey = "unit_price";
|
|
|
|
const LISTS: Array<[ListKey, Parameters<typeof L>[0]]> = [
|
|
["unit_price", "B09_Sheet_UnitPriceList"],
|
|
["price_basis", "B09_Sheet_BasisList"],
|
|
["machine", "B09_Sheet_MachineList"],
|
|
["material", "B09_Sheet_List_Material"],
|
|
["labor", "B09_Sheet_List_Labor"],
|
|
["expense", "B09_Sheet_List_Expense"],
|
|
["lumpsum", "B09_Sheet_List_Lumpsum"],
|
|
];
|
|
|
|
interface ListRow {
|
|
label: string;
|
|
name: string;
|
|
spec: string;
|
|
unit: string;
|
|
money: string[];
|
|
note: string;
|
|
open?: [string, string];
|
|
}
|
|
|
|
function rowsOf(bill: BillDto, key: ListKey): ListRow[] {
|
|
if (key === "unit_price") {
|
|
return bill.unit_price_sheet.entries.map((entry) => ({
|
|
label: entry.label,
|
|
name: entry.name,
|
|
spec: entry.spec,
|
|
unit: entry.unit,
|
|
money: [entry.total_krw ?? "", entry.labor_krw, entry.material_krw, entry.expense_krw],
|
|
note: entry.unconfirmed ? `${L("B09_Sheet_Unconfirmed")} ${entry.unconfirmed}` : "",
|
|
open: ["unit_price", entry.code],
|
|
}));
|
|
}
|
|
if (key === "price_basis") {
|
|
return bill.price_basis.entries.map((entry) => ({
|
|
label: `${L("B09_Sheet_Basis_Long")} ${entry.number}${L("B09_Sheet_Basis_Suffix")}`,
|
|
name: entry.name,
|
|
spec: entry.spec,
|
|
unit: entry.unit,
|
|
money: [entry.unit_price_krw ?? "", entry.labor_krw, entry.material_krw, entry.expense_krw],
|
|
note: "",
|
|
open: ["price_basis", entry.code],
|
|
}));
|
|
}
|
|
return bill.lists[key].map((row) => ({
|
|
label: String(row.number),
|
|
name: row.name,
|
|
spec: row.spec,
|
|
unit: row.unit,
|
|
money:
|
|
key === "machine"
|
|
? [
|
|
row.unit_price_krw ?? "",
|
|
row.unit_labor_krw ?? "",
|
|
row.unit_material_krw ?? "",
|
|
row.unit_expense_krw ?? "",
|
|
]
|
|
: [row.unit_price_krw ?? ""],
|
|
note: row.note,
|
|
open: key === "machine" ? ["machine", row.code] : undefined,
|
|
}));
|
|
}
|
|
|
|
function draw(ctx: B09TabContext, bill: BillDto): void {
|
|
ctx.body.append(
|
|
segmented(
|
|
LISTS.map(([key, label]) => [key, L(label)]),
|
|
active,
|
|
(key) => {
|
|
active = key as ListKey;
|
|
ctx.body.replaceChildren();
|
|
draw(ctx, bill);
|
|
},
|
|
),
|
|
);
|
|
const fourWay = active === "unit_price" || active === "price_basis" || active === "machine";
|
|
const money = fourWay
|
|
? [
|
|
L("B09_Sheet_Col_Total"),
|
|
L("B09_Sheet_Col_Labor"),
|
|
L("B09_Sheet_Col_Material"),
|
|
L("B09_Sheet_Col_Expense"),
|
|
]
|
|
: [L("B09_Sheet_Col_UnitPrice")];
|
|
const { wrap, tbody } = plainTable([
|
|
L("B09_Sheet_Col_Sheet"),
|
|
L("B09_Sheet_Col_Name"),
|
|
L("B09_Sheet_Col_Spec"),
|
|
L("B09_Sheet_Col_Unit"),
|
|
...money,
|
|
L("B09_Sheet_Col_Note"),
|
|
]);
|
|
const rows = rowsOf(bill, active);
|
|
for (const row of rows) {
|
|
const tr = el("tr");
|
|
tr.append(
|
|
el("td", "", row.label),
|
|
el("td", "", row.name),
|
|
el("td", "", row.spec),
|
|
el("td", "", row.unit),
|
|
);
|
|
for (const value of row.money) tr.append(numberCell(won(value)));
|
|
tr.append(el("td", "b09s-note", row.note));
|
|
if (row.open) {
|
|
const [tab, code] = row.open;
|
|
tr.classList.add("is-clickable");
|
|
tr.title = L("B09_Sheet_Drill");
|
|
tr.addEventListener("click", () => ctx.open(tab, code));
|
|
}
|
|
tbody.append(tr);
|
|
}
|
|
ctx.body.append(wrap);
|
|
if (rows.length === 0) ctx.body.append(hint(L("B09_Sheet_EmptyGroup")));
|
|
}
|
|
|
|
export const listsTab: B09Tab = {
|
|
key: "lists",
|
|
label: () => L("B09_Sheet_Tab_Lists"),
|
|
render(ctx) {
|
|
if (!ctx.projectId) {
|
|
ctx.body.append(hint(L("B09_Sheet_NoProject")));
|
|
return;
|
|
}
|
|
ctx.body.append(hint(L("B09_Sheet_Loading")));
|
|
loadBill(ctx.projectId)
|
|
.then((bill) => {
|
|
ctx.body.replaceChildren();
|
|
draw(ctx, bill);
|
|
})
|
|
.catch((error: Error) => {
|
|
ctx.body.replaceChildren(hint(`${L("B09_Sheet_LoadFailed")} ${error.message}`, true));
|
|
});
|
|
},
|
|
};
|