diff --git a/M01_MasterData/SheetLogic/M01_SL_Table_Build.ts b/M01_MasterData/SheetLogic/M01_SL_Table_Build.ts index 3e62745ac..8f8984437 100644 --- a/M01_MasterData/SheetLogic/M01_SL_Table_Build.ts +++ b/M01_MasterData/SheetLogic/M01_SL_Table_Build.ts @@ -159,7 +159,7 @@ export function planLookup(info: TblInfo, roles: TblRoles): TblPlan { const bl = blankable.slice(0, 2); const subsets = bl.length === 2 ? [[bl[0]], [bl[1]], [bl[0], bl[1]]] : bl.length === 1 ? [[bl[0]]] : []; - variants.push(...subsets); + variants.push(...subsets.filter((sub) => sub.length < exact.length)); // 모든 조건 칸이 빈칸인 변형은 열쇠가 없음 } const keyCols = exact.length ? variants : []; const kStart = W + 1; @@ -310,6 +310,8 @@ export function applyTable( if (si < 0) throw new Error("상자의 시트가 없음"); const plan = planLookup(info, roles); const sheet: Sheet = next.workbook.시트[si]; + // 이미 있던 입력 칸 내용(예시값 · 선을 그은 참조 수식)은 같은 이름이면 그대로 가져감 + const oldIn = Object.fromEntries(node.pillsIn.map((p) => [p.name, sheet.칸[p.cell]])); const keep = Object.entries(sheet.칸).filter( ([a]) => colIndex(a.replace(/\d+/g, "")) < plan.helperFrom, ); @@ -319,6 +321,8 @@ export function applyTable( Object.assign(sheet.칸, blockCells(info)); } Object.assign(sheet.칸, plan.cells); + for (const [name, cell] of Object.entries(plan.inCells)) + if (oldIn[name]) sheet.칸[cell] = oldIn[name]; const pills = pillsOf(info, plan, opts.outside); const gone = (dir: "in" | "out", name: string): boolean => !(dir === "in" ? pills.pillsIn : pills.pillsOut).some((p) => p.name === name); diff --git a/resources/tester/test_m01_sl_table_build.py b/resources/tester/test_m01_sl_table_build.py index c135e728c..2676fd680 100644 --- a/resources/tester/test_m01_sl_table_build.py +++ b/resources/tester/test_m01_sl_table_build.py @@ -36,6 +36,17 @@ const out = jobs.map((job) => { return { cells, plan }; } if (job.op === "paste") return m.parsePaste(job.text); + if (job.op === "reapply") { + const base = { key: null, name: "t", resultUnit: "원/㎡", edges: [], + workbook: { 종류: "통합문서", 판: 1, 열: "t", 서식: [{}], 시트: [{ id: "s1", 이름: "표", 칸: {} }] }, + nodes: [{ id: "n1", kind: "table", name: "표", sheetId: "s1", pillsIn: [], pillsOut: [], stage: 1 }] }; + const opts = { ref: "T1", fillBlock: true, outside: false }; + const first = m.applyTable(base, "n1", job.info, job.roles, opts).sl; + const cell = first.nodes[0].pillsIn[0].cell; + first.workbook.시트[0].칸[cell] = { 식: "'다른 시트'!B2" }; + const second = m.applyTable(first, "n1", job.info, job.roles, { ...opts, fillBlock: false }).sl; + return { cell, kept: second.workbook.시트[0].칸[cell], pills: second.nodes[0].pillsIn.map((p) => p.name), name: second.nodes[0].name }; + } return null; }); process.stdout.write(JSON.stringify(out)); @@ -173,3 +184,16 @@ def test_붙여넣기_읽기(): assert info["vals"] == {"할증": ""} assert info["rows"][1] == {"경사": "급", "범위": [10, None], "할증": 10} assert run([{"op": "paste", "text": "한 줄뿐"}])[0]["error"] + + +def test_다시_짜도_선_수식은_그대로(): + out = run([{"op": "reapply", "info": MINE, "roles": {"cond": ["경사"], "out": ["할증"]}}])[0] + assert out["kept"] == {"식": "'다른 시트'!B2"} + assert out["pills"] == ["경사"] and out["name"] == "표 · 견본" + + +def test_표가_비었어도_수식이_깨지지_않음(): + """내 표를 막 만든 때(줄이 다 빈칸) — 빈칸 변형 열쇠가 빈 수식이 되면 안 됨.""" + empty = {**MINE, "rows": [{}, {}]} + r = solve(empty, {"경사": "급"}, ["할증"]) + assert r["상태"] == "맞는 줄 없음" and r["할증"] == "#N/A"