Files
Aislo/tools/est-test/native-autolink.js
T
eomsangdonandClaude Opus 5.5 e41a20b89b feat(est-test): 데이타 자동연결
- 원본 처리 근거대로 품명·규격·단위 정규화 맞춤으로 현재 공사 단가 표에 연결
- 9개 선택·Over Write·노임 맞바꿈·전체연결 끊기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:54:07 +09:00

141 lines
6.1 KiB
JavaScript

// 원본 데이타 자동연결(ActionAutoLink Ctrl+Alt+L 0x10d54f4 → TfrmDataAutoLink 적용 0x10d3008, 행 처리 10d2a48, 맞춤 10e0e58).
// 내역서·일위대가·기계경비 화면에서 현재 공사의 노임·자재·견적·표준·기계경비·단가산출·일위대가 표로 품명(·규격·단위)을 맞춰 연결.
// 정규화: 빈칸·|·(·) 제거, 단위는 「당」·[·] 를 한 번씩 더 지움. 노임은 품명만(못 찾으면 규격으로 찾고 품명·규격을 맞바꿈),
// 규격·단위 비면 품명+규격, 단위 있으면 품명+규격+단위. 노임·기계는 단위 무시. 처음 맞는 행. Over Write 끄면 이미 연결된 행은 건너뜀.
// 쓰는 값: Code, TU(=원본 TC), 내역·일위대가는 NU·GU·JU·SU(=원본 NC·GC·JC·SC), LinkIndex, 원본 SCheck 3 이면 SCheck 3.
// 「전체연결 끊기」는 원본처럼 모든 행 LinkIndex 를 0 으로 한다(조건 없음).
const EstAutoLink = (() => {
const num = (value) => Number(value) || 0;
const sources = {
6: "LaborcDat",
5: "StuffcDat",
101: "EstiMateDat",
21: "SilJukDat",
105: "MachTitleDat",
104: "CostTitleDat",
103: "HeadTitleDat",
};
const order = [6, 5, 101, 21, 105, 104, 103];
const tags = { EstMainDat: 1, HeadMainDat: 2, MachMainDat: 4 };
const norm = (s) => String(s ?? "").replace(/[\s|()]/g, "");
const unit = (s) => norm(s).replace("당", "").replace("[", "").replace("]", "");
function key(row, tag) {
let name = row.Name ?? "",
size = row.Size ?? "";
const unite = row.Unite ?? "";
if (tag === 4) {
name = String(name).replace(/[\s|]/g, "");
size = String(size).replace(/[\s|]/g, "");
if ([5, 212].includes(num(row.LinkIndex)) && name.includes("주연료")) {
name = size;
size = "";
}
}
return { name: String(name).trim(), size: String(size).trim(), unite: String(unite).trim() };
}
function find(project, kind, k) {
const rows = project.tables[sources[kind]] || [],
useUnit = ![6, 105].includes(kind) && k.unite;
const mode = !k.size && !useUnit ? (kind === 6 ? "name" : "size") : useUnit ? "unit" : "size";
return rows.find((src) => {
if (!src.Code) return false;
if (kind !== 101 && src.LinkIndex != null && num(src.LinkIndex) !== kind) return false;
if (src.DataKind != null && num(src.DataKind) !== 1) return false;
if (norm(src.Name) !== norm(k.name)) return false;
if (mode === "name" || kind === 6) return true;
if (norm(src.Size) !== norm(k.size)) return false;
if (mode === "size" || kind === 105) return true;
return unit(src.Unite) === unit(k.unite);
});
}
function linkKind(project, view, kind, overwrite) {
const tag = tags[view],
rows = project.tables[view] || [];
let linked = 0;
for (const row of rows) {
if ((tag === 2 || tag === 4) && num(row.Index) > 0) continue;
if (tag === 1 && (num(row.Level) > 0 || [54, 55, 58].includes(num(row.LinkIndex)))) continue;
const k = key(row, tag);
if (!k.name && !k.size && !k.unite) continue;
if (!overwrite && num(row.LinkIndex)) continue;
let src = find(project, kind, kind === 6 ? { name: k.name, size: "", unite: "" } : k);
if (!src && kind === 6 && k.size) {
src = find(project, kind, { name: k.size, size: "", unite: "" });
if (src) [row.Name, row.Size] = [row.Size, row.Name];
}
if (!src) continue;
row.Code = src.Code;
row.TU = num(src.TC);
if (tag === 1 || tag === 2) {
row.NU = num(src.NC);
row.GU = num(src.GC);
row.JU = num(src.JC);
row.SU = num(src.SC);
}
row.LinkIndex = kind;
if (num(src.SCheck) === 3) row.SCheck = 3;
linked++;
}
return linked;
}
function run(project, view, choice, overwrite) {
if (!tags[view]) throw Error("내역서·일위대가·기계경비 화면에서 자동연결하세요.");
if (choice === "unlink") {
for (const row of project.tables[view] || []) row.LinkIndex = 0;
return (project.tables[view] || []).length;
}
const kinds =
choice === "all"
? order.filter((k) => k !== 105 || [1, 2].includes(tags[view]))
: [Number(choice)];
return kinds.reduce((n, kind) => n + linkKind(project, view, kind, overwrite), 0);
}
const choices = [
["unlink", "전체연결 끊기"],
["all", "전체자동 연결"],
["6", "노임단가 연결"],
["5", "자재단가 연결"],
["101", "견적단가 연결"],
["21", "표준단가 연결"],
["105", "기계경비 연결"],
["104", "단가산출 연결"],
["103", "일위대가 연결"],
];
function open(state) {
if (!tags[state.view]) throw Error("내역서·일위대가·기계경비 화면에서 자동연결하세요.");
EstApp.showDialog(
"데이타 자동연결",
`<div class="form-fields"><label>연결데이타 선택<select name="al-choice">${choices.map(([v, l]) => `<option value="${v}" ${v === "all" ? "selected" : ""}>${l}</option>`).join("")}</select></label><label><input type="checkbox" name="al-over">Over Write</label></div><button data-action="autolink-apply">적 용</button><p id="autolink-result" role="status"></p>`,
);
}
function apply(state) {
const choice = document.querySelector('#dialog-content [name="al-choice"]').value,
overwrite = document.querySelector('#dialog-content [name="al-over"]').checked;
const ask =
choice === "unlink"
? "데이타를 연결을 끊을까요?"
: choice === "all"
? "전체 데이타를 연결 시킬까요?"
: null;
if (ask && !confirm(ask)) return;
let count;
EstApp.change((project) => (count = run(project, state.view, choice, overwrite)));
EstApp.byId("autolink-result").textContent =
choice === "unlink" ? `${count}행의 연결을 끊었습니다.` : `${count}행을 연결했습니다.`;
}
function handle(name, state) {
if (name === "autolink") open(state);
else if (name === "autolink-apply") apply(state);
else return false;
return true;
}
return { run, find, key, handle };
})();