fix(M02): 51-2 일위대가 줄 상세 — 표 아래 따로 상자 대신 고른 줄 바로 아래 하위 행(접고 폄)
- 줄 누름 = 그 줄 아래 하위 행(같은 표 · 같은 칸 · 이름 앞 ▸/▾ · 한 줄만 · 다시 누름 = 닫힘) - 단가산출 · 승격 = 그 로직 재료 · 노무 · 경비 줄 · 사전조합 = 안 줄 · 금액 칸 = 기준 / 반영 두 줄 꼴 - 서버 승격 줄 = 1 단위로 나눔(하위 합 = 위 줄 기준) - 50-4 M01 시험계산 상자 · 안 쓰는 글 걷어냄 - 시험: 승격 줄 합 = 기준 · ui_checks 51-2 시나리오 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -170,7 +170,9 @@ def test_승격_줄_상세는_원_로직_키_표_사전조합은_안_줄만(worl
|
||||
doc = labor.clean({"열": "rv", "담은로직": [{"참조": {**SYS, "키": k}} for k in (sp, sc)]})
|
||||
promote, combo = labor.calc(doc, {})["줄"]
|
||||
assert promote["원로직"] == TEN and promote["줄"]
|
||||
assert sum(x["금액"] for x in promote["줄"]) == pytest.approx(float(_base(TEN)["계"]))
|
||||
assert sum(x["금액"] for x in promote["줄"]) == pytest.approx(
|
||||
promote["기준"]["계"]
|
||||
) # 1 단위(51-2)
|
||||
assert "줄" not in combo and [x["참조"]["키"] for x in combo["안"]] == [sp]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/* 51-2 — 일위대가 단가산출 로직 표 줄 누름 = 그 줄 바로 아래 하위 행(같은 표 · 같은 칸 · 접고 폄) — 카드 · 상세 두 곳
|
||||
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 ·
|
||||
* 나무에 item1(구조물 조합 · 일위대가 로직 둘 이상)이 있음
|
||||
* 부르는 법: orca eval --expression "$(tr '\n' ' ' < 이 파일)" (async 식 · JSON 글을 돌려줌)
|
||||
* 하는 일: item1 카드 표 줄마다 누름(하위 행 셈) · 같은 줄 다시 → 일위대가 카드 [수정]으로 상세 → 같은 차례 → item1 다시
|
||||
* 기대 수치(카드 · 상세 같음):
|
||||
* - lines[*].subs ≥ 1 · lines[*].cells = head(칸 수 = 머리 칸 수) · lines[*].next = true(고른 줄 바로 아래)
|
||||
* - lines[*].base ≈ lines[*].subBase · lines[*].applied ≈ lines[*].subApplied(반올림 차 ≤ 하위 행 수)
|
||||
* - lines[*].open = 1 · lines[*].arrow = "▾"(한 줄만 열림)
|
||||
* - again.subs = 0 · again.selected = 0(같은 줄 다시 = 닫힘) · box = 0(따로 상자 없음)
|
||||
*/
|
||||
(async () => {
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const until = async (fn, ms = 8000) => {
|
||||
for (let t = 0; t < ms; t += 150) {
|
||||
const v = fn();
|
||||
if (v) return v;
|
||||
await w(150);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const n = (s) => Number(String(s ?? "").replace(/[^0-9.-]/g, "")) || 0;
|
||||
const rowsOf = (t) => [...t.querySelectorAll("tbody tr[data-labor-logic]")];
|
||||
const settled = (get) =>
|
||||
until(() => {
|
||||
const t = get();
|
||||
return t && !/…/.test(t.querySelector("tbody").innerText) ? t : null;
|
||||
}, 10000);
|
||||
const total = (tr, part) => {
|
||||
const tds = tr.querySelectorAll(".m02-labrow__money");
|
||||
return n(tds[tds.length - 1].querySelector(part).innerText);
|
||||
};
|
||||
const measure = async (get) => {
|
||||
const t = await settled(get);
|
||||
const head = t.querySelectorAll("thead th").length;
|
||||
const keys = rowsOf(t).map((r) => r.getAttribute("data-labor-logic"));
|
||||
const click = async (key) => {
|
||||
get().querySelector(`tr[data-labor-logic="${key}"] .m02-labrow__name`).click();
|
||||
await w(300);
|
||||
};
|
||||
const lines = [];
|
||||
for (const key of keys) {
|
||||
await click(key);
|
||||
const tb = get();
|
||||
const tr = tb.querySelector(`tr[data-labor-logic="${key}"]`);
|
||||
const subs = [...tb.querySelectorAll("tbody tr[data-labor-sub]")];
|
||||
/* 고른 줄 바로 아래 — 옵션 값 줄(있으면) 다음이 하위 행 */
|
||||
let at = tr.nextElementSibling;
|
||||
if (at?.hasAttribute("data-labor-opt")) at = at.nextElementSibling;
|
||||
lines.push({
|
||||
key,
|
||||
subs: subs.length,
|
||||
next: at === subs[0],
|
||||
cells: [
|
||||
...new Set(subs.map((s) => [...s.children].reduce((a, c) => a + (c.colSpan || 1), 0))),
|
||||
],
|
||||
base: total(tr, ".m02-labrow__base"),
|
||||
subBase: Math.round(subs.reduce((a, s) => a + total(s, ".m02-labrow__base"), 0)),
|
||||
applied: total(tr, ".m02-labrow__applied"),
|
||||
subApplied: Math.round(subs.reduce((a, s) => a + total(s, ".m02-labrow__applied"), 0)),
|
||||
open: [...tb.querySelectorAll(".m02-labrow__fold")].filter((f) => f.innerText === "▾")
|
||||
.length,
|
||||
arrow: tr.querySelector(".m02-labrow__fold")?.innerText,
|
||||
names: subs.map((s) => s.querySelector(".m02-labrow__subname").innerText).slice(0, 4),
|
||||
});
|
||||
}
|
||||
await click(keys[keys.length - 1]);
|
||||
const again = {
|
||||
subs: get().querySelectorAll("tbody tr[data-labor-sub]").length,
|
||||
selected: rowsOf(get()).filter((r) => r.classList.contains("is-selected")).length,
|
||||
};
|
||||
return { head, lines, again, box: document.querySelectorAll("[data-labor-linedet]").length };
|
||||
};
|
||||
const node = await until(() => document.querySelector('[data-node="i|item1"]'));
|
||||
if (!node) return JSON.stringify({ error: "item1 없음 — 폴더를 펼쳐 둘 것" });
|
||||
node.click();
|
||||
const card = await until(() =>
|
||||
[...document.querySelectorAll(".m02-struct__card")].find((c) =>
|
||||
c.querySelector("table.m02-labrow tbody tr[data-labor-logic]"),
|
||||
),
|
||||
);
|
||||
if (!card) return JSON.stringify({ error: "일위대가 카드 표 없음" });
|
||||
await w(1500);
|
||||
const out = { card: await measure(() => card.querySelector("table.m02-labrow")) };
|
||||
const edit = [...card.querySelectorAll(".m02-struct__card-tools button")].find(
|
||||
(b) => b.innerText.trim() === "수정",
|
||||
);
|
||||
if (!edit) return JSON.stringify({ ...out, error: "일위대가 [수정] 없음" });
|
||||
edit.click();
|
||||
const dt = await until(() =>
|
||||
[...document.querySelectorAll("table.m02-labrow")].find(
|
||||
(t) => !t.closest(".m02-struct__card") && t.querySelector("tbody tr[data-labor-logic]"),
|
||||
),
|
||||
);
|
||||
if (!dt) return JSON.stringify({ ...out, error: "상세 표 없음" });
|
||||
await w(2000);
|
||||
out.detail = await measure(() =>
|
||||
[...document.querySelectorAll("table.m02-labrow")].find((t) => !t.closest(".m02-struct__card")),
|
||||
);
|
||||
/* 카드 화면으로 돌아감(고름만 했으니 버림 물음 없음 — 뜨면 확인) */
|
||||
document.querySelector('[data-node="i|item1"]')?.click();
|
||||
await w(800);
|
||||
[...document.querySelectorAll(".ui-modal button, dialog button")]
|
||||
.find((b) => /확인|버림|떠나기/.test(b.innerText))
|
||||
?.click();
|
||||
await w(1200);
|
||||
out.back = !!document.querySelector(".m02-struct__card table.m02-labrow");
|
||||
return JSON.stringify(out);
|
||||
})();
|
||||
Reference in New Issue
Block a user