// 원본 「조달청 표준공사코드 적용」(mnuCodeChange 0x10d10fc → TfrmCodeChange — 디스어셈블 근거 tmp 명세 spec_codechange). // 작업데이타 7종(일위 103·단산 104·기계 105·표준 21·견적 101·자재 5·노임 6)의 코드를 메모리 사본에서 바꾼 뒤 「적용」 때 // 자기 표 Code, 본문 머리 Code·TitleCode, 참조 행(LinkIndex = 종류 · Index ≤ 0 · Code = 옛 코드) Code 만 바꾼다(재계산 없음). // 검사(df5e34): EST 코드 = 노랑(1) · 다른 표에 같은 코드 = 연분홍(2, 비고) · 자리수(일위·단산·표준 12, 장비 17, 견적·자재·노임 16) = 진황(3). // 코드일괄변경(df2b98): 고른 .estx 의 같은 종류 표에서 품명·규격·단위 맞춤(자동연결 규칙) 첫 행 — 현재 EST 코드 → 비EST 코드만, // 표준(21)은 「.」 뺀 옛 코드를 부분일치로. 자동변경(표준 탭): 행 번호 2~2092 C · 2095~2717 A · 2720~3361 M 앞에, 끝에 0, 「.」 제거. // 다산코드정리: 종류 글자 + 10자리 일련번호. 사용자정의코드(df4b64): 고른 코드 앞자리 + Z 꼴 빈 번호. // 원본의 조달청 목록(PPS_STUFFC·PPS_HEAD)은 설치 폴더 System\MainSys 의 .dat 를 사용자가 골라 불러온다(내장 자료 없음). const EstCodeChange = (() => { const TABLES = { 103: "HeadTitleDat", 104: "CostTitleDat", 105: "MachTitleDat", 21: "SilJukDat", 101: "EstiMateDat", 5: "StuffcDat", 6: "LaborcDat", }; const MAIN = { 103: "HeadMainDat", 104: "CostMainDat", 105: "MachMainDat" }; const NAMES = { 103: "일위대가", 104: "단가산출", 105: "기계경비", 21: "표준시장단가", 101: "견적단가", 5: "자재단가", 6: "노임단가", }; const SHORT = { 103: "일위", 104: "단가", 105: "장비", 21: "표준", 101: "견적", 5: "자재", 6: "노임", }; const LETTER = { 5: "M", 6: "L", 15: "N", 21: "S", 101: "W", 103: "U", 104: "D", 105: "E", 151: "G", }; const ORDER = [103, 104, 105, 21, 101, 5, 6]; const num = (v) => Number(v) || 0; const trim = (v) => String(v ?? "").trim(); const norm = (s) => String(s ?? "").replace(/[\s|()]/g, ""); const letter = (tag) => LETTER[tag] || "Z"; const isEst = (code, tag) => { const c = trim(code); return c.length > 0 && c.length <= 11 && c[0] === letter(tag) && c[1] === "0"; }; function load(project, tag) { return (project.tables[TABLES[tag]] || []).map((row) => ({ Index: row.Index, OldCode: row.Code ?? "", Code: row.Code ?? "", Name: row.Name ?? "", Size: row.Size ?? "", Unite: row.Unite ?? "", Flag: 0, Checked: false, Bigo: "", })); } // 다른 6 표에 같은 코드가 있으면 그 행 설명 function elsewhere(project, tag, code) { for (const t of ORDER) { if (t === tag) continue; const row = (project.tables[TABLES[t]] || []).find((r) => trim(r.Code) === code); if (row) return `${SHORT[t]}"${row.Index ?? ""}.${row.Name ?? ""}(${row.Size ?? ""})" `; } return ""; } function check(project, tag, row, code = trim(row.Code)) { row.Flag = 0; if (isEst(code, tag)) return (row.Flag = 1); const other = elsewhere(project, tag, code); if (other) { row.Bigo = other; return (row.Flag = 2); } let size = [21, 103, 104].includes(tag) ? 12 : tag === 105 ? 17 : 16; if (tag === 105 && !/^[0-9Z]/.test(code)) return (row.Flag = 1); if (code.length !== size) row.Flag = 3; return row.Flag; } function validate(project, tag, rows) { for (const row of rows) { row.Flag = 0; row.Checked = false; row.Bigo = ""; if (trim(row.Code)) check(project, tag, row); } return rows.filter((r) => r.Flag).length; } const MESSAGE = { 1: "조달청 코드가 아닙니다.", 3: "코드자리수가 틀립니다.일위,단산,표준:12자리, 장비:17자리 ,견적,노임,자재:16자리", }; // 코드변경(df20f4): 목록 코드 또는 직접 입력을 현재 행에. 실패하면 문구를 던진다. function input(project, tag, rows, i, code, from) { let c = trim(code); if (tag === 105 && c.length === 16) c += "0"; if (!rows[i]) throw Error("바꿀 작업데이타 행을 고르세요."); const dup = rows.findIndex((r, j) => j !== i && trim(r.Code) === c); if (dup >= 0) { rows[dup].Flag = 2; throw Error("같은 코드가 있습니다. 확인하세요."); } const probe = { ...rows[i] }; const flag = check(project, tag, probe, c); if (flag === 2) throw Error(`${probe.Bigo}에 같은 코드가 있습니다.`); if (flag) throw Error(MESSAGE[flag]); rows[i].Code = c; if (from?.nameChange && trim(from.Name)) { rows[i].Name = from.Name; rows[i].Size = from.Size ?? ""; rows[i].Unite = from.Unit ?? from.Unite ?? ""; } } function cancel(rows, i) { if (rows[i]) rows[i].Code = rows[i].OldCode; } // 적용(df1c40) function apply(project, tag, rows) { const t = project.tables, own = t[TABLES[tag]] || []; let changed = 0; for (const r of rows) { const neu = trim(r.Code), old = trim(r.OldCode); if (!neu || neu === old) continue; const same = (x) => (x.Name ?? "") === r.Name && (x.Size ?? "") === r.Size; let hit = own.find((x) => trim(x.Code) === old); if (hit && !same(hit)) hit = own.find((x) => trim(x.Code) === old && same(x)); if (hit) { hit.Code = neu; changed++; } if (MAIN[tag]) for (const x of t[MAIN[tag]] || []) if (trim(x.TitleCode) === old) { x.Code = neu; x.TitleCode = neu; } const refs = tag === 103 ? ["HeadMainDat", "CostMainDat"] : tag === 104 ? ["CostMainDat", "HeadMainDat"] : tag === 105 ? ["MachMainDat", "CostMainDat", "HeadMainDat"] : ["MachMainDat", "CostMainDat", "HeadMainDat"]; for (const name of [...refs, "EstMainDat"]) for (const x of t[name] || []) if (num(x.LinkIndex) === tag && trim(x.Code) === old && num(x.Index) <= 0) { x.Code = neu; changed++; } r.OldCode = neu; } return changed; } // 코드일괄변경(df2b98) — source = 고른 .estx 를 읽은 공사 function bulk(project, tag, rows, source) { const list = source.tables?.[TABLES[tag]] || []; let changed = 0; rows.forEach((r) => (r.Checked = false)); for (const r of rows) { const cur = trim(r.Code); if (!cur) continue; let found; if (tag === 21) { if (!cur.includes(".")) continue; const k = cur.replaceAll(".", ""); found = list.find((x) => String(x.Code ?? "").includes(k)); if (!found) continue; const c = trim(found.Code); if (rows.some((x) => trim(x.Code) === c) || elsewhere(project, tag, c)) continue; r.Code = c; } else { found = EstAutoLink.find(source, tag, EstAutoLink.key(r, 0)); if (!found) continue; const c = trim(found.Code); if (rows.some((x) => trim(x.Code) === c) || elsewhere(project, tag, c)) continue; if (!isEst(cur, tag) || isEst(c, tag)) continue; r.Code = c; r.Flag = 0; } r.Checked = true; changed++; } return changed; } function auto(rows) { rows.forEach((r, i) => { const n = i + 1, c = trim(r.Code); if (!c) return; const head = n >= 2 && n <= 2092 ? "C" : n >= 2095 && n <= 2717 ? "A" : n >= 2720 && n <= 3361 ? "M" : ""; r.Code = (head ? `${head}${c}0` : c).replaceAll(".", ""); }); } function dasan(rows, tag) { rows.forEach((r, i) => (r.Code = letter(tag) + String(i).padStart(10, "0"))); } function userCode(project, tag, rows, base) { const s = `${trim(base)}00000000`; const make = (n) => { const nn = `000${n}`; if (tag === 103 || tag === 104) return `${s.slice(0, 8)}Z${nn.slice(-3)}`; if (tag === 105) return `${s.slice(0, 8)}000Z${nn.slice(-4)}0`; if (tag === 101 || tag === 5) return `${s.slice(0, 6)}ZZ000Z${nn.slice(-4)}`; if (tag === 6) return `${s.slice(0, 8)}010Z${nn.slice(-4)}`; return ""; }; const extra = { 103: "CostTitleDat", 104: "HeadTitleDat", 105: "MachTitleDat" }[tag]; for (let n = 0; n <= 999; n++) { const c = make(n); if (!c) return ""; if (rows.some((r) => trim(r.Code) === c)) continue; if (extra && (project.tables[extra] || []).some((r) => trim(r.Code) === c)) continue; return c; } throw Error("사용자 정의코드 갯수범위를 초과 했습니다."); } // 목록 검색(df6b5c): 정규화한 품명+규격에 검색어 포함, 「&」 이면 두 조각 모두 function search(list, word) { const w = norm(word); if (!w) return list; const parts = w.includes("&") ? w.split("&").slice(0, 2) : [w]; return list.filter((x) => { const k = norm(x.Name) + norm(x.Size); return parts.every((p) => k.includes(p)); }); } return { TABLES, NAMES, ORDER, isEst, load, validate, input, cancel, apply, bulk, auto, dasan, userCode, search, }; })(); if (typeof module !== "undefined" && module.exports) module.exports = EstCodeChange;