wip(est-test): 개발 중 저장 3 — 깃 싱크(11-08)

This commit is contained in:
2026-11-08 17:44:13 +09:00
parent d39eece45e
commit db71cdbf7c
28 changed files with 1027 additions and 202 deletions
+41 -17
View File
@@ -141,7 +141,22 @@ const EstQuantity = (() => {
});
return { rows, errors };
}
function totals(project, selected, allowEmpty = false) {
function estimateTargets(project) {
const byKey = new Map(),
byCode = new Map();
project.tables.EstMainDat.forEach((row, index) => {
if (Number(row.Level)) return;
const add = (map, key) => {
const entries = map.get(key) || [];
entries.push(index);
map.set(key, entries);
};
if (row.PkID != null) add(byKey, `id:${row.PkID}`);
add(byCode, row.Code);
});
return { byKey, byCode };
}
function totals(project, selected, allowEmpty = false, targets = estimateTargets(project)) {
let items;
if (selected && selected !== "manual") {
const sheet = project.quantitySheets?.find((s) => s.Code === selected);
@@ -154,20 +169,15 @@ const EstQuantity = (() => {
if (output.errors.length) throw Error(output.errors.join(" / "));
items = output.rows;
}
const { byKey, byCode } = targets;
const quantities = new Map();
for (const item of items) {
const code = item.TargetCode ?? item.LinkCode;
if (!code && !item.TargetKey) continue;
const targets = project.tables.EstMainDat.map((row, index) => ({ row, index })).filter(
({ row }) =>
!Number(row.Level) &&
(item.TargetKey
? row.PkID != null && `id:${row.PkID}` === item.TargetKey
: row.Code === code),
);
const targets = (item.TargetKey ? byKey.get(item.TargetKey) : byCode.get(code)) || [];
if (!targets.length) throw Error(`연결 내역 없음: ${item.TargetKey || code}`);
if (targets.length > 1) throw Error(`연결 내역 중복: ${item.TargetKey || code}`);
const index = targets[0].index;
const index = targets[0];
quantities.set(index, (quantities.get(index) || 0) + Number(item.GN || 0));
}
if (!quantities.size && !allowEmpty) throw Error("연결할 내역코드를 먼저 입력하세요.");
@@ -181,8 +191,9 @@ const EstQuantity = (() => {
new Set(codes).size !== codes.length
)
throw Error("수량산출서 코드가 없거나 중복됩니다.");
const targets = estimateTargets(project);
for (const selected of ["manual", ...codes])
for (const [index, value] of totals(project, selected, true))
for (const [index, value] of totals(project, selected, true, targets))
combined.set(index, (combined.get(index) || 0) + value);
if (!combined.size) throw Error("연결할 내역코드를 먼저 입력하세요.");
return combined;
@@ -192,13 +203,10 @@ const EstQuantity = (() => {
}
function targetSelect(project, item, section, index) {
const { esc } = EstApp;
const rows = project.tables.EstMainDat.filter((row) => row.PkID != null && !Number(row.Level));
return `<select data-quantity-select="${section}" data-index="${index}" aria-label="연결 내역 행"><option value="">내역 행 선택</option>${rows
.map((row) => {
const key = `id:${row.PkID}`;
return `<option value="${esc(key)}" ${item.TargetKey === key ? "selected" : ""}>${esc(row.Name || "내역")} · ${esc(row.Code || "코드 없음")} · ${esc(row.PkID)}</option>`;
})
.join("")}</select>`;
const selected = item.TargetKey
? `<option value="${esc(item.TargetKey)}" selected>${esc(item.TargetCode || "연결 내역")} · ${esc(item.TargetKey)}</option>`
: "";
return `<select data-quantity-select="${section}" data-index="${index}" aria-label="연결 내역 행"><option value="">내역 행 선택</option>${selected}</select>`;
}
function newCode(items) {
const codes = new Set(items.map((row) => row.Code));
@@ -310,6 +318,22 @@ const EstQuantity = (() => {
`<div class="business-tools"><label>산출서명 <input data-quantity-name value="${esc(sheet.Name)}"></label><button data-quantity-add-item>수량 항목 추가</button><button data-quantity-add>산출 행 추가</button><button data-quantity-apply>내역 수량 반영</button><button data-quantity-delete-sheet>산출서 삭제</button></div><table class="sheet"><thead><tr><th>산출코드</th><th>품명</th><th>규격</th><th>단위</th><th>저장 수량</th><th>재계산 수량</th><th>연결 내역코드</th><th>작업</th></tr></thead><tbody>${output.items.map((item, index) => `<tr><td>${esc(item.Code)}</td><td><input data-quantity-item="Name" data-index="${index}" value="${esc(item.Name || "")}" aria-label="품명"></td><td><input data-quantity-item="Size" data-index="${index}" value="${esc(item.Size || "")}" aria-label="규격"></td><td><input data-quantity-item="Unite" data-index="${index}" value="${esc(item.Unite || "")}" aria-label="단위"></td><td>${number(sheet.items[index]?.GN ?? 0)}</td><td>${number(item.GN)}</td><td><input data-quantity-target="${index}" value="${esc(item.TargetCode ?? item.LinkCode ?? "")}" aria-label="연결 내역코드">${targetSelect(state.project, item, "sheet", index)}</td><td><button data-quantity-delete-item="${index}">삭제</button></td></tr>`).join("")}</tbody></table><p class="business-note">${esc(output.errors.join(" / "))} 산출 행의 결과 표지로 수량 항목을 연결합니다. 신규 산출서와 행 변경은 JSON으로 저장합니다.</p><table class="sheet"><thead><tr><th>줄</th><th>산출근거 편집</th><th>작업</th></tr></thead><tbody>${sheet.rows.map((row, index) => `<tr><td>${index + 1}</td><td><textarea data-quantity-line="${index}" rows="1" style="min-width:750px" aria-label="산출근거 ${index + 1}">${esc(EstText.display(output.rows[index].LineData))}</textarea></td><td><button data-quantity-add data-index="${index}">아래 추가</button><button data-quantity-move="-1" data-index="${index}" ${index === 0 ? "disabled" : ""}>↑</button><button data-quantity-move="1" data-index="${index}" ${index === sheet.rows.length - 1 ? "disabled" : ""}>↓</button><button data-quantity-delete-row="${index}">삭제</button></td></tr>`).join("")}</tbody></table>`;
}
function install() {
document.addEventListener("focusin", (event) => {
const node = event.target;
if (node.dataset.quantitySelect === undefined || node.dataset.quantityOptionsLoaded) return;
const value = node.value;
const { esc, state } = EstApp;
node.innerHTML =
'<option value="">내역 행 선택</option>' +
state.project.tables.EstMainDat.filter((row) => row.PkID != null && !Number(row.Level))
.map((row) => {
const key = `id:${row.PkID}`;
return `<option value="${esc(key)}">${esc(row.Name || "내역")} · ${esc(row.Code || "코드 없음")} · ${esc(row.PkID)}</option>`;
})
.join("");
node.value = value;
node.dataset.quantityOptionsLoaded = "1";
});
document.addEventListener("change", (event) => {
const node = event.target,
{ state } = EstApp;