feat(est): apply all quantity sheets

This commit is contained in:
2026-11-07 11:04:23 +09:00
parent b6a3392bc4
commit 83d3d43f10
6 changed files with 42 additions and 3 deletions
@@ -1,6 +1,26 @@
from test_est_rea_parity import run
def test_apply_all_quantities_sums_sheets_and_rejects_errors_atomically():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.EstMainDat=[{PkID:1,Code:'A',GN:1},{PkID:2,Code:'B',GN:2}];
p.quantities=[{Code:'Q1',formula:'2',TargetKey:'id:1'}];
p.quantitySheets=[{Code:'S1',Name:'sheet',options:{},rows:[{LineData:'3 = \\x120\\x12',markers:['a']}],
items:[{Code:'a',TargetKey:'id:1'}]},
{Code:'S2',Name:'sheet2',options:{},rows:[{LineData:'4 = \\x120\\x12',markers:['b']}],
items:[{Code:'b',TargetKey:'id:2'}]}];
const values=EstQuantity.allTotals(p);EstQuantity.applyTotals(p,values);
const updated=p.tables.EstMainDat.map(row=>row.GN),before=JSON.stringify(p.tables.EstMainDat);
p.quantitySheets[1].rows[0].LineData='bad';let rejected=false;
try{EstQuantity.applyTotals(p,EstQuantity.allTotals(p))}catch{rejected=true}
return {updated,rejected,unchanged:before===JSON.stringify(p.tables.EstMainDat)};
})()""",
)
assert result == {"updated": [5, 4], "rejected": True, "unchanged": True}
def test_quantity_apply_requires_one_detail_row_per_code():
result = run(
"math",
+3
View File
@@ -1,4 +1,7 @@
## Codex · EST 독립 테스트 앱 (2026-11-04)
- [x] 모든 수량산출서·직접 수식표의 내역 수량 일괄 합산 반영 → 동일 대상 합산·오류 시 전체 차단 시험.
- 자체 검증: 동일 내역 합산·오류 시 전체 차단 포함 수량·문서 시험 18개 통과, Edge에서 일괄 반영·취소 확인.
- 2026-11-07: 사용자 지시로 EST 작업 계획과 검증 기록을 공유 PLAN.md에서 이 파일로 이관. 업무 회귀 시험 79개 통과, openpyxl 경고 7건. Git push는 보류.
+2
View File
@@ -41,6 +41,8 @@ Ctrl+S는 입력 중인 셀도 확정한 뒤 저장합니다. 연속 저장 요
재계산 메뉴의 수량 반영도 현재 선택한 산출서 또는 직접 작성 수식표를 사용합니다. 반영 전에 모든 연결 대상과 수식을 검사하며, 기존 원본 연결 데이터는 그대로 보관합니다.
재계산 메뉴의 `모든 수량산출 합산 반영`은 모든 산출서와 직접 작성 수식표의 연결 수량을 내역 행별로 합산합니다. 어느 산출서든 계산 오류가 있거나 연결 대상이 모호하면 전체 반영을 중단합니다.
수량산출서 인쇄 서식에는 직접 작성한 수식표와 산출서의 산식, 계산 수량, 연결 내역이 함께 표시됩니다. 계산할 수 없는 수식은 오류를 표시합니다.
산출 행의 결과 표지 개수가 연결 정보와 다르면 계산 오류로 표시하며, 기존 저장 수량을 내역에 반영하지 않습니다. 화면이나 Excel에서 수식을 수정할 때 결과 표지와 연결 항목을 유지하세요.
+1
View File
@@ -96,6 +96,7 @@
<button data-action="recalculate">일건서류 전체 재계산</button
><button data-action="recalculate">내역서 재계산</button
><button data-action="apply-quantity">수량산출 반영 후 재계산</button>
<button data-action="apply-all-quantities">모든 수량산출 합산 반영</button>
</div>
</details>
<details>
+5 -1
View File
@@ -45,7 +45,11 @@ const EstBusinessActions = (() => {
const { state } = EstApp,
project = state.project,
round = currentRound();
if (name === "move-quantity") {
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;
+11 -2
View File
@@ -141,7 +141,7 @@ const EstQuantity = (() => {
});
return { rows, errors };
}
function totals(project, selected) {
function totals(project, selected, allowEmpty = false) {
let items;
if (selected && selected !== "manual") {
const sheet = project.quantitySheets?.find((s) => s.Code === selected);
@@ -170,9 +170,17 @@ const EstQuantity = (() => {
const index = targets[0].index;
quantities.set(index, (quantities.get(index) || 0) + Number(item.GN || 0));
}
if (!quantities.size) throw Error("연결할 내역코드를 먼저 입력하세요.");
if (!quantities.size && !allowEmpty) throw Error("연결할 내역코드를 먼저 입력하세요.");
return quantities;
}
function allTotals(project) {
const combined = new Map();
for (const selected of ["manual", ...(project.quantitySheets || []).map((sheet) => sheet.Code)])
for (const [index, value] of totals(project, selected, true))
combined.set(index, (combined.get(index) || 0) + value);
if (!combined.size) throw Error("연결할 내역코드를 먼저 입력하세요.");
return combined;
}
function applyTotals(project, quantities) {
for (const [index, value] of quantities) project.tables.EstMainDat[index].GN = value;
}
@@ -337,6 +345,7 @@ const EstQuantity = (() => {
calculate,
manual,
totals,
allTotals,
applyTotals,
targetSelect,
newCode,