Files
Aislo/tools/est-test/native-business-actions.js
T
eomsangdonandClaude Opus 5.5 db9bc2dee4 feat(est-test): 품셈 보기와 품셈자료 붙여넣기
- 원본 BookData 7권 해독(DBISAM 암호 표 Blowfish-CBC, zlib RTF) 서버 book-store.cjs, /api/book 경로
- 목차 트리·본문 표·제목/본문/키워드 검색·본문 강조·복사하기, 단가산출·내역·일위·기계 붙여넣기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:41:02 +09:00

696 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const EstBusinessActions = (() => {
function setEstimateClassification(project, row, index, value, rowTable = "EstMainDat") {
index = Number(index);
if (!Number.isInteger(index) || project.tables[rowTable]?.[index] !== row)
throw Error("내역 행을 다시 선택하세요.");
const kind = Number(row.LinkIndex),
table = EstNative.sourceTables[kind];
if (
!table ||
(rowTable === "EstMainDat" ? Number(row.Level) : Number(row.Index) > 0) ||
[1, 3].includes(Number(row.CalcIndex)) ||
![0, 1, 2, 3].includes(value)
) {
row.SCheck = value;
return;
}
const total = EstMath.units.reduce((sum, key) => sum + EstMath.num(row[key]), 0);
if (!Number.isFinite(total) || Math.abs(total) > Number.MAX_SAFE_INTEGER)
throw Error("현재 단가 합계가 유효하지 않습니다.");
if (value !== 0) {
const target = { 1: "GU", 2: "NU", 3: "JU" }[value];
for (const key of EstMath.units) row[key] = key === target ? total : 0;
row.SCheck = value;
return;
}
if (!Number(row.SCheck)) {
row.SCheck = value;
return;
}
const sources = project.tables[table] || [];
let matches = row.Code ? sources.filter((source) => source.Code === row.Code) : [];
if (!matches.length && row.Code)
matches = sources.filter(
(source) => Array.isArray(source.PreviousCodes) && source.PreviousCodes.includes(row.Code),
);
if (!matches.length && row.Name)
matches = sources.filter(
(source) =>
EstNative.normalize(source.Name) === EstNative.normalize(row.Name) &&
(!row.Size || EstNative.normalize(source.Size) === EstNative.normalize(row.Size)) &&
(!row.Unite || EstNative.normalize(source.Unite) === EstNative.normalize(row.Unite)),
);
if (matches.length > 1) throw Error(`원천 코드가 중복되었습니다: ${row.Code}`);
const had = Object.hasOwn(row, "SCheck"),
before = row.SCheck;
row.SCheck = value;
try {
let failures = null;
const calculated = EstNative.calculate(
project,
rowTable === "HeadMainDat"
? { refreshHeadRows: [index], onHeadRefresh: (errors) => (failures = errors) }
: { refreshEstimateRows: [index], onEstimateRefresh: (errors) => (failures = errors) },
);
if (!failures) throw Error("내역 비목 재계산을 확인할 수 없습니다.");
if (failures.length) throw Error(failures.join(" / "));
const refreshed = calculated.tables[rowTable][index];
const refreshedTotal = EstMath.units.reduce(
(sum, key) => sum + EstMath.num(refreshed[key]),
0,
);
if (
!Number.isFinite(refreshedTotal) ||
Math.abs(refreshedTotal - total) >
Number.EPSILON * Math.max(1, Math.abs(total), Math.abs(refreshedTotal)) * 4
)
throw Error("현재 단가와 원천 단가가 달라 비목별 단가를 복원할 수 없습니다.");
for (const key of EstMath.units) row[key] = refreshed[key];
} catch (error) {
if (had) row.SCheck = before;
else delete row.SCheck;
throw error;
}
}
function setClassification(project, row, node, value) {
const table = node.dataset.table;
if (["EstMainDat", "HeadMainDat"].includes(table))
setEstimateClassification(project, row, node.dataset.row, value, table);
else row.SCheck = value;
}
let purpose = "",
documentKey;
const currentRound = () =>
EstApp.state.project.progress.find((round) => round.id === EstApp.state.roundId);
function numeric(value) {
const n = Number(value);
if (!Number.isFinite(n) || Math.abs(n) > Number.MAX_SAFE_INTEGER)
throw Error("유효한 수치를 입력하세요.");
return n;
}
function change(node) {
const { state } = EstApp;
if (node.dataset.option === "EscDesignTotal") {
const value = numeric(node.value);
if (value < 0) throw Error("조사 도급액은 0 이상입니다.");
EstApp.change(() => (state.project.options.EscDesignTotal = value), false);
return true;
}
if (node.dataset.escAuto !== undefined) {
EstApp.change(
() =>
(state.project.tables.EstMainDat[Number(node.dataset.escAuto)].EscAuto = node.checked),
false,
);
return true;
}
if (node.id === "resource-kind-filter") {
state.resourceKind = node.value;
EstApp.render();
return true;
}
if (node.id === "resource-by-work") {
state.resourceByWork = Boolean(node.checked);
EstApp.render();
return true;
}
if (node.id === "progress-components" || node.id === "progress-digits") {
const round = currentRound();
if (round.locked) throw Error("확정 회차입니다.");
const value = node.id === "progress-components" ? node.checked : numeric(node.value);
if (node.id === "progress-digits" && (!Number.isInteger(value) || value < 0 || value > 4))
throw Error("금액 소수자리는 0~4입니다.");
EstApp.change(
() =>
(round[node.id === "progress-components" ? "separateComponents" : "amountDigits"] =
value),
false,
);
return true;
}
return false;
}
async function handle(name, node) {
const { state } = EstApp,
project = state.project,
round = currentRound();
if (name === "replace-cost-words") {
if (state.view !== "CostMainDat") throw Error("단가산출 화면에서 문구를 바꾸세요.");
const revision = state.revision,
groupCode = state.group;
EstActions.form(
"산출근거 문구 바꾸기",
[
{ name: "search", label: "찾을 문구", value: "" },
{ name: "replacement", label: "바꿀 문구", value: "" },
{
name: "scope",
label: "범위",
value: "group",
choices: [
["group", "현재 호표"],
["all", "전체 산출근거"],
],
},
],
(values) => {
if (
state.project !== project ||
state.revision !== revision ||
state.view !== "CostMainDat" ||
state.group !== groupCode
)
throw Error("작업이 바뀌었습니다. 문구 바꾸기를 다시 열어주세요.");
const rows = project.tables.CostMainDat,
group = EstNative.groups(rows).get(groupCode),
input = document.querySelector('#dialog-content [name="search"]');
if (values.scope === "group" && !group) throw Error("현재 호표를 선택하세요.");
input.setCustomValidity(values.search ? "" : "찾을 문구를 입력하세요.");
if (!input.reportValidity()) throw Error("찾을 문구를 입력하세요.");
const candidates = rows
.map((row, index) => ({ row, index }))
.filter(
({ index }) =>
values.scope === "all" ||
(index >= group.start && index < group.start + group.rows.length),
),
updates = EstText.replaceWords(candidates, values.search, values.replacement);
input.setCustomValidity(updates.length ? "" : "변경할 문구가 없습니다.");
if (!input.reportValidity()) throw Error("변경할 문구가 없습니다.");
if (confirm(`${updates.length}개 행의 문구를 바꾸시겠습니까?`))
EstApp.change(() =>
updates.forEach(({ index, value }) => (rows[index].LineData = value)),
);
},
);
} else if (name === "clear-estimate-quantities") {
if (state.view !== "EstMainDat") throw Error("내역서에서 수량을 초기화하세요.");
const rows = project.tables.EstMainDat,
selected = [...state.selection];
EstActions.form(
"내역 수량 초기화",
[
{
name: "scope",
label: "초기화 범위 (비율·합계 자동행 제외)",
value: selected.length ? "selected" : "all",
choices: [
["selected", "선택한 상세 행"],
["all", "모든 상세 행"],
],
},
],
(values) => {
const indices = (
values.scope === "selected" ? selected : rows.map((_, index) => index)
).filter((index) => {
const row = rows[index];
return (
row &&
!Number(row.Level) &&
![51, 52, 54, 55, 57, 58, 59, 62, 67, 68].includes(Number(row.LinkIndex)) &&
!["%", "%"].includes(row.Unite)
);
});
if (!indices.length) throw Error("초기화할 상세 행이 없습니다.");
EstApp.change(() => indices.forEach((index) => (rows[index].GN = 0)));
},
);
} else if (name === "clear-progress") {
if (confirm("모든 기성 회차와 확정 결과를 초기화하시겠습니까?"))
EstApp.change(() => {
project.progress = [];
state.roundId = undefined;
}, false);
} else if (name === "set-row-precision") {
const rows = project.tables[state.view],
indices = [...state.selection].sort((a, b) => a - b);
if (!rows || !EstModel.columns(state.view).includes("DigetIndex"))
throw Error("이 자료에는 자릿수 설정이 없습니다.");
if (!indices.length || indices.some((index) => !rows[index]))
throw Error("설정할 행을 선택하세요.");
if (state.view === "Wonga") return EstWongaTools.positionDialog(state, indices);
EstActions.form(
"선택 행 자릿수",
[
{
name: "method",
label: "처리방식",
value: rows[indices[0]].DigetIndex || 0,
choices: [
[0, "자동"],
[1, "사사오입"],
[2, "절사"],
[3, "올림식"],
],
},
{
name: "digits",
label: "소수 자릿수 (0~6)",
type: "number",
value: Math.max(0, Number(rows[indices[0]].Diget || 1) - 1),
},
],
(values) => {
const method = Number(values.method),
digits = Number(values.digits);
if (
![0, 1, 2, 3].includes(method) ||
!Number.isInteger(digits) ||
digits < 0 ||
digits > 6
)
throw Error("자릿수는 0~6이고 처리방식을 선택해야 합니다.");
EstApp.change(() => {
for (const index of indices) {
rows[index].DigetIndex = method;
rows[index].Diget = method ? digits + 1 : 0;
}
});
},
);
} else if (name === "apply-all-quantities") {
const totals = EstQuantity.allTotals(project);
if (confirm("모든 수량산출서와 수식표의 연결 수량을 합산해 내역에 반영하시겠습니까?"))
EstApp.change(() => EstQuantity.applyTotals(project, totals));
} else if (name === "move-quantity") {
const index = Number(node.dataset.row),
direction = Number(node.dataset.direction),
next = index + direction;
if (
!Number.isInteger(index) ||
![-1, 1].includes(direction) ||
index < 0 ||
next < 0 ||
index >= project.quantities.length ||
next >= project.quantities.length
)
throw Error("수량 수식표 이동 위치 오류");
EstApp.change(() => {
[project.quantities[index], project.quantities[next]] = [
project.quantities[next],
project.quantities[index],
];
}, false);
} else if (name === "move-estimate-up" || name === "move-estimate-down") {
const isEst = state.view === "EstMainDat",
isGroup = ["HeadMainDat", "CostMainDat", "MachMainDat"].includes(state.view);
if (!isEst && !isGroup)
throw Error(
"내역서 또는 호표(일위대가·단가산출·기계경비) 화면에서만 행을 이동할 수 있습니다.",
);
const direction = name === "move-estimate-up" ? -1 : 1,
tableRows = project.tables[state.view];
const indices = isEst
? EstBusiness.moveEstimateRows(structuredClone(tableRows), state.selection, direction)
: EstBusiness.moveGroupDetailRows(
structuredClone(tableRows),
state.group,
state.selection,
direction,
state.view,
);
if (indices.every((index) => state.selection.has(index))) return;
EstApp.change(() => {
state.selection = new Set(
isEst
? EstBusiness.moveEstimateRows(tableRows, state.selection, direction)
: EstBusiness.moveGroupDetailRows(
tableRows,
state.group,
state.selection,
direction,
state.view,
),
);
});
} else if (name === "unlock-round") {
if (confirm("이 회차와 이후 회차의 확정을 해제하시겠습니까? 입력값은 유지됩니다."))
EstApp.change(() => (state.project = EstProgress.unlock(project, round.id)), false);
} else if (name === "delete-round") {
if (round.locked) throw Error("확정을 먼저 해제하세요.");
if (project.progress.at(-1) !== round) throw Error("마지막 회차부터 삭제하세요.");
if (confirm("선택한 기성 회차를 삭제하시겠습니까?"))
EstApp.change(() => {
project.progress.pop();
state.roundId = project.progress.at(-1)?.id;
}, false);
} else if (name === "rename-round") {
if (round.locked) throw Error("확정을 먼저 해제하세요.");
EstActions.form(
"기성 회차 이름",
[{ name: "name", label: "회차 이름", value: round.name }],
(values) => {
const name = values.name.trim();
if (
!name ||
project.progress.some(
(item) => item !== round && String(item.name || "").trim() === name,
)
)
throw Error("회차 이름은 비어 있거나 중복될 수 없습니다.");
EstApp.change(() => (round.name = name), false);
},
);
} else if (name === "fill-progress") {
if (round.locked) throw Error("확정 회차입니다.");
EstActions.form(
"기성 일괄 입력",
[
{
name: "prefix",
label: "대상",
value: "Gum",
choices: [
["Gum", "금회기성"],
["Sa", "금회사정"],
],
},
{
name: "mode",
label: "방법",
value: "ratio",
choices: [
["ratio", "비율 입력"],
["full", "전량"],
],
},
{ name: "ratio", label: "비율 %", type: "number", value: 100 },
],
(values) => {
const value = numeric(values.ratio);
const ambiguous = EstProgress.ambiguousKeys(project.tables.EstMainDat);
const hasAmbiguous = project.tables.EstMainDat.some(
(row, index) =>
Number(row.Level || 0) <= 0 &&
Number(row.CalcIndex || 0) < 2 &&
![54, 55, 58, 59, 62, 67].includes(Number(row.LinkIndex)) &&
!(values.mode === "full" && Number(row.LinkIndex) === 57) &&
ambiguous.has(EstBusiness.rowKey(row, index)),
);
if (hasAmbiguous) throw Error("기성 내역 식별자가 중복되어 일괄 입력할 수 없습니다.");
EstApp.change(() => {
project.tables.EstMainDat.forEach((row, index) => {
if (
Number(row.Level) > 0 ||
Number(row.CalcIndex) >= 2 ||
[54, 55, 58, 59, 62, 67].includes(Number(row.LinkIndex))
)
return;
if (values.mode === "full" && Number(row.LinkIndex) === 57) return;
const key = EstBusiness.rowKey(row, index);
round.inputs[key] ||= {};
round.inputs[key][values.prefix + "Mode"] =
values.mode === "full" ? "quantity" : "ratio";
round.inputs[key][values.prefix] =
values.mode === "full" ? Number(row.GN) || 0 : value;
});
}, false);
},
);
} else if (name === "load-progress-exclusion") {
const locked = project.progress.filter((round) => round.locked && round.result);
if (!locked.length) throw Error("확정한 기성 회차가 없습니다.");
EstActions.form(
"기성 누계수량 불러오기",
[
{
name: "id",
label: "기성 회차",
value: locked.at(-1).id,
choices: locked.map((round) => [round.id, round.name]),
},
],
(values) => {
const result = locked.find((round) => round.id === values.id).result;
const updates = EstBusiness.progressExclusions(project, result);
EstApp.change(() => {
for (const { index, value } of updates)
project.tables.EstMainDat[index].EscProgress = value;
}, false);
},
);
} else if (name === "vat-calc") {
if (state.view !== "StuffcDat")
throw Error("자재단가 화면에서만 부가세 계산을 실행할 수 있습니다.");
const rows = project.tables.StuffcDat || [];
const indices = [...state.selection].sort((a, b) => a - b);
EstActions.form(
"자재 부가세 계산",
[
{
name: "calcMethod",
label: "계산방법",
value: 1,
choices: [
[0, "부가세포함(×1.1)"],
[1, "부가세제외(÷1.1)"],
],
},
{
name: "digetMethod",
label: "단단위 계산방식",
value: 1,
choices: [
[0, "사사오입"],
[1, "절하"],
[2, "절상"],
],
},
{ name: "diget", label: "단단위 자리수(0=원, 1=십원..)", type: "number", value: "0" },
],
(values) => {
EstApp.change(() => {
state.project = EstVat.apply(state.project, indices, {
calcMethod: Number(values.calcMethod),
digetMethod: Number(values.digetMethod),
diget: Number(values.diget),
});
});
},
);
} else if (name === "haljung-calc") {
if (state.view !== "HeadMainDat")
throw Error("일위대가 화면에서만 할증계산을 실행할 수 있습니다.");
const rows = project.tables.HeadMainDat || [];
const grps = EstNative.groups(rows);
const currentGroup = grps.get(state.group);
if (!currentGroup) throw Error("할증을 적용할 공종(묶음)을 선택하세요.");
EstActions.form(
"공종 내 할증계산",
[
{ name: "name", label: "명 칭", value: "할 증" },
{
name: "source",
label: "불러올비목(규격)",
value: 0,
choices: [
[0, "비목별"],
[1, "재료비의"],
[2, "노무비의"],
[3, "경비의"],
[4, "합계의"],
],
},
{
name: "target",
label: "적용비목",
value: 0,
choices: [
[0, "비목별"],
[1, "재료비에"],
[2, "노무비에"],
[3, "경비에"],
],
},
{ name: "gn", label: "적용율(%)", type: "number", value: "10" },
],
(values) => {
const rate = Number(values.gn);
if (!Number.isFinite(rate) || rate === 0)
throw Error("할증 적용율은 0이 아닌 유효한 숫자여야 합니다.");
EstApp.change(() => {
state.project = EstHalJung.apply(state.project, state.view, state.group, {
name: values.name,
sourceKind: Number(values.source),
targetKind: Number(values.target),
rate,
});
});
},
);
} else if (name === "unit-calc") {
if (state.view !== "HeadMainDat")
throw Error("일위대가 화면에서만 공종 단위환산 계산을 실행할 수 있습니다.");
const grps = EstNative.groups(project.tables.HeadMainDat || []);
const currentGroup = grps.get(state.group);
if (!currentGroup) throw Error("환산할 일위대가 공종(묶음)을 선택하세요.");
const title = currentGroup.rows[0];
EstActions.form(
"공종의 단위환산 계산",
[
{ name: "name", label: "명 칭", value: title.Name || "단위환산" },
{ name: "size", label: "규 격", value: title.Size || "" },
{ name: "gn", label: "수 량(환산계수)", type: "number", value: "1" },
{ name: "unite", label: "단 위", value: title.Unite || "식" },
],
(values) => {
const factor = Number(values.gn);
if (!Number.isFinite(factor) || factor === 0)
throw Error("수량은 0이 아닌 유효한 숫자여야 합니다.");
EstApp.change(() => {
state.project = EstBusiness.convertGroupUnit(state.project, state.group, {
name: values.name,
size: values.size,
factor,
unit: values.unite,
});
});
},
);
} else if (name === "toggle-jc-bimog") {
EstExtra.toggleJcBimog(state);
} else if (name === "toggle-sun-gongsa") {
EstExtra.toggleSunGongsa(state);
} else if (name === "uses-data-search") {
const rows = state.project.tables[state.view] || [];
const selected = [...state.selection];
const targetRow = selected.length ? rows[selected[0]] : null;
if (!targetRow) throw Error("사용위치를 검색할 행을 먼저 선택하세요.");
EstExtra.showUsesSearchDialog(targetRow);
} else if (name === "set-cost-line-kind") {
const rows = state.project.tables.CostMainDat,
indices = [...state.selection].sort((a, b) => a - b);
if (state.view !== "CostMainDat" || !rows)
throw Error("단가산출 화면에서만 산출근거 비목을 지정할 수 있습니다.");
if (!indices.length || indices.some((i) => !rows[i]))
throw Error("비목을 지정할 산출근거 행을 선택하세요.");
const curKind = Number(rows[indices[0]].OCheck) || 0;
EstActions.form(
"산출근거 비목 지정",
[
{
name: "kind",
label: "지정 비목",
value: curKind,
choices: [
[0, "일반 산출식 / 해제"],
[-1, "노무비 (-1)"],
[-2, "재료비 (-2)"],
[-3, "경비 (-3)"],
[-5, "소계 (-5)"],
],
},
],
({ kind }) => {
kind = Number(kind);
EstApp.change(() => {
for (const idx of indices) {
const r = rows[idx];
if (kind === 0) {
delete r.OCheck;
delete r.SCheck;
} else {
r.OCheck = kind;
r.SCheck = kind;
}
}
});
},
);
} else if (
EstBlockActions.handle(name, state) ||
(typeof EstPick !== "undefined" && EstPick.handle(name, state)) ||
(typeof EstFind !== "undefined" && EstFind.handle(name, state)) ||
(typeof EstBlank !== "undefined" && EstBlank.handle(name, state)) ||
(typeof EstQuantitySheets !== "undefined" && EstQuantitySheets.handle(name, state, node)) ||
(typeof EstExcelMap !== "undefined" && EstExcelMap.handle(name, state, node)) ||
(typeof EstTransfer !== "undefined" && EstTransfer.handle(name, state)) ||
(typeof EstBid !== "undefined" && EstBid.handle(name, state)) ||
(typeof EstFormsLibrary !== "undefined" && EstFormsLibrary.handle(name, state, node)) ||
(typeof EstJungSang !== "undefined" && EstJungSang.handle(name, state)) ||
(typeof EstCleanup !== "undefined" && EstCleanup.handle(name, state)) ||
(typeof EstNameReg !== "undefined" && EstNameReg.handle(name, state)) ||
(typeof EstTidy !== "undefined" && EstTidy.handle(name, state)) ||
(typeof EstMultiSearch !== "undefined" && EstMultiSearch.handle(name, state, node)) ||
(typeof EstDocs !== "undefined" && EstDocs.handle(name, state, node)) ||
(typeof EstWongaTools !== "undefined" && EstWongaTools.handle(name, state)) ||
(typeof EstEstimatePrint !== "undefined" && EstEstimatePrint.handle(name, state)) ||
(typeof EstXmlCheck !== "undefined" && EstXmlCheck.handle(name, state)) ||
(typeof EstReviewAuto !== "undefined" && EstReviewAuto.handle(name, state)) ||
(typeof EstAutoLink !== "undefined" && EstAutoLink.handle(name, state)) ||
(typeof EstEstTools !== "undefined" && EstEstTools.handle(name, state)) ||
(typeof EstPrintBatch !== "undefined" && EstPrintBatch.handle(name, state)) ||
(typeof EstMachFormula !== "undefined" && EstMachFormula.handle(name, state, node)) ||
(typeof EstDefaults !== "undefined" && EstDefaults.handle(name, state, node)) ||
(typeof EstExcelView !== "undefined" && EstExcelView.handle(name, state, node)) ||
(typeof EstProgressWonga !== "undefined" && EstProgressWonga.handle(name, state)) ||
(typeof EstCodeChangeView !== "undefined" && EstCodeChangeView.handle(name, state, node)) ||
(typeof EstSources !== "undefined" && EstSources.handle(name, state)) ||
(typeof EstDocEditor !== "undefined" && EstDocEditor.handle(name, state, node)) ||
(typeof EstDocForms !== "undefined" && EstDocForms.handle(name, state, node)) ||
(typeof EstBlockFile !== "undefined" && EstBlockFile.handle(name, state)) ||
(typeof EstDisplay !== "undefined" && EstDisplay.handle(name, state)) ||
(typeof EstCellClip !== "undefined" && EstCellClip.handle(name, state)) ||
(typeof EstRowLinks !== "undefined" && EstRowLinks.handle(name, state)) ||
(typeof EstEsInfo !== "undefined" && EstEsInfo.handle(name, state)) ||
(typeof EstJdlCompare !== "undefined" && EstJdlCompare.handle(name, state, node)) ||
(typeof EstGlobalVars !== "undefined" && EstGlobalVars.handle(name, state, node)) ||
(typeof EstNewEstimate !== "undefined" && EstNewEstimate.handle(name, state)) ||
(typeof EstBook !== "undefined" && EstBook.handle(name, state, node))
) {
EstApp.render();
} else if (name === "insert-annual") {
// 원본 열(차수) 삽입(TFRMESTMULTIEDIT popInsert): 고른 차수 앞에 새 차수
const at = Math.max(0, Math.min(project.annual.length, Number(node.dataset.row) || 0));
EstApp.change(() => {
project.annual.splice(at, 0, {
name: EstBusiness.newAnnualName(project.annual),
formula: "[기준]",
});
}, false);
} else if (name === "load-contract" || name === "load-bid") {
purpose = name;
documentKey = state.documentKey;
document.getElementById("business-file").click();
}
}
function install() {
const input = document.createElement("input");
input.type = "file";
input.id = "business-file";
input.accept = ".json,.estx";
input.hidden = true;
document.body.append(input);
input.addEventListener("change", async () => {
try {
const file = input.files[0];
if (!file) return;
const raw = /\.estx$/i.test(file.name)
? await EstApp.api("/api/import?name=" + encodeURIComponent(file.name), {
method: "POST",
body: await file.arrayBuffer(),
})
: JSON.parse(await file.text());
const incoming = EstModel.legacy(raw);
if (documentKey !== EstApp.state.documentKey)
throw Error("문서가 변경되어 불러오기를 취소했습니다.");
EstApp.change(
() =>
(EstApp.state.project =
purpose === "load-contract"
? EstBusiness.importContract(EstApp.state.project, incoming)
: EstBusiness.importBid(EstApp.state.project, incoming)),
false,
);
} catch (error) {
EstApp.state.notice = error.message;
EstApp.render();
} finally {
input.value = "";
}
});
}
return { handle, change, install, setEstimateClassification, setClassification };
})();