Files
Aislo/M02_MasterTemplete/DataFlow/M02_DF_Round.ts
T
eomsangdonandClaude Sonnet 5.5 d24851d1a4 feat(m02-df): 흐름도 검증 칸(끊긴 곳 · 재료 자리 · 조건 자리) · 출력 끝수 기본 · 합계 끝수
- 새 Verify: 끊긴 곳 목록(on.select) · 변수 예시값 · 재료 선택 자리(찾기 · 쪽 · 고르기) · 재료 조건 자리(잡힌 품목 · 0 · 여럿) · 이어지지 않은 다른 로직 값
- 새 Round · Material: 금액 단위 표준에서 끝수 기본(금액란 0.1원 · 계금 1원 미만 버림) · 재료 찾기 · 단가
- OutputPanel 끝수 고르기 · Summary 합계 끝수

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EauA2ATmqNRbeV48s9CcBr
2026-10-01 19:47:43 +09:00

102 lines
3.9 KiB
TypeScript

/* =============================================================================
* M02_DF_Round.ts
* 끝수 기본값 — 금액 단위 표준(산림품셈 1-2-2 「금액의 단위표준」)을 읽어
* 출력(비용) 기본 = 일위대가표 금액란 · 원가 합계 기본 = 일위대가표 계금 (모두 미만 버림)
* 표 읽기 = 서버 `GET /api/m02/df/standards` → {unit, money, saved} · 아직 없으면 `_standard/금액단위표준.json`
* 모양은 docs/raw/M01_쉬운단가/흐름도_정함.md 11 · 12. 이 폴더째 지워도 됨.
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import type { CfRound } from "./M02_DF_Model";
/** 표 한 줄 — 원문 그대로(종목 · 단위 · 지위 · 비고) */
export interface CfMoneyRow {
종목: string;
단위?: string;
지위: number;
비고?: string;
}
const FILES = import.meta.glob<{ 줄?: CfMoneyRow[] }>("./_standard/금액단위표준.json", {
eager: true,
import: "default",
});
/** 표 모양 둘(`{줄: […]}` · 줄 목록)을 줄 목록으로 */
const rowsOf = (x: unknown): CfMoneyRow[] => {
const list = Array.isArray(x) ? x : (x as { 줄?: unknown } | null)?.줄;
return Array.isArray(list) ? (list as CfMoneyRow[]) : [];
};
let loaded: Promise<CfMoneyRow[]> | null = null;
/** 금액 단위 표준 줄 — 사용자가 고쳐 저장한 것이 있으면 그것 · 서버가 없으면 처음 값 */
export function loadMoneyStandard(): Promise<CfMoneyRow[]> {
loaded ??= (async () => {
try {
const r = await fetch(`${API_BASE_URL}/m02/df/standards`, { credentials: "include" });
if (r.ok) {
const d = (await r.json()) as { money?: unknown; saved?: { money?: unknown } | null };
const rows = rowsOf(d.saved?.money ?? d.money);
if (rows.length) return rows;
}
} catch {
/* 서버가 없으면 처음 값 */
}
return rowsOf(Object.values(FILES)[0]);
})();
return loaded;
}
/** 표를 다시 읽게(표를 고친 뒤) */
export function resetStandard(): void {
loaded = null;
}
/** 표 줄 → 끝수 — 지위 0.1 = 소수 1자리 · 1 이상 = 0자리 · 비고 「미만버림」 */
export function roundOf(row: CfMoneyRow): CfRound {
const step = row.지위 > 0 ? row.지위 : 1;
const method =
/올림/.test(row.비고 ?? "") && !/버림/.test(row.비고 ?? "")
? "올림"
: /반올림/.test(row.비고 ?? "")
? "반올림"
: "버림";
return { places: step >= 1 ? 0 : Math.round(-Math.log10(step)), method };
}
const find = (rows: CfMoneyRow[], word: string): CfMoneyRow | undefined =>
rows.find((r) => r.종목.includes("일위대가표") && r.종목.includes(word));
/** 출력(비용) 기본 — 일위대가표 금액란 0.1원 미만 버림 */
export async function defaultItemRound(): Promise<CfRound> {
const row = find(await loadMoneyStandard(), "금액란");
return row ? roundOf(row) : { places: 1, method: "버림" };
}
/** 원가 합계 기본 — 일위대가표 계금 1원 미만 버림 */
export async function defaultTotalRound(): Promise<CfRound> {
const row = find(await loadMoneyStandard(), "계금");
return row ? roundOf(row) : { places: 0, method: "버림" };
}
/** 끝수 적용 — 소수 자리 · 방법(부동소수 오차는 아주 작은 값으로 눌러 줌) */
export function roundBy(r: CfRound, v: number): number {
const k = 10 ** r.places;
const x = v * k;
const eps = Math.abs(x) * 1e-12 + 1e-9;
const f =
r.method === "올림"
? Math.ceil(x - eps)
: r.method === "반올림"
? Math.round(x + eps)
: Math.floor(x + eps);
return f / k;
}
/** 「0.1원 미만 버림」 꼴 쉬운 말 */
export function roundText(r: CfRound): string {
const step = r.places > 0 ? Number((10 ** -r.places).toFixed(r.places)) : 1;
return `${step}원 미만 ${r.method}`;
}