feat(est): inspect standard price usage

This commit is contained in:
2026-11-07 11:31:12 +09:00
parent ba95a6d606
commit f915e0c33b
14 changed files with 130 additions and 2 deletions
+1
View File
@@ -20,6 +20,7 @@ for (const file of [
"native-schedule.js",
"native-issues.js",
"native-attachments.js",
"native-standard.js",
"native-model.js",
"native-c3r.js",
"native-exchange.js",
+6 -2
View File
@@ -5,13 +5,17 @@ def test_custom_print_form_documents_are_saved_and_unknown_keys_rejected():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.documents={'annual-items':'stage note','progress-costs':'progress note'};
const p=EstNative.empty();p.documents={'annual-items':'stage note','progress-costs':'progress note','standard-usage':'standard note'};
const valid=EstModel.validate(p).documents;let rejected=false;
const q=EstNative.empty();q.documents={'invalid-key':'bad'};try{EstModel.validate(q)}catch{rejected=true}
return {valid,rejected};
})()""",
)
assert result["valid"] == {"annual-items": "stage note", "progress-costs": "progress note"}
assert result["valid"] == {
"annual-items": "stage note",
"progress-costs": "progress note",
"standard-usage": "standard note",
}
assert result["rejected"]
+24
View File
@@ -0,0 +1,24 @@
from test_est_rea_parity import run
def test_standard_usage_reports_applied_and_source_prices_without_mutation():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.SilJukDat=[{Code:'A',Name:'source',ValueIndex:4,InputTC:120},
{Code:'B',ValueIndex:4,InputTC:50},{Code:'B',ValueIndex:4,InputTC:60}];
p.tables.EstMainDat=[{Code:'A',Name:'work',LinkIndex:21,GN:2,TU:100,TC:200},
{Code:'B',Name:'duplicate',LinkIndex:21,GN:1,TU:45,TC:45},
{Code:'C',Name:'missing',LinkIndex:21,GN:1,TU:20,TC:20},
{Code:'A',Name:'material',LinkIndex:5,GN:1,TU:10}];
const before=JSON.stringify(p), rows=EstStandard.usage(p), print=EstForms.data('standard-usage',p);
return {rows,print,same:before===JSON.stringify(p)};
})()""",
)
assert result["same"]
assert [row["status"] for row in result["rows"]] == ["연결됨", "코드 중복", "연결 없음"]
assert result["rows"][0]["sourcePrice"] == 120
assert result["rows"][0]["appliedPrice"] == 100
assert result["rows"][0]["difference"] == -20
assert result["rows"][1]["sourcePrice"] is None
assert result["print"]["rows"] == result["rows"]
+3
View File
@@ -1,4 +1,7 @@
## Codex · EST 독립 테스트 앱 (2026-11-04)
- [x] 표준시장단가 사용 내역·기초 단가 비교 화면·출력 구현 → 연결·중복·누락·행 이동 시험.
- 자체 검증: 연결 단가·중복·누락·문서 시험 12개 통과, Edge에서 화면·인쇄·해당 내역 이동 확인.
- [x] 추가 인쇄 서식의 본문 저장·미리보기·Excel 연결 보완 → 서식 문서 검증·재열기 시험.
- 자체 검증: 문서·Excel 시험 24개 통과, Edge에서 본문 저장·재열기·인쇄·Excel 요약 확인.
+2
View File
@@ -6,6 +6,8 @@
작업모드의 `자료 오류 검색`은 코드 중복, 연결 자료 누락, 연결된 품명·규격 차이와 계산 오류를 표시합니다. 항목의 `행 열기`로 해당 자료 위치로 이동할 수 있습니다. 품명·규격 차이는 사용자가 확인할 항목으로 표시합니다.
작업모드의 `표준시장단가 사용 내역`은 표준시장단가에 연결된 내역별 적용 단가와 현재 기초 단가, 차이와 연결 상태를 보여 줍니다. 원래 내역으로 이동하거나 해당 표를 인쇄·Excel로 출력할 수 있습니다.
행을 선택한 뒤 편집 메뉴의 `선택 행 자릿수 설정`에서 처리방식과 소수 자릿수를 일괄 적용할 수 있습니다. 기존 계산 설정은 실행 취소로 되돌릴 수 있습니다.
내역서 편집 메뉴의 `내역 수량 초기화`는 선택한 상세 행 또는 모든 상세 행의 수량을 0으로 바꿉니다. 비율·합계 자동행은 건드리지 않으며 실행 취소할 수 있습니다.
+2
View File
@@ -21,6 +21,7 @@
<script defer src="native-schedule.js"></script>
<script defer src="native-issues.js"></script>
<script defer src="native-attachments.js"></script>
<script defer src="native-standard.js"></script>
<script defer src="native-model.js"></script>
<script defer src="native-views.js"></script>
<script defer src="native-extra.js"></script>
@@ -114,6 +115,7 @@
<summary>작업모드(M)</summary>
<div class="menu">
<button data-view="EstMainDat">내역서</button
><button data-view="standard">표준시장단가 사용 내역</button
><button data-view="change">설계변경</button><button data-view="progress">기성고</button
><button data-view="escalation">물가변동</button
><button data-view="annual">연차공사</button><button data-view="review">계약심사</button
+1
View File
@@ -677,6 +677,7 @@ const EstActions = (() => {
}
});
EstAttachments.install();
EstStandard.install();
}
return { install, form };
})();
+1
View File
@@ -36,6 +36,7 @@ const EstApp = (() => {
schedule: "예정공정표",
issues: "자료 오류 검색",
attachments: "첨부파일",
standard: "표준시장단가 사용 내역",
drawing: "도면·문서",
};
const esc = (value) =>
+4
View File
@@ -28,6 +28,10 @@ const EstExtra = (() => {
EstAttachments.render();
return;
}
if (state.view === "standard") {
EstStandard.render();
return;
}
const { project, view } = state,
{ esc, number, byId } = EstApp;
let html = "";
+18
View File
@@ -1,6 +1,22 @@
const EstForms = (() => {
const common = ["Code", "Name", "Size", "Unite"];
function data(id, project, state = {}) {
if (id === "standard-usage")
return {
columns: [
"Code",
"Name",
"Size",
"Unite",
"GN",
"appliedPrice",
"sourcePrice",
"difference",
"TC",
"status",
],
rows: EstStandard.usage(project),
};
if (id === "change-costs")
return {
columns: ["Gubun", "Name", "originalCost", "changedCost", "difference"],
@@ -301,6 +317,8 @@ const EstForms = (() => {
originalCost: "당초 금액",
changedCost: "변경 금액",
stage: "차수",
appliedPrice: "내역 단가",
sourcePrice: "기초 단가",
TargetCode: "연결 내역",
TU: "단가",
TC: "금액",
+1
View File
@@ -352,6 +352,7 @@ const EstModel = (() => {
"annual-summary",
"annual-items",
"change-costs",
"standard-usage",
]);
for (const [key, value] of Object.entries(project.documents))
if (
+1
View File
@@ -143,6 +143,7 @@ const EstReport = (() => {
{ Index: "annual-summary", Name: "연차 배분표", Name2: "연차 배분표" },
{ Index: "annual-items", Name: "연차별 내역서", Name2: "연차별 내역서" },
{ Index: "change-costs", Name: "설계변경 원가 증감표", Name2: "설계변경 원가 증감표" },
{ Index: "standard-usage", Name: "표준시장단가 사용 내역", Name2: "표준시장단가 사용 내역" },
);
const table = EstApp.state.project.tables[EstApp.state.view];
const s = setting(),
+65
View File
@@ -0,0 +1,65 @@
const EstStandard = (() => {
let page = 0;
function usage(project) {
const sources = new Map();
for (const row of project.tables.SilJukDat || []) {
if (!row.Code) continue;
const rows = sources.get(row.Code) || [];
rows.push(row);
sources.set(row.Code, rows);
}
return (project.tables.EstMainDat || []).flatMap((row, index) => {
if (Number(row.Level) > 0 || Number(row.LinkIndex) !== 21) return [];
const matches = sources.get(row.Code) || [];
const source = matches.length === 1 ? EstNative.price(structuredClone(matches[0]), 21) : null;
const appliedPrice =
row.TU == null
? EstMath.units.reduce((sum, field) => sum + EstMath.num(row[field]), 0)
: EstMath.num(row.TU);
const sourcePrice = source ? EstMath.num(source.TC) : null;
return [
{
index,
Code: row.Code || "",
Name: row.Name || "",
Size: row.Size || "",
Unite: row.Unite || "",
GN: EstMath.num(row.GN),
appliedPrice,
sourcePrice,
difference: source ? appliedPrice - sourcePrice : null,
TC: EstMath.num(row.TC),
status: !matches.length ? "연결 없음" : matches.length > 1 ? "코드 중복" : "연결됨",
},
];
});
}
function render() {
const { state, esc, number, byId } = EstApp,
rows = usage(state.project);
page = Math.max(0, Math.min(page, Math.ceil(rows.length / 100) - 1));
const visible = rows.slice(page * 100, page * 100 + 100),
value = (v) => (v == null ? "" : number(v));
byId("work-content").innerHTML =
`<div class="business-tools"><b>표준시장단가 사용 내역</b><span>${rows.length}건 · ${page + 1}/${Math.max(1, Math.ceil(rows.length / 100))}쪽</span><button data-standard-page="-1" ${page === 0 ? "disabled" : ""}>이전</button><button data-standard-page="1" ${(page + 1) * 100 >= rows.length ? "disabled" : ""}>다음</button></div><p>내역에 적용된 단가와 현재 표준시장단가 자료를 비교합니다.</p><table class="sheet"><thead><tr><th>코드</th><th>품명</th><th>규격</th><th>단위</th><th>수량</th><th>내역 단가</th><th>기초 단가</th><th>차이</th><th>금액</th><th>연결</th><th>작업</th></tr></thead><tbody>${visible.map((row) => `<tr><td>${esc(row.Code)}</td><td>${esc(row.Name)}</td><td>${esc(row.Size)}</td><td>${esc(row.Unite)}</td><td>${value(row.GN)}</td><td>${value(row.appliedPrice)}</td><td>${value(row.sourcePrice)}</td><td>${value(row.difference)}</td><td>${value(row.TC)}</td><td>${esc(row.status)}</td><td><button data-standard-row="${row.index}">내역 열기</button></td></tr>`).join("")}</tbody></table>`;
}
function install() {
document.addEventListener("click", (event) => {
const step = event.target.closest("[data-standard-page]");
if (step) {
page += Number(step.dataset.standardPage);
render();
return;
}
const node = event.target.closest("[data-standard-row]");
if (!node) return;
const index = Number(node.dataset.standardRow);
if (!EstApp.state.project.tables.EstMainDat[index]) return;
EstApp.navigate("EstMainDat");
EstApp.state.page = Math.floor(index / 100);
EstApp.state.selection = new Set([index]);
EstApp.render();
});
}
return { usage, render, install };
})();
+1
View File
@@ -55,6 +55,7 @@ const allowed = new Set([
"native-business-actions.js",
"native-issues.js",
"native-attachments.js",
"native-standard.js",
"native-extra.js",
"native-settings.js",
"native-report.js",