feat(est): show standard price design changes

This commit is contained in:
2026-11-07 11:35:02 +09:00
parent f915e0c33b
commit 600d7b0ab1
11 changed files with 70 additions and 6 deletions
+25
View File
@@ -1,6 +1,31 @@
from test_est_rea_parity import run
def test_standard_changes_include_new_deleted_and_changed_link_types():
result = run(
"math",
"""(()=>{
const p=EstNative.empty();p.tables.EstMainDat=[
{PkID:1,Code:'A',Name:'changed',LinkIndex:21,GN:2,TC:200},
{PkID:2,Code:'B',Name:'changed link',LinkIndex:5,GN:1,TC:50},
{PkID:4,Code:'D',Name:'new',LinkIndex:21,GN:1,TC:40}];
p.baseline={tables:{EstMainDat:[
{PkID:1,Code:'A',Name:'changed',LinkIndex:21,GN:1,TC:100},
{PkID:2,Code:'B',Name:'changed link',LinkIndex:21,GN:1,TC:50},
{PkID:3,Code:'C',Name:'deleted',LinkIndex:21,GN:3,TC:300},
{PkID:5,Code:'E',Name:'other',LinkIndex:5,GN:1,TC:10}]}};
return {rows:EstStandard.changes(p),print:EstForms.data('standard-change',p)};
})()""",
)
rows = {row["Code"]: row for row in result["rows"]}
assert set(rows) == {"A", "B", "C", "D"}
assert rows["A"]["difference"] == 100
assert rows["B"]["status"] == "변경"
assert rows["C"]["status"] == "삭제" and rows["C"]["index"] is None
assert rows["D"]["status"] == "신규"
assert result["print"]["rows"] == result["rows"]
def test_standard_usage_reports_applied_and_source_prices_without_mutation():
result = run(
"math",
+3
View File
@@ -1,4 +1,7 @@
## Codex · EST 독립 테스트 앱 (2026-11-04)
- [x] 표준시장단가 설계변경 내역·증감 화면·출력 구현 → 신규·삭제·연결 변경 시험.
- 자체 검증: 표준 내역의 신규·삭제·연결 변경과 문서 기능 시험 21개 통과. Edge에서 증감·삭제 행·인쇄·내역 이동 확인.
- [x] 표준시장단가 사용 내역·기초 단가 비교 화면·출력 구현 → 연결·중복·누락·행 이동 시험.
- 자체 검증: 연결 단가·중복·누락·문서 시험 12개 통과, Edge에서 화면·인쇄·해당 내역 이동 확인.
+2
View File
@@ -8,6 +8,8 @@
작업모드의 `표준시장단가 사용 내역`은 표준시장단가에 연결된 내역별 적용 단가와 현재 기초 단가, 차이와 연결 상태를 보여 줍니다. 원래 내역으로 이동하거나 해당 표를 인쇄·Excel로 출력할 수 있습니다.
`표준시장단가 설계변경`에서는 기준 내역과 비교한 신규·삭제·변경 및 금액 증감을 확인하고 인쇄·Excel로 출력할 수 있습니다.
행을 선택한 뒤 편집 메뉴의 `선택 행 자릿수 설정`에서 처리방식과 소수 자릿수를 일괄 적용할 수 있습니다. 기존 계산 설정은 실행 취소로 되돌릴 수 있습니다.
내역서 편집 메뉴의 `내역 수량 초기화`는 선택한 상세 행 또는 모든 상세 행의 수량을 0으로 바꿉니다. 비율·합계 자동행은 건드리지 않으며 실행 취소할 수 있습니다.
+1
View File
@@ -116,6 +116,7 @@
<div class="menu">
<button data-view="EstMainDat">내역서</button
><button data-view="standard">표준시장단가 사용 내역</button
><button data-view="standard-change">표준시장단가 설계변경</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
@@ -37,6 +37,7 @@ const EstApp = (() => {
issues: "자료 오류 검색",
attachments: "첨부파일",
standard: "표준시장단가 사용 내역",
"standard-change": "표준시장단가 설계변경",
drawing: "도면·문서",
};
const esc = (value) =>
+16 -3
View File
@@ -123,6 +123,7 @@ const EstBusiness = (() => {
: null;
const original = saved || stored;
return {
index,
key: rowKey(row, index),
Code: row.Code || "",
Name: row.Name || "",
@@ -130,13 +131,23 @@ const EstBusiness = (() => {
originalGN: num(original?.GN),
TC: num(row.TC),
originalTC: num(original?.TC),
LinkIndex: row.LinkIndex,
originalLinkIndex: original?.LinkIndex ?? row.LinkIndex,
difference: num(row.TC) - num(original?.TC),
NewItem: !original,
status: !original
? "new"
: ["Code", "Name", "Size", "Unite", "GN", "TC", ...units, ...components].some(
(key) => original[key] != null && original[key] !== row[key],
)
: [
"Code",
"Name",
"Size",
"Unite",
"GN",
"TC",
"LinkIndex",
...units,
...components,
].some((key) => original[key] != null && original[key] !== row[key])
? "changed"
: "unchanged",
originalSource: saved ? "baseline" : stored ? "file" : null,
@@ -145,11 +156,13 @@ const EstBusiness = (() => {
for (const group of groups.values())
for (const { row, index } of group) {
changes.push({
index: null,
key: rowKey(row, index),
Code: row.Code || "",
Name: row.Name || "",
originalGN: num(row.GN),
originalTC: num(row.TC),
originalLinkIndex: row.LinkIndex,
GN: 0,
TC: 0,
difference: -num(row.TC),
+1 -1
View File
@@ -28,7 +28,7 @@ const EstExtra = (() => {
EstAttachments.render();
return;
}
if (state.view === "standard") {
if (["standard", "standard-change"].includes(state.view)) {
EstStandard.render();
return;
}
+5
View File
@@ -1,6 +1,11 @@
const EstForms = (() => {
const common = ["Code", "Name", "Size", "Unite"];
function data(id, project, state = {}) {
if (id === "standard-change")
return {
columns: ["status", "Code", "Name", "originalGN", "GN", "originalTC", "TC", "difference"],
rows: EstStandard.changes(project),
};
if (id === "standard-usage")
return {
columns: [
+1
View File
@@ -353,6 +353,7 @@ const EstModel = (() => {
"annual-items",
"change-costs",
"standard-usage",
"standard-change",
]);
for (const [key, value] of Object.entries(project.documents))
if (
+1
View File
@@ -144,6 +144,7 @@ const EstReport = (() => {
{ Index: "annual-items", Name: "연차별 내역서", Name2: "연차별 내역서" },
{ Index: "change-costs", Name: "설계변경 원가 증감표", Name2: "설계변경 원가 증감표" },
{ Index: "standard-usage", Name: "표준시장단가 사용 내역", Name2: "표준시장단가 사용 내역" },
{ Index: "standard-change", Name: "표준시장단가 설계변경", Name2: "표준시장단가 설계변경" },
);
const table = EstApp.state.project.tables[EstApp.state.view];
const s = setting(),
+14 -2
View File
@@ -34,12 +34,24 @@ const EstStandard = (() => {
];
});
}
function changes(project) {
const labels = { new: "신규", deleted: "삭제", changed: "변경", unchanged: "동일" };
return EstBusiness.changes(project)
.filter((row) => Number(row.LinkIndex) === 21 || Number(row.originalLinkIndex) === 21)
.map((row) => ({ ...row, status: labels[row.status] || row.status }));
}
function render() {
const { state, esc, number, byId } = EstApp,
rows = usage(state.project);
isChange = state.view === "standard-change",
rows = isChange ? changes(state.project) : 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));
if (isChange) {
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><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>${visible.map((row) => `<tr><td>${esc(row.status)}</td><td>${esc(row.Code)}</td><td>${esc(row.Name)}</td><td>${value(row.originalGN)}</td><td>${value(row.GN)}</td><td>${value(row.originalTC)}</td><td>${value(row.TC)}</td><td>${value(row.difference)}</td><td>${row.index == null ? "" : `<button data-standard-row="${row.index}">내역 열기</button>`}</td></tr>`).join("")}</tbody></table>`;
return;
}
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>`;
}
@@ -61,5 +73,5 @@ const EstStandard = (() => {
EstApp.render();
});
}
return { usage, render, install };
return { usage, changes, render, install };
})();