diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts index 619b2a157..d6e8dede0 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts @@ -454,6 +454,22 @@ export function openMaterialModal(target: ModalTarget): void { }; setSum(known); const rate = el("div", { className: "m01lab__modal-body" }); + /** `prices` 에 있으면 그대로 · 없으면 키로 찾아 옴 — 못 찾으면 「관리자 값 미확정」 */ + const lookup = (ref: string, label: string): void => { + const show = (brief: ElementBrief | null): void => { + rate.replaceChildren( + brief ? priceView(ref, brief) : muted(`${label} — ${tl("Pill_NoValue")}`), + ); + setSum(brief); + }; + rate.append(muted("…")); + const cached = target.prices[ref]; + if (cached) show(cached); + else + void searchElements(groupOf(ref), ref) + .then((got) => show(got.items.find((it) => it.ref === ref) ?? null)) + .catch(() => show(null)); + }; if (known && priceRef) { rate.append(priceView(priceRef, known, target.usedSlot)); } else if (logicKey) { @@ -484,25 +500,9 @@ export function openMaterialModal(target: ModalTarget): void { if (!ref) { const names = [...element.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]).join(" · "); rate.append(muted(`${target.unitText || names} — ${tl("Pill_Unresolved")}`)); - } else { - const show = (brief: ElementBrief | null): void => { - rate.replaceChildren( - brief - ? priceView(ref, brief) - : muted(`${target.unitText ?? ref} — ${tl("Pill_NoValue")}`), - ); - setSum(brief); - }; - rate.append(muted("…")); - const cached = target.prices[ref]; - if (cached) show(cached); - else - void searchElements(groupOf(ref), ref) - .then((got) => show(got.items.find((it) => it.ref === ref) ?? null)) - .catch(() => show(null)); - } + } else lookup(ref, target.unitText ?? ref); } else if (element) { - rate.append(muted(`${element} — ${tl("Pill_NoValue")}`)); + lookup(element, element); // 더한 줄(M02) 등 `prices` 에 없는 요소 — 키로 찾아 옴(725-4) } body.replaceChildren( ...(target.edit ? [target.edit(close)] : []), diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_RowModal.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_RowModal.ts index 395279114..2696ff5d8 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_RowModal.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_RowModal.ts @@ -14,6 +14,7 @@ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { askConfirm, openModal } from "@ui/ui_template_modal"; import { createText } from "@ui/ui_template_text"; import type { ItemLogic } from "./M02_MasterTemplete_Api_Fetch"; +import { HOPYO_BUILT } from "./M02_MasterTemplete_Hopyo_Check"; import type { LaborCalc, LaborMat, LaborRow, MatMarkKey } from "./M02_MasterTemplete_Labor_Api"; import { openLaborInput } from "./M02_MasterTemplete_Labor_Input"; import { logicMatRows } from "./M02_MasterTemplete_Labor_LogicMats"; @@ -175,6 +176,7 @@ export function openRowModal(o: RowModalOptions): void { try { const name = await saveAsSk(row, got?.patch ?? row.패치 ?? null, o.title, o.unit); showToast(t("Saved").replace("{k}", name), "success"); + document.dispatchEvent(new Event(HOPYO_BUILT)); // 좌측 단가산출 나무 다시 읽음(725-4) } catch (error) { return showToast(error instanceof Error ? error.message : String(error), "error"); } diff --git a/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts b/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts index a7f97bb36..7ad6f00ee 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts @@ -139,6 +139,9 @@ export function mountSangeunDetail( ); const opened = (): boolean => (doc.열린입력 ?? []).length > 0; + // 바탕 없는 SK 의 지문 = 이름 · 단위 따라 바뀜 — 바탕이 있을 때만 M01 개정으로 봄(725-3) + const revisedBase = (): boolean => + !!(doc.바탕 && doc.바탕판 && ans?.바탕판 && doc.바탕판 !== ans.바탕판); const baseKey = (): string | null => doc.바탕?.로직 ?? null; /** 바탕 없는 · 참조 바탕 SK 의 기본정보 자리 줄 */ @@ -191,7 +194,7 @@ export function mountSangeunDetail( }; const baseName = base ? `${base.logic.키} ${base.logic.이름}` : null; const baseText = baseName ?? doc.바탕?.참조?.키 ?? L("M02_SangeunNoBase"); - const revised = !!(doc.바탕판 && ans?.바탕판 && doc.바탕판 !== ans.바탕판); + const revised = revisedBase(); pick.hidden = !edit || !!doc.바탕 || !!doc.뺀줄?.length || !!Object.keys(doc.덮개 ?? {}).length; head.replaceChildren( field(L("M02_Name"), "이름"), @@ -211,7 +214,7 @@ export function mountSangeunDetail( function drawNotes(): void { const lost = ans?.짝잃음 ?? []; - const revised = !!(doc.바탕판 && ans?.바탕판 && doc.바탕판 !== ans.바탕판); + const revised = revisedBase(); notes.replaceChildren( ...(fail || ans?.까닭 ? [createBanner({ tone: "danger", text: fail || String(ans?.까닭) })] diff --git a/resources/tester/test_717_2b_sangeun_detail.py b/resources/tester/test_717_2b_sangeun_detail.py index d22602679..cacc11c8f 100644 --- a/resources/tester/test_717_2b_sangeun_detail.py +++ b/resources/tester/test_717_2b_sangeun_detail.py @@ -233,3 +233,15 @@ def test_화면_문서_꼴_서버_저장_셈(world): # noqa: F811 — 가져온 assert added["수량"] == pytest.approx(0.01) assert added["단가"] == pytest.approx(lines["labor:LB000002#0"]["단가"]) assert got["기준"]["계"] == pytest.approx(sum(x["금액"] for x in got["줄"])) + + +def test_725_바탕_없으면_개정_안_뜸_새_SK_나무_다시_읽음_더한줄_단가_찾음(): + """725-3 바탕 없는 SK 는 이름 · 단위 따라 지문이 바뀌어도 M01 개정 아님 · 725-4 새 SK 뒤 나무 알림 · + `prices` 에 없는 요소 = 키로 찾아 옴.""" + detail = _text(M02 / "M02_MasterTemplete_Sangeun_Detail.ts") + assert "!!(doc.바탕 && doc.바탕판 && ans?.바탕판 && doc.바탕판 !== ans.바탕판)" in detail + assert detail.count("const revised = revisedBase();") == 2 + modal = _text(M02 / "M02_MasterTemplete_Labor_RowModal.ts") + assert modal.index("saveAsSk(row") < modal.index("dispatchEvent(new Event(HOPYO_BUILT))") + lab = _text(ROOT / "M01_MasterData" / "M01_MasterData_UI_LogicLab_Modal.ts") + assert "lookup(element, element)" in lab and "Pill_NoValue" in lab