fix(751-1): 원화 꼴 흠 — B09 · 일위대가표 격자 「.0」 꼬리 걷음 · 단가 소수 셋 · 환율표 · 빈 값 · 금액 정수 꼴 공용 갈음
- 격자 단가 칸 소수 셋 · 금액 칸 소수 하나 끝 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<Row> {
|
||||
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<Row>
|
||||
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 || [];
|
||||
|
||||
@@ -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<Item[]> {
|
||||
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<Item[]> {
|
||||
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),
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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<string, string | null> = { "": "관급", 관급: "별
|
||||
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 {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 ? "단가산출 값" : "품셈 값");
|
||||
|
||||
@@ -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<string, string> = {
|
||||
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) : "빈칸");
|
||||
|
||||
/* ── 순수 — 산근 블록 → 계산서 묶음 ── */
|
||||
/** 줄이 쓰는 비목 */
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
|
||||
@@ -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<string, [string, string]> = {
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user