- 원본 BookData 7권 해독(DBISAM 암호 표 Blowfish-CBC, zlib RTF) 서버 book-store.cjs, /api/book 경로 - 목차 트리·본문 표·제목/본문/키워드 검색·본문 강조·복사하기, 단가산출·내역·일위·기계 붙여넣기 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
696 lines
28 KiB
JavaScript
696 lines
28 KiB
JavaScript
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 };
|
||
})();
|