|
|
|
@@ -315,13 +315,64 @@ const EstQuantity = (() => {
|
|
|
|
|
{ esc, number, byId } = EstApp;
|
|
|
|
|
byId("work-content").innerHTML =
|
|
|
|
|
selector(state) +
|
|
|
|
|
`<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>`;
|
|
|
|
|
`<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><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 style="white-space:normal"><input data-quantity-target="${index}" value="${esc(item.TargetCode ?? item.LinkCode ?? "")}" aria-label="연결 내역코드">${targetSelect(state.project, item, "sheet", index)}</td><td>${formatSelect(sheet.items[index], 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>`;
|
|
|
|
|
}
|
|
|
|
|
// 원본 수량목록 자리수 메뉴(mnuAuto·mnuTrunc·mnuUp·mnuRound): 결과 코드 뒤 방식(1 반올림·2 절사·3 올림)+자리수.
|
|
|
|
|
function resultFormat(code) {
|
|
|
|
|
const match = String(code ?? "").match(/^([LrG]\d{6})(?:([123])(\d))?$/);
|
|
|
|
|
return match
|
|
|
|
|
? {
|
|
|
|
|
base: match[1],
|
|
|
|
|
method: Number(match[2] || 0),
|
|
|
|
|
digits: match[3] ? Number(match[3]) : null,
|
|
|
|
|
}
|
|
|
|
|
: null;
|
|
|
|
|
}
|
|
|
|
|
function setResultFormat(sheet, index, method, digits) {
|
|
|
|
|
const item = sheet.items[index],
|
|
|
|
|
parsed = resultFormat(item?.Code);
|
|
|
|
|
if (!parsed) throw Error("이 수량 항목은 결과 자리수를 지정할 수 없는 코드입니다.");
|
|
|
|
|
if (![0, 1, 2, 3].includes(method)) throw Error("끝수 처리 방식을 고르세요.");
|
|
|
|
|
if (method && !(Number.isInteger(digits) && digits >= 0 && digits <= 5))
|
|
|
|
|
throw Error("자리수는 0~5만 지정할 수 있습니다.");
|
|
|
|
|
const old = item.Code,
|
|
|
|
|
code = method ? `${parsed.base}${method}${digits}` : parsed.base;
|
|
|
|
|
if (code === old) return code;
|
|
|
|
|
if (
|
|
|
|
|
sheet.items.some((other, i) => i !== index && other.Code === code) ||
|
|
|
|
|
sheet.rows.some((row) => (row.markers || []).includes(code))
|
|
|
|
|
)
|
|
|
|
|
throw Error(`같은 결과 코드가 이미 있습니다: ${code}`);
|
|
|
|
|
item.Code = code;
|
|
|
|
|
for (const row of sheet.rows)
|
|
|
|
|
if (row.markers) row.markers = row.markers.map((marker) => (marker === old ? code : marker));
|
|
|
|
|
return code;
|
|
|
|
|
}
|
|
|
|
|
function formatSelect(item, index) {
|
|
|
|
|
const parsed = resultFormat(item.Code);
|
|
|
|
|
if (!parsed) return "";
|
|
|
|
|
const current = parsed.method ? `${parsed.method}:${parsed.digits}` : "0";
|
|
|
|
|
const options = [["0", "AUTO(산출서 기본)"]];
|
|
|
|
|
for (const [method, label] of [
|
|
|
|
|
[2, "절사"],
|
|
|
|
|
[3, "올림"],
|
|
|
|
|
[1, "반올림"],
|
|
|
|
|
])
|
|
|
|
|
for (let digits = 0; digits <= 5; digits++)
|
|
|
|
|
options.push([`${method}:${digits}`, `${label} ${digits}자리`]);
|
|
|
|
|
return `<select data-quantity-format="${index}" aria-label="끝수 처리">${options
|
|
|
|
|
.map(
|
|
|
|
|
([value, label]) =>
|
|
|
|
|
`<option value="${value}" ${value === current ? "selected" : ""}>${label}</option>`,
|
|
|
|
|
)
|
|
|
|
|
.join("")}</select>`;
|
|
|
|
|
}
|
|
|
|
|
function nextItemCode(sheet) {
|
|
|
|
|
const codes = new Set([
|
|
|
|
|
...sheet.items.map((item) => item.Code),
|
|
|
|
|
...sheet.rows.flatMap((row) => row.markers || []),
|
|
|
|
|
]);
|
|
|
|
|
const codes = new Set(
|
|
|
|
|
[...sheet.items.map((item) => item.Code), ...sheet.rows.flatMap((row) => row.markers || [])]
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.map((code) => resultFormat(code)?.base || code),
|
|
|
|
|
);
|
|
|
|
|
for (let index = 0; index < 1000000; index++) {
|
|
|
|
|
const code = `r${String(index).padStart(6, "0")}`;
|
|
|
|
|
if (!codes.has(code)) return code;
|
|
|
|
@@ -385,6 +436,17 @@ const EstQuantity = (() => {
|
|
|
|
|
(sheet.rows[Number(node.dataset.quantityLine)].LineData = EstText.encode(node.value)),
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
if (node.dataset.quantityFormat !== undefined) {
|
|
|
|
|
const [method, digits] = node.value.split(":").map(Number);
|
|
|
|
|
try {
|
|
|
|
|
EstApp.change(() =>
|
|
|
|
|
setResultFormat(sheet, Number(node.dataset.quantityFormat), method, digits),
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
state.notice = error.message;
|
|
|
|
|
EstApp.render();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (node.dataset.quantityTarget !== undefined)
|
|
|
|
|
EstApp.change(() => {
|
|
|
|
|
const item = sheet.items[Number(node.dataset.quantityTarget)];
|
|
|
|
@@ -613,6 +675,9 @@ const EstQuantity = (() => {
|
|
|
|
|
excelUpdates,
|
|
|
|
|
targetSelect,
|
|
|
|
|
newCode,
|
|
|
|
|
resultFormat,
|
|
|
|
|
nextItemCode,
|
|
|
|
|
setResultFormat,
|
|
|
|
|
selector,
|
|
|
|
|
render,
|
|
|
|
|
install,
|
|
|
|
|