From a5edf43d6e4cdecbaf0d20a771bcdc1ca07a7449 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 2 Oct 2026 00:34:17 +0900 Subject: [PATCH] =?UTF-8?q?fix(m01):=20=ED=9D=90=EB=A6=84=20=ED=95=A9?= =?UTF-8?q?=EA=B3=84=20=EB=81=9D=EC=88=98=20=3D=20=EB=B9=84=EC=9A=A9=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EC=83=81=EC=9E=90=EC=97=90=20=EC=8B=A4?= =?UTF-8?q?=EB=A6=B0=20=EB=A1=9C=EC=A7=81=20=EC=9E=90=EC=8B=A0=EC=9D=98=20?= =?UTF-8?q?=EB=81=9D=EC=88=98=20=C2=B7=20=EC=83=88=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EB=A7=8C=20=EA=B8=B0=EB=B3=B8=201=EC=9B=90=20=EB=AF=B8?= =?UTF-8?q?=EB=A7=8C=20=EB=B2=84=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 왼쪽 흐름 보기 소계 · 계와 오른쪽 원가 합계가 같은 끝수를 씀(Round.totalRoundOf) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EauA2ATmqNRbeV48s9CcBr --- .../M01_MasterData_UI_LogicLab_FlowView.ts | 17 +++++++--- M02_MasterTemplete/DataFlow/M02_DF_Round.ts | 12 +++++++ M02_MasterTemplete/DataFlow/M02_DF_Summary.ts | 17 +++++++--- resources/tester/test_m01_logic_flowview.py | 33 ++++++++++++++++++- 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_FlowView.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_FlowView.ts index cdf1e7456..6b4acb35c 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_FlowView.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_FlowView.ts @@ -21,7 +21,12 @@ import { type FormulaLine, } from "../M02_MasterTemplete/DataFlow/M02_DF_FormulaView"; import type { CfLogic } from "../M02_MasterTemplete/DataFlow/M02_DF_Model"; -import { itemRoundNow, totalRoundNow } from "../M02_MasterTemplete/DataFlow/M02_DF_Round"; +import { + itemRoundNow, + roundText, + totalRoundNow, + totalRoundOf, +} from "../M02_MasterTemplete/DataFlow/M02_DF_Round"; import { flowRows, type FlowFunc, @@ -42,7 +47,7 @@ const TEXT = { HValue: ["값", "Value"], Sub: ["소계", "Subtotal"], Total: ["계", "Total"], - TotalNote: ["합계 끝수 기본 = 1원 미만 버림", "Total rounding default: floor to 1 won"], + TotalNote: ["합계 끝수", "Total rounding"], None: ["출력 상자가 없음", "No output box"], NoFunc: ["함수 상자가 없음", "No function box"], } as const; @@ -158,7 +163,8 @@ export function buildFlowView( ); return; } - const rows = flowRows(lg, ev, { item: itemRoundNow(), total: totalRoundNow() }); + const totalRound = totalRoundOf(lg, totalRoundNow()); + const rows = flowRows(lg, ev, { item: itemRoundNow(), total: totalRound }); const detail = section(tf("Detail"), [ ...(rows.groups.length ? rows.groups.map(groupTable) @@ -168,7 +174,10 @@ export function buildFlowView( attrs: { "data-sum": String(rows.total) }, text: `${tf("Total")} ${rows.totalText}`, }), - el("p", { className: "m01-logic__muted", text: tf("TotalNote") }), + el("p", { + className: "m01-logic__muted", + text: `${tf("TotalNote")}: ${roundText(totalRound)}`, + }), ...rows.errors.slice(0, 3).map((t) => el("p", { className: "m01flow__warn", text: t })), ]); /** 함수 상자마다 식 두 줄(sub2 식 보기 · 받는 알약 값을 칸에 넣고 셈) — 못 그리면 이름 = 값만 */ diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Round.ts b/M02_MasterTemplete/DataFlow/M02_DF_Round.ts index 5101ad946..f4ddf13d0 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Round.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Round.ts @@ -107,3 +107,15 @@ export function showRounded(v: number, r?: CfRound): string { let totalNow: CfRound = { places: 0, method: "버림" }; void defaultTotalRound().then((r) => (totalNow = r)); export const totalRoundNow = (): CfRound => totalNow; + +/** 그 로직의 합계 끝수 — 비용 출력 상자에 끝수가 실려 있으면 그것(옛 로직을 옮긴 흐름은 자신의 끝수) · + * 없으면 기본(새 로직 = 일위대가표 계금 1원 미만 버림) */ +export function totalRoundOf( + lg: { boxes: { kind: string; output?: { cost: string; round?: CfRound } }[] }, + fallback: CfRound, +): CfRound { + const hit = lg.boxes.find( + (b) => b.kind === "output" && b.output?.cost !== "값" && b.output?.round, + ); + return hit?.output?.round ?? fallback; +} diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts b/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts index a4686af39..148e15827 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Summary.ts @@ -12,7 +12,14 @@ import { formatMoney } from "../../M01_MasterData/M01_MasterData_UI_Logic_Money" import type { CfEvalOptions, CfEvalResult, CfValues } from "./M02_DF_Eval"; import type { CfBox, CfLogic, CfRound } from "./M02_DF_Model"; import { showValue } from "./M02_DF_OutputPanel"; -import { defaultTotalRound, itemRoundNow, roundBy, roundText, showRounded } from "./M02_DF_Round"; +import { + defaultTotalRound, + itemRoundNow, + roundBy, + roundText, + showRounded, + totalRoundOf, +} from "./M02_DF_Round"; import { renderVerify, type CfSel } from "./M02_DF_Verify"; import "./M02_DF_Summary.css"; @@ -69,18 +76,20 @@ export function renderSummary( ? ts("Total") : ts("Per", { per: /^\d/.test(perUnit) ? perUnit : `1${perUnit}` }); const { cost } = result; + /** 합계 끝수 — 비용 출력 상자에 실린 끝수(옮긴 로직의 자신 끝수) · 없으면 기본 1원 미만 버림 */ + const sumRound = totalRoundOf(lg, totalRound); const big = el("p", { className: "m02-dfsum__big", children: [ el("span", { text: money ? perText : ts("Total") }), - el("strong", { text: `${formatMoney(roundBy(totalRound, cost.계))} ${ts("Won")}` }), + el("strong", { text: `${formatMoney(roundBy(sumRound, cost.계))} ${ts("Won")}` }), ], }); const costs = el("dl", { className: "m02-dfsum__costs", children: COSTS.flatMap((c) => [ el("dt", { text: ts(COST_KEY[c]) }), - el("dd", { text: formatMoney(roundBy(totalRound, cost[c])) }), + el("dd", { text: formatMoney(roundBy(sumRound, cost[c])) }), ]), }); @@ -161,7 +170,7 @@ export function renderSummary( : []), el("p", { className: "m02-dfsum__muted", - text: `${ts("RoundTotal")}: ${roundText(totalRound)}`, + text: `${ts("RoundTotal")}: ${roundText(sumRound)}`, }), verifyHost, ); diff --git a/resources/tester/test_m01_logic_flowview.py b/resources/tester/test_m01_logic_flowview.py index 67a4d35ea..de7d5380e 100644 --- a/resources/tester/test_m01_logic_flowview.py +++ b/resources/tester/test_m01_logic_flowview.py @@ -37,8 +37,9 @@ function load(f) { return box.exports; } const m = load(file); +const r = load(path.join(root, "M02_MasterTemplete", "DataFlow", "M02_DF_Round.ts")); const jobs = JSON.parse(fs.readFileSync(0, "utf8")); -const out = jobs.map((job) => m.flowRows(job.lg, job.ev, job.round)); +const out = jobs.map((job) => job.op === "totalRound" ? r.totalRoundOf(job.lg, job.fallback) : m.flowRows(job.lg, job.ev, job.round)); process.stdout.write(JSON.stringify(out)); setTimeout(() => process.exit(0), 50); """ @@ -182,3 +183,33 @@ def test_missing_value_shows_dash(): rows = run([{"lg": lg, "ev": ev, "round": ROUND}])[0] assert group(rows, "labor")["lines"][0]["text"] == "—" assert rows["errors"] == ["끊김"] + + +def test_total_round_follows_converted_logic_own_round(): + """옮긴 로직 = 비용 출력 상자에 실린 끝수 · 새 로직(끝수 없음) = 기본.""" + default = {"places": 0, "method": "버림"} + own = {"places": 1, "method": "반올림"} + converted = logic( + [out_box("o1", "인건비", "노무비", round_=own), out_box("o2", "작업량", "값")] + ) + fresh = logic([out_box("o1", "인건비", "노무비")]) + only_plain = logic([out_box("o3", "작업량", "값", round_={"places": 3, "method": "버림"})]) + got = run( + [ + {"op": "totalRound", "lg": converted, "fallback": default}, + {"op": "totalRound", "lg": fresh, "fallback": default}, + {"op": "totalRound", "lg": only_plain, "fallback": default}, + ] + ) + assert got == [own, default, default] # 값(비용 아님)의 끝수는 합계 끝수가 아님 + + +def test_subtotal_and_total_use_converted_round(): + own = {"places": 1, "method": "반올림"} + lg = logic([out_box("o1", "인건비", "노무비", round_=own)]) + ev = result( + {"o1": 3216.7}, {"노무비": 3216.7, "재료비": 0, "경비": 0, "값들": {}, "계": 3216.7} + ) + rows = run([{"lg": lg, "ev": ev, "round": {"item": ROUND["item"], "total": own}}])[0] + assert group(rows, "labor")["subText"] == "3,216.7" + assert rows["totalText"] == "3,216.7"