From 1e29f5e0b2d5a7e3865432d65885bcbb0555ab5a Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 25 Sep 2026 16:53:17 +0900 Subject: [PATCH] =?UTF-8?q?fix(sheet):=20=EC=88=98=20=EC=B9=B8=EC=97=90=20?= =?UTF-8?q?=EC=88=98=20=EC=95=84=EB=8B=8C=20=EA=B8=80=EC=9D=B4=EB=A9=B4=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=83=89=20+=20=ED=92=8D=EC=84=A0=20?= =?UTF-8?q?=C2=B7=20=ED=95=A9=EA=B3=84=20=EC=B9=B8=EC=97=90=EB=8F=84=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20(M02=20=EC=A0=84=EC=88=98=20=EC=9E=AC?= =?UTF-8?q?=EA=B2=80=EC=A6=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Tjoit7rxvpLMM7cafeVTo1 --- resources/tester/test_sheet_recalc.py | 20 ++++++++++++++ ui_template/sheet/ui_template_sheet_recalc.ts | 27 +++++++++++++++++++ ui_template/sheet/ui_template_sheet_render.ts | 4 +-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/resources/tester/test_sheet_recalc.py b/resources/tester/test_sheet_recalc.py index 07b21e6f..c1aa94e3 100644 --- a/resources/tester/test_sheet_recalc.py +++ b/resources/tester/test_sheet_recalc.py @@ -193,3 +193,23 @@ def test_mirror_random_docs_match_decimal(): assert out["계산"] == {r: {n: _plain(v) for n, v in w.items()} for r, w in want.items()} for name, total in sums.items(): assert out["합계"]["s"][name] == _plain(total) + + +def test_text_in_number_column_is_flagged_and_skipped(): + doc = _doc( + [{"id": "a"}, {"id": "n", "꼴": "글"}, {"id": "f", "식": "[a]*2"}], + [ + {"id": "r1", "값": {"a": 5, "n": "abc"}}, + {"id": "r2", "값": {"a": "abc", "n": "x"}}, + {"id": "r3", "값": {"a": "1,000"}}, + {"id": "r4", "값": {"a": ""}}, + ], + 합계줄=[{"id": "t", "이름": "합계", "식": "SUM"}], + ) + out = _run(doc) + assert out["합계"]["t"]["a"] == "1005" # abc 는 빼고 · 1,000 은 수 · 빈 칸은 0 + errors = {(e["줄"], e["열"]): e["까닭"] for e in out["오류"]} + assert errors[("r2", "a")] == "수가 아님" + assert errors[("t", "a")].startswith("수가 아닌 칸 1 개") + # 글 칸 · 정상 수 칸은 표시 없음(r2 의 식 칸은 글을 곱해 식 오류 — 따로) + assert not any(k[1] == "n" or k[0] in ("r1", "r3", "r4") for k in errors) diff --git a/ui_template/sheet/ui_template_sheet_recalc.ts b/ui_template/sheet/ui_template_sheet_recalc.ts index a19e1026..50eba71d 100644 --- a/ui_template/sheet/ui_template_sheet_recalc.ts +++ b/ui_template/sheet/ui_template_sheet_recalc.ts @@ -34,6 +34,13 @@ import type { const ROUND_MODE = { 반올림: "round", 올림: "away", 버림: "trunc" } as const; +/** 수 칸에 수가 아닌 글 — 계산은 그 칸을 빼고 하되 알림(엑셀처럼 무시 · 오류 목록에는 이 까닭으로). */ +export const NOT_A_NUMBER = "수가 아님"; +export const SKIPPED_PREFIX = "수가 아닌 칸"; +/** 값은 그대로 두고 알리기만 하는 까닭인가 — 화면이 글을 「#오류」 로 바꾸지 않게. */ +export const isNotice = (why: string): boolean => + why === NOT_A_NUMBER || why.startsWith(SKIPPED_PREFIX); + export const isNumberColumn = (col: SheetColumn): boolean => col.꼴 !== "글"; /** 합계 줄 한 칸의 식 — 없으면 null(빈 칸). `"SUM"` 은 그 열의 열 합. */ @@ -167,6 +174,26 @@ export function recalcSheet(doc: SheetDoc): SheetResult { }; for (const id of rows.keys()) solve(id, result.계산); for (const id of totals.keys()) solve(id, result.합계); + + // 수 칸에 든 수 아닌 글 — 그 칸 + 그 열을 더하는 합계 칸에 알림 + const badCount = new Map(); + for (const row of doc.줄) { + for (const col of doc.열) { + if (!isNumberColumn(col) || formulaOf(row.id, col)) continue; + if (typeof inputValue(row.값[col.id]) !== "string") continue; + result.오류.push({ 줄: row.id, 열: col.id, 까닭: NOT_A_NUMBER }); + badCount.set(col.id, (badCount.get(col.id) ?? 0) + 1); + } + } + for (const total of totals.values()) { + for (const col of doc.열) { + const n = badCount.get(col.id); + const formula = n && totalFormula(total, col); + if (!formula || !formula.includes(`[${col.id}`)) continue; + const 까닭 = `${SKIPPED_PREFIX} ${n} 개는 빼고 더함`; + result.오류.push({ 줄: total.id, 열: col.id, 까닭 }); + } + } return result; } diff --git a/ui_template/sheet/ui_template_sheet_render.ts b/ui_template/sheet/ui_template_sheet_render.ts index 7bef596f..2ef7da45 100644 --- a/ui_template/sheet/ui_template_sheet_render.ts +++ b/ui_template/sheet/ui_template_sheet_render.ts @@ -18,7 +18,7 @@ import { pagePlan, type SheetMode, } from "./ui_template_sheet_ops"; -import { groupDigits, totalFormula } from "./ui_template_sheet_recalc"; +import { groupDigits, isNotice, totalFormula } from "./ui_template_sheet_recalc"; import { st } from "./ui_template_sheet_text"; import type { SheetColumn, SheetDoc, SheetResult, SheetRow } from "./ui_template_sheet_types"; @@ -130,7 +130,7 @@ export function renderSheet(state: RenderState): HTMLElement { const why = errors.get(`${key}|${col.id}`); if (why) { td.classList.add("is-error"); - td.textContent = st("Error"); + if (!isNotice(why)) td.textContent = st("Error"); td.title = why; } else if (text) td.title = text; if (state.sel?.r === key && state.sel.c === col.id) td.classList.add("is-selected");