From 5f9b5432ecb200e4c95c03bccb6cd780adf9bfda Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 7 Nov 2026 10:27:09 +0900 Subject: [PATCH] feat(est): print progress and adjustment costs --- resources/tester/test_est_documents.py | 39 ++++++++++++++++++++++++++ tools/est-test/README.md | 2 +- tools/est-test/native-forms.js | 30 ++++++++++++++++++-- tools/est-test/native-report.js | 9 +++++- 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/resources/tester/test_est_documents.py b/resources/tester/test_est_documents.py index f4d4cfe52..2628c2671 100644 --- a/resources/tester/test_est_documents.py +++ b/resources/tester/test_est_documents.py @@ -44,6 +44,45 @@ def test_progress_and_escalation_print_use_selected_calculations(): assert result["empty"]["errors"] +def test_progress_cost_print_preserves_selected_round_costs(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.options.SourceRefresh=false; + p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100},{LinkIndex:54}]; + p.tables.Wonga=[{Gubun:'D',Name:'direct',BaseField:'GC'},{Gubun:'T',Name:'total',LinkIndex:55,VSusic:'D'}]; + p.progress=[{id:'one',inputs:{'1':{Gum:2,Sa:1}},approved:true}]; + const q=EstProgress.lock(EstNative.calculate(p),'one'); + q.tables.EstMainDat[0].GU=200;q.progress.push({id:'two',inputs:{'1':{Gum:3}}}); + return {one:EstForms.data('progress-costs',q,{roundId:'one'}),two:EstForms.data('progress-costs',q,{roundId:'two'})}; + })()""", + ) + assert result["one"]["rows"][-1]["ContractCost"] == 1000 + assert result["one"]["rows"][-1]["GumCost"] == 200 + assert result["one"]["rows"][-1]["SaCost"] == 100 + assert result["two"]["rows"][-1]["JunCost"] == 100 + assert result["two"]["rows"][-1]["GumCost"] == 600 + assert result["two"]["rows"][-1]["TotalCost"] == 700 + + +def test_escalation_cost_print_uses_computed_cost_breakdowns(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.options.SourceRefresh=false;p.options.EscDesignTotal=1000; + p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:80},{LinkIndex:54}]; + p.tables.Wonga=[{Gubun:'D',Name:'direct',BaseField:'GC'},{Gubun:'T',Name:'total',LinkIndex:55,VSusic:'D'}]; + const q=EstBusiness.baseline(EstNative.calculate(p)); + Object.assign(q.tables.EstMainDat[0],{AGU:100,GU:120,EscActual:3}); + return EstForms.data('escalation-costs',q); + })()""", + ) + assert result["errors"] == [] + assert result["rows"][-1]["ContractCost"] == 800 + assert result["rows"][-1]["adjustedCost"] == 960 + assert result["rows"][-1]["excludedCost"] == 240 + + def test_resource_report_separates_each_work_item_without_changing_aggregate(): result = run( "math", diff --git a/tools/est-test/README.md b/tools/est-test/README.md index db8cc911c..82693f6da 100644 --- a/tools/est-test/README.md +++ b/tools/est-test/README.md @@ -6,7 +6,7 @@ 인쇄 미리보기는 화면의 100행 페이지 제한 없이 검색 결과/현재 호표의 전체 행을 표시합니다. 출력 범위에서 현재 자료의 모든 호표·행도 선택할 수 있습니다. 원본 인쇄 옵션의 41개 항목 중 CAD를 제외한 40개 서식 이름을 선택하며, ESTX에서 가져온 해당 프로젝트의 인쇄 설정을 우선 사용합니다. 내역·원가·호표/총괄·증감·동원·수량·공정은 표 자료로 출력합니다. 표지·갑지·설명서·시방서·심사 문서는 서식별 본문을 직접 입력합니다. 용지·방향·여백·글자 크기·페이지당 행·시작 쪽·업체명을 저장하고 인쇄/PDF로 출력합니다. ESTX 수정 저장은 서식별 여백과 페이지당 행 수를 반영합니다. 다른 웹 인쇄 설정의 ESTX 쓰기와 원본 인쇄 도형·병합·글꼴 배치의 동등성은 지원하지 않습니다. -별도 인쇄 서식으로 기성 내역서, 물가변동 내역서, 연차 배분표를 선택할 수 있습니다. 기성은 선택한 회차를 출력하고 확정된 회차는 확정 당시 결과를 사용합니다. +별도 인쇄 서식으로 기성·물가변동의 내역서와 원가계산서, 연차 배분표를 선택할 수 있습니다. 기성은 선택한 회차를 출력하고 확정된 회차는 확정 당시 결과를 사용합니다. 저장은 이 폴더의 `data/`에 작업을 보관하고 `.estweb.json` 파일도 내려받습니다. 브라우저 임시 저장은 같은 탭에서 복원하는 용도입니다. JSON은 모든 웹 업무와 원본 압축 파일·바이너리를 보관하는 전체 저장 형식입니다. ESTX 수정 저장은 아래 지원 조건을 따릅니다. diff --git a/tools/est-test/native-forms.js b/tools/est-test/native-forms.js index 8bebdeb26..170392641 100644 --- a/tools/est-test/native-forms.js +++ b/tools/est-test/native-forms.js @@ -14,12 +14,19 @@ const EstForms = (() => { errors: output.errors, }; } - if (id === "progress-summary") { + if (["progress-summary", "progress-costs"].includes(id)) { const round = project.progress.find((item) => item.id === state.roundId) || project.progress.at(-1); if (!round) return { columns: ["Name"], rows: [], errors: ["기성 회차가 없습니다."] }; const result = round.locked && round.result ? round.result : EstProgress.calculate(project, round.id); + if (id === "progress-costs") + return { + columns: ["Gubun", "Name", "ContractCost", "JunCost", "GumCost", "SaCost", "TotalCost"], + rows: result.costs || [], + errors: result.errors, + detail: `${round.name || "기성"} (${round.locked ? "확정" : "작성 중"})`, + }; return { columns: [ "Code", @@ -41,8 +48,20 @@ const EstForms = (() => { detail: `${round.name || "기성"} (${round.locked ? "확정" : "작성 중"})`, }; } - if (id === "escalation-summary") { + if (["escalation-summary", "escalation-costs"].includes(id)) { const result = EstEscalation.calculate(project); + if (id === "escalation-costs") + return { + columns: ["Gubun", "Name", "ContractCost", "adjustedCost", "excludedCost"], + rows: result.wonga.map((row, index) => ({ + ...row, + ContractCost: result.contractWonga[index]?.Cost, + adjustedCost: row.Cost, + excludedCost: result.excludedWonga[index]?.Cost, + })), + errors: result.errors, + detail: `도급액 ${result.contractTotal} · 조정액 ${result.adjustment} · 조정률 ${result.rate}%`, + }; return { columns: [ "Code", @@ -256,6 +275,13 @@ const EstForms = (() => { ESC: "산출경비 증감", directCost: "순공사비", contractCost: "도급액", + ContractCost: "도급금액", + JunCost: "전회 금액", + GumCost: "금회 금액", + SaCost: "사정 금액", + TotalCost: "누계 금액", + adjustedCost: "조정 금액", + excludedCost: "제외 금액", TargetCode: "연결 내역", TU: "단가", TC: "금액", diff --git a/tools/est-test/native-report.js b/tools/est-test/native-report.js index 6de74e170..daf9b72d6 100644 --- a/tools/est-test/native-report.js +++ b/tools/est-test/native-report.js @@ -85,7 +85,12 @@ const EstReport = (() => { ? EstNative.groups(state.project.tables[state.view] || []).get(state.group)?.rows[0] ?.Name : ""); - const totals = ["progress-summary", "escalation-summary"].includes(form?.Index) + const totals = [ + "progress-summary", + "progress-costs", + "escalation-summary", + "escalation-costs", + ].includes(form?.Index) ? null : state.project.calculation; const errors = [...(totals?.errors || []), ...(output?.errors || [])]; @@ -132,7 +137,9 @@ const EstReport = (() => { })); catalog.push( { Index: "progress-summary", Name: "기성 내역서", Name2: "기성 내역서" }, + { Index: "progress-costs", Name: "기성 원가계산서", Name2: "기성 원가계산서" }, { Index: "escalation-summary", Name: "물가변동 내역서", Name2: "물가변동 내역서" }, + { Index: "escalation-costs", Name: "물가변동 원가계산서", Name2: "물가변동 원가계산서" }, { Index: "annual-summary", Name: "연차 배분표", Name2: "연차 배분표" }, ); const table = EstApp.state.project.tables[EstApp.state.view];