From e96a0ef8ac119e09c4d6beb756a484ebc0ac6fda Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 4 Oct 2026 20:41:41 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(M02):=20=EC=9D=BC=EC=9C=84=EB=8C=80?= =?UTF-8?q?=EA=B0=80=20=EB=B3=B4=EA=B8=B0=20=EA=B0=88=EB=9E=98=20setQty(?= =?UTF-8?q?=EC=88=98=EB=9F=89,=20=EC=B9=B8)=20=E2=80=94=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=A0=84=20=EC=97=B0=EA=B2=B0=20=EC=B9=B8=20?= =?UTF-8?q?=EC=A3=BC=EC=86=8C=EB=8F=84=20=E3=80=8C=EC=88=98=EB=9F=89=20B6?= =?UTF-8?q?=20=3D=2020=E3=80=8D=20=EC=9C=BC=EB=A1=9C(=EB=A9=B4=EC=A0=81?= =?UTF-8?q?=EC=82=B0=EC=B6=9C=20=EC=98=A4=EB=A5=B8=EC=AA=BD=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 칸 = {알약 키: 칸 주소} · 주면 그 뒤로 저장본 연결보다 앞섬 · 열을 다시 보이면 비움 - 시험: test_m02_labor_values 에 수량 글 칸 주소 줄 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7 --- .../M02_MasterTemplete_Labor_View.ts | 20 +++++--- resources/tester/test_m02_labor_values.py | 51 +++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts index 35d805140..9547beba0 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts @@ -3,7 +3,8 @@ * 일위대가 보기 갈래(39-3 · 고치기 없음) — 그 열 일위대가의 담은 로직 표 + 자재목록 표(기준 · 반영). * 면적산출 상세 오른쪽 패널(sub3) · 구조물 조합 일위대가 카드(sub1)가 씀. 표 모양은 상세와 한 벌(`Labor_Rows`). * · `show(층, 열, 수량?)` = 문서 · 로직 입력 목록 · calc 를 받아 그림(최신 응답만) - * · `setQty({알약 키: 칸 값})` = 면적 칸 값이 바뀌면 서버 없이 짝 셈(`Labor_Calc.settle`)으로 바로 곱함 + * · `setQty({알약 키: 칸 값}, {알약 키: 칸 주소}?)` = 면적 칸 값이 바뀌면 서버 없이 짝 셈(`Labor_Calc.settle`)으로 + * 바로 곱함 · 칸 주소를 주면 저장 전 연결(setLink · 알약 끌기)도 「수량 B6 = 20」 으로 보임(안 주면 저장본 연결) * (그 열 면적 알약 값 전부를 줌 — 빠진 키는 「수량 없음」) * · `onRow` = 줄 누름(알약 키 · 이름 · 단위 — 칸 지정 모달 등) · 없으면 누름 없음 * ========================================================================== */ @@ -37,8 +38,9 @@ export interface LaborViewHandle { root: HTMLElement; /** 그 열 일위대가를 읽어 그림 — `수량` = 저장 안 한 면적 칸 값(없으면 저장된 면적산출) */ show: (layer: Layer, column: string | null, 수량?: Record) => Promise; - /** 면적 칸 값이 바뀜(알약 값 전부) — 받은 기준에 바로 곱함(서버 안 부름) */ - setQty: (수량: Record) => void; + /** 면적 칸 값이 바뀜(알약 값 전부) — 받은 기준에 바로 곱함(서버 안 부름) · + * `칸` = 지금 시트 연결 전부(알약 키 → 칸 주소 · 저장 전 연결 포함) — 주면 그 뒤로 이것만 씀 */ + setQty: (수량: Record, 칸?: Record) => void; /** 지금 문서(없으면 null) */ doc: () => LaborDoc | null; destroy: () => void; @@ -51,6 +53,8 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH let calc: LaborCalc | null = null; let fail: string | null = null; let cells = new Map(); + /** 화면이 준 칸 주소(저장 전 연결) — 있으면 저장본 연결보다 앞섬 */ + let given: Map | null = null; let seq = 0; const paint = (): void => { @@ -97,6 +101,7 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH 수량?: Record, ): Promise => { const mine = ++seq; + given = null; // 열이 바뀌면 화면이 다시 줌(refresh) doc = null; calc = null; fail = null; @@ -116,7 +121,7 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH ]); if (mine !== seq) return; for (const l of inputs.로직) logics.set(l.로직, l); - cells = new Map([...areaLinks(area)].map(([k, v]) => [k, v.칸])); + cells = given ?? new Map([...areaLinks(area)].map(([k, v]) => [k, v.칸])); paint(); // 금액 「…」 calc = await calcLabor(layer, column, null, 수량); } catch (error) { @@ -138,9 +143,10 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH return { root, show, - setQty: (수량) => { - if (!doc || !calc) return; - calc = settle(calc, doc, 수량); + setQty: (수량, 칸) => { + if (칸) cells = given = new Map(Object.entries(칸)); + if (!doc) return; + if (calc) calc = settle(calc, doc, 수량); paint(); }, doc: () => doc, diff --git a/resources/tester/test_m02_labor_values.py b/resources/tester/test_m02_labor_values.py index 1757129cb..527d83367 100644 --- a/resources/tester/test_m02_labor_values.py +++ b/resources/tester/test_m02_labor_values.py @@ -99,3 +99,54 @@ def test_값_셈() -> None: assert got["known"] == {"kept": {"높이": 2}, "lost": ["없앤것"]} assert got["line"] == {"text": "높이 2m · 규격 40", "empty": 2} # 종류 · 지역 = 자동값 없음 assert got["lineEmpty"] == {"text": "", "empty": 3} # 높이는 자동값이 있어 빈 값 아님 + + +ROWS_SCRIPT = r""" +const fs = require("fs"); +const path = require("path"); +const root = process.argv[1]; +const ts = require(path.join(root, "config", "node_modules", "typescript")); +const stub = new Proxy({}, { + get: (_t, k) => (k === "currentLanguageIndex" ? 0 : () => undefined), +}); +function load(f) { + const js = ts.transpileModule(fs.readFileSync(f, "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, + }).outputText; + const box = { exports: {} }; + const req = (id) => { + if (id.startsWith(".") && !id.includes("M01_MasterData")) { + const p = path.resolve(path.dirname(f), id); + return fs.existsSync(p + ".ts") ? load(p + ".ts") : stub; + } + return stub; + }; + new Function("exports", "module", "require", js)(box.exports, box, req); + return box.exports; +} +const m = load(path.join(root, "M02_MasterTemplete", "M02_MasterTemplete_Labor_Rows.ts")); +const cells = new Map([["돌쌓기|㎡", "B6"]]); +console.log(JSON.stringify([ + m.qtyText({ 수량: 20, 수량출처: "칸", 알약: "돌쌓기|㎡" }, "㎡", cells), + m.qtyText({ 수량: 20, 수량출처: "칸", 알약: "돌쌓기|㎡" }, "㎡"), + m.qtyText({ 수량: 3, 수량출처: "손", 알약: "돌쌓기|㎡" }, "㎡", cells), + m.qtyText({ 수량: null, 수량출처: "없음", 알약: "돌쌓기|㎡" }, "㎡", cells), +])); +""" + + +def test_수량_글은_이은_칸_주소를_보임() -> None: + """`Labor_View.setQty(수량, 칸)` 의 칸 주소로 「수량 B6 = 20 ㎡」 — 없으면 「면적산출 칸」.""" + node = shutil.which("node") + if not node: + pytest.skip("node 없음") + out = subprocess.run( + [node, "-e", ROWS_SCRIPT, str(ROOT)], capture_output=True, text=True, encoding="utf-8" + ) + assert out.returncode == 0, out.stderr + assert json.loads(out.stdout) == [ + "수량 B6 = 20 ㎡", + "수량 면적산출 칸 = 20 ㎡", + "수량 손 입력 3 ㎡", + "", + ] From 8cf26e0b0cf47865f94a557b34783488f79d4bd6 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 4 Oct 2026 20:41:41 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test(M02):=20=EC=B9=B4=EB=93=9C=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=C2=B7=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EC=A4=91=20=EC=8B=9C=ED=97=98=20=EA=B8=B0=EB=8C=80=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=EC=A7=80=EA=B8=88=20=EA=BC=B4=EB=A1=9C=20=E2=80=94?= =?UTF-8?q?=20=EA=B3=A0=EB=A5=B4=EA=B8=B0=20=EB=AA=A8=EB=8B=AC=20retry=20?= =?UTF-8?q?=ED=95=98=EB=82=98=20=C2=B7=20=EC=9D=BC=EC=9C=84=EB=8C=80?= =?UTF-8?q?=EA=B0=80=20=EB=82=98=EB=AC=B4(Labor.ts)=20=EC=A7=80=EC=9B=80?= =?UTF-8?q?=20=EB=92=A4=20=EC=8B=A4=EB=AC=B4=EB=AC=B8=EC=84=9C=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7 --- resources/tester/test_m02_card_shell.py | 6 ++++-- resources/tester/test_ui_loading.py | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/tester/test_m02_card_shell.py b/resources/tester/test_m02_card_shell.py index 4972c8711..9e77f5f8a 100644 --- a/resources/tester/test_m02_card_shell.py +++ b/resources/tester/test_m02_card_shell.py @@ -57,5 +57,7 @@ def test_실패는_카드_안_글과_다시_불러오기(): assert "showToast" not in cards # 토스트 안 씀(저장 알림은 틀) assert "c.shell.fail(" in cards and "reload" in cards picker = (ROOT / "ui_template" / "ui_template_item_picker_detail.ts").read_text("utf-8") - assert "inputs.set(null, why(error), () => void paintInputs(row, seq));" in picker - assert "const stop = inputs.loading();" in picker + # 일위대가 카드 · 값 카드가 다시 불러오기 하나(retry)를 같이 씀(39-1c) + assert "const retry = (): void => void paintLabor(row, seq);" in picker + assert "inputs.set(null, why(error), retry);" in picker + assert "const stops = [labor.loading(), inputs.loading()];" in picker diff --git a/resources/tester/test_ui_loading.py b/resources/tester/test_ui_loading.py index cb1b173ab..87b8ddfa3 100644 --- a/resources/tester/test_ui_loading.py +++ b/resources/tester/test_ui_loading.py @@ -80,6 +80,9 @@ def test_실패_글과_다시_불러오기_M02_자리(): edit = _text(M02 / "M02_MasterTemplete_Labor_Edit.ts") assert "loadingError(reason(error), () => void open(key))" in edit assert "(current ? () => {} : showLoading(view))" in edit - assert "const stop = editor.loading();" in _text(M02 / "M02_MasterTemplete_Labor.ts") + # 좌측 일위대가 조합 나무(Labor.ts)는 39-2 에 지움 — 그 목록 자리는 실무문서 화면(같은 꼴) + practice = _text(M02 / "M02_MasterTemplete_Practice.ts") + assert "const stop = showLoading(view);" in practice + assert "loadingError(why(error), () => void drawList())" in practice assert "cards.map((c) => c.shell.loading())" in _text(M02 / "M02_MasterTemplete_ItemCards.ts") assert "loading: () => showLoading(host)," in _text(M02 / "M02_MasterTemplete_CardShell.ts")