fix(est): apply selected quantity calculations
This commit is contained in:
@@ -102,3 +102,28 @@ def test_insert_and_move_quantity_rows_keeps_item_results_linked():
|
||||
assert result["linked"]["errors"] == []
|
||||
assert result["linked"]["items"][1]["GN"] == 6
|
||||
assert result["forward"]["errors"]
|
||||
|
||||
|
||||
def test_manual_and_selected_sheet_totals_validate_before_application():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.EstMainDat=[{Code:'X',Level:0,GN:1,MulCode:{binary:'YWJj'}}];
|
||||
p.quantities=[{Code:'A',formula:'2',TargetCode:'X'},
|
||||
{Code:'B',formula:'[A]*3',TargetCode:'X'}];
|
||||
const manual=EstQuantity.totals(p,'manual');EstQuantity.applyTotals(p,manual);
|
||||
const a=structuredClone(p.tables.EstMainDat[0]);
|
||||
p.quantitySheets=[{Code:'Q',options:{},rows:[{LineData:'4+5 = \\x120\\x12',markers:['r1']}],
|
||||
items:[{Code:'r1',GN:0,TargetCode:'X'}]}];
|
||||
const sheet=EstQuantity.totals(p,'Q');EstQuantity.applyTotals(p,sheet);
|
||||
let invalid='';p.quantities.push({Code:'C',formula:'1',TargetCode:'missing'});
|
||||
try{EstQuantity.totals(p,'manual')}catch(e){invalid=e.message}
|
||||
return {a,b:p.tables.EstMainDat[0],invalid};
|
||||
})()""",
|
||||
)
|
||||
assert result["a"]["GN"] == 8
|
||||
assert result["a"]["MulCode"] == {"binary": "YWJj"}
|
||||
assert result["b"]["GN"] == 9
|
||||
assert result["b"]["MulCode"] == {"binary": "YWJj"}
|
||||
assert result["invalid"]
|
||||
|
||||
@@ -29,6 +29,8 @@ Ctrl+S는 입력 중인 셀도 확정한 뒤 저장합니다. 연속 저장 요
|
||||
|
||||
수량 화면에서 새 산출서를 만들고 이름과 산출 항목의 품명·규격을 편집할 수 있습니다. `산출 행 추가`로 결과 표지가 연결된 행과 항목을 함께 만든 뒤 수식을 입력하고, 연결 내역코드를 지정해 계산 수량을 내역에 반영합니다. 행 아래 삽입, 위·아래 이동과 산출 행·산출서 삭제도 가능하며, 이 구조 변경은 JSON 저장과 다시 열기를 지원합니다. 기존 연결은 내역코드를 비워 해제할 수 있습니다.
|
||||
|
||||
재계산 메뉴의 수량 반영도 현재 선택한 산출서 또는 직접 작성 수식표를 사용합니다. 반영 전에 모든 연결 대상과 수식을 검사하며, 기존 원본 연결 데이터는 그대로 보관합니다.
|
||||
|
||||
## 검증과 남은 범위
|
||||
|
||||
기계 성분·합계 48건, 일위 41건, 내역 37건, 산출 결과 118건과 호표 8건, 원가 수식 23건을 대조합니다. 원가 도급액 고정 반복 차이 `324 → -32 → 3 → 0`을 재현합니다. 단가 갱신 후 전체 재계산과 반복 계산의 안정성도 시험합니다.
|
||||
|
||||
@@ -410,28 +410,9 @@ const EstActions = (() => {
|
||||
false,
|
||||
);
|
||||
else if (name === "apply-quantity") {
|
||||
const values = Object.create(null),
|
||||
totals = new Map();
|
||||
for (const row of project.quantities) {
|
||||
const value = EstMath.floating(
|
||||
EstExpression.evaluate(row.formula, (key) => values[key]),
|
||||
row.digits ?? 2,
|
||||
1,
|
||||
);
|
||||
values[row.Code] = value;
|
||||
if (row.TargetCode) totals.set(row.TargetCode, (totals.get(row.TargetCode) || 0) + value);
|
||||
}
|
||||
if (!totals.size) throw Error("연결 내역코드를 입력하세요.");
|
||||
const totals = EstQuantity.totals(project, state.quantitySheet);
|
||||
if (confirm("연결된 내역 수량을 산출 결과로 바꾸시겠습니까?"))
|
||||
EstApp.change(() => {
|
||||
for (const row of project.tables.EstMainDat)
|
||||
if (totals.has(row.Code)) {
|
||||
row.GN = totals.get(row.Code);
|
||||
row.MullangIndex = 1;
|
||||
row.ExcelSheetIndex = 0;
|
||||
row.MulCode = row.Code;
|
||||
}
|
||||
});
|
||||
EstApp.change(() => EstQuantity.applyTotals(project, totals));
|
||||
} else if (name === "add-round") {
|
||||
const next = {
|
||||
id: uid(),
|
||||
|
||||
@@ -101,6 +101,7 @@ const EstApp = (() => {
|
||||
state.selection.clear();
|
||||
state.group = "";
|
||||
state.page = 0;
|
||||
state.quantitySheet = state.project.quantitySheets?.[0]?.Code || "manual";
|
||||
state.roundId =
|
||||
state.project.progress.find((round) => round.id === state.roundId)?.id ||
|
||||
state.project.progress[0]?.id;
|
||||
|
||||
@@ -113,6 +113,44 @@ const EstQuantity = (() => {
|
||||
errors: result.errors,
|
||||
};
|
||||
}
|
||||
function totals(project, selected) {
|
||||
let items;
|
||||
if (selected && selected !== "manual") {
|
||||
const sheet = project.quantitySheets?.find((s) => s.Code === selected);
|
||||
if (!sheet) throw Error("선택한 수량산출서가 없습니다.");
|
||||
const output = calculate(sheet);
|
||||
if (output.errors.length) throw Error(output.errors.join(" / "));
|
||||
items = output.items;
|
||||
} else {
|
||||
const values = Object.create(null);
|
||||
items = (project.quantities || []).map((row) => {
|
||||
if (!row.Code || Object.hasOwn(values, row.Code))
|
||||
throw Error(`산출코드가 비어 있거나 중복됩니다: ${row.Code || ""}`);
|
||||
const GN = EstMath.floating(
|
||||
EstExpression.evaluate(row.formula || "0", (key) => values[key]),
|
||||
row.digits ?? 2,
|
||||
1,
|
||||
);
|
||||
values[row.Code] = GN;
|
||||
return { ...row, GN };
|
||||
});
|
||||
}
|
||||
const quantities = new Map();
|
||||
for (const item of items) {
|
||||
const code = item.TargetCode ?? item.LinkCode;
|
||||
if (!code) continue;
|
||||
const targets = project.tables.EstMainDat.filter((r) => r.Code === code && !Number(r.Level));
|
||||
if (!targets.length) throw Error(`연결 내역코드 없음: ${code}`);
|
||||
if (targets.length > 1) throw Error(`연결 내역코드 중복: ${code}`);
|
||||
quantities.set(code, (quantities.get(code) || 0) + Number(item.GN || 0));
|
||||
}
|
||||
if (!quantities.size) throw Error("연결할 내역코드를 먼저 입력하세요.");
|
||||
return quantities;
|
||||
}
|
||||
function applyTotals(project, quantities) {
|
||||
for (const row of project.tables.EstMainDat)
|
||||
if (quantities.has(row.Code) && !Number(row.Level)) row.GN = quantities.get(row.Code);
|
||||
}
|
||||
function selector(state) {
|
||||
const { esc } = EstApp;
|
||||
return `<div class="business-tools"><label>수량산출서 <select id="quantity-sheet-choice"><option value="manual">수식표 직접 작성</option>${(state.project.quantitySheets || []).map((sheet) => `<option value="${esc(sheet.Code)}" ${state.quantitySheet === sheet.Code ? "selected" : ""}>${esc(sheet.Name || sheet.Code)}</option>`).join("")}</select></label><button data-quantity-new>새 산출서</button></div>`;
|
||||
@@ -224,30 +262,14 @@ const EstQuantity = (() => {
|
||||
}
|
||||
if (!event.target.closest("[data-quantity-apply]")) return;
|
||||
try {
|
||||
const output = calculate(sheet);
|
||||
if (output.errors.length) throw Error(output.errors.join(" / "));
|
||||
const quantities = new Map();
|
||||
for (const item of output.items)
|
||||
if (item.TargetCode ?? item.LinkCode) {
|
||||
const code = item.TargetCode ?? item.LinkCode;
|
||||
const targets = state.project.tables.EstMainDat.filter(
|
||||
(r) => r.Code === code && !Number(r.Level),
|
||||
);
|
||||
if (!targets.length) throw Error(`연결 내역코드 없음: ${code}`);
|
||||
if (targets.length > 1) throw Error(`연결 내역코드 중복: ${code}`);
|
||||
quantities.set(code, (quantities.get(code) || 0) + Number(item.GN || 0));
|
||||
}
|
||||
if (!quantities.size) throw Error("연결할 내역코드를 먼저 입력하세요.");
|
||||
EstApp.change(() => {
|
||||
for (const row of state.project.tables.EstMainDat)
|
||||
if (quantities.has(row.Code) && !Number(row.Level)) row.GN = quantities.get(row.Code);
|
||||
});
|
||||
const quantities = totals(state.project, state.quantitySheet);
|
||||
EstApp.change(() => applyTotals(state.project, quantities));
|
||||
} catch (error) {
|
||||
EstApp.state.notice = error.message;
|
||||
EstApp.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
return { entities, fromWpt, calculate, selector, render, install };
|
||||
return { entities, fromWpt, calculate, totals, applyTotals, selector, render, install };
|
||||
})();
|
||||
if (typeof module !== "undefined" && module.exports) module.exports = EstQuantity;
|
||||
|
||||
Reference in New Issue
Block a user