From 27f33a8c5328d0fdae8612d8ec1b4f0e299efb83 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 2 Nov 2026 22:20:43 +0900 Subject: [PATCH] =?UTF-8?q?fix(751-1):=20=EC=9B=90=ED=99=94=20=EA=BC=B4=20?= =?UTF-8?q?=ED=9D=A0=20=E2=80=94=20B09=20=C2=B7=20=EC=9D=BC=EC=9C=84?= =?UTF-8?q?=EB=8C=80=EA=B0=80=ED=91=9C=20=EA=B2=A9=EC=9E=90=20=E3=80=8C.0?= =?UTF-8?q?=E3=80=8D=20=EA=BC=AC=EB=A6=AC=20=EA=B1=B7=EC=9D=8C=20=C2=B7=20?= =?UTF-8?q?=EB=8B=A8=EA=B0=80=20=EC=86=8C=EC=88=98=20=EC=85=8B=20=C2=B7=20?= =?UTF-8?q?=ED=99=98=EC=9C=A8=ED=91=9C=20=C2=B7=20=EB=B9=88=20=EA=B0=92=20?= =?UTF-8?q?=C2=B7=20=EA=B8=88=EC=95=A1=20=EC=A0=95=EC=88=98=20=EA=BC=B4=20?= =?UTF-8?q?=EA=B3=B5=EC=9A=A9=20=EA=B0=88=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 격자 단가 칸 소수 셋 · 금액 칸 소수 하나 끝 0 없음(275,790.0 → 275,790) - B09 money() 소수 자리 있으면 끝 0 없음 · 환율표 59,020.00 → 59,020 · 단가 열 소수 셋(1,846.39 · 22.9) - 엑셀 불러오기 대조 글 빈 값 = 빈칸(0 아님) · en-US 정수 → 공용 formatMoney · 재료 창고 금액 공용 꼴 Co-Authored-By: Claude Sonnet 5.5 --- B09_Estimation/B09_Estimation_UI_Import.ts | 7 ++++--- B09_Estimation/B09_Estimation_UI_Store.ts | 7 +++---- B09_Estimation/B09_Estimation_UI_Tabs.ts | 1 + B09_Estimation/B09_Estimation_UI_Tabs_Base.ts | 17 +++++++++-------- B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts | 2 +- .../B09_Estimation_UI_Tabs_Bill_Fold.ts | 4 ++-- .../B09_Estimation_UI_Tabs_Sangeun_Sheet.ts | 4 ++-- .../B09_Estimation_UI_Tabs_Summary.ts | 2 +- B09_Estimation/B09_Estimation_UI_Tabs_Total.ts | 3 ++- ui_template/ui_template_hopyo_grid.ts | 10 ++++++---- 10 files changed, 31 insertions(+), 26 deletions(-) diff --git a/B09_Estimation/B09_Estimation_UI_Import.ts b/B09_Estimation/B09_Estimation_UI_Import.ts index 7a93ac418..fd9d2f7ec 100644 --- a/B09_Estimation/B09_Estimation_UI_Import.ts +++ b/B09_Estimation/B09_Estimation_UI_Import.ts @@ -40,11 +40,13 @@ import { import { openModal } from "@ui/ui_template_modal"; import { createText } from "@ui/ui_template_text"; import { currentProjectId } from "../A00_Common/b_project_switch"; +import { formatMoney, formatPrice } from "../common_util/common_util_money"; import type { Row } from "./B09_Estimation_Calc_Base"; import { openBillLink } from "./B09_Estimation_UI_Link"; import { money, qty, text, type TabContext, type TabId } from "./B09_Estimation_UI_Tabs"; -const won = (v: unknown): string => Number(v || 0).toLocaleString("ko-KR"); +const won = (v: unknown): string => + v === null || v === undefined || v === "" ? "빈칸" : formatPrice(Number(v)); async function post(projectId: string, path: string, body: BodyInit, json = false): Promise { const response = await fetch(`${API_BASE_URL}/projects/${projectId}/estimation/import/${path}`, { @@ -357,8 +359,7 @@ async function postSheet(kind: Kind, user: Row, file: File | null): Promise return body!; } -const wonOrBlank = (v: unknown): string => - typeof v === "number" ? Math.round(v).toLocaleString("en-US") : ""; +const wonOrBlank = (v: unknown): string => (typeof v === "number" ? formatMoney(v) : ""); function ratesView(got: Row): { table: DataRow[]; lines: string[]; columns: DataColumn[] } { const check: Row[] = got.check?.lines || []; diff --git a/B09_Estimation/B09_Estimation_UI_Store.ts b/B09_Estimation/B09_Estimation_UI_Store.ts index 674c12d06..5cfd44bc8 100644 --- a/B09_Estimation/B09_Estimation_UI_Store.ts +++ b/B09_Estimation/B09_Estimation_UI_Store.ts @@ -38,6 +38,7 @@ import type { TabContext } from "./B09_Estimation_UI_Tabs"; import { applyAt, openMenuAt } from "./B09_Estimation_UI_Tabs_Bill_Edit"; import { askInputs, importLib, type ApplyRef } from "./B09_Estimation_UI_Tabs_Bill_Link"; import { pickedKey } from "./B09_Estimation_UI_Tabs_Bill_Level"; +import { formatMoney } from "../common_util/common_util_money"; type Source = "here" | "mine" | "company" | "sangeun" | "poomsem" | "past"; const SOURCES: Array<[Source, string]> = [ @@ -133,7 +134,7 @@ async function sangeunItems(): Promise { return (await listSangeun([{ 층: "system", 주인: null }, ...own])).map((r) => { const name = r.제목 || r.이름; const sub = [r.구분, r.상세구분].filter(Boolean).join(" "); - const money = r.기준금액 === null ? "" : `${Math.round(r.기준금액).toLocaleString("ko-KR")}원`; + const money = r.기준금액 === null ? "" : `${formatMoney(r.기준금액)}원`; return { tag: `단가산출 · ${layerText[r.ref.층] ?? r.ref.층}`, name, @@ -161,9 +162,7 @@ async function poomsemItems(): Promise { return list.줄.map((r) => ({ tag: r.공종 || "품셈", name: r.이름, - sub: [r.규격, r.금액 === null ? "" : `${Math.round(r.금액).toLocaleString("ko-KR")}원`] - .filter(Boolean) - .join(" · "), + sub: [r.규격, r.금액 === null ? "" : `${formatMoney(r.금액)}원`].filter(Boolean).join(" · "), unit: r.단위, hit: (q) => !q || matchLogic(q, { ...r, 이름: `${r.이름} ${r.규격}` }).ok, go: (_c, apply) => askStandard(apply, r), diff --git a/B09_Estimation/B09_Estimation_UI_Tabs.ts b/B09_Estimation/B09_Estimation_UI_Tabs.ts index a91838c22..3f1283adf 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs.ts @@ -179,6 +179,7 @@ export const money = (key: string, label: string, group?: string[], decimals = 0 group, kind: "number", decimals, + trim: decimals > 0, // 끝 0 없음(「18,064.0」 아님) width: 96, }); diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Base.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Base.ts index f11a5f6e6..52c23735d 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Base.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Base.ts @@ -18,6 +18,7 @@ import type { MapContextMenuItem } from "@ui/ui_template_context_menu"; import type { DataRow } from "@ui/ui_template_data_table"; import { SOURCES, type Row } from "./B09_Estimation_Calc_Base"; +import { formatPrice } from "../common_util/common_util_money"; import { dateText } from "../common_util/common_util_time"; import { openQuotes } from "./B09_Estimation_UI_Tabs_Base_Quote"; import { @@ -41,7 +42,7 @@ function priceList(rows: Row[], unitPrice: string, hint: string): TabTable { text("name", "명칭", 200), text("spec", "규격", 140), text("unit", "단위", 56), - money("price", unitPrice), + money("price", unitPrice, undefined, 3), text("source", "비고", 220), ], rows: rows.map((r) => ({ ...r })), @@ -121,7 +122,7 @@ const SUPPLY_NEXT: Record = { "": "관급", 관급: "별 function appliedChoices(r: Row, pick: (s: string | null) => void): MapContextMenuItem[] { const have = SOURCES.filter((s) => (r.prices[s] ?? 0) > 0); const label = (s: string): string => - `${s === r.applied_from ? "✓ " : ""}${s} ${Number(r.prices[s]).toLocaleString("ko-KR")}` + + `${s === r.applied_from ? "✓ " : ""}${s} ${formatPrice(Number(r.prices[s]))}` + (s === r.lowest_from ? " · 최저" : ""); return [ ...have.map((s): MapContextMenuItem => [label(s), () => pick(s)]), @@ -196,11 +197,11 @@ export const buildCompare: TabBuild = ({ sheets, user, setUser }): TabTable => { // 185-4 — 출처 = 단가 하나(출처 뜻은 단가 칸 호버) · 견적 = 단가 · 견적서 ...SOURCES.flatMap((s) => s === "견적" - ? [money(`p:${s}`, "단가", [s]), text("files", "견적서", 72, [s])] - : [money(`p:${s}`, "단가", [s])], + ? [money(`p:${s}`, "단가", [s], 3), text("files", "견적서", 72, [s])] + : [money(`p:${s}`, "단가", [s], 3)], ), - money("lowest", "최저"), - money("applied", "적용 단가", ["적용"]), + money("lowest", "최저", undefined, 3), + money("applied", "적용 단가", ["적용"], 3), text("applied_from", "출처", 80, ["적용"]), text("supply", "관급 · 별도", 80), ], @@ -294,7 +295,7 @@ export const buildMaterial: TabBuild = ({ sheets }): TabTable => { text("spec", "규격", 160), text("unit", "단위", 56), qty("qty", "수량", 3), - money("price", "단가"), + money("price", "단가", undefined, 3), money("amount", "총액"), text("supply", "관급 · 별도", 80), text("uses", "비고(쓰는 곳)", 240), @@ -379,7 +380,7 @@ export const buildMachineBlocks: TabBuild = ({ sheets }): TabTable => { links.push(null); }); const pair = (key: string, label: string) => [ - money(`${key}_price`, "단가", [label], 1), + money(`${key}_price`, "단가", [label], 3), money(key, "금액", [label], 1), ]; return { diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts index e2362ed8d..a1cf55f11 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts @@ -55,7 +55,7 @@ export const buildBill: TabBuild = (ctx): BillTable => { })); const direct: Row = bill.totals?.direct || {}; const pair = (key: string, label: string) => [ - money(`price_${key}`, "단가", [label]), + money(`price_${key}`, "단가", [label], 3), money(key, "금액", [label]), ]; /** 고친 칸 — 셈 줄에 자동 값(auto_칸)이 있음 · 연결 칸 = auto_ref */ diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts index 50ff41147..483889634 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Fold.ts @@ -28,6 +28,7 @@ import { foldMenu } from "./B09_Estimation_UI_Tabs_Bill_Fold_Menu"; import { API_BASE_URL } from "@config/config_frontend"; import { hideLoadingOverlay, showLoadingOverlay, showToast } from "@ui/ui_template_toast"; import { currentProjectId } from "../A00_Common/b_project_switch"; +import { formatNumber } from "../common_util/common_util_money"; import { ADD } from "./B09_Estimation_Calc_Labor"; import { withCols } from "../M02_MasterTemplete/M02_MasterTemplete_Labor_Paste"; import { @@ -57,8 +58,7 @@ interface FoldLine { /** 지금 펼친 내역 줄 키(다시 그려도 그대로) */ let open: string | null = null; -const plain = (v: unknown): string => - typeof v === "number" ? v.toLocaleString("ko-KR", { maximumFractionDigits: 4 }) : "빈칸"; +const plain = (v: unknown): string => (typeof v === "number" ? formatNumber(v) : "빈칸"); /** 되돌림 값 이름 — 단가산출 블록 줄 = 단가산출 값 · 그 밖 = 품셈 값 */ const backName = (line: Row | undefined): string => (line?.sk ? "단가산출 값" : "품셈 값"); diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Sangeun_Sheet.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Sangeun_Sheet.ts index 818243b7c..35b3ac136 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Sangeun_Sheet.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Sangeun_Sheet.ts @@ -27,6 +27,7 @@ import { createButton, el } from "@ui/ui_template_elements"; import { attachHoverInfo } from "@ui/ui_template_hover_info"; import { createText } from "@ui/ui_template_text"; import { calcSheetRows, type SheetGroup, type SheetLine } from "../A00_Common/calc_sheet"; +import { formatNumber } from "../common_util/common_util_money"; import type { Row } from "./B09_Estimation_Calc_Base"; import { fields, lineId } from "./B09_Estimation_Sangeun_Cover"; import { money, parseNumber, qty, text, type TabContext } from "./B09_Estimation_UI_Tabs"; @@ -49,8 +50,7 @@ const FIELD: Record = { expense_price: "expense", }; -const fmt = (v: unknown): string => - typeof v === "number" ? v.toLocaleString("ko-KR", { maximumFractionDigits: 4 }) : "빈칸"; +const fmt = (v: unknown): string => (typeof v === "number" ? formatNumber(v) : "빈칸"); /* ── 순수 — 산근 블록 → 계산서 묶음 ── */ /** 줄이 쓰는 비목 */ diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Summary.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Summary.ts index 3a70481f6..585b4a8e6 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Summary.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Summary.ts @@ -47,7 +47,7 @@ function resourceTab(id: string, list: TabId, unitPrice: string, none = ""): Tab text("spec", "규격", 140), qty("qty", "수량", 2), text("unit", "단위", 56), - money("price", unitPrice), + money("price", unitPrice, undefined, 3), money("amount", "금액"), text("note", "비고", 120), ], diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Total.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Total.ts index e84107e8b..d2e432fd4 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Total.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Total.ts @@ -16,6 +16,7 @@ * ========================================================================== */ import type { DataRow } from "@ui/ui_template_data_table"; +import { formatMoney } from "../common_util/common_util_money"; import type { Row } from "./B09_Estimation_Calc_Base"; import { CURRENT_ONLY, RATE_LINES, TAIL_LINES } from "./B09_Estimation_Calc_Total"; import { @@ -31,7 +32,7 @@ import { type Num = number | null; -const won = (v: Num): string => (v === null ? "빈칸" : Math.round(v).toLocaleString("en-US")); +const won = (v: Num): string => (v === null ? "빈칸" : formatMoney(v)); /** 손입력 칸 — 줄 키 → user.estimate 칸 */ const HAND: Record = { diff --git a/ui_template/ui_template_hopyo_grid.ts b/ui_template/ui_template_hopyo_grid.ts index 7a2b4a5dd..b31260993 100644 --- a/ui_template/ui_template_hopyo_grid.ts +++ b/ui_template/ui_template_hopyo_grid.ts @@ -98,12 +98,14 @@ const hopyoText = (key: string, label: string, width?: number): DataColumn => ({ label, width, }); -const hopyoMoney = (key: string, label: string, group: string): DataColumn => ({ +/** 단가 = 소수 셋까지 · 금액 = 소수 하나까지 — 끝 0 없음(`275,790` · `109,326.3`) */ +const hopyoMoney = (key: string, label: string, group: string, decimals: number): DataColumn => ({ key, label, group: [group], kind: "number", - decimals: 1, + decimals, + trim: true, width: 96, }); @@ -114,8 +116,8 @@ export const HOPYO_COLUMNS: DataColumn[] = [ { key: "qty", label: "수량", kind: "number", decimals: 4, trim: true, width: 76 }, hopyoText("unit", "단위", 56), ...HOPYO_COSTS.flatMap(([key, label]) => [ - hopyoMoney(`${key}_price`, "단가", label), - hopyoMoney(key, "금액", label), + hopyoMoney(`${key}_price`, "단가", label, 3), + hopyoMoney(key, "금액", label, 1), ]), hopyoText("note", "비고", 120), hopyoText("tag", "구분", 56),