feat(est): exchange annual and draft progress

This commit is contained in:
2026-11-07 08:24:00 +09:00
parent 50d66fae78
commit bc41c8716f
4 changed files with 201 additions and 3 deletions
+96 -2
View File
@@ -35,7 +35,17 @@ const EstExchange = (() => {
const name = sheet.getAttribute("name");
if (
(!EstNative.empty().tables[name] &&
!["Quantities", "Schedule", "QDocuments", "QLines", "QItems", "Review"].includes(name)) ||
![
"Quantities",
"Schedule",
"QDocuments",
"QLines",
"QItems",
"Review",
"Annual",
"ProgressDrafts",
"ProgressInputs",
].includes(name)) ||
name.startsWith("Cad")
)
continue;
@@ -196,8 +206,82 @@ const EstExchange = (() => {
const next = structuredClone(project);
const sheets = quantitySheets(next, tables);
if (sheets) next.quantitySheets = sheets;
if (Object.hasOwn(tables, "ProgressDrafts") || Object.hasOwn(tables, "ProgressInputs")) {
if (!Object.hasOwn(tables, "ProgressDrafts") || !Object.hasOwn(tables, "ProgressInputs"))
throw Error("기성 Excel에는 ProgressDrafts와 ProgressInputs 시트가 모두 필요합니다.");
const old = next.progress || [],
locked = old.filter((round) => round.locked);
if (old.slice(0, locked.length).some((round) => !round.locked))
throw Error("확정 기성 회차 순서 오류");
const previous = new Map(
old.filter((round) => !round.locked).map((round) => [round.id, round]),
);
const ids = new Set(locked.map((round) => round.id)),
drafts = new Map();
for (const row of tables.ProgressDrafts) {
if (typeof row.id !== "string" || !row.id || ids.has(row.id))
throw Error("기성 회차 ID가 없거나 중복됩니다.");
ids.add(row.id);
const round = {
...previous.get(row.id),
id: row.id,
name: row.name ?? "",
locked: false,
approved: row.approved ?? false,
separateComponents: row.separateComponents ?? true,
inputs: Object.create(null),
};
delete round.contract;
delete round.result;
if (row.amountDigits == null) delete round.amountDigits;
else round.amountDigits = row.amountDigits;
drafts.set(row.id, round);
}
const inputFields = [
"GumMode",
"Gum",
"GumNC",
"GumGC",
"GumJC",
"GumSC",
"SaMode",
"Sa",
"SaNC",
"SaGC",
"SaJC",
"SaSC",
"QuantityDigits",
"AmountDigits",
];
for (const row of tables.ProgressInputs) {
const round = drafts.get(row.RoundId);
if (
!round ||
typeof row.RowKey !== "string" ||
!row.RowKey ||
Object.hasOwn(round.inputs, row.RowKey)
)
throw Error("기성 입력의 회차·행 식별자가 없거나 중복됩니다.");
const prior = previous.get(row.RoundId)?.inputs?.[row.RowKey] || {};
const input = Object.fromEntries(
Object.entries(prior).filter(([key]) => !inputFields.includes(key)),
);
for (const field of inputFields) if (row[field] != null) input[field] = row[field];
round.inputs[row.RowKey] = input;
}
next.progress = [...locked, ...drafts.values()];
}
for (const [table, rows] of Object.entries(tables)) {
if (["QDocuments", "QLines", "QItems"].includes(table)) continue;
if (["QDocuments", "QLines", "QItems", "ProgressDrafts", "ProgressInputs"].includes(table))
continue;
if (table === "Annual") {
next.annual = rows.map((row) => {
if (typeof row.name !== "string" || typeof row.formula !== "string")
throw Error("연차 이름·수식 형식 오류");
return { name: row.name, formula: row.formula };
});
continue;
}
if (table === "Review") {
const edits = {};
for (const row of rows) {
@@ -300,6 +384,16 @@ const EstExchange = (() => {
return merged;
});
}
if (Object.hasOwn(tables, "ProgressDrafts")) {
const counts = new Map();
next.tables.EstMainDat.forEach((row, index) => {
const key = EstBusiness.rowKey(row, index);
counts.set(key, (counts.get(key) || 0) + 1);
});
for (const round of next.progress.filter((item) => !item.locked))
for (const key of Object.keys(round.inputs))
if (counts.get(key) !== 1) throw Error(`기성 입력 내역이 없거나 중복됩니다: ${key}`);
}
return EstModel.validate(next);
}
async function exportExcel() {