feat(M02): 50-4 일위대가 줄 누르면 표 아래 상세 · 구분 열
- 서버 calc 답 줄마다 그 로직 재료 · 노무 · 경비 「줄」 실음(버리던 것) - 상세 · 카드 표 줄 누름 = 표 아래 M01 시험계산 표(answerView 다시 씀) + 반영 합 · 한 줄만 · 같은 줄 다시 누름 = 닫힘 - 이름 왼쪽 「구분」 열 — 키 머리에서 읽음(SP- 단가승격 · SC- 일위조합 · 그 밖 단가산출 · 저장 안 함) - 시험: 줄 실림 · 합 = 기준 · ui_checks 50-4 시나리오 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -90,6 +90,14 @@ def test_반영은_기준_곱_수량_나누기_배_돈_아닌_로직은_반영_
|
||||
assert got["반영합"]["계"] == pytest.approx(ten["반영"]["계"])
|
||||
|
||||
|
||||
def test_돈_로직_줄에_재료_노무_경비_줄_실림_합은_기준():
|
||||
got = labor.calc(labor.clean(_doc(담은로직=[{"로직": TEN}, {"로직": PLAIN}])), {})
|
||||
ten, plain = got["줄"]
|
||||
assert ten["줄"] and all({"이름", "수량", "단위", "금액"} <= set(x) for x in ten["줄"])
|
||||
assert sum(x["금액"] for x in ten["줄"]) == pytest.approx(ten["기준"]["계"])
|
||||
assert "줄" not in plain
|
||||
|
||||
|
||||
def test_수량_손이_칸보다_앞섬_칸_값은_알약_키로():
|
||||
doc = labor.clean(
|
||||
_doc(
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/* 50-4 — 일위대가 단가산출 로직 표 줄 누름 = 표 아래 그 줄 상세(M01 시험계산 표) · 다시 누르면 닫힘 · 「구분」 열 — 카드 · 상세 두 곳
|
||||
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 ·
|
||||
* 나무에 item1(구조물 조합 · 일위대가 로직 둘 이상)이 있음
|
||||
* 부르는 법: orca eval --expression "$(tr '\n' ' ' < 이 파일)" (async 식 · JSON 글을 돌려줌)
|
||||
* 하는 일: item1 카드 표 줄 누름 · 다른 줄 · 같은 줄 다시 → 일위대가 카드 [수정]으로 상세 → 같은 차례 → item1 다시(값 안 바뀜)
|
||||
* 기대 수치(카드 · 상세 같음):
|
||||
* - head[1] = "구분" · kinds = 줄마다 "단가산출"
|
||||
* - first.open = 1(상세 상자 하나) · first.rows ≥ 1 · first.sum = first.total = first.base(줄 합 = 상세 계 = 표 기준 합계)
|
||||
* - first.applied = first.appliedRow(상세 반영 계 = 표 반영 합계 · 수량 없으면 둘 다 「—」 = 0)
|
||||
* - second.title 가 둘째 줄 키 · second.open = 1(한 줄만)
|
||||
* - again.open = 0 · again.selected = 0(같은 줄 다시 = 닫힘)
|
||||
* - back = true(카드 화면으로 돌아감)
|
||||
*/
|
||||
(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 det = (box) => {
|
||||
const dl = [...box.querySelectorAll("dl.m01-logic__sums")];
|
||||
const total = (d) => {
|
||||
const dt = d && [...d.querySelectorAll("dt")].find((x) => /계|합계|Total/.test(x.innerText));
|
||||
return dt ? n(dt.nextElementSibling.innerText) : null;
|
||||
};
|
||||
const rows = [...box.querySelectorAll("table.m01-logic__grid tbody tr")];
|
||||
return {
|
||||
title: box.querySelector("h4")?.innerText ?? null,
|
||||
rows: rows.length,
|
||||
sum: rows.reduce((a, r) => a + n(r.lastElementChild.innerText), 0),
|
||||
total: total(dl[0]),
|
||||
applied: dl[1] ? total(dl[1]) : "없음",
|
||||
};
|
||||
};
|
||||
const measure = async (get, boxOf) => {
|
||||
const t = await settled(get);
|
||||
const rows = rowsOf(t);
|
||||
const head = [...t.querySelectorAll("thead th")].map((th) => th.innerText.trim());
|
||||
const kinds = rows.map((r) => r.querySelector(".m02-labrow__kind")?.innerText.trim());
|
||||
const click = async (key) => {
|
||||
get().querySelector(`tr[data-labor-logic="${key}"] .m02-labrow__name`).click();
|
||||
await w(300);
|
||||
};
|
||||
const opened = () =>
|
||||
[...boxOf().querySelectorAll("[data-labor-linedet]")].filter((b) => b.childElementCount);
|
||||
const k0 = rows[0].getAttribute("data-labor-logic");
|
||||
const k1 = rows[1]?.getAttribute("data-labor-logic") ?? k0;
|
||||
const sumCell = (key) => {
|
||||
const tds = get().querySelectorAll(`tr[data-labor-logic="${key}"] .m02-labrow__money`);
|
||||
const last = tds[tds.length - 1];
|
||||
return {
|
||||
base: n(last.querySelector(".m02-labrow__base").innerText),
|
||||
applied: n(last.querySelector(".m02-labrow__applied").innerText),
|
||||
};
|
||||
};
|
||||
await click(k0);
|
||||
const d0 = det(opened()[0]);
|
||||
const row0 = sumCell(k0);
|
||||
const first = {
|
||||
key: k0,
|
||||
open: opened().length,
|
||||
...d0,
|
||||
base: row0.base,
|
||||
appliedRow: row0.applied,
|
||||
};
|
||||
await click(k1);
|
||||
const second = {
|
||||
key: k1,
|
||||
open: opened().length,
|
||||
title: opened()[0]?.querySelector("h4")?.innerText,
|
||||
};
|
||||
await click(k1);
|
||||
const again = {
|
||||
open: opened().length,
|
||||
selected: rowsOf(get()).filter((r) => r.classList.contains("is-selected")).length,
|
||||
};
|
||||
return { head, kinds, first, second, again };
|
||||
};
|
||||
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"),
|
||||
() => card,
|
||||
),
|
||||
};
|
||||
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);
|
||||
const table = () =>
|
||||
[...document.querySelectorAll("table.m02-labrow")].find((t) => !t.closest(".m02-struct__card"));
|
||||
out.detail = await measure(table, () => table().closest("[data-labor]"));
|
||||
/* 카드 화면으로 돌아감(고름만 했으니 버림 물음 없음 — 뜨면 확인) */
|
||||
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