feat(est-test): 동원 확인 탭(장비·단산·일위 대조)·내역서 줄별 펼침
- 호표 1 단위 펼친 항목 합과 호표 금액 대조, 10원 넘게 어긋나면 표시 - 당초값·계산값(노무·재료·경비·실적)과 항목 목록 - 동원 계산에 호표 단위 펼치기(roots) 추가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ for (const file of [
|
||||
"native-annual-copy.js",
|
||||
"native-print-range.js",
|
||||
"native-work-mode.js",
|
||||
"native-dongwon-check.js",
|
||||
"native-business-actions.js",
|
||||
"native-forms.js",
|
||||
"native-excel-to-est.js",
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""동원 확인 탭: 호표 1 단위 펼침 합과 호표 금액 대조(10원 넘으면 표시)."""
|
||||
|
||||
from test_est_rea_parity import run
|
||||
|
||||
|
||||
def test_head_check_flags_mismatch_over_ten_won():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.LaborcDat=[{Code:'L1',Name:'보통인부',TC:100,NC:100}];
|
||||
p.tables.StuffcDat=[{Code:'M1',Name:'자갈',TC:50,GC:50}];
|
||||
p.tables.HeadMainDat=[{Code:'H1',Index:1,Name:'A'},{Code:'L1',LinkIndex:6,GN:2,NU:100,NC:200},
|
||||
{Code:'M1',LinkIndex:5,GN:1,GU:50,GC:50}];
|
||||
const q=EstNative.calculate(p);
|
||||
const a=EstDongwonCheck.check(q,103).rows[0];
|
||||
q.tables.HeadTitleDat[0].TC=a.TC+11;
|
||||
const b=EstDongwonCheck.check(q,103).rows[0];
|
||||
return {tc:a.TC,sum:Math.trunc(a.sum),calc:[a.calc.NC,a.calc.GC],a:a.checked,b:b.checked,
|
||||
items:a.items.map(r=>r.Code).sort()};
|
||||
})()""",
|
||||
)
|
||||
assert result["tc"] == 250 and result["sum"] == 250
|
||||
assert result["calc"] == [200, 50]
|
||||
assert result["a"] is False and result["b"] is True
|
||||
assert result["items"] == ["L1", "M1"]
|
||||
@@ -506,6 +506,8 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD
|
||||
- [x] 작업모드 표준내역서·표준설계변경·최근 계약심사목록(spec_pc §4) — 작업모드 메뉴 내역서·설계변경·표준내역서·표준설계변경이 모드 값(CurrentWorkMode, ESTX 읽기·쓰기)을 바꿈. 일반↔표준 묶음이 바뀌면 지금 원가계산서를 옛 묶음 칸에 두고 새 묶음 것을 꺼냄(없으면 설치 양식목록 — 일반 WongaFileList·표준 SWongaFileList 각 30종 — 에서 고름, 번호 기준 계산). 원본은 모드를 바꿀 때 문서를 닫고 최근 목록을 띄우나 웹은 지금 작업 유지. 최근 계약심사목록 = 저장한 작업 중 계약심사 입력이 있는 것.
|
||||
- 미결: 표준 양식의 산업안전보건관리비 식 「A × 0.496」 의 A 가 무엇인지 미확인 — 웹은 「참조 항목 없음: A」 로 알림(원본 확인 필요).
|
||||
- 검증완료: 작업모드 시험 2개·ESTX 저장 시험 34개 통과. ORCA 브라우저에서 표준내역서로 바꿈 → 표준 양식 30종 → 열기, 내역서로 돌아오면 원래 원가계산서 31행 복원, 계약심사목록 창 확인, 원복.
|
||||
- [x] 동원 확인·내역서 탭(TfrmDongWon Button1~4 · spec_part §4) — 동원 화면 단추 확인 장비·단산·일위: 호표마다 1 단위를 펼친 항목 금액 합과 호표 금액을 대조해 10원 넘게 어긋나면 표시(어긋난 것만 보기), 행을 고르면 당초값·계산값(노무·재료·경비·실적)·항목 목록. 내역서: 내역 줄마다 펼친 항목. 웹 동원 계산은 기계경비를 더 쪼개지 않아 장비 확인은 늘 맞음.
|
||||
- 검증완료: 확인 시험 1개·동원 관련 시험 35개 통과. ORCA 브라우저에서 일위 8건 모두 일치·당초값=계산값, 항목 3개 표시.
|
||||
- [x] 다른 공사 불러오기 창(원본 barbtnSelfLoad → TfrmOtherLoad, 찾기 popSerch) — 저장된 다른 작업을 골라 같은 표를 띄우고 찾기(품명·규격, A&B 둘 다)·고른 줄을 하위 자료와 함께 가져오기(내역서는 고른 줄 아래에 줄, 나머지는 자료·호표 합침, 같은 코드는 이 공사 것). 검증완료: 바인딩 통과, ORCA 브라우저에서 산불진화임도 작업의 일위대가 3개 → 「취수장」 1개 → 가져오기 호표 8→9·하위 41건, 샘플 복구.
|
||||
- [x] 사용위치 검색 창(원본 TfrmUsesDataSerch btnSerch·btnAllDisplay) — 현재 표 항목을 품명·규격(빈칸·|·괄호 무시)으로 거르고 골라 사용위치 보기. 검증완료: 바인딩 통과, ORCA 브라우저에서 노임 9개 → 「보 통」 1개 → 보통인부 사용위치 11곳.
|
||||
- [x] 인쇄양식 불러오기·저장하기(원본 mnuPrintFormatLoad·Save 0x10ed318·0x10ed000 — spec_pc §3) — .pfot(인쇄정보표 MIDAS 이진) 읽기·쓰기 `print-format.cjs`(설명 BLOB·빈 칸 표지까지 보존, 원본 파일 그대로 다시 쓰면 바이트 같음). 불러오면 공사 인쇄표를 그 값으로, 저장은 지금 인쇄표를 원본 설치 main.pfot 필드 설명자 위에(`EST_PFOT_TEMPLATE`). .gfot(cxGrid 열 배치)는 웹 격자와 호환되지 않아 하지 않음.
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<script defer src="native-annual-copy.js"></script>
|
||||
<script defer src="native-print-range.js"></script>
|
||||
<script defer src="native-work-mode.js"></script>
|
||||
<script defer src="native-dongwon-check.js"></script>
|
||||
<script defer src="native-actions-more.js"></script>
|
||||
<script defer src="native-wonga.js"></script>
|
||||
<script defer src="native-jungsang.js"></script>
|
||||
|
||||
@@ -12,6 +12,7 @@ const EstActionsMore = (() => {
|
||||
typeof EstAnnualCopy !== "undefined" && EstAnnualCopy,
|
||||
typeof EstPrintRange !== "undefined" && EstPrintRange,
|
||||
typeof EstWorkMode !== "undefined" && EstWorkMode,
|
||||
typeof EstDongwonCheck !== "undefined" && EstDongwonCheck,
|
||||
].filter(Boolean);
|
||||
function handle(name, state, node) {
|
||||
for (const module of modules()) if (module.handle(name, state, node)) return true;
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// 원본 동원인원·자재·장비 「확인」·「내역서」 탭(TfrmDongWon Button1~4 — spec_part §4): 동원 계산이 펼친 중간 결과 보기.
|
||||
// 장비·단산·일위 = 호표마다 1 단위를 동원 항목으로 펼쳐 금액 합을 내고, 호표 금액(TC)과 10원 넘게 어긋나면 표시(1095cdc
|
||||
// Checked := |TC − Trunc(Σ Cost)| > 10). 행을 고르면 당초값(호표 TC·NC·GC·JC)과 계산값(항목 금액을 노무·재료·경비·실적으로
|
||||
// 나눠 더한 값)·항목 목록. 내역서 = 내역 줄마다 펼친 항목(점검 없음). 웹 동원 계산은 기계경비를 더 쪼개지 않아 장비는 늘 맞음.
|
||||
const EstDongwonCheck = (() => {
|
||||
const num = (v) => Number(v) || 0;
|
||||
const TITLE = { 103: "HeadTitleDat", 104: "CostTitleDat", 105: "MachTitleDat" };
|
||||
const KIND = { 5: "자재", 6: "노임", 21: "표준", 101: "견적", 105: "기계" };
|
||||
// 항목 → 비목: 노임·노무비 금액분 = 노무, 자재·견적·재료비 금액분 = 재료, 기계·경비 = 경비, 표준 = 실적
|
||||
const part = (item) =>
|
||||
item.Code === "LZZZZZZZZZZ" || item.kind === 6
|
||||
? "NC"
|
||||
: item.Code === "EZZZZZZZZZZ" || item.kind === 105
|
||||
? "JC"
|
||||
: item.Code === "SZZZZZZZZZZ" || item.kind === 21
|
||||
? "SC"
|
||||
: "GC";
|
||||
|
||||
function check(project, kind) {
|
||||
const titles = (project.tables[TITLE[kind]] || []).filter((t) => t.Code);
|
||||
const out = EstResources.calculate(
|
||||
project,
|
||||
true,
|
||||
titles.map((t) => ({ kind, code: t.Code, WorkCode: t.Code, WorkName: t.Name })),
|
||||
);
|
||||
const items = new Map();
|
||||
for (const row of out.rows)
|
||||
(items.get(row.WorkCode) || items.set(row.WorkCode, []).get(row.WorkCode)).push(row);
|
||||
return {
|
||||
errors: out.errors,
|
||||
rows: titles.map((t) => {
|
||||
const list = items.get(t.Code) || [],
|
||||
sum = list.reduce((s, r) => s + num(r.amount), 0),
|
||||
calc = { NC: 0, GC: 0, JC: 0, SC: 0 };
|
||||
for (const r of list) calc[part(r)] += num(r.amount);
|
||||
return {
|
||||
Code: t.Code,
|
||||
Name: t.Name,
|
||||
Size: t.Size,
|
||||
TC: num(t.TC),
|
||||
NC: num(t.NC),
|
||||
GC: num(t.GC),
|
||||
JC: num(t.JC),
|
||||
sum,
|
||||
calc,
|
||||
checked: Math.abs(num(t.TC) - Math.trunc(sum)) > 10,
|
||||
items: list,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
function estimate(project) {
|
||||
const out = EstResources.calculate(project, true),
|
||||
groups = new Map();
|
||||
for (const row of out.rows) {
|
||||
const key = `${row.WorkCode}\u0000${row.WorkName}`;
|
||||
if (!groups.has(key)) groups.set(key, { Code: row.WorkCode, Name: row.WorkName, items: [] });
|
||||
groups.get(key).items.push(row);
|
||||
}
|
||||
return { errors: out.errors, rows: [...groups.values()] };
|
||||
}
|
||||
|
||||
let shown = null;
|
||||
function draw(state) {
|
||||
const { esc, number } = EstApp,
|
||||
{ kind, data, pick, only } = shown;
|
||||
const list = data.rows.map((r, i) => [r, i]).filter(([r]) => !only || r.checked);
|
||||
const chosen = data.rows[pick];
|
||||
const head =
|
||||
kind === "est"
|
||||
? "<tr><th>코드</th><th>공종</th><th>항목 수</th></tr>"
|
||||
: "<tr><th>코드</th><th>품명</th><th>규격</th><th>호표 금액</th><th>항목 합</th><th>어긋남</th></tr>";
|
||||
const body = list
|
||||
.map(([r, i]) =>
|
||||
kind === "est"
|
||||
? `<tr data-dw-pick="${i}" style="cursor:pointer${i === pick ? ";background:#dde8ff" : ""}"><td>${esc(r.Code)}</td><td style="text-align:left">${esc(r.Name)}</td><td>${r.items.length}</td></tr>`
|
||||
: `<tr data-dw-pick="${i}" style="cursor:pointer${i === pick ? ";background:#dde8ff" : ""}${r.checked ? ";color:#b00" : ""}"><td>${esc(r.Code)}</td><td style="text-align:left">${esc(r.Name)}</td><td>${esc(r.Size ?? "")}</td><td>${number(r.TC)}</td><td>${number(Math.trunc(r.sum))}</td><td>${r.checked ? "✔" : ""}</td></tr>`,
|
||||
)
|
||||
.join("");
|
||||
const detail = chosen
|
||||
? (kind === "est"
|
||||
? ""
|
||||
: `<p>당초값 계 ${number(chosen.TC)} · 노무 ${number(chosen.NC)} · 재료 ${number(chosen.GC)} · 경비 ${number(chosen.JC)}<br>계산값 계 ${number(Math.trunc(chosen.sum))} · 노무 ${number(Math.trunc(chosen.calc.NC))} · 재료 ${number(Math.trunc(chosen.calc.GC))} · 경비 ${number(Math.trunc(chosen.calc.JC))}${chosen.calc.SC ? ` · 실적 ${number(Math.trunc(chosen.calc.SC))}` : ""}</p>`) +
|
||||
`<table class="sheet"><thead><tr><th>종류</th><th>코드</th><th>품명</th><th>규격</th><th>소요량</th><th>금액</th></tr></thead><tbody>${chosen.items.map((r) => `<tr><td>${esc(KIND[r.kind] || r.kind)}</td><td>${esc(r.Code)}</td><td style="text-align:left">${esc(r.Name)}</td><td>${esc(r.Size ?? "")}</td><td>${number(r.quantity)}</td><td>${number(r.amount)}</td></tr>`).join("")}</tbody></table>`
|
||||
: "<p>위에서 행을 고르세요.</p>";
|
||||
const title = { 105: "확인 — 장비", 104: "확인 — 단산", 103: "확인 — 일위", est: "내역서" }[
|
||||
kind
|
||||
];
|
||||
EstApp.showDialog(
|
||||
`동원인원,자재,장비 · ${title}`,
|
||||
`${kind === "est" ? "" : `<label><input type="checkbox" data-dw-only ${only ? "checked" : ""}> 어긋난 것만 (${data.rows.filter((r) => r.checked).length}건)</label>`} <span>${data.rows.length}건</span><div style="max-height:260px;overflow:auto"><table class="sheet"><thead>${head}</thead><tbody>${body}</tbody></table></div><div style="max-height:260px;overflow:auto;margin-top:6px">${detail}</div>${data.errors.length ? `<p class="business-note">${esc(data.errors.slice(0, 5).join(" / "))}</p>` : ""}`,
|
||||
);
|
||||
}
|
||||
function install() {
|
||||
document.addEventListener("click", (event) => {
|
||||
const tr = event.target.closest?.("[data-dw-pick]");
|
||||
if (!tr || !shown) return;
|
||||
shown.pick = Number(tr.dataset.dwPick);
|
||||
draw(EstApp.state);
|
||||
});
|
||||
document.addEventListener("change", (event) => {
|
||||
if (event.target.dataset?.dwOnly === undefined || !shown) return;
|
||||
shown.only = event.target.checked;
|
||||
draw(EstApp.state);
|
||||
});
|
||||
}
|
||||
if (typeof document !== "undefined" && document.addEventListener) install();
|
||||
|
||||
function handle(name, state, node) {
|
||||
if (name !== "dongwon-check") return false;
|
||||
const kind = node.dataset.kind === "est" ? "est" : Number(node.dataset.kind);
|
||||
shown = {
|
||||
kind,
|
||||
data: kind === "est" ? estimate(state.project) : check(state.project, kind),
|
||||
pick: -1,
|
||||
only: false,
|
||||
};
|
||||
draw(state);
|
||||
return true;
|
||||
}
|
||||
return { check, estimate, handle };
|
||||
})();
|
||||
@@ -220,7 +220,11 @@ const EstExtra = (() => {
|
||||
});
|
||||
html = box(
|
||||
"동원인원·자재·장비",
|
||||
`${kindSelect} ${byWorkCheck} <span>(총 ${filtered.length}건)</span>`,
|
||||
`${kindSelect} ${byWorkCheck} <span>(총 ${filtered.length}건)</span>` +
|
||||
button("dongwon-check", "확인 장비", 'data-kind="105"') +
|
||||
button("dongwon-check", "확인 단산", 'data-kind="104"') +
|
||||
button("dongwon-check", "확인 일위", 'data-kind="103"') +
|
||||
button("dongwon-check", "내역서(줄별)", 'data-kind="est"'),
|
||||
table(headers, rows),
|
||||
esc(output.errors.join(" / ")),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const EstResources = (() => {
|
||||
const { num, components } = EstMath;
|
||||
function calculate(input, byWork = false) {
|
||||
// roots: [{ kind, code, WorkCode, WorkName }] 를 주면 내역서 대신 그 호표마다 1 단위를 따로 펼친다(동원 확인 탭)
|
||||
function calculate(input, byWork = false, roots = null) {
|
||||
const project = EstNative.calculate({
|
||||
...structuredClone(input),
|
||||
options: { ...input.options, ResourceTracing: true },
|
||||
@@ -180,7 +181,12 @@ const EstResources = (() => {
|
||||
const source = tables[EstNative.sourceTables[kind]]?.find((row) => row.Code === code);
|
||||
record(kind, code, multiplier * weightedRate(source, rates));
|
||||
}
|
||||
for (const [index, row] of tables.EstMainDat.entries()) {
|
||||
for (const [index, root] of (roots || []).entries()) {
|
||||
workKey = `${index}:`;
|
||||
work = { WorkCode: root.WorkCode, WorkName: root.WorkName };
|
||||
expand(root.kind, root.code, 1, []);
|
||||
}
|
||||
for (const [index, row] of (roots ? [] : tables.EstMainDat).entries()) {
|
||||
if (num(row.LinkIndex) === 54) break;
|
||||
if (
|
||||
num(row.Level) > 0 ||
|
||||
|
||||
@@ -111,6 +111,7 @@ const allowed = new Set([
|
||||
"native-annual-copy.js",
|
||||
"native-print-range.js",
|
||||
"native-work-mode.js",
|
||||
"native-dongwon-check.js",
|
||||
"native-actions-more.js",
|
||||
"native-wonga-tools.js",
|
||||
"native-estimate-print.js",
|
||||
|
||||
Reference in New Issue
Block a user