Files

699 lines
30 KiB
JavaScript

const EstExtra = (() => {
function field(section, index, key, value, type = "number") {
return `<input data-extra="${section}" data-row="${index}" data-field="${key}" type="${type}" step="any" value="${EstApp.esc(value)}" aria-label="${EstApp.esc(key)}">`;
}
function button(action, text, extra = "") {
return `<button data-action="${action}" ${extra}>${text}</button>`;
}
function table(headers, rows) {
return `<table class="sheet"><thead><tr>${headers.map((h) => `<th>${h}</th>`).join("")}</tr></thead><tbody>${rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
}
function box(title, tools, body, note = "") {
return `<div class="business-tools"><b>${title}</b>${tools}</div>${body}${note ? `<p class="business-note">${note}</p>` : ""}`;
}
function render(state) {
if (state.view === "review") {
EstReview.render(state);
return;
}
if (state.view === "schedule") {
EstSchedule.render();
return;
}
if (state.view === "issues") {
EstIssues.render();
return;
}
if (state.view === "attachments") {
EstAttachments.render();
return;
}
if (["standard", "standard-change"].includes(state.view)) {
EstStandard.render();
return;
}
if (["estimate-usage", "estimate-change"].includes(state.view)) {
EstEstimate.render();
return;
}
if (["material-usage", "material-change", "labor-usage", "labor-change"].includes(state.view)) {
EstBasicPrices.render();
return;
}
if (["machine-usage", "machine-change"].includes(state.view)) {
EstMachine.render();
return;
}
const { project, view } = state,
{ esc, number, byId } = EstApp;
let html = "";
if (view === "quantity") {
if (project.quantitySheets?.length && state.quantitySheet !== "manual") {
EstQuantity.render(state);
return;
}
const rows = EstQuantity.manual(project.quantities).rows.map((row, index) => {
return [
field("quantities", index, "Code", row.Code, "text"),
field("quantities", index, "Name", row.Name, "text"),
field("quantities", index, "TargetCode", row.TargetCode, "text") +
EstQuantity.targetSelect(project, row, "manual", index),
field("quantities", index, "formula", row.formula, "text"),
field("quantities", index, "digits", row.digits ?? 2),
esc(row.Error || number(row.GN)),
button(
"move-quantity",
"↑",
`data-row="${index}" data-direction="-1" ${index === 0 ? "disabled" : ""}`,
) +
button(
"move-quantity",
"↓",
`data-row="${index}" data-direction="1" ${index === project.quantities.length - 1 ? "disabled" : ""}`,
) +
button("delete-extra", "삭제", `data-section="quantities" data-row="${index}"`),
];
});
html =
EstQuantity.selector(state) +
box(
"수량산출서",
button("add-quantity", "산출 항목 추가") + button("apply-quantity", "내역에 수량 반영"),
table(["산출코드", "품명", "연결 내역코드", "수식", "소수자리", "결과", ""], rows),
"앞선 산출 항목을 [산출코드]로 참조합니다. 내역 반영은 연결된 코드의 산출값을 합산하여 수량에 반영합니다.",
);
} else if (view === "change") {
const changes = EstBusiness.changes(project);
const page = EstBlockActions.changePage(state, changes);
html = box(
"설계변경",
button("capture-baseline", "현재 내역을 당초로 확정") +
button("load-contract", "당초·계약 내역 불러오기") +
button("recalculate-original", "당초 단가 재계산") +
button("bid-rates", "낙찰률 적용") +
button("restore-bid", "당초단가(100%) 복원") +
button("change-prev", "이전", state.changePage === 0 ? "disabled" : "") +
`<span>${state.changePage + 1} / ${page.pages}쪽 · ${page.total}행</span>` +
button("change-next", "다음", state.changePage + 1 >= page.pages ? "disabled" : ""),
table(
["구분", "코드", "품명", "당초 수량", "변경 수량", "당초 금액", "변경 금액", "증감"],
page.rows.map(({ row }) => [
esc({ new: "신규", deleted: "삭제", changed: "변경", unchanged: "동일" }[row.status]),
esc(row.Code),
esc(row.Name),
number(row.originalGN),
number(row.GN),
number(row.originalTC),
number(row.TC),
number(row.difference),
]),
) +
table(
["원가 구분", "비목", "당초 금액", "변경 금액", "증감"],
EstBusiness.costChanges(project).map((row) => [
esc(row.Gubun),
esc(row.Name),
number(row.originalCost),
number(row.changedCost),
number(row.difference),
]),
),
project.baseline
? `당초: ${esc(project.baseline.name)}. 당초 자료는 별도 사본으로 보존합니다.`
: changes.some((row) => row.originalSource === "file")
? "원본 파일에 저장된 당초 수량·금액과 비교합니다."
: "현재 작업을 저장한 뒤 당초 내역을 확정하세요.",
);
} else if (view === "progress") {
state.roundId ||= project.progress[0]?.id;
const round = project.progress.find((r) => r.id === state.roundId);
let body = '<p class="empty">회차를 추가하세요.</p>',
tools =
button("add-round", "기성 회차 추가") +
(project.progress.length ? button("clear-progress", "기성 전체 초기화") : "");
if (round) {
tools +=
`<select id="round-choice">${project.progress.map((r) => `<option value="${esc(r.id)}" ${r.id === round.id ? "selected" : ""}>${esc(r.name)}${r.locked ? " (확정)" : ""}</option>`).join("")}</select>` +
button(
round.locked ? "unlock-round" : "lock-round",
round.locked ? "확정 해제(이후 포함)" : "회차 확정·저장",
) +
button("fill-progress", "전량·비율 일괄 입력") +
button("rename-round", "회차 이름") +
button("delete-round", "회차 삭제") +
button("reset-round-inputs", "회차 입력 초기화") +
`<label><input id="progress-components" type="checkbox" ${round.separateComponents !== false ? "checked" : ""} ${round.locked ? "disabled" : ""}> 비목별 계산</label><label>금액 소수자리 <input id="progress-digits" type="number" min="0" max="4" value="${round.amountDigits ?? 0}" ${round.locked ? "disabled" : ""}></label>` +
`<label><input id="use-approved" type="checkbox" ${round.approved ? "checked" : ""} ${round.locked ? "disabled" : ""}> 누계에 사정 사용</label>`;
let output;
try {
output =
round.locked && round.result ? round.result : EstBusiness.progress(project, round.id);
} catch (e) {
output = { rows: [], errors: [e.message] };
}
state.business = output;
const page = EstBlockActions.progressPage(state, output.rows);
tools +=
button("progress-prev", "이전", state.progressPage === 0 ? "disabled" : "") +
`<span>${state.progressPage + 1} / ${page.pages}쪽 · ${page.total}행</span>` +
button("progress-next", "다음", state.progressPage + 1 >= page.pages ? "disabled" : "");
body =
table(
[
"코드",
"공종",
"도급수량",
"전회수량",
"금회 입력",
"입력방법",
"사정 입력",
"입력방법",
"금회금액",
"사정금액",
"누계수량",
"누계금액",
],
page.rows.map(({ row, index }) => {
const editable = ![54, 55, 58, 59, 62, 67].includes(Number(row.LinkIndex));
const key = EstBusiness.rowKey(row, index),
input = round.inputs?.[key] || {};
const edit = (prefix) =>
`<input data-progress="${prefix}" data-key="${esc(key)}" type="number" step="any" value="${input[prefix] ?? row[prefix + "GN"] ?? 0}" ${round.locked || !editable || input[prefix + "Mode"] === "components" ? "disabled" : ""}>` +
(input[prefix + "Mode"] === "components"
? EstMath.components
.map(
(field, i) =>
`<label>${["노무", "재료", "경비", "실적"][i]}<input data-progress="${prefix + field}" data-key="${esc(key)}" type="number" step="any" value="${input[prefix + field] ?? 0}" ${round.locked ? "disabled" : ""}></label>`,
)
.join("")
: "");
const method = (prefix) =>
!editable
? "자동계산"
: `<select data-progress="${prefix}Mode" data-key="${esc(key)}" ${round.locked ? "disabled" : ""}>${[
["quantity", "수량"],
["amount", "금액"],
["ratio", "비율 %"],
["components", "비목 금액"],
]
.map(
([v, label]) =>
`<option value="${v}" ${v === (input[`${prefix}Mode`] || "quantity") ? "selected" : ""}>${label}</option>`,
)
.join("")}</select>`;
return [
esc(row.Code),
esc(row.Name),
number(row.GN),
number(row.JunGN),
edit("Gum"),
method("Gum"),
edit("Sa"),
method("Sa"),
number(row.GumTC),
number(row.SaTC),
number(row.TotalGN),
number(row.TotalTC),
];
}),
) +
`<p class="business-note">${esc((output.errors || []).join(" / "))} 금회 도급액 ${number(output.total)} · 기성비율 ${number(output.ratio)}%</p>`;
body += table(
["원가 비목", "도급금액", "전회", "금회", "사정", "누계"],
(output.costs || []).map((row) => [
esc(row.Name || row.Gubun),
number(row.ContractCost),
number(row.JunCost),
number(row.GumCost),
number(row.SaCost),
number(row.TotalCost),
]),
);
}
html = box(
"기성고",
tools,
body,
"전회는 직전 확정 회차 누계를 읽습니다. 금회·사정은 수량·금액·비율 중 하나를 입력해 계산합니다.",
);
} else if (view === "escalation") {
const output = EstBusiness.escalation(project);
state.business = output;
const comp = state.escalationComponent || "NU";
const choices = EstMath.units
.map(
(key, i) =>
`<option value="${key}" ${key === comp ? "selected" : ""}>${["노무", "재료", "경비", "실적"][i]}</option>`,
)
.join("");
const page = EstBlockActions.escalationPage(state, project.tables.EstMainDat);
const rows = page.rows.map(({ row, index }) => {
const result = output.rows[index];
return [
esc(row.Code),
esc(row.Name),
EstViews.cell("EstMainDat", index, `A${comp}`, row[`A${comp}`]),
number(result[`Contract${comp}`]),
EstViews.cell("EstMainDat", index, comp, row[comp]),
number(result[`D${comp}`]),
number(result[`E${comp}`]),
number(result[`F${comp}`]),
EstViews.cell("EstMainDat", index, "EscPlanned", row.EscPlanned || 0),
EstViews.cell("EstMainDat", index, "EscActual", row.EscActual || 0),
EstViews.cell("EstMainDat", index, "EscProgress", row.EscProgress || 0),
number(result.EscExcluded),
number(result.EscApplied),
`<input type="checkbox" data-esc-auto="${index}" ${row.EscAuto !== false ? "checked" : ""} aria-label="적용제외 자동계산">`,
EstViews.cell("EstMainDat", index, "EscManualQuantity", row.EscManualQuantity ?? 0),
EstViews.cell("EstMainDat", index, "EscConstruction", row.EscConstruction ?? 0),
EstViews.cell("EstMainDat", index, "EscPS", row.EscPS ?? 0),
EstViews.cell("EstMainDat", index, "EscBefore", row.EscBefore ?? 0),
];
});
html = box(
"물가변동 품목조정",
`<select id="escalation-component">${choices}</select>` +
button("capture-bid", "현재 단가를 입찰단가로 기록") +
button("load-bid", "입찰단가 불러오기") +
button("load-contract", "계약 내역 불러오기") +
button("load-progress-exclusion", "기성 누계수량 불러오기") +
button("reset-escalation", "물가변동 입력 초기화") +
button("capture-baseline", "계약 내역 확정") +
button("escalation-prev", "이전", state.escalationPage === 0 ? "disabled" : "") +
`<span>${state.escalationPage + 1} / ${page.pages}쪽 · ${page.total}행</span>` +
button(
"escalation-next",
"다음",
state.escalationPage + 1 >= page.pages ? "disabled" : "",
) +
`<label>조사 도급액(0=단가로 계산) <input type="number" step="any" data-option="EscDesignTotal" value="${project.options.EscDesignTotal ?? 0}"></label>`,
table(
[
"코드",
"품명",
"입찰 당시",
"계약 단가",
"변동 당시",
"등락률 %",
"등락폭",
"조정단가",
"예정공정수량",
"실행공정수량",
"기성수량",
"적용제외",
"적용수량",
"제외 자동",
"수동 제외수량",
"시공중 수량(참고)",
"PS 수량(참고)",
"선시공 수량(참고)",
],
rows,
),
`조사 도급액 ${number(output.designTotal)} · 계약 도급액 ${number(output.contractTotal)} · 조정 도급액 ${number(output.adjustedTotal)} · 적용제외(간접비 포함) ${number(output.excludedTotal)} · 적용대가 ${number(output.applied)} · 조정률 ${number(output.rate)}% · 조정금액 ${number(output.adjustment)}. 계약·입찰 파일은 동일 행 순서로 불러옵니다. ${esc(output.errors.join(" / "))}`,
);
} else if (view === "annual") {
const output = EstBusiness.annual(project, false);
state.business = output;
html = box(
"연차공사",
button("add-annual", "차수 추가") + button("reset-annual", "연차 차수 초기화(웹보조)"),
table(
["차수", "수량·금액 배분 수식", "순공사비", "도급액", "작업", ""],
project.annual.map((year, index) => [
field("annual", index, "name", year.name, "text"),
field("annual", index, "formula", year.formula, "text"),
number(output.results[index]?.project.calculation.direct.TC),
number(output.results[index]?.project.calculation.total),
button("open-annual", "차수 내역 열기", `data-row="${index}"`),
button(
"move-annual",
"↑",
`data-row="${index}" data-direction="-1" ${index === 0 ? "disabled" : ""}`,
) +
button(
"move-annual",
"↓",
`data-row="${index}" data-direction="1" ${index === project.annual.length - 1 ? "disabled" : ""}`,
) +
button("delete-extra", "삭제", `data-section="annual" data-row="${index}"`),
]),
),
`수식 예: [기준]*0.5, [기준]-[1차]. 같은 수식을 수량과 각 성분 금액에 따로 적용하고 금액은 원 미만 절사합니다. ${esc(output.errors.join(" / "))}`,
);
} else if (view === "resources") {
state.resourceKind = state.resourceKind || "all";
state.resourceByWork = state.resourceByWork ?? false;
const byWork = Boolean(state.resourceByWork);
const output = EstResources.calculate(project, byWork);
const filtered = output.rows.filter(
(r) => state.resourceKind === "all" || String(r.kind) === String(state.resourceKind),
);
const kinds = [
["all", "전체"],
["6", "노임"],
["5", "자재"],
["105", "기계"],
["21", "표준"],
["101", "견적"],
];
const kindSelect = `<label>구분 <select id="resource-kind-filter">${kinds
.map(
([k, label]) =>
`<option value="${k}" ${state.resourceKind === k ? "selected" : ""}>${esc(label)}</option>`,
)
.join("")}</select></label>`;
const byWorkCheck = `<label><input type="checkbox" id="resource-by-work" ${byWork ? "checked" : ""}> 공종별 전개</label>`;
const headers = byWork
? [
"공종코드",
"공종명",
"종류",
"코드",
"품명",
"규격",
"단위",
"소요량 / 환산시간",
"금액",
]
: ["종류", "코드", "품명", "규격", "단위", "소요량 / 환산시간", "금액"];
const rows = filtered.map((row) => {
const baseCells = [
esc({ 5: "자재", 6: "노임", 21: "표준", 101: "견적", 105: "기계" }[row.kind]),
esc(row.Code),
esc(row.Name),
esc(row.Size),
esc(row.Unite),
number(row.quantity),
number(row.amount),
];
return byWork ? [esc(row.WorkCode), esc(row.WorkName), ...baseCells] : baseCells;
});
html = box(
"동원인원·자재·장비",
`${kindSelect} ${byWorkCheck} <span>(총 ${filtered.length}건)</span>`,
table(headers, rows),
esc(output.errors.join(" / ")),
);
} else if (view === "drawing") {
const rows = project.tables.CadMainDat || [];
html = box(
"원본 도면·문서 자료",
"",
table(
["번호", "코드", "이름", "보존 필드"],
rows.map((row, index) => [
index + 1,
esc(row.Code),
esc(row.Name),
esc(
Object.keys(row)
.filter((key) => row[key]?.binary)
.join(", "),
),
]),
),
"가져온 도면 바이너리는 프로젝트에 보존됩니다. VEC0650/CAD 편집·출력의 재현 근거는 현재 자료에 없어 동작을 구현하지 않았습니다.",
);
}
byId("work-content").innerHTML = html;
}
function findDataUsages(project, target) {
if (!project || !target) return [];
const code = target.Code ? String(target.Code).trim() : "";
const name = target.Name ? EstNative.normalize(target.Name) : "";
const size = target.Size ? EstNative.normalize(target.Size) : "";
if (!code && !name) return [];
const results = [];
const tables = [
{ id: "EstMainDat", label: "내역서" },
{ id: "HeadMainDat", label: "일위대가" },
{ id: "CostMainDat", label: "단가산출" },
{ id: "MachMainDat", label: "기계경비" },
];
for (const info of tables) {
const rows = project.tables[info.id] || [];
const grouped = ["HeadMainDat", "CostMainDat", "MachMainDat"].includes(info.id);
let curGroup = "";
for (let i = 0; i < rows.length; i++) {
const r = rows[i];
if (grouped && Number(r.Index) > 0 && r.Code) curGroup = r.Code;
// 대상 자기 자신의 대표 행은 검색 결과에서 제외
if (r === target) continue;
let matched = false;
if (code && r.Code === code) {
matched = true;
} else if (!code && name) {
const rName = r.Name ? EstNative.normalize(r.Name) : "";
const rSize = r.Size ? EstNative.normalize(r.Size) : "";
if (rName === name && (!size || rSize === size)) {
matched = true;
}
}
if (matched) {
const rowName = r.Name || (r.LineData ? EstText.clean(r.LineData) : "");
results.push({
table: info.id,
tableLabel: info.label,
index: i,
group: curGroup,
code: r.Code || "",
name: rowName,
size: r.Size || "",
unite: r.Unite || "",
quantity: r.GN != null ? Number(r.GN) : null,
price: r.TC != null ? Number(r.TC) : null,
});
}
}
}
return results;
}
function showUsesSearchDialog(targetRow) {
const { state, esc, number, showDialog, byId } = EstApp;
if (!targetRow) throw Error("사용위치를 검색할 행을 먼저 선택하세요.");
const rowName = targetRow.Name || (targetRow.LineData ? EstText.clean(targetRow.LineData) : "");
const results = findDataUsages(state.project, targetRow);
const title = `데이터 사용위치 검색: ${targetRow.Code ? `[${targetRow.Code}] ` : ""}${rowName || "선택 항목"}`;
let bodyHtml = "";
if (!results.length) {
bodyHtml =
'<p style="padding:15px;color:#666;">내역서, 일위대가, 단가산출, 기계경비에서 사용된 위치를 찾지 못했습니다.</p>';
} else {
bodyHtml = `
<div style="max-height:420px;overflow-y:auto;border:1px solid #ccc;margin-bottom:12px;">
<table class="sheet" style="width:100%;font-size:12px;">
<thead>
<tr>
<th>구분</th><th>코드</th><th>품명 / 산출근거</th><th>규격</th><th>단위</th><th>수량</th><th>금액</th><th>이동</th>
</tr>
</thead>
<tbody>
${results
.map(
(res) => `
<tr>
<td><b>${esc(res.tableLabel)}</b></td>
<td>${esc(res.code)}</td>
<td style="text-align:left;">${esc(res.name)}</td>
<td>${esc(res.size)}</td>
<td>${esc(res.unite)}</td>
<td style="text-align:right;">${res.quantity != null ? number(res.quantity) : "-"}</td>
<td style="text-align:right;">${res.price != null ? number(res.price) : "-"}</td>
<td>
<button class="tiny" data-action="goto-use-location" data-table="${res.table}" data-index="${res.index}" data-group="${esc(res.group)}">
이동
</button>
</td>
</tr>
`,
)
.join("")}
</tbody>
</table>
</div>
<div style="font-size:12px;color:#555;">총 <b>${results.length}</b>곳에서 사용 중입니다. [이동] 버튼을 누르면 해당 화면과 행으로 이동합니다.</div>
`;
}
showDialog(title, bodyHtml);
const dialogContent = byId("dialog-content");
const clickHandler = (e) => {
const btn = e.target.closest('[data-action="goto-use-location"]');
if (!btn) return;
const targetTable = btn.dataset.table;
const targetIndex = Number(btn.dataset.index);
const targetGroup = btn.dataset.group || "";
byId("dialog").close();
dialogContent.removeEventListener("click", clickHandler);
EstApp.navigate(targetTable, targetGroup);
state.page = Math.floor(targetIndex / 100);
state.selection = new Set([targetIndex]);
EstApp.render();
};
dialogContent.addEventListener("click", clickHandler);
}
function install() {
// 필요한 경우 이벤트 리스너 등록
}
function toggleJcBimog(state) {
// REA ActionSetJCBimog (0xf17c10 / 0x10dba14): 경비성단가 지정/해제 -> SCheck 3 토글
const rows = state.project.tables[state.view],
indices = [...state.selection].sort((a, b) => a - b);
if (!rows || !["EstMainDat", "HeadMainDat"].includes(state.view))
throw Error("내역서 또는 일위대가 화면에서만 경비성단가를 지정/해제할 수 있습니다.");
if (!indices.length || indices.some((index) => !rows[index]))
throw Error("경비성단가를 지정/해제할 행을 선택하세요.");
// 원자적 검증: 한 행이라도 복원 정보가 없거나 거부되면 앞선 행도 변경되지 않도록 사전 검사
const plans = [];
for (const index of indices) {
const row = rows[index];
if (Number(row.SCheck) === 3) {
// 경비성 해제 -> 명시적 0/1/2 이력(_prevSCheck) 확인
let restoreVal =
row._prevSCheck !== undefined && [0, 1, 2].includes(Number(row._prevSCheck))
? Number(row._prevSCheck)
: null;
if (
restoreVal == null &&
row.SCheck_ !== undefined &&
[0, 1, 2].includes(Number(row.SCheck_))
) {
restoreVal = Number(row.SCheck_);
}
if (restoreVal == null) {
throw Error(
`행 ${index + 1}: 이전 비목 이력(0:비목별, 1:재료, 2:노무)이 없는 기존 경비성(3) 행은 임의 변환할 수 없습니다.`,
);
}
plans.push({ index, row, isToggleOn: false, restoreVal });
} else {
// 경비성 지정 -> 현재 SCheck(0, 1, 2)를 이력으로 보존
const cur = Number(row.SCheck) || 0;
plans.push({ index, row, isToggleOn: true, cur });
}
}
// 전수 검증 통과 후 일괄 적용
EstApp.change(() => {
for (const p of plans) {
if (p.isToggleOn) {
p.row._prevSCheck = p.cur;
EstBusinessActions.setEstimateClassification(
state.project,
p.row,
p.index,
3,
state.view,
);
} else {
delete p.row._prevSCheck;
EstBusinessActions.setEstimateClassification(
state.project,
p.row,
p.index,
p.restoreVal,
state.view,
);
}
}
});
}
function toggleSunGongsa(state) {
// REA popMainSetTotalSumLine: 순직공비 계산끝(순공사비) 지정/해제 -> LinkIndex 54 토글
const rows = state.project.tables.EstMainDat,
indices = [...state.selection].sort((a, b) => a - b);
if (state.view !== "EstMainDat" || !rows)
throw Error("내역서 화면에서만 순공사비 경계 행을 지정/해제할 수 있습니다.");
if (indices.length !== 1 || !rows[indices[0]])
throw Error("순공사비 경계 행으로 지정하거나 해제할 행 1개를 선택하세요.");
const index = indices[0],
row = rows[index];
// 연결 행 보호: 원천 연결(자재/노임/일위/단가/기계 등 LinkIndex 5,6,21,101~105) 또는
// 수량이 있는 행, 코드가 있는 상세 행은 순공사비 경계선으로 지정 불가 (원천/연결 파괴 차단)
const curLink = Number(row.LinkIndex) || 0;
if (curLink !== 54) {
if (
row.Code ||
Number(row.GN) > 0 ||
[5, 6, 21, 51, 52, 55, 57, 58, 61, 62, 67, 68, 101, 103, 104, 105].includes(curLink)
) {
throw Error(
"단가·수량 연결 행이나 기존 특수 공종 행은 순공사비 경계선으로 지정할 수 없습니다. 빈 제목/구분 행을 선택하세요.",
);
}
}
EstApp.change(() => {
if (curLink === 54) {
// 기존 54 해제 -> 보존된 원래 LinkIndex 또는 원래 유형이 확인되는 경우에만 복원
let origLink = row._origLinkIndex !== undefined ? Number(row._origLinkIndex) : null;
if (origLink == null && row.LinkIndex_ !== undefined && Number(row.LinkIndex_) !== 54) {
origLink = Number(row.LinkIndex_);
}
if (origLink == null) {
// 복원 정보가 없는 기존 원본 54 행: 원래 유형을 알 수 없으므로 임의 0 변환 차단
throw Error(
"기존 파일에 저장된 원본 순공사비(54) 행이거나 원래 유형을 확인할 수 없어 임의 0으로 변환할 수 없습니다.",
);
}
row.LinkIndex = origLink;
delete row._origLinkIndex;
if (row._origName !== undefined) {
row.Name = row._origName;
delete row._origName;
} else if (row.Name === "순공사비") {
row.Name = "공종";
}
} else {
// 기존 다른 54 행이 있으면 원래 상태로 안전 복원
for (const r of rows) {
if (Number(r.LinkIndex) === 54) {
let origL = r._origLinkIndex !== undefined ? Number(r._origLinkIndex) : null;
if (origL == null && r.LinkIndex_ !== undefined && Number(r.LinkIndex_) !== 54) {
origL = Number(r.LinkIndex_);
}
if (origL == null) {
throw Error(
"기존 원본 순공사비(54) 행의 원래 유형을 알 수 없어 다른 행으로 대체할 수 없습니다.",
);
}
r.LinkIndex = origL;
delete r._origLinkIndex;
if (r._origName !== undefined) {
r.Name = r._origName;
delete r._origName;
} else if (r.Name === "순공사비") {
r.Name = "공종";
}
}
}
row._origLinkIndex = curLink;
row._origName = row.Name ?? "";
row.LinkIndex = 54;
if (!row.Name || row.Name === "공종") row.Name = "순공사비";
}
});
}
return { render, findDataUsages, showUsesSearchDialog, install, toggleJcBimog, toggleSunGongsa };
})();