|
|
|
@@ -49,7 +49,11 @@ class FakeNode {
|
|
|
|
|
return this._text + this.children.map((c) => c.textContent).join("");
|
|
|
|
|
}
|
|
|
|
|
append(...kids) {
|
|
|
|
|
kids.forEach((k) => this.children.push(typeof k === "string" ? Object.assign(new FakeNode("#text"), { _text: k }) : k));
|
|
|
|
|
kids.forEach((k) =>
|
|
|
|
|
this.children.push(
|
|
|
|
|
typeof k === "string" ? Object.assign(new FakeNode("#text"), { _text: k }) : k,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
replaceChildren(...kids) {
|
|
|
|
|
this.children = [];
|
|
|
|
@@ -72,7 +76,13 @@ globalThis.Option = function Option(text, value) {
|
|
|
|
|
};
|
|
|
|
|
globalThis.requestAnimationFrame = (fn) => fn();
|
|
|
|
|
const session = new Map();
|
|
|
|
|
globalThis.window = { sessionStorage: { getItem: (k) => session.get(k) ?? null, setItem: (k, v) => session.set(k, String(v)), removeItem: (k) => session.delete(k) } };
|
|
|
|
|
globalThis.window = {
|
|
|
|
|
sessionStorage: {
|
|
|
|
|
getItem: (k) => session.get(k) ?? null,
|
|
|
|
|
setItem: (k, v) => session.set(k, String(v)),
|
|
|
|
|
removeItem: (k) => session.delete(k),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
globalThis.sessionStorage = globalThis.window.sessionStorage;
|
|
|
|
|
|
|
|
|
|
const STUBS = {
|
|
|
|
@@ -110,38 +120,85 @@ const { renderOptionFields } = B05("B05_Profile_UI_Structures_Panel_Options");
|
|
|
|
|
const commitModule = B05("B05_Profile_UI_Structures_Panel_Commit");
|
|
|
|
|
|
|
|
|
|
const out = {};
|
|
|
|
|
const A = { layer: "personal", owner: "삼성/홍길동", key: "SS000007", name: "돌기슭막이 메쌓기 1.2m" };
|
|
|
|
|
const A = {
|
|
|
|
|
layer: "personal",
|
|
|
|
|
owner: "삼성/홍길동",
|
|
|
|
|
key: "SS000007",
|
|
|
|
|
name: "돌기슭막이 메쌓기 1.2m",
|
|
|
|
|
};
|
|
|
|
|
const B = { layer: "system", owner: null, key: "SS000004", name: "옹벽 콘크리트" };
|
|
|
|
|
|
|
|
|
|
// ── 순수 규칙 ───────────────────────────────────────────────────────────
|
|
|
|
|
out.visible = [logic.isOptionVisible({}), logic.isOptionVisible({ visible: true }), logic.isOptionVisible({ visible: false })];
|
|
|
|
|
const before = { type_id: "wall", options: { H: 2, stone_kind: "화강암", back_length_m: 0.6, memo_only: "x" } };
|
|
|
|
|
out.visible = [
|
|
|
|
|
logic.isOptionVisible({}),
|
|
|
|
|
logic.isOptionVisible({ visible: true }),
|
|
|
|
|
logic.isOptionVisible({ visible: false }),
|
|
|
|
|
];
|
|
|
|
|
const before = {
|
|
|
|
|
type_id: "wall",
|
|
|
|
|
options: { H: 2, stone_kind: "화강암", back_length_m: 0.6, memo_only: "x" },
|
|
|
|
|
};
|
|
|
|
|
out.mergeSame = logic.mergeStructureOptions(before, "wall", ["H", "stone_kind"], { H: 3 });
|
|
|
|
|
out.mergeCleared = logic.mergeStructureOptions(before, "wall", ["H", "stone_kind"], {});
|
|
|
|
|
out.mergeTypeChange = logic.mergeStructureOptions(before, "fence", ["H"], { H: 1 });
|
|
|
|
|
out.mergeNoPrev = logic.mergeStructureOptions(null, "wall", ["H"], { H: 9 });
|
|
|
|
|
out.mergePrevNoOptions = logic.mergeStructureOptions({ type_id: "wall" }, "wall", ["H"], { H: 9 });
|
|
|
|
|
|
|
|
|
|
out.isItem = [item.isSummaryItem(A), item.isSummaryItem({ layer: "x", owner: null, key: "K", name: "n" }), item.isSummaryItem(null), item.isSummaryItem({ ...A, key: "" })];
|
|
|
|
|
out.isItem = [
|
|
|
|
|
item.isSummaryItem(A),
|
|
|
|
|
item.isSummaryItem({ layer: "x", owner: null, key: "K", name: "n" }),
|
|
|
|
|
item.isSummaryItem(null),
|
|
|
|
|
item.isSummaryItem({ ...A, key: "" }),
|
|
|
|
|
];
|
|
|
|
|
out.readItem = [item.readSummaryItem(A)?.key ?? null, item.readSummaryItem("nope")];
|
|
|
|
|
out.same = [item.sameSummaryItem(A, { ...A, name: "이름만 바뀜" }), item.sameSummaryItem(A, B), item.sameSummaryItem(null, null), item.sameSummaryItem(A, null)];
|
|
|
|
|
out.same = [
|
|
|
|
|
item.sameSummaryItem(A, { ...A, name: "이름만 바뀜" }),
|
|
|
|
|
item.sameSummaryItem(A, B),
|
|
|
|
|
item.sameSummaryItem(null, null),
|
|
|
|
|
item.sameSummaryItem(A, null),
|
|
|
|
|
];
|
|
|
|
|
out.label = [item.summaryLabel(null), item.summaryLabel(A)];
|
|
|
|
|
out.tip = [item.summaryTooltip(null), item.summaryTooltip(A)];
|
|
|
|
|
|
|
|
|
|
// 관 계열 merge — 집계 항목 이어받기 · 지우기
|
|
|
|
|
const pipePrev = { facility: "pipe", options: { pipe_kind: "흄관", extra: 1 }, summary_item: A };
|
|
|
|
|
out.pipeKeep = merge.mergeFacilityOptions(pipePrev, { facility: "pipe", options: { pipe_kind: "VR관" } }).summary_item ?? null;
|
|
|
|
|
out.pipeClear = merge.mergeFacilityOptions(pipePrev, { facility: "pipe", options: {}, summary_item: null }).summary_item;
|
|
|
|
|
out.pipeSet = merge.mergeFacilityOptions(pipePrev, { facility: "pipe", options: {}, summary_item: B }).summary_item.key;
|
|
|
|
|
out.pipeKeep =
|
|
|
|
|
merge.mergeFacilityOptions(pipePrev, { facility: "pipe", options: { pipe_kind: "VR관" } })
|
|
|
|
|
.summary_item ?? null;
|
|
|
|
|
out.pipeClear = merge.mergeFacilityOptions(pipePrev, {
|
|
|
|
|
facility: "pipe",
|
|
|
|
|
options: {},
|
|
|
|
|
summary_item: null,
|
|
|
|
|
}).summary_item;
|
|
|
|
|
out.pipeSet = merge.mergeFacilityOptions(pipePrev, {
|
|
|
|
|
facility: "pipe",
|
|
|
|
|
options: {},
|
|
|
|
|
summary_item: B,
|
|
|
|
|
}).summary_item.key;
|
|
|
|
|
|
|
|
|
|
// 집계 항목 줄
|
|
|
|
|
const row = buildSummaryRow();
|
|
|
|
|
const clicks = [];
|
|
|
|
|
row.bind({ pick: () => clicks.push("pick"), clear: () => clicks.push("clear") });
|
|
|
|
|
const [labelNode, nameNode, pickBtn, clearBtn] = row.root.children;
|
|
|
|
|
out.rowEmpty = { name: nameNode.textContent, assigned: nameNode.classList.contains("is-assigned"), clearDisabled: clearBtn.disabled, get: row.get(), label: labelNode.textContent, pick: pickBtn.textContent, clear: clearBtn.textContent };
|
|
|
|
|
out.rowEmpty = {
|
|
|
|
|
name: nameNode.textContent,
|
|
|
|
|
assigned: nameNode.classList.contains("is-assigned"),
|
|
|
|
|
clearDisabled: clearBtn.disabled,
|
|
|
|
|
get: row.get(),
|
|
|
|
|
label: labelNode.textContent,
|
|
|
|
|
pick: pickBtn.textContent,
|
|
|
|
|
clear: clearBtn.textContent,
|
|
|
|
|
};
|
|
|
|
|
row.set(A);
|
|
|
|
|
out.rowSet = { name: nameNode.textContent, assigned: nameNode.classList.contains("is-assigned"), clearDisabled: clearBtn.disabled, title: nameNode.title, key: row.get().key, nameShowsKey: nameNode.textContent.includes("SS000007") };
|
|
|
|
|
out.rowSet = {
|
|
|
|
|
name: nameNode.textContent,
|
|
|
|
|
assigned: nameNode.classList.contains("is-assigned"),
|
|
|
|
|
clearDisabled: clearBtn.disabled,
|
|
|
|
|
title: nameNode.title,
|
|
|
|
|
key: row.get().key,
|
|
|
|
|
nameShowsKey: nameNode.textContent.includes("SS000007"),
|
|
|
|
|
};
|
|
|
|
|
pickBtn.click();
|
|
|
|
|
clearBtn.click();
|
|
|
|
|
out.clicks = clicks;
|
|
|
|
@@ -150,11 +207,22 @@ out.rowCleared = { name: nameNode.textContent, clearDisabled: clearBtn.disabled
|
|
|
|
|
|
|
|
|
|
// 목록 줄 — 지정 색 · 툴팁
|
|
|
|
|
const list = new FakeNode("ul");
|
|
|
|
|
const types = new Map([["wall", { name: "옹벽" }], ["fence", { name: "울타리" }], ["pipe", { name: "배수관" }]]);
|
|
|
|
|
const types = new Map([
|
|
|
|
|
["wall", { name: "옹벽" }],
|
|
|
|
|
["fence", { name: "울타리" }],
|
|
|
|
|
["pipe", { name: "배수관" }],
|
|
|
|
|
]);
|
|
|
|
|
renderStructureList({
|
|
|
|
|
list,
|
|
|
|
|
structures: [
|
|
|
|
|
{ structure_id: "s1", type_id: "wall", placement: "point", chainage_m: 100, options: {}, summary_item: A },
|
|
|
|
|
{
|
|
|
|
|
structure_id: "s1",
|
|
|
|
|
type_id: "wall",
|
|
|
|
|
placement: "point",
|
|
|
|
|
chainage_m: 100,
|
|
|
|
|
options: {},
|
|
|
|
|
summary_item: A,
|
|
|
|
|
},
|
|
|
|
|
{ structure_id: "s2", type_id: "fence", placement: "point", chainage_m: 200, options: {} },
|
|
|
|
|
],
|
|
|
|
|
pipeFacilities: [
|
|
|
|
@@ -168,10 +236,21 @@ renderStructureList({
|
|
|
|
|
onSelectStructure() {},
|
|
|
|
|
onSelectPipe() {},
|
|
|
|
|
});
|
|
|
|
|
out.list = list.children.map((li) => ({ assigned: li.classList.contains("is-assigned"), title: li.title }));
|
|
|
|
|
out.list = list.children.map((li) => ({
|
|
|
|
|
assigned: li.classList.contains("is-assigned"),
|
|
|
|
|
title: li.title,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 옵션 칸 그리기 — visible:false 숨김 · 상세(detail) 는 includeDetail 일 때만 · 정의는 그대로
|
|
|
|
|
const opt = (key, extra = {}) => ({ key, label: key, input: "number", choices: [], unit: null, default: null, ...extra });
|
|
|
|
|
const opt = (key, extra = {}) => ({
|
|
|
|
|
key,
|
|
|
|
|
label: key,
|
|
|
|
|
input: "number",
|
|
|
|
|
choices: [],
|
|
|
|
|
unit: null,
|
|
|
|
|
default: null,
|
|
|
|
|
...extra,
|
|
|
|
|
});
|
|
|
|
|
const wallType = {
|
|
|
|
|
type_id: "wall",
|
|
|
|
|
options: [
|
|
|
|
@@ -206,7 +285,13 @@ out.definitionKept = wallType.options.map((o) => o.key); // 정의는 그대로
|
|
|
|
|
|
|
|
|
|
// [수정]/[추가] 저장 — 폼 값이 정본 기록으로 나가는 자리(Commit.commit)를 fake 폼으로 돌림
|
|
|
|
|
const saveType = { type_id: "wall", placement: "point", managed_by: null, options: [] };
|
|
|
|
|
async function saveWith({ previous, summary, formOptions }) {
|
|
|
|
|
async function saveWith({
|
|
|
|
|
previous,
|
|
|
|
|
summary,
|
|
|
|
|
formOptions,
|
|
|
|
|
requiredEmpty = false,
|
|
|
|
|
summaryOnly = false,
|
|
|
|
|
}) {
|
|
|
|
|
const structures = previous ? [previous] : [];
|
|
|
|
|
let summaryValue = summary;
|
|
|
|
|
let changed = null;
|
|
|
|
@@ -221,10 +306,10 @@ async function saveWith({ previous, summary, formOptions }) {
|
|
|
|
|
facilityOptions: {},
|
|
|
|
|
optionInputs: Object.entries(formOptions).map(([key, value]) => ({
|
|
|
|
|
key,
|
|
|
|
|
required: false,
|
|
|
|
|
input: {},
|
|
|
|
|
required: requiredEmpty,
|
|
|
|
|
input: { focus() {} },
|
|
|
|
|
read: () => value,
|
|
|
|
|
isEmpty: () => false,
|
|
|
|
|
isEmpty: () => requiredEmpty,
|
|
|
|
|
})),
|
|
|
|
|
pipeFacilities: () => [],
|
|
|
|
|
interval: () => 20,
|
|
|
|
@@ -252,8 +337,12 @@ async function saveWith({ previous, summary, formOptions }) {
|
|
|
|
|
summary: () => summaryValue,
|
|
|
|
|
setSummary: (value) => (summaryValue = value),
|
|
|
|
|
};
|
|
|
|
|
if (summaryOnly) {
|
|
|
|
|
const done = commitModule.commitSummaryOnly(ctx);
|
|
|
|
|
return { done, record: changed ? changed[0] : null };
|
|
|
|
|
}
|
|
|
|
|
await commitModule.commit(ctx, true);
|
|
|
|
|
return changed[0];
|
|
|
|
|
return changed ? changed[0] : null;
|
|
|
|
|
}
|
|
|
|
|
const prev = () => ({
|
|
|
|
|
structure_id: "s1",
|
|
|
|
@@ -278,6 +367,41 @@ async function main() {
|
|
|
|
|
out.commitKept = { key: kept.summary_item?.key, options: kept.options };
|
|
|
|
|
const swapped = await saveWith({ previous: prev(), summary: B, formOptions: { H: 3 } });
|
|
|
|
|
out.commitSwapped = swapped.summary_item?.key;
|
|
|
|
|
// 집계 항목만 바뀐 때(PLAN 21-10 흠 2) — 필수 옵션이 비어 폼 검증에 걸려도 기록에 바로 반영
|
|
|
|
|
const blocked = await saveWith({
|
|
|
|
|
previous: prev(),
|
|
|
|
|
summary: B,
|
|
|
|
|
formOptions: { H: 3 },
|
|
|
|
|
requiredEmpty: true,
|
|
|
|
|
});
|
|
|
|
|
out.formBlocks = blocked === null; // 종전 흐름(commit)은 필수 옵션이 비면 아무것도 안 함
|
|
|
|
|
const only = await saveWith({
|
|
|
|
|
previous: prev(),
|
|
|
|
|
summary: B,
|
|
|
|
|
formOptions: { H: 3 },
|
|
|
|
|
requiredEmpty: true,
|
|
|
|
|
summaryOnly: true,
|
|
|
|
|
});
|
|
|
|
|
out.summaryOnlyAssign = {
|
|
|
|
|
done: only.done,
|
|
|
|
|
key: only.record?.summary_item?.key,
|
|
|
|
|
options: only.record?.options,
|
|
|
|
|
};
|
|
|
|
|
const onlyClear = await saveWith({
|
|
|
|
|
previous: prev(),
|
|
|
|
|
summary: null,
|
|
|
|
|
formOptions: { H: 3 },
|
|
|
|
|
requiredEmpty: true,
|
|
|
|
|
summaryOnly: true,
|
|
|
|
|
});
|
|
|
|
|
out.summaryOnlyClear = { done: onlyClear.done, hasKey: "summary_item" in onlyClear.record };
|
|
|
|
|
const onlyNew = await saveWith({
|
|
|
|
|
previous: null,
|
|
|
|
|
summary: B,
|
|
|
|
|
formOptions: { H: 3 },
|
|
|
|
|
summaryOnly: true,
|
|
|
|
|
});
|
|
|
|
|
out.summaryOnlyNew = onlyNew.done; // 새 항목은 [추가] 몫 — 반영 안 함
|
|
|
|
|
process.stdout.write(JSON.stringify(out));
|
|
|
|
|
}
|
|
|
|
|
main();
|
|
|
|
|