Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1
충돌 = Labor_Text 글 키 둘 다 남김(50-4 상세 글 · 51-3 사전조합 글) · docs 수정요구 = 메인 판
This commit is contained in:
@@ -353,7 +353,16 @@ class Expand:
|
||||
"로직": line.get("로직"),
|
||||
"기준": {k: Decimal(str(v)) / times for k, v in base.items()} if base else None,
|
||||
"까닭": why,
|
||||
"줄": got.get("줄"), # 원 로직 표(줄 누름 상세 · 50-3 ④) — 원 로직 결과단위 그대로
|
||||
# 원 로직 줄(줄 누름 하위 행 · 51-2) — 1 단위로 나눔(하위 합 = 기준)
|
||||
"줄": [
|
||||
{
|
||||
**x,
|
||||
"수량": Decimal(str(x["수량"])) / times,
|
||||
"금액": Decimal(str(x["금액"])) / times,
|
||||
"비목": {k: Decimal(str(v)) / times for k, v in (x.get("비목") or {}).items()},
|
||||
}
|
||||
for x in got.get("줄") or []
|
||||
],
|
||||
}
|
||||
|
||||
def _combo(self, doc: dict, trail: tuple[str, ...]) -> dict[str, Any]:
|
||||
|
||||
@@ -136,9 +136,20 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 줄 상세(50-4) — 표 아래 · 고른 줄만 */
|
||||
.m02-labor__linedet {
|
||||
margin-top: var(--spacing-8);
|
||||
/* 줄 접고 폄 표(51-2) · 고른 줄 하위 행 — 이름 들여쓰기 · 흐린 바탕 */
|
||||
.m02-labrow__fold {
|
||||
margin-right: var(--spacing-4);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.m02-labrow__sub td {
|
||||
background: var(--color-bg);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-labrow__sub .m02-labrow__subname {
|
||||
padding-left: var(--spacing-24);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 별칭 칸(44-6) — 칸 안 글상자 · 누르면 바로 고침 */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* M02_MasterTemplete_Labor_Detail.ts
|
||||
* 일위대가 상세(39-3) — 구조물 열마다 하나(`labor/<열>.json`) · UI_Main 종류 `labor` 가 이 부품을 붙임
|
||||
* (양식 길 읽기 · [저장] · [다시 읽기]는 UI_Main). 차례 = 정보(단위 · 본뜬) → 담은 로직 → 자재목록 → 다이어그램.
|
||||
* · 담은 로직 줄 누름 = 고름(하이라이트 · 다시 그려도 유지 · 47-5) + 표 아래 그 줄 상세(50-4 · M01 시험계산 표) ·
|
||||
* · 담은 로직 줄 누름 = 고름(하이라이트 · 다시 그려도 유지 · 47-5) + 그 줄 바로 아래 하위 행(51-2 · `Labor_Rows`) ·
|
||||
* 같은 줄 다시 누르면 닫힘 · [옵션] = 입력 모달(`Labor_Input`) ·
|
||||
* [확인] = 화면 캐시만 · 저장은 [저장]
|
||||
* · 금액 = 서버 calc(기준 ③ · 최신 응답만 · 저장 안 함) · 고칠 때마다 뒤에서 다시 셈
|
||||
@@ -18,9 +18,6 @@ import { ROUTES } from "@config/config_frontend";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { setPendingLogicKey } from "../M01_MasterData/M01_MasterData_UI_Side";
|
||||
import { createSourceView } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Source";
|
||||
import { answerView } from "../M01_MasterData/M01_MasterData_UI_Logic_Calc";
|
||||
import type { CalcAnswer } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
|
||||
import { formatMoney } from "../M01_MasterData/M01_MasterData_UI_Logic_Money";
|
||||
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import { recalcWorkbook } from "../A00_Common/spreadsheet/spreadsheet_recalc";
|
||||
import {
|
||||
@@ -32,15 +29,13 @@ import {
|
||||
type Layer,
|
||||
} from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { linkAreaCell } from "./M02_MasterTemplete_AreaCellPick";
|
||||
import { lineId, MONEY_KEYS, settle, type LaborRef } from "./M02_MasterTemplete_Labor_Calc";
|
||||
import { lineId, settle, type LaborRef } from "./M02_MasterTemplete_Labor_Calc";
|
||||
import {
|
||||
areaLinks,
|
||||
calcLabor,
|
||||
fetchLogicInputs,
|
||||
normalizeLabor,
|
||||
type LaborCalc,
|
||||
type LaborCalcLine,
|
||||
type LaborCost,
|
||||
type LaborDoc,
|
||||
type LaborQty,
|
||||
type LaborRow,
|
||||
@@ -53,7 +48,7 @@ import {
|
||||
import { openLaborInput, type GoArea } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { openQtyModal } from "./M02_MasterTemplete_Labor_QtyModal";
|
||||
import { openLogicPicker } from "./M02_MasterTemplete_Labor_Pick";
|
||||
import { kindOf, logicTable, matTable, qtyShort } from "./M02_MasterTemplete_Labor_Rows";
|
||||
import { logicTable, matTable, qtyShort } from "./M02_MasterTemplete_Labor_Rows";
|
||||
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
||||
import { keepKnown } from "./M02_Labor_Values_Model";
|
||||
import { pickMaterial } from "./M02_Material_Pick";
|
||||
@@ -148,109 +143,6 @@ export function mountLaborDetail(
|
||||
const lineOf = (key: string) => calc?.줄.find((l) => lineId(l) === key);
|
||||
const matOf = (code: string) => calc?.자재.find((m) => m.코드 === code);
|
||||
|
||||
/** 고른 줄 상세(50-4) — 단가산출 = M01 시험계산 표(재료 · 노무 · 경비 줄 + 기준 합) ·
|
||||
* 단가승격 = 원 로직 표 + 원 로직 키 + 기준(1 단위) · 사전조합 = 안 줄 목록 한 층 + 기준 ·
|
||||
* 셋 다 아래 반영 합 · 고른 줄 없으면 비움 */
|
||||
const lineDet = el("div", {
|
||||
className: "m02-labor__linedet",
|
||||
attrs: { "data-labor-linedet": "" },
|
||||
});
|
||||
const sums = (title: string, money: Partial<LaborCost> | null | undefined): HTMLElement[] => [
|
||||
el("h4", { text: title }),
|
||||
el("dl", {
|
||||
className: "m01-logic__sums",
|
||||
children: MONEY_KEYS.flatMap((k) => [
|
||||
el("dt", { text: k === "계" ? tc("Prev_Sum") : k }),
|
||||
el("dd", {
|
||||
className: "m01-logic__money",
|
||||
text: money ? formatMoney(money[k] ?? 0) : "—",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
];
|
||||
/** 사전조합 안 줄 — 구분 · 이름 · 수량 · 금액 넷(= 그 줄 1 단위 기준 × 수량) */
|
||||
const innerTable = (inner: NonNullable<LaborCalcLine["안"]>): HTMLElement =>
|
||||
el("table", {
|
||||
className: "m01-logic__grid",
|
||||
attrs: { "data-labor-inner": "" },
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [
|
||||
el("tr", {
|
||||
children: [tc("Sub"), tc("Col_Name"), tc("Labor_Qty"), ...MONEY_KEYS].map((h) =>
|
||||
el("th", { text: h === "계" ? tc("Prev_Sum") : h }),
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
el("tbody", {
|
||||
children: inner.map((x) =>
|
||||
el("tr", {
|
||||
children: [
|
||||
el("td", { text: kindOf({ 로직: "", 참조: x.참조 }) }),
|
||||
el("td", {
|
||||
text: `${x.참조.키} ${x.이름 ?? ""}${x.까닭 ? ` · ${x.까닭}` : ""}`.trim(),
|
||||
}),
|
||||
el("td", { text: `${x.수량} ${x.단위 ?? ""}`.trim() }),
|
||||
...MONEY_KEYS.map((k) =>
|
||||
el("td", {
|
||||
className: "m01-logic__money",
|
||||
text: x.기준 ? formatMoney(x.기준[k] * x.수량) : "—",
|
||||
}),
|
||||
),
|
||||
],
|
||||
}),
|
||||
),
|
||||
}),
|
||||
],
|
||||
});
|
||||
function showLine(): void {
|
||||
if (!picked) return void lineDet.replaceChildren();
|
||||
const row = cur.담은로직.find((r) => lineId(r) === picked);
|
||||
const line = lineOf(picked);
|
||||
const logic = logics.get(picked);
|
||||
const name = row?.참조 ? (line?.이름 ?? "") : (logic?.이름 ?? "");
|
||||
const head = el("h4", {
|
||||
text: tc("Labor_LineDetail").replace("{value}", `${row?.참조?.키 ?? picked} ${name}`.trim()),
|
||||
});
|
||||
if (!line)
|
||||
return void lineDet.replaceChildren(
|
||||
head,
|
||||
el("p", { className: "m01-logic__muted", text: fail ?? "…" }),
|
||||
);
|
||||
const stop: CalcAnswer | null = line.까닭 ? { ok: false, reason: line.까닭 } : null;
|
||||
const unit = `${tc("Labor_LineBase")} (1 ${line.분모 ?? ""})`.trim();
|
||||
const body: HTMLElement[] = !row?.참조
|
||||
? answerView(
|
||||
stop ?? {
|
||||
ok: true,
|
||||
lines: line.줄,
|
||||
sums: line.기준 ?? undefined,
|
||||
result: line.결과,
|
||||
middle: {},
|
||||
},
|
||||
false,
|
||||
(logic?.결과단위 ?? "").startsWith("원"),
|
||||
)
|
||||
: line.안
|
||||
? [
|
||||
innerTable(line.안),
|
||||
...(stop ? answerView(stop, false, true) : []),
|
||||
...sums(unit, line.기준),
|
||||
]
|
||||
: [
|
||||
el("p", {
|
||||
className: "m01-logic__muted",
|
||||
text: `${tc("Labor_FromLogic")} ${line.원로직 ?? "—"}`,
|
||||
}),
|
||||
...answerView(stop ?? { ok: true, lines: line.줄, middle: {} }, false, true),
|
||||
...sums(unit, line.기준),
|
||||
];
|
||||
// 반영 = 기준 × 수량 ÷ 배(표 아래 줄과 같음) · 수량 없으면 「—」 · 돈 아닌 로직은 없음
|
||||
const applied = line.기준 ? sums(tc("Labor_LineApplied"), line.반영) : [];
|
||||
lineDet.replaceChildren(head, ...body, ...applied);
|
||||
}
|
||||
|
||||
const drawDg = (): void => {
|
||||
if (opt.card || lib || !dgBox.open) return; // 카드 갈래 · 서고 모드엔 다이어그램 없음
|
||||
const data = buildLaborDiagram({
|
||||
@@ -273,7 +165,7 @@ export function mountLaborDetail(
|
||||
};
|
||||
dgBox.addEventListener("toggle", drawDg);
|
||||
|
||||
const draw = (): void => {
|
||||
const drawLogic = (): void =>
|
||||
logicHost.replaceChildren(
|
||||
logicTable(
|
||||
{ rows: cur.담은로직, logics, calc, fail, cells: cells(), selected: picked },
|
||||
@@ -289,9 +181,9 @@ export function mountLaborDetail(
|
||||
}
|
||||
: { open: pick, option: openRow },
|
||||
),
|
||||
lineDet,
|
||||
);
|
||||
showLine();
|
||||
const draw = (): void => {
|
||||
drawLogic();
|
||||
matHost.replaceChildren(
|
||||
matTable(
|
||||
{ mats: cur.자재, calc, fail, cells: cells() },
|
||||
@@ -413,14 +305,12 @@ export function mountLaborDetail(
|
||||
[cur.담은로직[i], cur.담은로직[j]] = [cur.담은로직[j], cur.담은로직[i]];
|
||||
changed();
|
||||
}
|
||||
/** 줄 고름(47-5) — 하이라이트(표만 · 다이어그램 안 다시 그림) + 줄 상세(50-4) + 원문 패널(47-4) ·
|
||||
/** 줄 고름(47-5) — 하이라이트 + 하위 행(51-2 · 표만 다시 그림 · 다이어그램 안 다시 그림) + 원문 패널(47-4) ·
|
||||
* 고른 줄 다시 누름 = 닫음 · 화면 캐시 안 바뀜 */
|
||||
function pick(i: number): void {
|
||||
const key = lineId(cur.담은로직[i]) ?? "";
|
||||
picked = picked === key ? null : key;
|
||||
for (const tr of logicHost.querySelectorAll<HTMLElement>("[data-labor-logic],[data-labor-opt]"))
|
||||
tr.classList.toggle("is-selected", (tr.dataset.laborLogic ?? tr.dataset.laborOpt) === picked);
|
||||
showLine();
|
||||
drawLogic();
|
||||
showSource();
|
||||
}
|
||||
function removeRow(i: number): void {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* 자재목록 = 코드 · 이름 · 규격 · 수량산출 · 단위 · 가격 다섯(기준 칸 강조) · 기준단가 / 반영 금액(43-3).
|
||||
* 금액은 calc 답(없으면 「…」).
|
||||
* 줄 누름 · 줄 단추(위로 · 아래로 · 옵션 · 삭제 · 묶음 `ui-btn-group`)는 `on` 이 있을 때만(보기 갈래 = 없음) ·
|
||||
* 상세는 줄 누름 = 고름(`selected` 줄 하이라이트) · 넣을 값 모달은 [옵션](47-5).
|
||||
* 상세는 줄 누름 = 고름(`selected` 줄 하이라이트 · 이름 앞 ▸/▾ · 그 줄 바로 아래 하위 행 · 51-2) · 넣을 값 모달은 [옵션](47-5).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
@@ -228,6 +228,60 @@ export interface LogicTableData {
|
||||
selected?: string | null;
|
||||
}
|
||||
|
||||
/** 고른 줄 하위 행(51-2 · 같은 표 · 같은 칸) — 단가산출 · 단가승격 = 그 로직 재료 · 노무 · 경비 줄 ·
|
||||
* 사전조합 = 안 줄 · 금액 칸 = 위 기준(위 줄 기준과 같은 바탕) / 아래 반영(× 위 줄 수량 ÷ 배) ·
|
||||
* 하위 합 = 위 줄 금액 · 줄이 없으면 빈 한 줄(「…」 · 까닭 · 「—」) */
|
||||
function subRows(line: LaborCalcLine | undefined, wait: boolean, tools: boolean): HTMLElement[] {
|
||||
const times = line?.수량 === null || line?.수량 === undefined ? null : line.수량 / (line.배 || 1);
|
||||
const items = line?.안
|
||||
? line.안.map((x) => ({
|
||||
name: `${x.참조.키} ${x.이름 ?? ""}${x.까닭 ? ` · ${x.까닭}` : ""}`.trim(),
|
||||
qty: x.수량,
|
||||
unit: x.단위 ?? "",
|
||||
cost: x.기준 ? Object.fromEntries(COSTS.map((c) => [c, x.기준![c] * x.수량])) : null,
|
||||
}))
|
||||
: (line?.줄 ?? []).map((x) => ({
|
||||
name: x.출처 ? `${x.이름} (${x.출처.replace(/[A-Z]{2}\d{6}\./g, "")})` : x.이름,
|
||||
qty: x.수량,
|
||||
unit: x.단위,
|
||||
cost: { ...x.비목, 계: x.금액 } as Record<string, number>,
|
||||
}));
|
||||
const blank = (className: string): HTMLElement => el("td", { className, text: "" });
|
||||
const none = wait ? "…" : (line?.까닭 ?? "—");
|
||||
return (items.length ? items : [null]).map((x) =>
|
||||
el("tr", {
|
||||
className: "m02-labrow__sub",
|
||||
attrs: { "data-labor-sub": "" },
|
||||
children: [
|
||||
blank("m02-labrow__no"),
|
||||
blank("m02-labrow__kind"),
|
||||
el("td", {
|
||||
className: "m02-labrow__name m02-labrow__subname",
|
||||
text: x?.name ?? none,
|
||||
attrs: { title: x?.name ?? none },
|
||||
}),
|
||||
blank("m02-labrow__alias"),
|
||||
el("td", {
|
||||
className: "m02-labrow__qty",
|
||||
text: x ? num(x.qty) : "",
|
||||
attrs: { "data-label": tc("Labor_QtyCol") },
|
||||
}),
|
||||
el("td", { className: "m02-labrow__unit", text: x?.unit ?? "" }),
|
||||
...COSTS.map((c) => {
|
||||
const base = x?.cost ? Number(x.cost[c] ?? 0) : null;
|
||||
return moneyCell(
|
||||
base,
|
||||
base === null || times === null ? null : base * times,
|
||||
wait,
|
||||
tc(HEAD[c]),
|
||||
);
|
||||
}),
|
||||
...(tools ? [blank("")] : []),
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** 담은 로직 표 — 보기 갈래는 `on` 없이 */
|
||||
export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement {
|
||||
const wait = !data.calc && !data.fail;
|
||||
@@ -269,6 +323,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
el("td", {
|
||||
className: "m02-labrow__name",
|
||||
children: [
|
||||
...(on ? [el("span", { className: "m02-labrow__fold", text: sel ? "▾" : "▸" })] : []),
|
||||
el("span", { className: "m02-labrow__key", text: row.참조?.키 ?? row.로직 }),
|
||||
el("strong", {
|
||||
text: (row.참조 ? line?.이름 : logic?.이름) ?? row.참조?.키 ?? row.로직,
|
||||
@@ -319,7 +374,8 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
],
|
||||
});
|
||||
if (on) tr.addEventListener("click", () => on.open(i));
|
||||
if (!note) return [tr];
|
||||
const subs = sel ? subRows(line, wait, tools) : [];
|
||||
if (!note) return [tr, ...subs];
|
||||
// 옵션 값 줄(44-6) — 이름 ~ 합계 칸에 걸친 한 줄(말줄임 · 전체 글은 title) · 행 높이 안 커짐
|
||||
const opt = el("tr", {
|
||||
className: (on ? "m02-labrow__optrow is-open" : "m02-labrow__optrow") + sel,
|
||||
@@ -335,7 +391,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
],
|
||||
});
|
||||
if (on) opt.addEventListener("click", () => on.open(i));
|
||||
return [tr, opt];
|
||||
return [tr, opt, ...subs];
|
||||
});
|
||||
const sum = (part: Partial<LaborCost> | undefined, c: (typeof COSTS)[number]): number | null =>
|
||||
part?.[c] ?? null;
|
||||
|
||||
@@ -79,7 +79,7 @@ const TEXT = {
|
||||
Labor_BasePrice: ["기준 단가", "Base price"],
|
||||
Labor_AppliedSum: ["반영 금액", "Applied amount"],
|
||||
Labor_Calc_Fail: ["계산 못 함", "Calculation failed"],
|
||||
// 구분 · 줄 상세(50-4)
|
||||
// 구분(50-4)
|
||||
Kind_Logic: ["단가산출", "Logic"],
|
||||
Kind_Promote: ["단가승격", "Promoted"],
|
||||
Kind_Combo: ["일위조합", "Combination"],
|
||||
@@ -87,6 +87,7 @@ const TEXT = {
|
||||
Labor_LineApplied: ["반영(수량 곱함)", "Applied (× quantity)"],
|
||||
Labor_LineBase: ["기준", "Base"],
|
||||
Labor_FromLogic: ["원 로직", "Source logic"],
|
||||
Lib_Precombo: ["사전조합", "Pre-combination"],
|
||||
Lib_System: ["마스터", "Master"],
|
||||
Lib_Company: ["회사", "Company"],
|
||||
Lib_Personal: ["개인", "Personal"],
|
||||
|
||||
@@ -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