Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_6

This commit is contained in:
2026-10-05 20:07:17 +09:00
7 changed files with 198 additions and 41 deletions
+3 -3
View File
@@ -80,9 +80,9 @@ def test_일위대가_카드는_상세_카드_갈래():
detail = _text("M02_MasterTemplete_Labor_Detail.ts")
assert "...(opt.card ? {} : { move })" in detail # 순서 단추 없음 · 빼기는 그대로
# 서고 모드(50-3 · lib)도 더하기 · 다이어그램 없음
assert (
"opt.card || promote ? null : lib ? addRef : addLogic"
) # 51-6 사전조합 = 줄 고르기 in detail
# 51-10 사전조합 = 같은 [로직 추가] · 서고 줄만
assert "opt.card || promote ? null : addLogic" in detail
assert "lib ? { ...library, only: true } : library" in detail
assert "...(opt.card || lib ? [] : [dgBox])," in detail
assert "opt.card ? null : addMat" in detail # 49-6 — 카드엔 [자재 추가] 없음 · 상세는 그대로
rows = _text("M02_MasterTemplete_Labor_Rows.ts")
@@ -0,0 +1,149 @@
/* 51-10 — 서고 목록표(단가산출 승격 · 일위대가 사전조합) = M01 단가산출 로직 목록 표 양식 · 단추 꼴 ·
* 사전조합 상세 [로직 추가] = 일위대가 상세 [로직 추가] 와 같은 고르기 창(서고 줄만)
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=일위대가조합&root=master) · 시스템 관리자
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌)
* 쓰기 없음 — 서고 목록 · 문서 읽기는 가짜 응답(fetch 바꿔 끼움 · 끝에 되돌림)
* 기대 수치:
* - table.wrap = "m01-master__grid-wrap" · table.table = "m01-master__grid" · table.row = "m01-master__table-row"
* - table.filters = true(찾기 칸 + 쪽 `m01-master__pager`) · table.sameCell = true(M01 표 칸과 padding · 글자 · 밑줄 같음)
* - table.search = 1(「값 없음」 치면 한 줄)
* - buttons.variants = ["filled", "ghost", "danger"](M01 [신규 로직] · [로직 복사/생성] · [로직 지우기] 와 같음) · buttons.redDelete = true
* - precombo.add = "로직 추가" · picker.label = "로직 고르기" · picker.kinds ⊂ {단가승격, 일위조합} · picker.logicRows = 0
*/
(async () => {
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
const until = async (fn, ms = 4000) => {
for (let t = 0; t < ms; t += 100) {
const v = fn();
if (v) return v;
await wait(100);
}
return null;
};
const SYS = { 층: "system", 주인: null };
const rows = [
{
종류: "promote",
이름: "SP-999901",
판: "a",
수정일: "2026-10-05",
제목: "시험 승격",
단위: "㎡",
기준금액: 1234.5,
},
{
종류: "promote",
이름: "SP-999902",
판: "b",
수정일: "2026-10-05",
제목: "값 없음",
단위: null,
기준금액: null,
},
{
종류: "precombo",
이름: "SC-999901",
판: "c",
수정일: "2026-10-05",
제목: "시험 묶음",
단위: "개소",
기준금액: 10,
},
];
const doc = {
종류: "사전조합",
키: "SC-999901",
이름: "시험 묶음",
단위: "개소",
담은로직: [{ 참조: { ...SYS, 키: "SP-999901" }, 수량: { 손: 1 } }],
};
const json = (body, status = 200) =>
new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
const real = window.fetch;
window.fetch = async (input, init = {}) => {
const url = String(input?.url ?? input);
const method = (init.method ?? "GET").toUpperCase();
if (method === "GET" && /\/m02\/layers\/system\/templates(\?|$)/.test(url)) {
const got = await (await real(input, init)).json();
return json({ ...got, 양식: [...got.양식.filter((r) => !/^S[PC]-/.test(r.이름)), ...rows] });
}
if (method === "GET" && url.includes("/templates/precombo/SC-999901"))
return json({ 종류: "precombo", 이름: "SC-999901", 판: "c", 문서: doc });
if (method !== "GET" && !url.includes("/calc"))
return json({ detail: "시험 — 쓰기 막음" }, 400);
return real(input, init);
};
const sec = [...document.querySelectorAll(".m02-side__lists > section")].find(
(s) => s.querySelector(".ui-collapsible__title")?.textContent?.trim() === "일위대가 조합",
);
const items = [...sec.querySelectorAll("[data-precombo]")];
const list = (kind) => document.querySelector(`[data-lib-list="${kind}"]`);
const tools = () => [...document.querySelectorAll(".m02-main__slot .m02-main__tools button")];
const box = (n) => {
const s = getComputedStyle(n);
return [s.paddingTop, s.paddingLeft, s.fontSize, s.borderBottomWidth].join("|");
};
try {
items[0].click();
await until(() => list("promote")?.querySelector(`[data-lib-key="SP-999902"]`));
const wrap = list("promote");
const td = wrap.querySelector("[data-lib-key] td:nth-child(3)");
// M01 목록 표와 같은 클래스로 짠 견본 칸 — 같은 CSS 면 같은 칸 꼴
const ref = document.createElement("div");
ref.className = "m01-master__grid-wrap";
ref.innerHTML =
'<table class="m01-master__grid"><tbody><tr class="m01-master__table-row"><td>시험 승격</td></tr></tbody></table>';
wrap.parentElement.append(ref);
const table = {
wrap: wrap.className,
table: wrap.querySelector("table").className,
row: wrap.querySelector("[data-lib-key]").className,
filters:
!!wrap.parentElement.querySelector(".m01-head__filters input[type=search]") &&
!!wrap.parentElement.querySelector(".m01-head__filters .m01-master__pager"),
sameCell: box(td) === box(ref.querySelector("td")),
cell: box(td),
};
ref.remove();
const search = wrap.parentElement.querySelector(".m01-head__filters input[type=search]");
search.value = "값 없음";
search.dispatchEvent(new Event("input"));
await wait(100);
table.search = list("promote").querySelectorAll("[data-lib-key]").length;
search.value = "";
search.dispatchEvent(new Event("input"));
const v = (b) => ["filled", "ghost", "danger"].find((x) => b.className.includes(`--${x}`));
const buttons = {
labels: tools().map((b) => b.textContent),
variants: tools().map(v),
redDelete: /rgb\((1\d\d|2\d\d), (\d|[1-9]\d), (\d|[1-9]\d)\)/.test(
getComputedStyle(tools()[2]).backgroundColor + getComputedStyle(tools()[2]).color,
),
deleteColor: getComputedStyle(tools()[2]).backgroundColor,
};
// 사전조합 상세 [로직 추가]
items[1].click();
const row = await until(() =>
document.querySelector('[data-lib-key="SC-999901"] td:nth-child(3)'),
);
row.click();
const edit = await until(() => document.querySelector(".m02-main__host [data-labor]"));
await wait(300);
const add = [...edit.querySelectorAll("button")].find((b) => b.textContent === "로직 추가");
const precombo = { add: add?.textContent ?? null };
add?.click();
const pick = await until(() => document.querySelector(".m01-logic__pick"));
await until(() => pick?.querySelectorAll("[data-pick]").length, 3000);
const kinds = [...(pick?.querySelectorAll("[data-pick-kind]") ?? [])].map((x) => x.textContent);
const picker = {
label: pick?.getAttribute("aria-label") ?? null,
kinds: [...new Set(kinds)],
logicRows: kinds.filter((k) => k === "단가산출").length,
rows: kinds.length,
};
pick?.closest(".m01-logic__backdrop")?.remove();
return JSON.stringify({ table, buttons, precombo, picker });
} finally {
window.fetch = real;
}
})();
@@ -115,7 +115,7 @@
await wait(300);
};
const pickerRow = async (key) => {
btn(edit(), "추가").click();
btn(edit(), "로직 추가").click();
const row = await until(() => document.querySelector(`.m01-logic__pick [data-pick="${key}"]`));
return row;
};
@@ -123,13 +123,13 @@
// 단가승격 상세 — 한 줄 고정
await openDoc(0, "SP-999901");
const promote = {
add: !!btn(edit(), "추가"),
add: !!btn(edit(), "로직 추가"),
rows: lines().length,
move: edit().querySelectorAll(".m02-labrow__move button").length,
};
// 사전조합 상세
await openDoc(1, "SC-999901");
const precombo = { add: !!btn(edit(), "추가"), rows: lines().length };
const precombo = { add: !!btn(edit(), "로직 추가"), rows: lines().length };
const first = await pickerRow("SP-999902");
const pick = document.querySelector(".m01-logic__pick");
const picker = {