From 6a429560fb73768b7860e24c55a01d2e07030397 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 2 Nov 2026 00:49:31 +0900 Subject: [PATCH] =?UTF-8?q?fix(725-6):=20=EB=B0=94=ED=83=95=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EB=8B=A8=EA=B0=80=EC=82=B0=EC=B6=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=98=B5=EC=85=98=20=EC=B9=B8(=EB=8D=94=ED=95=9C?= =?UTF-8?q?=EC=A4=84=20=EB=A1=9C=EC=A7=81=20=EC=9E=85=EB=A0=A5=20=EB=AA=A8?= =?UTF-8?q?=EC=9D=8C)=20=C2=B7=20=EC=83=81=EC=84=B8=20=EB=AA=BB=20?= =?UTF-8?q?=EC=85=88=20=EC=A4=84=20=EA=B8=88=EC=95=A1=20=E3=80=8C=E2=80=94?= =?UTF-8?q?=E3=80=8D=20+=20=EA=B9=8C=EB=8B=AD=20=ED=92=8D=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 바탕 없는 SK = 더한줄 로직 입력을 모아(같은 이름 하나) 공통 옵션 칸으로 보임 · 값 자리 = 시험 입력 그대로 · 바탕 있으면 지금 그대로 - 입력 바꾸면 SK 셈 바로(바탕 없음 = 시험 계산 단추 없음) · 셈 때 SK 줄 입력 아닌 이름은 거름(공통 옵션 지역이 SK 줄에서 「모르는 입력」으로 멈춤) - 공용 상세 줄 금액 칸 = 까닭 있는 줄만 「—」 + 풍선(M01 줄은 까닭 없음 = 전후 같음) - 시험 test_725_1 에 바탕 없음 지역 서울 5,297.37 · 화면 짜임 단언 더함 Co-Authored-By: Claude Sonnet 5.5 --- .../M01_MasterData_UI_LogicLab_Ho.ts | 5 ++- M01_MasterData/M01_MasterData_UI_Logic_Api.ts | 2 ++ .../M02_MasterTemplete_Sangeun_Detail.ts | 31 ++++++++++++++++++- M02_MasterTemplete/M02_Sangeun_Calc.py | 4 ++- .../tester/test_725_1_sangeun_add_inputs.py | 18 +++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Ho.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Ho.ts index 328bc0264..3b9ff7be2 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Ho.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Ho.ts @@ -454,6 +454,9 @@ function nameCell(e: Entry, label: string, tag: string): HTMLElement { }); } +/** 못 셈 줄(725 · M02 단가산출) — 금액 「—」 + 까닭 풍선 */ +const gapCell = (why: string): HTMLElement => el("span", { text: "—", attrs: { title: why } }); + /** 보통 줄 · 몫 줄 — 누르면 그 줄 모달 */ function lineRow(e: Entry, ctx: HoContext, label = e.name): HTMLTableRowElement { const tag = editTag(ctx, e.idx); @@ -463,7 +466,7 @@ function lineRow(e: Entry, ctx: HoContext, label = e.name): HTMLTableRowElement e.unit, formulaCell(e, ctx), e.price, - money(e.amount), + e.line?.까닭 ? gapCell(e.line.까닭) : money(e.amount), ]); tr.classList.add("m01lab__line"); if (e.extra) tr.classList.add("m01lab__line--extra"); diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts index 7586b1b50..cfd0e310d 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts @@ -101,6 +101,8 @@ export interface CalcLine { /** % 덧줄(149-4 · 서버 `M02_Labor_Doc.pct_extras`) — 율 · 밑수 글(「노무비의 %」 · 복합 = 빈 글) */ 율?: number; 규격?: string; + /** 못 셈 줄(725 · M02 단가산출) — 금액 없음 까닭 */ + 까닭?: string; } /** % 덧줄 표기(149-4) — 율 있으면 수량 = 율 · 단위 % · 이름 뒤 규격 괄호 · 금액은 그대로(M01 · M02 한 벌) */ diff --git a/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts b/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts index e659114bb..1aa58d037 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts @@ -23,6 +23,7 @@ import { fetchLogic, setLogicApiPaths, type CalcLine, + type LogicInput, type LogicOne, type LogicRow, type TextAnswer, @@ -82,6 +83,8 @@ export function mountSangeunDetail( let ans: SkAnswer | null = null; let bare: SkAnswer | null = null; let fail = ""; + let extra: LogicInput[] = []; // 바탕 없는 SK — 더한줄 로직 입력 모음 = 공통 옵션 칸(725-6) + const inputsOf = new Map(); let seq = 0; let alive = true; @@ -151,12 +154,35 @@ export function mountSangeunDetail( 이름: doc.이름 ?? "", 결과단위: `원/${doc.단위 ?? ""}`, 출처: doc.바탕?.참조?.키 ?? L("M02_SangeunNoBase"), - 입력: [], + 입력: extra, 중간: [], 호표: [], 덧줄: [], }); + /** 바탕 없을 때만 — 더한줄 로직들의 입력을 모음(같은 이름은 하나) · 바탕 있으면 바탕 입력 그대로 */ + async function syncExtra(): Promise { + const keys = [ + ...new Set( + doc.바탕 ? [] : (doc.더한줄 ?? []).flatMap((x) => ("로직" in x ? [String(x.로직)] : [])), + ), + ]; + await Promise.all( + keys + .filter((k) => !inputsOf.has(k)) + .map((k) => + fetchLogic(k).then( + (r) => void inputsOf.set(k, r.logic.입력 ?? []), + () => void inputsOf.set(k, []), + ), + ), + ); + const seen = new Set(); + extra = keys + .flatMap((k) => inputsOf.get(k) ?? []) + .filter((i) => !seen.has(i.이름) && seen.add(i.이름)); + } + const touch = (again = true): void => { opt.onChanged(edit && JSON.stringify(doc) !== saved); if (again) void recalc(); @@ -165,6 +191,8 @@ export function mountSangeunDetail( async function recalc(): Promise { const mine = ++seq; + await syncExtra(); + if (mine !== seq || !alive) return; const plain = { ...doc, 덮개: {}, 뺀줄: [], 더한줄: [] }; try { const [got, was] = await Promise.all([ @@ -356,6 +384,7 @@ export function mountSangeunDetail( values[name] = v; drawCalc(); calcHost.querySelector("[data-calc-run]")?.click(); + if (!base) void recalc(); // 바탕 없음 — 시험 계산 단추 없음 · SK 셈을 바로 } function drawCalc(): void { diff --git a/M02_MasterTemplete/M02_Sangeun_Calc.py b/M02_MasterTemplete/M02_Sangeun_Calc.py index e6cbee6af..9a49015cb 100644 --- a/M02_MasterTemplete/M02_Sangeun_Calc.py +++ b/M02_MasterTemplete/M02_Sangeun_Calc.py @@ -194,7 +194,9 @@ def run( gaps = {at: x for at, x in enumerate(row["호표"]) if x.get("빠짐")} key = str(doc.get("키") or "SK-000000") over = _Over(whole, key, {**row, "키": key}) - values = {**got["입력값"], **(given or {})} + # 이 SK 줄의 입력만 — 더한줄 로직 입력은 `_fill` 이 따로 채움 + names = {str(i.get("이름")) for i in row.get("입력") or []} + values = {k: v for k, v in {**got["입력값"], **(given or {})}.items() if k in names} out = labor.run_line(over, key, values, got["켜쌓기율"], got["고름"] or None) covers = doc.get("덮개") or {} lines = [] diff --git a/resources/tester/test_725_1_sangeun_add_inputs.py b/resources/tester/test_725_1_sangeun_add_inputs.py index b567402e9..32eec4ca1 100644 --- a/resources/tester/test_725_1_sangeun_add_inputs.py +++ b/resources/tester/test_725_1_sangeun_add_inputs.py @@ -96,3 +96,21 @@ def test_screen_shows_dropped_lines(): """화면 — 빠진 줄 띠(이름 · 까닭) · 금액 없는 줄 계산서 금액 —.""" ts = (ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Sangeun_Detail.ts").read_text("utf-8") assert "ans?.빠진줄" in ts and 'L("M02_SangeunGap")' in ts + + +def test_no_base_common_option_seoul(world): # noqa: F811 + """725-6 — 바탕 없는 SK 도 공통 옵션(지역 서울) 따라감 = M01 셈.""" + doc = _sk(world["personal"], 더한줄=[GF_LINE]) + got = _run(world["personal"], doc, {"지역": "서울"}) + assert got["빠진줄"] == [], got + assert got["기준"], {k: v for k, v in got.items() if k != "줄"} + assert got["기준"]["계"] == pytest.approx(_m01(GF, {"지역": "서울", "보정작업": "해당 없음"})) + assert round(float(got["기준"]["계"]), 2) == 5297.37 + + +def test_screen_no_base_inputs_and_gap_dash(): + """725-6 화면 — 바탕 없음 = 더한줄 로직 입력 모음 칸 · 못 셈 줄 금액 「—」 + 까닭 풍선.""" + ts = (ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Sangeun_Detail.ts").read_text("utf-8") + assert "입력: extra" in ts and "syncExtra" in ts and "if (!base) void recalc()" in ts + ho = (ROOT / "M01_MasterData" / "M01_MasterData_UI_LogicLab_Ho.ts").read_text("utf-8") + assert "e.line?.까닭 ? gapCell" in ho and "title: why" in ho