diff --git a/resources/tester/test_166_2_b09_bill_hand.py b/resources/tester/test_166_2_b09_bill_hand.py index 38c5666db..9b2c75113 100644 --- a/resources/tester/test_166_2_b09_bill_hand.py +++ b/resources/tester/test_166_2_b09_bill_hand.py @@ -195,37 +195,50 @@ def test_새_산근_사본_뒤_번호_호표_탭_집계표(tmp_path): def test_우클릭_메뉴_줄_추가_지우기_연결_TS(): - src = _ts(["UI_Tabs", "UI_Tabs_Bill_Edit"]) + # 184-3 — 모든 줄 메뉴 · 셈 출력 줄 = 계약 꼴(parent · auto · auto_{칸}) · 첫 손질 = v2 로 굳힘 + src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit"]) rows = [ - {"kind": "title", "key": "sec:1.2", "section": "1.2"}, - {"kind": "item", "key": "earth:4", "section": "1.2"}, - {"kind": "item", "key": "hand:b", "section": "1.2", "hand": True}, - ] + {"kind": "title", "key": "sec:1", "parent": None, "auto": True}, + {"kind": "title", "key": "sec:1.2", "parent": "sec:1", "auto": True}, + {"kind": "item", "key": "earth:4", "parent": "sec:1.2", "auto": True, "qty": 100, + "auto_qty": 5675}, + {"kind": "item", "key": "hand:b", "parent": "sec:1.2", "hand": True}, + ] # fmt: skip body = f""" const rows = {json.dumps(rows, ensure_ascii=False)}; const first = {json.dumps(HAND[1], ensure_ascii=False)}; -let user = {{ bill: {{ qty: {{ "earth:4": 100 }}, rows: [first] }} }}; +const start = {{ bill: {{ qty: {{ "earth:4": 100 }}, rows: [first] }} }}; +let user = start; const ctx = {{ sheets: {{}}, payload: {{}}, get user() {{ return user; }}, setUser: (u) => (user = u) }}; const name = (x) => (x === "-" ? "-" : Array.isArray(x) ? x[0] : x.label); const labels = (i) => billMenu(ctx, rows, i).map(name); -const out = {{ title: labels(0), auto: labels(1), hand: labels(2) }}; -billMenu(ctx, rows, 1).find((x) => Array.isArray(x) && x[0] === "위에 줄 추가")[1](); -out.above = user.bill.rows[1]; -billMenu(ctx, rows, 1).find((x) => Array.isArray(x) && x[0] === "수량 자동으로 되돌리기")[1](); -out.qty = user.bill.qty; -billMenu(ctx, rows, 2).find((x) => Array.isArray(x) && x[0] === "줄 지우기")[1](); -out.left = user.bill.rows.map((r) => [r.key.slice(0, 5), r.after]); -out.edit = ["name", "qty", "sum"].map(handEditable); +const run = (i, label) => {{ + user = start; + billMenu(ctx, rows, i).find((x) => Array.isArray(x) && x[0] === label)[1](); + return user.bill; +}}; +const out = {{ title: labels(1), auto: labels(2), hand: labels(3) }}; +out.above = run(2, "위에 줄 추가"); +out.reset = run(2, "자동값으로 되돌리기"); +out.left = run(3, "줄 지우기"); console.log(JSON.stringify(out)); """ out = json.loads(_node(src + body)) - assert out["title"] == ["아래에 줄 추가"] - assert out["auto"] == ["위에 줄 추가", "아래에 줄 추가", "-", "수량 자동으로 되돌리기"] - assert out["hand"] == ["위에 줄 추가", "아래에 줄 추가", "-", "연결", "연결 풀기", "-", - "묶음 줄로 바꾸기", "-", "줄 지우기"] # fmt: skip # 182 레벨 - assert out["above"]["after"] == "sec:1.2" and out["above"]["name"] == "새 줄" - assert out["qty"] == {} - # hand:b 지움 — 남은 새 줄은 그대로(자기 자리) · 지운 줄 뒤에 붙던 줄은 지운 줄 자리로 - assert out["left"] == [["hand:", "sec:1.2"]] - assert out["edit"] == [True, True, False] + add = ["위에 줄 추가", "아래에 줄 추가"] + assert out["title"] == [*add, "아래에 하위 줄 추가", "새 공종 추가(1단)", "-", "줄 지우기"] + assert out["auto"] == [*add, "새 공종 추가(1단)", "-", "연결", "-", "아래로 ↓", "내어쓰기 ←", + "-", "자동값으로 되돌리기", "줄 지우기"] # fmt: skip + assert out["hand"] == [*add, "새 공종 추가(1단)", "-", "연결", "연결 풀기", "-", "위로 ↑", + "내어쓰기 ←", "묶음 줄로 바꾸기", "-", "줄 지우기"] # fmt: skip + # 위에 줄 = 같은 부모 앞 · 옛 꼴 → v2(옛 손 줄 칸 · 수량 덮개 옮김) + above = out["above"] + assert [e["key"][:5] for e in above["rows"]] == ["sec:1", "sec:1", "hand:", "earth", "hand:"] + new, earth, hand = above["rows"][2:] + assert (new["name"], new["parent"], above["v"]) == ("새 줄", "sec:1.2", 2) + assert earth == {"key": "earth:4", "parent": "sec:1.2", "qty": 100} + assert (hand["key"], hand["qty"], hand["ref"]) == ("hand:b", 4, HAND[1]["ref"]) + assert "qty" not in out["reset"]["rows"][2] # 자동값으로 = 수량 덮개 지움 + # 새 줄 지움 = 덮개에서 빠짐(gone 은 자동 줄만) + assert [e["key"] for e in out["left"]["rows"]] == ["sec:1", "sec:1.2", "earth:4"] + assert out["left"]["gone"] == [] diff --git a/resources/tester/test_182_b09_bill_level.py b/resources/tester/test_182_b09_bill_level.py index 6cbf385b8..f9f68a211 100644 --- a/resources/tester/test_182_b09_bill_level.py +++ b/resources/tester/test_182_b09_bill_level.py @@ -177,46 +177,62 @@ const labels = (key) => billMenu(ctx, rows, at(key)).map(name); const run = (key, label) => { user = start; billMenu(ctx, rows, at(key)).find((x) => Array.isArray(x) && x[0] === label)[1](); - return user.bill.rows; + return user.bill; +}; +// 1.2 공종 차례 [키(새 줄 = NEW), 부모] +const order = (bill) => { + const list = bill.rows.map((r) => [r.name === "새 줄" ? "NEW" : r.key, r.parent ?? null]); + const from = list.findIndex(([k]) => k === "sec:1.2"); + return list.slice(from + 1, list.findIndex(([k]) => k === "earth:5") + 1); }; const out = { g1: labels("hand:g1"), a: labels("hand:a"), g2: labels("hand:g2"), b: labels("hand:b"), x: labels("hand:x"), auto: labels("earth:4"), }; -out.child = run("hand:g2", "아래에 하위 줄 추가").at(-1); -out.below = run("hand:g1", "아래에 줄 추가").at(-1); -out.belowB = run("hand:b", "아래에 줄 추가").at(-1); -out.indent = run("hand:x", "들여쓰기 →").find((r) => r.key === "hand:x"); -out.outdent = run("hand:g2", "내어쓰기 ←").find((r) => r.key === "hand:g2"); -out.toItem = run("hand:g2", "항목으로 바꾸기").filter((r) => ["hand:g2", "hand:b"].includes(r.key)); -out.toGroup = run("hand:x", "묶음 줄로 바꾸기").find((r) => r.key === "hand:x"); -out.removed = run("hand:g1", "줄 지우기").filter((r) => ["hand:a", "hand:g2"].includes(r.key)); +out.child = order(run("hand:g2", "아래에 하위 줄 추가")); +out.below = order(run("hand:g1", "아래에 줄 추가")); +out.belowB = order(run("hand:b", "아래에 줄 추가")); +out.indentBill = run("hand:x", "들여쓰기 →"); +out.indent = order(out.indentBill); +out.outdent = order(run("hand:g2", "내어쓰기 ←")); +out.toItem = run("hand:g2", "항목으로 바꾸기").rows.filter((r) => ["hand:g2", "hand:b"].includes(r.key)); +out.toGroup = run("hand:x", "묶음 줄로 바꾸기").rows.find((r) => r.key === "hand:x"); +out.removed = order(run("hand:g1", "줄 지우기")); console.log(JSON.stringify(out)); """ +#: 1.2 공종 기본 차례(셈 출력) — [키, 부모] +BASE = [ + ["earth:4", "sec:1.2"], ["hand:g1", "sec:1.2"], ["hand:a", "hand:g1"], ["hand:g2", "hand:g1"], + ["hand:b", "hand:g2"], ["hand:c", "hand:g2"], ["hand:x", "sec:1.2"], ["earth:5", "sec:1.2"], +] # fmt: skip + + +def _put(order, at, *pairs): + return [*order[:at], *[list(p) for p in pairs], *order[at:]] + def test_우클릭_레벨_메뉴_TS(): + # 184-3 — 모든 줄 메뉴 · 손질 = user.bill v2(차례 · 부모) rows = compute_estimation(INPUTS, USER, None, BOOKS)["bill"]["rows"] body = MENU_JS.replace("__ROWS__", json.dumps(rows, ensure_ascii=False)).replace( "__USER__", json.dumps(USER, ensure_ascii=False) ) - out = json.loads(_node(_ts(["UI_Tabs", "UI_Tabs_Bill_Edit"]) + body)) + out = json.loads(_node(_ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit"]) + body)) level = {"묶음 줄로 바꾸기", "항목으로 바꾸기", "들여쓰기 →", "내어쓰기 ←"} pick = lambda key: [x for x in out[key] if x in level] # noqa: E731 assert out["g1"][:3] == ["위에 줄 추가", "아래에 줄 추가", "아래에 하위 줄 추가"] - assert "연결" not in out["g1"] and pick("g1") == ["항목으로 바꾸기"] - assert pick("a") == ["묶음 줄로 바꾸기"] # 뒤에 형제(g2) → 내어쓰기 없음 - assert pick("g2") == ["항목으로 바꾸기", "내어쓰기 ←"] # 막내 - assert pick("b") == [] # 묶음 안 묶음 안 = 깊이 한도 · 형제 c 뒤 → 내어쓰기 없음 - assert pick("x") == ["묶음 줄로 바꾸기", "들여쓰기 →"] # 위 형제 g1 - assert pick("auto") == [] and "줄 지우기" not in out["auto"] - assert {k: out["child"][k] for k in ("section", "after", "parent")} == { - "section": "1.2", "after": "hand:c", "parent": "hand:g2"} # fmt: skip - # g1 자손 끝 · 부모 = 셈 parent 그대로(184 공종 제목 키 — 옛 꼴 다듬기가 hand: 아닌 부모 버림) - assert (out["below"]["after"], out["below"]["parent"]) == ("hand:c", "sec:1.2") - assert (out["belowB"]["after"], out["belowB"]["parent"]) == ("hand:b", "hand:g2") - assert out["indent"]["parent"] == "hand:g1" - assert "parent" not in out["outdent"] # g1 이 맨 위 → 공종 아래 + assert "연결" not in out["g1"] and pick("g1") == ["내어쓰기 ←", "항목으로 바꾸기"] + assert pick("a") == ["내어쓰기 ←", "묶음 줄로 바꾸기"] # 위 형제 없음 → 들여쓰기 없음 + assert pick("g2") == ["내어쓰기 ←", "항목으로 바꾸기"] # 위 형제 a = 항목 + assert pick("b") == ["내어쓰기 ←", "묶음 줄로 바꾸기"] # 레벨 5 묶음까지 + assert pick("x") == ["들여쓰기 →", "내어쓰기 ←", "묶음 줄로 바꾸기"] # 위 형제 g1 + assert pick("auto") == ["내어쓰기 ←"] and "줄 지우기" in out["auto"] # 자동 줄도 지움 + assert out["child"] == _put(BASE, 6, ["NEW", "hand:g2"]) # g2 마지막 자식 뒤 + assert out["below"] == _put(BASE, 6, ["NEW", "sec:1.2"]) # g1 자손 끝 뒤 · 같은 부모 + assert out["belowB"] == _put(BASE, 5, ["NEW", "hand:g2"]) + assert out["indent"] == [*BASE[:6], ["hand:x", "hand:g1"], BASE[7]] # g1 마지막 자식 + assert out["outdent"] == [*BASE[:3], ["hand:g2", "sec:1.2"], *BASE[4:]] # g1 뒤 assert [(r["key"], r.get("kind"), r["parent"]) for r in out["toItem"]] == [ ("hand:g2", None, "hand:g1"), ("hand:b", None, "hand:g1")] # fmt: skip assert (out["toGroup"]["kind"], out["toGroup"]["qty"], out["toGroup"]["ref"]) == ( @@ -224,12 +240,10 @@ def test_우클릭_레벨_메뉴_TS(): None, None, ) - # g1 지움 — 자식은 공종 아래로 · 바로 뒤 줄은 g1 자리(earth:4 뒤)로 - assert [(r["key"], r["after"], r.get("parent")) for r in out["removed"]] == [ - ("hand:a", "earth:4", None), ("hand:g2", "hand:a", None)] # fmt: skip + # g1 지움 — 자식은 g1 부모(공종) 아래 · 그 자리 + assert out["removed"] == [BASE[0], ["hand:a", "sec:1.2"], ["hand:g2", "sec:1.2"], *BASE[4:]] # 들여쓴 x = g1 금액에 듦(셈 짝 그대로) - user = {"bill": {"rows": [r if r["key"] != "hand:x" else out["indent"] for r in HAND]}} - rows2 = _rows(compute_estimation(INPUTS, user, None, BOOKS)) + rows2 = _rows(compute_estimation(INPUTS, {"bill": out["indentBill"]}, None, BOOKS)) assert (rows2["hand:x"]["level"], rows2["hand:g1"]["sum"]) == (4, 12300 + 2460 + 1230) @@ -274,7 +288,7 @@ table.onCell(at("hand:g1"), "connect"); table.onCell(at("earth:4"), "connect"); table.onCell(at("hand:c"), "connect"); out.menus = opened.map((m) => [m.x, m.y, m.items.map(name)]); -opened[1].items[0][1](); // hand:c → 일위대가 호표 고르기 창 +opened[2].items[0][1](); // hand:c → 일위대가 호표 고르기 창 picked.rows[0].onPick(); out.picked = [picked.title, picked.closed, picked.rows.map((r) => r.tag)]; out.ref = user.bill.rows.find((r) => r.key === "hand:c").ref; @@ -289,17 +303,18 @@ def test_연결_단추_칸_메뉴_고르기_창_TS(): .replace("__BILL__", json.dumps(sheets["bill"], ensure_ascii=False)) .replace("__DAEGA__", json.dumps(BOOKS["daega"], ensure_ascii=False)) ) - src = _ts(["UI_Tabs", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"]) + src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"]) out = json.loads(_node(src + body)) assert out["columns"] == ["connect", "note"] # 비고 앞 「연결」 칸 - assert out["connect"] == ["대가3 ▾", "연결 ▾", "", ""] # 손 항목 줄만 · 연결됨 = 호표 번호 + # 184-3 모든 항목 줄 · 새 줄 연결됨 = 호표 번호 · 자동 줄 연결됨 = 「연결됨」 · 묶음 = 없음 assert out["link"] == [None, {"tab": "dg", "key": "DG:a"}] # 단추 칸 = 이동 안 함 assert out["cls"] == [ "is-lv1 is-title", "is-lv3 is-group is-hand", "is-lv4 is-hand", "is-lv3"] # fmt: skip assert out["edit"] == [True, False] # 묶음 줄 = 이름만 picks = ["일위대가 호표", "단가산출 호표", "단가산출 로직(M01 · 새 산근)"] - # 누른 칸 아래(left · bottom) · 묶음 줄 · 자동 줄 = 안 엶 · 연결된 줄 = 연결 풀기 더함 - assert out["menus"] == [[10, 40, [*picks, "-", "연결 풀기"]], [10, 40, picks]] + # 누른 칸 아래(left · bottom) · 묶음 줄 = 안 엶 · 연결된 줄(자동 줄도) = 연결 풀기 더함 + unlink = [10, 40, [*picks, "-", "연결 풀기"]] + assert out["menus"] == [unlink, unlink, [10, 40, picks]] assert out["picked"] == ["일위대가 호표 고르기", True, ["대가3"]] # 166-2 고르기 창 재사용 assert out["ref"] == DG @@ -327,6 +342,7 @@ console.log(JSON.stringify(out)); def test_글_칸_위_우클릭도_줄_메뉴_TS(): - out = json.loads(_node(_ts(["UI_Tabs", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"]) + ROW_MENU_JS)) + src = _ts(["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"]) + out = json.loads(_node(src + ROW_MENU_JS)) assert out["onInput"] is True and out["onCell"] is True assert out["opened"] == [[5, 6, ["줄 7"]], [5, 6, ["줄 7"]]]