From a5157dd3a9235bdf7a161987ba32686ffe9847ed Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 6 Nov 2026 15:13:29 +0900 Subject: [PATCH] feat(est): add document and exchange workflows --- resources/tester/helper_est_native.cjs | 3 + resources/tester/test_est_c3r.py | 67 + resources/tester/test_est_documents.py | 46 + resources/tester/test_est_exchange.py | 88 + resources/tester/test_est_jdl.py | 39 + resources/tester/test_est_native_export.py | 83 + tools/est-test/README.md | 20 +- tools/est-test/archive.cjs | 92 + tools/est-test/exchange.cjs | 106 + tools/est-test/export-estx.cjs | 239 + tools/est-test/import-estx.cjs | 2 +- tools/est-test/index.html | 12 + tools/est-test/jdl-schema.json | 641 +++ tools/est-test/jdl.cjs | 330 ++ tools/est-test/native-actions.js | 50 +- tools/est-test/native-app.js | 1 + tools/est-test/native-c3r.js | 579 +++ tools/est-test/native-engine.js | 33 +- tools/est-test/native-exchange.js | 279 ++ tools/est-test/native-extra.js | 4 + tools/est-test/native-forms.js | 139 + tools/est-test/native-model.js | 35 + tools/est-test/native-report.js | 128 +- tools/est-test/native-resources.js | 16 +- tools/est-test/native-schedule.js | 103 + tools/est-test/print-catalog.json | 4981 ++++++++++++++++++++ tools/est-test/server.cjs | 41 + 27 files changed, 8131 insertions(+), 26 deletions(-) create mode 100644 resources/tester/test_est_c3r.py create mode 100644 resources/tester/test_est_documents.py create mode 100644 resources/tester/test_est_exchange.py create mode 100644 resources/tester/test_est_jdl.py create mode 100644 resources/tester/test_est_native_export.py create mode 100644 tools/est-test/archive.cjs create mode 100644 tools/est-test/exchange.cjs create mode 100644 tools/est-test/export-estx.cjs create mode 100644 tools/est-test/jdl-schema.json create mode 100644 tools/est-test/jdl.cjs create mode 100644 tools/est-test/native-c3r.js create mode 100644 tools/est-test/native-exchange.js create mode 100644 tools/est-test/native-forms.js create mode 100644 tools/est-test/native-schedule.js create mode 100644 tools/est-test/print-catalog.json diff --git a/resources/tester/helper_est_native.cjs b/resources/tester/helper_est_native.cjs index e9617ce0a..4ddfc3f6d 100644 --- a/resources/tester/helper_est_native.cjs +++ b/resources/tester/helper_est_native.cjs @@ -16,7 +16,10 @@ for (const file of [ "native-progress.js", "native-business.js", "native-resources.js", + "native-schedule.js", "native-model.js", + "native-c3r.js", + "native-exchange.js", "native-verify.js", ]) { vm.runInContext(fs.readFileSync(path.join(base, file), "utf8"), context, { filename: file }); diff --git a/resources/tester/test_est_c3r.py b/resources/tester/test_est_c3r.py new file mode 100644 index 000000000..dbadecd31 --- /dev/null +++ b/resources/tester/test_est_c3r.py @@ -0,0 +1,67 @@ +from pathlib import Path +from lxml import etree +from test_est_rea_parity import run + +ROOT = Path(__file__).resolve().parents[2] +SCHEMA = next((ROOT / "resources/knowledge").rglob("조달청_공사원가호환규정.xsd")) + + +def test_c3r_export_validates_against_the_supplied_schema_and_preserves_prices(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.name='C3R test';p.options.SourceRefresh=false; + p.tables.StuffcDat=[{PkID:1,Code:'M1',Name:'material',TC:100,GC:100}]; + p.tables.LaborcDat=[{PkID:1,Code:'L1',Name:'labor',TC:50,NC:50}]; + p.tables.EstMainDat=[{PkID:1,Code:'M1',LinkIndex:5,Name:'material',GN:2,GU:100},{PkID:2,Code:'L1',LinkIndex:6,Name:'labor',GN:3,NU:50},{PkID:3,LinkIndex:54}]; + p.tables.Wonga=[{Gubun:'G',Name:'material',BaseField:'GC'}, {Gubun:'N',Name:'labor',BaseField:'NC'},{Gubun:'T',Name:'total',VSusic:'G+N'}]; + return EstC3r.output(EstNative.calculate(p),{company:'test',kind:'토목공사',date:'2026-11-06'}); + })()""", + ) + schema = etree.XMLSchema(etree.parse(str(SCHEMA))) + root = etree.fromstring(result["xml"].encode("utf-8")) + assert schema.validate(root), str(schema.error_log) + assert root.findtext(".//세부공종/재료비") == "200" + assert root.findtext(".//세부공종/수량") == "2" + assert root.findtext(".//작성정보/파일검증여부") == "false" + assert result["warnings"] == [] + + +def test_xml_excluded_rows_do_not_inflate_group_totals_and_invalid_codes_are_rejected(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.name='test';p.tables.StuffcDat=[{Code:'M1',Name:'material',TC:100}];p.tables.EstMainDat=[ + {Code:'M1',Name:'normal',LinkIndex:5,GN:2,GU:100,GC:200,TC:200}, + {Code:'X1',Name:'excluded',GN:1,GC:999,TC:999,CalcIndex:3},{LinkIndex:54}]; + const meta={company:'test',kind:'토목공사',date:'2026-11-06'}; + const result=EstC3r.output(p,meta);let blocked=false; + p.tables.EstMainDat[0].Code='bad code';try{EstC3r.output(p,meta)}catch{blocked=true} + return {...result,blocked}; + })()""", + ) + root = etree.fromstring(result["xml"].encode("utf-8")) + assert root.findtext(".//내역서/재료비") == "200" + assert root.findall(".//세부공종")[1].findtext("계산제외") == "true" + assert result["blocked"] + + +def test_xml_keeps_parent_and_child_groups_instead_of_dropping_empty_parent_headers(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.name='test';p.tables.EstMainDat=[ + {Code:'ROOT',Name:'parent',Level:3,GC:200,TC:200}, + {Code:'CHILD',Name:'child',Level:1,GC:200,TC:200}, + {Code:'FIX',Name:'fixed',GN:1,GC:200,TC:200,CalcIndex:1},{LinkIndex:54}]; + return EstC3r.output(p,{company:'test',kind:'토목공사',date:'2026-11-06'}); + })()""", + ) + root = etree.fromstring(result["xml"].encode("utf-8")) + schema = etree.XMLSchema(etree.parse(str(SCHEMA))) + assert schema.validate(root), str(schema.error_log) + groups = root.findall(".//내역서") + assert [group.findtext("내역서번호") for group in groups] == ["01", "0101"] + assert [group.findtext("재료비") for group in groups] == ["200", "200"] + assert len(groups[0].findall("세부공종")) == 0 + assert len(groups[1].findall("세부공종")) == 1 diff --git a/resources/tester/test_est_documents.py b/resources/tester/test_est_documents.py new file mode 100644 index 000000000..7dab80c36 --- /dev/null +++ b/resources/tester/test_est_documents.py @@ -0,0 +1,46 @@ +from test_est_rea_parity import run + + +def test_resource_report_separates_each_work_item_without_changing_aggregate(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.options.SourceRefresh=false; + p.tables.StuffcDat=[{Code:'M',Name:'material',InputTC:100,ValueIndex:4}]; + p.tables.EstMainDat=[{PkID:1,Code:'M',LinkIndex:5,GN:2,GU:100},{PkID:2,Code:'M',LinkIndex:5,GN:3,GU:100},{LinkIndex:54}]; + return {all:EstResources.calculate(p),split:EstResources.calculate(p,true)}; + })()""", + ) + assert len(result["all"]["rows"]) == 1 + assert len(result["split"]["rows"]) == 2 + assert ( + sum(r["quantity"] for r in result["split"]["rows"]) + == result["all"]["rows"][0]["quantity"] + == 5 + ) + assert ( + sum(r["amount"] for r in result["split"]["rows"]) + == result["all"]["rows"][0]["amount"] + == 500 + ) + + +def test_schedule_dates_ranges_and_weighted_user_progress(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.schedule=[ + {id:'a',Code:'A',Name:'first',start:'2026-11-01',end:'2026-11-05',weight:100,progress:25}, + {id:'b',Code:'B',Name:'second',start:'2026-11-06',end:'2026-11-10',weight:300,progress:50}]; + const valid=EstSchedule.calculate(p),bad=[]; + for(const change of [{start:'2026-02-30'},{progress:101},{weight:-1},{end:'2026-10-01'}]) { + const q=structuredClone(p);Object.assign(q.schedule[0],change); + try{EstModel.validate(q);bad.push(false)}catch{bad.push(true)} + } + return {valid,bad}; + })()""", + ) + assert result["valid"]["progress"] == 43.75 + assert result["valid"]["rows"][0]["width"] == 50 + assert result["valid"]["rows"][1]["left"] == 50 + assert result["bad"] == [True] * 4 diff --git a/resources/tester/test_est_exchange.py b/resources/tester/test_est_exchange.py new file mode 100644 index 000000000..e9ffdfd63 --- /dev/null +++ b/resources/tester/test_est_exchange.py @@ -0,0 +1,88 @@ +import io +import json +import subprocess +import zipfile +from pathlib import Path +import xml.etree.ElementTree as ET + +from test_est_rea_parity import run + +ROOT = Path(__file__).resolve().parents[2] + + +def export_workbook(project): + result = subprocess.run( + [ + "node", + "-e", + "const fs=require('fs');process.stdout.write(require('./tools/est-test/exchange.cjs').workbook(JSON.parse(fs.readFileSync(0,'utf8'))))", + ], + input=json.dumps(project, ensure_ascii=False).encode("utf-8"), + capture_output=True, + cwd=ROOT, + check=True, + ) + return result.stdout + + +def test_excel_is_valid_office_zip_with_numeric_text_types_and_no_executable_formulas(): + data = export_workbook( + { + "name": "한글 공사", + "tables": { + "EstMainDat": [ + { + "PkID": 1, + "Code": "001", + "Name": '=HYPERLINK("bad")', + "GN": 1.23456789, + "TC": 99, + "NewItem": False, + "Editor": {"binary": "AAAA"}, + } + ], + "CadMainDat": [{"Code": "excluded"}], + }, + } + ) + with zipfile.ZipFile(io.BytesIO(data)) as archive: + assert archive.testzip() is None + workbook = archive.read("xl/workbook.xml").decode("utf-8") + assert "EstMainDat" in workbook and "CadMainDat" not in workbook + root = ET.fromstring(archive.read("xl/worksheets/sheet2.xml")) + ns = {"s": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} + rows = root.findall(".//s:row", ns) + assert "Editor" not in ET.tostring(rows[0], encoding="unicode") + assert rows[1].find("s:c[@r='B2']", ns).get("t") == "inlineStr" + assert rows[1].find("s:c[@r='D2']/s:v", ns).text == "1.23456789" + assert root.findall(".//s:f", ns) == [] + assert "=HYPERLINK" in ET.tostring(root, encoding="unicode") + + +def test_print_settings_validation_blocks_css_injection_and_bad_pagination(): + result = run( + "math", + """(()=>{ + return [{paper:'A4;display:none'}, {rowsPerPage:0}, {rowsPerPage:1.5}, {font:100}, {margin:-1}, {orientation:'portrait',paper:'A4',rowsPerPage:20,font:9}].map(s=>{ + const p=EstNative.empty();p.printSettings=s; + try{EstModel.validate(p);return true}catch{return false} + }); + })()""", + ) + assert result == [False, False, False, False, False, True] + + +def test_excel_price_edits_refresh_linked_values_and_keep_binary_fields(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.tables.StuffcDat=[{PkID:1,Code:'M',Name:'material',InputTC:100,ValueIndex:4,Editor:{binary:'AAAA'}}]; + p.tables.EstMainDat=[{Code:'M',LinkIndex:5,GN:2},{LinkIndex:54}]; + const q=EstNative.calculate(p),updated=EstExchange.apply(q,{StuffcDat:[{PkID:1,Code:'M',Name:'material',TC:150,InputTC:100,ValueIndex:4}]}); + let rejected=false;try{EstExchange.apply(q,{StuffcDat:[{PkID:1,Code:'',TC:150}]})}catch{rejected=true} + return {next:EstNative.calculate(updated),rejected}; + })()""", + ) + assert result["rejected"] + assert result["next"]["tables"]["EstMainDat"][-1]["TC"] == 300 + assert result["next"]["tables"]["StuffcDat"][0]["Editor"]["binary"] == "AAAA" diff --git a/resources/tester/test_est_jdl.py b/resources/tester/test_est_jdl.py new file mode 100644 index 000000000..702ea34e4 --- /dev/null +++ b/resources/tester/test_est_jdl.py @@ -0,0 +1,39 @@ +import json +import subprocess +from pathlib import Path +from test_est_rea_parity import run + +ROOT = Path(__file__).resolve().parents[2] + + +def test_jdl_basic_tables_use_native_separators_encoding_and_preserve_direct_money(): + p = run( + "math", + """(()=>{ + const p=EstNative.empty();p.name='JDL test';p.options.SourceRefresh=false; + p.tables.StuffcDat=[{PkID:1,Code:'M',Name:'material',InputTC:100,ValueIndex:4}]; + p.tables.EstMainDat=[{PkID:1,Code:'M',Name:'material',LinkIndex:5,GN:10,GU:100}, + {PkID:2,Code:'FIX',Name:'fixed',GN:0,GC:80,TC:80,CalcIndex:1}, + {PkID:3,Code:'SKIP',Name:'excluded',GN:1,GC:999,TC:999,CalcIndex:3},{PkID:4,LinkIndex:54}]; + p.tables.Wonga=[{Gubun:'G',Name:'material',BaseField:'GC'},{Gubun:'T',Name:'total',VSusic:'G'}]; + return EstNative.calculate(p); + })()""", + ) + script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'); + const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);for(const name of Object.keys(empty.tables))empty.tables[name]=[]; + const out=j.output(p),files=a.unpack(out.bytes),next=j.read(out.bytes,empty); + const raw=files.get('mainpart.txt'); + process.stdout.write(JSON.stringify({next,files:[...files.keys()],tabs:raw.includes(9),terminator:raw.includes(5),warnings:out.warnings}));""" + result = json.loads( + subprocess.check_output( + ["node", "-e", script], + input=json.dumps(p, ensure_ascii=False).encode("utf-8"), + cwd=ROOT, + ).decode("utf-8") + ) + assert result["tabs"] and result["terminator"] + assert {"Global.ini", "Main.txt", "mainpart.txt", "CostAccount.txt"}.issubset(result["files"]) + assert result["next"]["tables"]["EstMainDat"][-1]["TC"] == 1080 + assert result["next"]["tables"]["Wonga"][-1]["Cost"] == 1080 + assert result["next"]["options"]["AutoCalc"] is False + assert result["warnings"] diff --git a/resources/tester/test_est_native_export.py b/resources/tester/test_est_native_export.py new file mode 100644 index 000000000..ad6f1e1b8 --- /dev/null +++ b/resources/tester/test_est_native_export.py @@ -0,0 +1,83 @@ +import json +import struct +import subprocess +import zipfile +import pytest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def test_recalculation_and_native_export_keep_original_title_identities_when_sample_exists(): + sample = Path("C:/Dasansoft/Est Plus 2/Data/ProjectData/sample.estx") + if not sample.exists(): + pytest.skip("installed EST sample unavailable") + script = """const fs=require('fs'),m=require('./tools/est-test/import-estx.cjs'),e=require('./tools/est-test/export-estx.cjs'),s=require('./tools/est-test/server.cjs'); + const bytes=fs.readFileSync(process.argv[1]),p=m.importEstx(bytes),q=s.calculate(p); + const ids=['HeadTitleDat','CostTitleDat','MachTitleDat'].every(key=>p.tables[key].every((r,i)=>r.PkID===q.tables[key][i].PkID)); + const row=q.tables.EstMainDat.find(r=>r.GN&&r.TC);row.GN+=1; + const edited=s.calculate(q),out=e.exportEstx(edited),again=m.importEstx(out),original=m.unzip(bytes),files=m.unzip(out); + const indexes=[...original].filter(([name])=>name.endsWith('.idx')).every(([name,value])=>value.equals(files.get(name))); + process.stdout.write(JSON.stringify({ids,indexes,gn:again.tables.EstMainDat.find(r=>r.PkID===row.PkID).GN,expected:row.GN}));""" + result = json.loads( + subprocess.check_output(["node", "-e", script, str(sample)], cwd=ROOT).decode("utf-8") + ) + assert result["ids"] and result["indexes"] + assert result["gn"] == result["expected"] + + +def fixture(path): + fields = [ + ("PkID", 2, 4), + ("Code", 1, 16), + ("Name", 1, 32), + ("GN", 7, 8), + ("GU", 7, 8), + ("TC", 7, 8), + ] + stride = 25 + sum(width + 1 for _, _, width in fields) + data = bytearray(512 + 768 * len(fields) + stride) + data[8:24] = bytes.fromhex("068abe8e592364cb403d71d2e3bc64d0") + struct.pack_into("= Math.max(0, buffer.length - 65557); end--) + if (buffer.readUInt32LE(end) === 0x06054b50) break; + if (end < Math.max(0, buffer.length - 65557)) throw Error("ZIP 디렉터리 없음"); + const count = buffer.readUInt16LE(end + 10), + files = new Map(); + if (count > 3000) throw Error("ZIP 항목 수 초과"); + let position = buffer.readUInt32LE(end + 16), + total = 0; + for (let i = 0; i < count; i++) { + if (buffer.readUInt32LE(position) !== 0x02014b50) throw Error("ZIP 헤더 오류"); + const flags = buffer.readUInt16LE(position + 8), + method = buffer.readUInt16LE(position + 10), + crc = buffer.readUInt32LE(position + 16), + compressed = buffer.readUInt32LE(position + 20), + size = buffer.readUInt32LE(position + 24), + length = buffer.readUInt16LE(position + 28), + extra = buffer.readUInt16LE(position + 30), + comment = buffer.readUInt16LE(position + 32), + at = buffer.readUInt32LE(position + 42), + name = buffer.subarray(position + 46, position + 46 + length).toString("utf8"); + if ( + flags & 1 || + !name || + name.startsWith("/") || + name.includes("\\") || + name.split("/").includes("..") + ) + throw Error("지원하지 않는 ZIP 경로/암호화"); + total += size; + if (total > 256 * 1024 * 1024) throw Error("압축 해제 크기 초과"); + if (buffer.readUInt32LE(at) !== 0x04034b50) throw Error("ZIP 로컬 헤더 오류"); + const start = at + 30 + buffer.readUInt16LE(at + 26) + buffer.readUInt16LE(at + 28); + if (start + compressed > buffer.length) throw Error("ZIP 데이터 범위 오류"); + const source = buffer.subarray(start, start + compressed), + data = + method === 8 + ? zlib.inflateRawSync(source, { maxOutputLength: 256 * 1024 * 1024 }) + : method === 0 + ? source + : null; + if (!data || data.length !== size || crc32(data) !== crc || files.has(name)) + throw Error("ZIP CRC/중복 오류"); + files.set(name, data); + position += 46 + length + extra + comment; + } + return files; +} +module.exports = { pack, unpack }; diff --git a/tools/est-test/exchange.cjs b/tools/est-test/exchange.cjs new file mode 100644 index 000000000..0d405695f --- /dev/null +++ b/tools/est-test/exchange.cjs @@ -0,0 +1,106 @@ +const { pack, unpack } = require("./archive.cjs"); +const xml = (value) => + String(value ?? "").replace( + /[&<>"']/g, + (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c], + ); +const column = (index) => { + let name = ""; + for (index++; index; index = Math.floor((index - 1) / 26)) + name = String.fromCharCode(65 + ((index - 1) % 26)) + name; + return name; +}; +function sheet(rows) { + if (rows.length > 1048576 || rows.some((r) => r.length > 16384)) + throw Error("Excel 행/열 한도 초과"); + return `${rows + .map( + (r, i) => + `${r + .map((v, j) => { + const ref = column(j) + (i + 1); + if (v == null) return ""; + if (typeof v === "number") { + if (!Number.isFinite(v)) throw Error("유효하지 않은 Excel 숫자"); + return `${v}`; + } + if (typeof v === "boolean") return `${v ? 1 : 0}`; + const text = typeof v === "object" ? JSON.stringify(v) : String(v); + if (text.length > 32767) throw Error("Excel 셀 문자 한도 초과: " + ref); + return `${xml(text)}`; + }) + .join("")}`, + ) + .join("")}`; +} +function workbook(project) { + const files = new Map(), + sheets = []; + for (const [name, rows] of Object.entries(project.tables)) { + if (!Array.isArray(rows) || name.startsWith("Cad") || name.length > 31) continue; + // Binary originals stay in JSON/ESTX; Excel edits scalar table fields only. + const keys = [ + ...new Set( + rows.flatMap((r) => + Object.keys(r).filter( + (k) => !k.startsWith("_") && r[k] != null && typeof r[k] !== "object", + ), + ), + ), + ]; + if (!keys.length) continue; + sheets.push({ + name, + rows: [ + keys, + ...rows.map((r) => + keys.map((k) => (typeof r[k] === "object" && r[k] != null ? null : r[k])), + ), + ], + }); + } + sheets.unshift({ + name: "공사정보", + rows: [ + ["항목", "값"], + ["공사명", project.name], + ["형식", "est-web-xlsx-1"], + ["설명", "표의 첫 행은 필드명입니다. 바이너리는 포함하지 않습니다."], + ], + }); + sheets.forEach((s, i) => files.set(`xl/worksheets/sheet${i + 1}.xml`, sheet(s.rows))); + const ns = "http://schemas.openxmlformats.org/"; + files.set( + "[Content_Types].xml", + `${sheets.map((s, i) => ``).join("")}`, + ); + files.set( + "_rels/.rels", + ``, + ); + files.set( + "xl/workbook.xml", + `${sheets.map((s, i) => ``).join("")}`, + ); + files.set( + "xl/_rels/workbook.xml.rels", + `${sheets.map((s, i) => ``).join("")}`, + ); + return pack(files); +} +function readWorkbook(bytes) { + const files = unpack(bytes), + selected = {}; + if (!files.has("xl/workbook.xml") || !files.has("xl/_rels/workbook.xml.rels")) + throw Error("Excel 통합문서 구조 없음"); + for (const [name, data] of files) + if ( + name === "xl/workbook.xml" || + name === "xl/_rels/workbook.xml.rels" || + name === "xl/sharedStrings.xml" || + /^xl\/worksheets\/[^/]+\.xml$/.test(name) + ) + selected[name] = data.toString("utf8"); + return selected; +} +module.exports = { workbook, readWorkbook, xml }; diff --git a/tools/est-test/export-estx.cjs b/tools/est-test/export-estx.cjs new file mode 100644 index 000000000..d1b21f378 --- /dev/null +++ b/tools/est-test/export-estx.cjs @@ -0,0 +1,239 @@ +const zlib = require("node:zlib"); +const { importEstx, unzip, Values, memoryRows } = require("./import-estx.cjs"); +const { pack } = require("./archive.cjs"); +const signature = Buffer.from("068abe8e592364cb403d71d2e3bc64d0", "hex"); +const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null); +let characters; +function cp949(text) { + if (!characters) { + characters = new Map(); + const decoder = new TextDecoder("euc-kr", { fatal: true }); + for (let i = 0; i < 128; i++) characters.set(String.fromCharCode(i), Buffer.from([i])); + for (let a = 0x81; a <= 0xfe; a++) + for (let b = 0x41; b <= 0xfe; b++) { + try { + const bytes = Buffer.from([a, b]), + s = decoder.decode(bytes); + if (s.length === 1 && !characters.has(s)) characters.set(s, bytes); + } catch {} + } + } + const parts = []; + for (const c of String(text)) { + const bytes = characters.get(c); + if (!bytes) throw Error(`CP949로 저장할 수 없는 문자: ${c}`); + parts.push(bytes); + } + return Buffer.concat(parts); +} +function value(data) { + if (data === null) return Buffer.from([13]); + if (typeof data === "boolean") return Buffer.from([data ? 9 : 8]); + if (typeof data === "number") { + if (!Number.isFinite(data)) throw Error("TPF0 숫자 오류"); + if (Number.isInteger(data) && data >= -128 && data <= 127) { + const b = Buffer.alloc(2); + b[0] = 2; + b.writeInt8(data, 1); + return b; + } + if (Number.isInteger(data) && data >= -2147483648 && data <= 2147483647) { + const b = Buffer.alloc(5); + b[0] = 4; + b.writeInt32LE(data, 1); + return b; + } + const b = Buffer.alloc(9); + b[0] = 21; + b.writeDoubleLE(data, 1); + return b; + } + if (typeof data === "string") { + const b = Buffer.from(data, "utf16le"), + h = Buffer.alloc(5); + h[0] = 18; + h.writeUInt32LE(data.length, 1); + return Buffer.concat([h, b]); + } + if (Buffer.isBuffer(data)) { + const h = Buffer.alloc(5); + h[0] = 10; + h.writeUInt32LE(data.length, 1); + return Buffer.concat([h, data]); + } + if (Array.isArray(data)) + return Buffer.concat([Buffer.from([1]), ...data.map(value), Buffer.from([0])]); + throw Error("지원하지 않는 TPF0 값"); +} +function memory(buffer, rows) { + const compressed = buffer[0] === 0x78, + bytes = compressed ? zlib.inflateSync(buffer) : buffer, + reader = new Values(bytes), + values = []; + while (reader.position < bytes.length) values.push(reader.value()); + const names = values[1].filter((v, i) => i % 10 === 0), + entries = values[4]; + if (rows.length !== entries.length) + throw Error("원가 서식 행 추가/삭제는 ESTX 인덱스 재생성이 필요합니다."); + values[4] = rows.map((row, i) => [ + entries[i][0], + ...names.flatMap((name) => (row[name] == null ? [true] : [false, row[name]])), + ]); + const result = Buffer.concat([Buffer.from("TPF0"), ...values.map(value)]); + const output = compressed ? zlib.deflateSync(result) : result; + if ( + !same( + memoryRows(output), + rows.map((row) => Object.fromEntries(names.map((name) => [name, row[name] ?? null]))), + ) + ) + throw Error("원가 서식 인코딩 검산 실패"); + return output; +} +function updateBlob(file, start, id, bytes) { + const blocks = [], + seen = new Set(); + let block = start; + while (block) { + if (seen.has(block) || block * 512 + 512 > file.length) throw Error("BLOB 체인 오류"); + seen.add(block); + blocks.push(block); + block = file.readUInt32LE(block * 512 + 4); + } + if (bytes.length > blocks.length * 494) + throw Error( + "변경 BLOB이 원본 할당 공간을 초과합니다. 원본 공간 확장은 아직 지원하지 않습니다.", + ); + if (file.readUInt32LE(start * 512 + 10) !== id) throw Error("BLOB ID 오류"); + let at = 0; + for (const [i, number] of blocks.entries()) { + const offset = number * 512, + take = Math.min(494, bytes.length - at); + file.writeUInt16LE(take, offset + 8); + file.fill(0, offset + 18, offset + 512); + bytes.copy(file, offset + 18, at, at + take); + at += take; + if (i === 0) file.writeUInt32LE(bytes.length, offset + 14); + } +} +function exportEstx(project) { + if (!project.originalArchive) + throw Error("원본 ESTX를 가져온 작업에서만 ESTX 수정 저장이 가능합니다."); + const original = Buffer.from(project.originalArchive, "base64"), + base = importEstx(original), + files = unzip(original); + if (project.name !== base.name) + throw Error("공사명 변경의 원본 옵션 저장 구조가 미확인입니다. JSON으로 보관하세요."); + for (const key of Object.keys(base.options)) + if ( + !["SourceRefresh", "AutoCalc"].includes(key) && + !same(project.options[key], base.options[key]) + ) + throw Error(`계산 옵션 ${key} 변경은 ESTX에 저장할 수 없습니다.`); + if (project.progress?.length || project.annual?.length || project.baseline) + throw Error( + "웹 기성/연차/당초 사본은 원본 별도 업무 파일 구조가 필요합니다. JSON 저장을 사용하세요.", + ); + if (!same(project.quantitySheets || [], base.quantitySheets || [])) + throw Error("편집한 수량산출서의 원본 WPTools 저장은 아직 지원하지 않습니다."); + let changed = 0; + const oldByLower = new Map(Object.keys(base.tables).map((key) => [key.toLowerCase(), key])); + for (const [filename, data] of files) { + if (!filename.endsWith(".dat") || !data.subarray(8, 24).equals(signature)) continue; + const key = oldByLower.get(filename.slice(0, -4)); + if (!key) continue; + const rows = project.tables[key], + before = base.tables[key]; + if (!rows || rows.length !== before.length) + throw Error(`${key}: 행 추가/삭제는 인덱스 재생성이 필요합니다.`); + const stride = data.readUInt16LE(45), + count = data.readUInt16LE(47), + start = 512 + 768 * count; + const blobFile = files.get(filename.replace(/\.dat$/, ".blb")); + for (let i = 0; i < rows.length; i++) + for (let f = 0; f < count; f++) { + const desc = 512 + 768 * f, + name = data.subarray(desc + 3, desc + 3 + data[desc + 2]).toString("ascii"), + kind = data[desc + 164], + width = data.readUInt16LE(desc + 169), + payload = data.readUInt32LE(desc + 172), + at = start + i * stride + payload, + old = before[i][name], + next = rows[i][name]; + if (same(next, old)) continue; + // All potential index keys stay byte-identical. No stale index is ever emitted. + if (/^(PkID|Index|Code|OldCode|TitleCode|RecNo|Level|Flag)$/i.test(name)) + throw Error(`${key}.${name}: 인덱스 키 변경은 지원하지 않습니다.`); + if (key.startsWith("Cad")) throw Error("CAD 수정 저장은 범위 밖입니다."); + if (next == null) { + data[at] = 0; + data.fill(0, at + 1, at + 1 + width); + changed++; + continue; + } + if (kind === 1) { + const bytes = cp949(next); + if (bytes.length >= width) throw Error(`${key}.${name}: 원본 문자열 길이 초과`); + data.fill(0, at + 1, at + 1 + width); + bytes.copy(data, at + 1); + } else if (kind === 7 && width === 8) { + if (typeof next !== "number" || !Number.isFinite(next)) throw Error("ESTX 실수 오류"); + data.writeDoubleLE(next, at + 1); + } else if ([2, 6].includes(kind) && width === 4) { + if (!Number.isInteger(next)) throw Error("ESTX 정수 오류"); + data.writeInt32LE(next, at + 1); + } else if (kind === 4 && width === 2) { + if (!Number.isInteger(next)) throw Error("ESTX 정수 오류"); + data.writeInt16LE(next, at + 1); + } else if (kind === 3 && width === 8 && next?.binary && old?.binary && blobFile && data[at]) + updateBlob( + blobFile, + data.readUInt32LE(at + 1), + data.readUInt32LE(at + 5), + Buffer.from(next.binary, "base64"), + ); + else throw Error(`${key}.${name}: 미확인 필드 저장형 ${kind}`); + data[at] = 1; + changed++; + } + } + // Wonga's active web form is explicit; the original archive/import always chooses form zero. + if (!same(project.tables.Wonga, base.tables.Wonga)) { + const dat = files.get("wongadat.dat"), + blb = files.get("wongadat.blb"); + if (!dat || !blb) throw Error("원가 서식 저장 표 없음"); + const count = dat.readUInt16LE(47), + start = 512 + 768 * count; + let field; + for (let f = 0; f < count; f++) { + const desc = 512 + f * 768; + if (dat[desc + 164] === 3) { + field = dat.readUInt32LE(desc + 172); + break; + } + } + if (field == null) throw Error("원가 BLOB 필드 없음"); + const originalBlob = Object.values(base.tables.wongadat[0]).find((v) => v?.binary); + const bytes = memory(Buffer.from(originalBlob.binary, "base64"), project.tables.Wonga); + updateBlob( + blb, + dat.readUInt32LE(start + field + 1), + dat.readUInt32LE(start + field + 5), + bytes, + ); + changed++; + } + if (!changed) return original; + const result = pack(files), + check = importEstx(result); + for (const [key, rows] of Object.entries(base.tables)) { + if (key === "Wonga" || key === "wongadat") continue; + for (let i = 0; i < rows.length; i++) + for (const name of Object.keys(rows[i])) + if (!same(check.tables[key][i][name], project.tables[key][i][name])) + throw Error(`${key}.${name}: 저장 후 재읽기 검산 실패`); + } + if (!same(check.tables.Wonga, project.tables.Wonga)) throw Error("원가 저장 후 재읽기 검산 실패"); + return result; +} +module.exports = { exportEstx, memory, cp949 }; diff --git a/tools/est-test/import-estx.cjs b/tools/est-test/import-estx.cjs index 508bad3cf..c2ae9d7ed 100644 --- a/tools/est-test/import-estx.cjs +++ b/tools/est-test/import-estx.cjs @@ -374,4 +374,4 @@ function importEstx(buffer, name = "가져온 내역서") { originalArchive: buffer.toString("base64"), }; } -module.exports = { importEstx, unzip, table, blob, memoryRows, optionObjects, crc32 }; +module.exports = { importEstx, unzip, table, blob, memoryRows, optionObjects, crc32, Values }; diff --git a/tools/est-test/index.html b/tools/est-test/index.html index b6562c8bc..9d1fb4674 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -17,11 +17,15 @@ + + + + @@ -46,6 +50,13 @@
+ + + + + + +
@@ -101,6 +112,7 @@ > +
diff --git a/tools/est-test/jdl-schema.json b/tools/est-test/jdl-schema.json new file mode 100644 index 000000000..a943b0a1d --- /dev/null +++ b/tools/est-test/jdl-schema.json @@ -0,0 +1,641 @@ +{ + "m.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "l.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "e.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "w.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "j.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "gongjong.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_QQTY", + "size": 26, + "kind": 6 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "unit.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "junit.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "danga.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_MEMO", + "size": 10, + "kind": 16 + } + ], + "mainpart.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_SEQ", + "size": 10, + "kind": 1 + }, + { + "name": "F_CODE1", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_QQTY", + "size": 26, + "kind": 6 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_SPROPER", + "size": 25, + "kind": 1 + }, + { + "name": "F_LOADBM", + "size": null, + "kind": 1 + }, + { + "name": "F_SAVEBM", + "size": null, + "kind": 1 + }, + { + "name": "F_SRATE", + "size": 26, + "kind": 6 + }, + { + "name": "F_KWAN", + "size": 26, + "kind": 1 + }, + { + "name": "F_CLASS", + "size": null, + "kind": 1 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "unitpart.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_SEQ", + "size": 10, + "kind": 1 + }, + { + "name": "F_CODE1", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_QQTY", + "size": 26, + "kind": 6 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_MCOST", + "size": 25, + "kind": 6 + }, + { + "name": "F_LCOST", + "size": 25, + "kind": 6 + }, + { + "name": "F_ECOST", + "size": 25, + "kind": 6 + }, + { + "name": "F_SPROPER", + "size": 25, + "kind": 1 + }, + { + "name": "F_LOADBM", + "size": null, + "kind": 1 + }, + { + "name": "F_SAVEBM", + "size": null, + "kind": 1 + }, + { + "name": "F_SRATE", + "size": 26, + "kind": 6 + }, + { + "name": "F_KWAN", + "size": 26, + "kind": 1 + }, + { + "name": "F_CLASS", + "size": null, + "kind": 1 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + } + ], + "jpart.txt": [ + { + "name": "F_CODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_SEQ", + "size": 10, + "kind": 1 + }, + { + "name": "F_CODE1", + "size": 12, + "kind": 1 + }, + { + "name": "F_DES", + "size": 40, + "kind": 1 + }, + { + "name": "F_SIZE", + "size": 40, + "kind": 1 + }, + { + "name": "F_UNIT", + "size": 40, + "kind": 1 + }, + { + "name": "F_QQTY", + "size": 26, + "kind": 6 + }, + { + "name": "F_MPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_LPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_EPRICE", + "size": 25, + "kind": 6 + }, + { + "name": "F_SPROPER", + "size": 25, + "kind": 1 + }, + { + "name": "F_LOADBM", + "size": null, + "kind": 1 + }, + { + "name": "F_SAVEBM", + "size": null, + "kind": 1 + }, + { + "name": "F_SRATE", + "size": 26, + "kind": 6 + }, + { + "name": "F_KWAN", + "size": 26, + "kind": 1 + }, + { + "name": "F_CLASS", + "size": null, + "kind": 1 + }, + { + "name": "F_ORICODE", + "size": 12, + "kind": 1 + }, + { + "name": "F_GBCODE", + "size": 12, + "kind": 1 + } + ] +} diff --git a/tools/est-test/jdl.cjs b/tools/est-test/jdl.cjs new file mode 100644 index 000000000..e9e21db49 --- /dev/null +++ b/tools/est-test/jdl.cjs @@ -0,0 +1,330 @@ +// JDLCHK.dll field declarations: 4e926c..4fb010; EST writer e44f28/e4739c/e541ec. +const { pack, unpack } = require("./archive.cjs"); +const { cp949 } = require("./export-estx.cjs"); +const schema = require("./jdl-schema.json"); +const decoder = new TextDecoder("euc-kr", { fatal: true }); +const num = (v) => { + const n = Number(v || 0); + if (!Number.isFinite(n)) throw Error("JDL 수치 오류"); + return n; +}; +function text(v, max = 40) { + const s = String(v ?? ""); + if (/[\x00-\x1f]/.test(s)) throw Error("JDL 문자열에 제어문자가 있습니다."); + if (cp949(s).length > max) throw Error(`JDL 문자열 길이 ${max}바이트 초과: ${s}`); + return s; +} +function encode(name, rows) { + const fields = schema[name]; + return cp949( + rows + .map( + (row) => + fields + .map((f) => { + const v = row[f.name]; + return f.kind === 6 ? String(num(v)) : text(v, f.kind === 16 ? 100000 : f.size || 1); + }) + .join("\t") + "\x05\r\n", + ) + .join(""), + ); +} +function parse(name, bytes) { + const fields = schema[name]; + if (!fields || !bytes) return []; + return decoder + .decode(bytes) + .split(/\r?\n/) + .filter((v) => v.trim()) + .map((line, i) => { + const values = line + .replace(/\x05\s*$/, "") + .split("\t") + .map((v) => v.trim()); + if (values.length !== fields.length) + throw Error( + `${name} ${i + 1}행: ${fields.length}개 필드가 필요합니다 (${values.length}개).`, + ); + return Object.fromEntries( + fields.map((f, j) => [ + f.name, + f.kind === 6 ? num(values[j]) : text(values[j], f.kind === 16 ? 100000 : f.size || 1), + ]), + ); + }); +} +function output(project) { + const tables = Object.fromEntries(Object.keys(schema).map((k) => [k, []])), + warnings = [], + map = new Map(); + let serial = 0; + function source(kind, row) { + const key = kind + ":" + row.Code; + if (map.has(key)) return map.get(key); + const prefix = kind === 5 ? "M" : kind === 6 ? "L" : "W", + code = prefix + String(++serial).padStart(10, "0"); + const price = + kind === 5 + ? { F_MPRICE: num(row.TC) } + : kind === 6 + ? { F_LPRICE: num(row.TC) } + : { F_MPRICE: num(row.GC), F_LPRICE: num(row.NC), F_EPRICE: num(row.JC) + num(row.SC) }; + const name = prefix === "M" ? "m.txt" : prefix === "L" ? "l.txt" : "w.txt"; + tables[name].push({ + F_CODE: code, + F_DES: row.Name, + F_SIZE: row.Size, + F_UNIT: row.Unite, + ...price, + F_ORICODE: "00000000000", + }); + map.set(key, code); + return code; + } + for (const [kind, table] of [ + [5, "StuffcDat"], + [6, "LaborcDat"], + [21, "SilJukDat"], + [101, "EstiMateDat"], + ]) + for (const row of project.tables[table] || []) source(kind, row); + for (const table of ["CostTitleDat", "MachTitleDat", "HeadTitleDat"]) + for (const row of project.tables[table] || []) + source({ CostTitleDat: 104, MachTitleDat: 105, HeadTitleDat: 103 }[table], row); + let group = null, + groupNumber = 0, + sequence = 0; + function addGroup(name) { + if (++groupNumber > 99) throw Error("JDL 단일 레벨 변환은 공종 99개까지 가능합니다."); + group = { + F_CODE: String(groupNumber).padStart(2, "0"), + F_DES: text(name), + F_QQTY: 1, + F_MPRICE: 0, + F_LPRICE: 0, + F_EPRICE: 0, + F_ORICODE: "00000000000", + }; + tables["gongjong.txt"].push(group); + sequence = 0; + } + for (const row of project.tables.EstMainDat) { + if (num(row.LinkIndex) === 54) break; + if (num(row.Level) > 0) { + addGroup(row.Name); + continue; + } + if (!group) addGroup(project.name); + if (num(row.CalcIndex) >= 2) { + warnings.push(`${row.Code || row.Name}: 합산 제외 행 생략`); + continue; + } + let key = map.get(num(row.LinkIndex) + ":" + row.Code), + gn = num(row.GN), + m = num(row.GU), + l = num(row.NU), + e = num(row.JU) + num(row.SU); + if ( + !key || + num(row.CalcIndex) === 1 || + num(row.CalcIndex) === 3 || + Math.abs(gn * (m + l + e) - num(row.TC)) > 0.000001 + ) { + key = source(101, { + Code: "LITERAL" + ++serial, + Name: row.Name, + Size: row.Size, + Unite: row.Unite, + GC: row.GC, + NC: row.NC, + JC: row.JC, + SC: row.SC, + }); + gn = 1; + m = num(row.GC); + l = num(row.NC); + e = num(row.JC) + num(row.SC); + warnings.push(`${row.Code || row.Name}: 저장 금액을 독립 항목으로 전개`); + } + tables["mainpart.txt"].push({ + F_CODE: group.F_CODE, + F_SEQ: String(++sequence), + F_CODE1: key, + F_DES: row.Name, + F_SIZE: row.Size, + F_UNIT: row.Unite, + F_QQTY: gn, + F_MPRICE: m, + F_LPRICE: l, + F_EPRICE: e, + F_SPROPER: "0", + F_LOADBM: "T", + F_SAVEBM: "T", + F_SRATE: 0, + F_KWAN: "0", + F_CLASS: "0", + F_ORICODE: "00000000000", + }); + group.F_MPRICE += num(row.GC); + group.F_LPRICE += num(row.NC); + group.F_EPRICE += num(row.JC) + num(row.SC); + } + if (!tables["gongjong.txt"].length) addGroup(project.name); + const direct = tables["gongjong.txt"].reduce( + (a, r) => ({ m: a.m + r.F_MPRICE, l: a.l + r.F_LPRICE, e: a.e + r.F_EPRICE }), + { m: 0, l: 0, e: 0 }, + ); + const global = `[BASIC]\r\nMAKER=ESTWEB\r\nVERSION=2.00\r\nCDATE=${new Date().toISOString().slice(0, 10).replaceAll("-", "")}\r\n[CPART]\r\nMQBIT=7C\r\nMABIT=0C\r\nMTBIT=0C\r\nMCMETHOD=0LME\r\nUABIT=0C\r\nUTBIT=0C\r\nUCMETHOD=0LME\r\nDCMETHOD=0LME\r\nJABIT=0C\r\nJTBIT=0C\r\nJCMETHOD=0LME\r\nDQBIT=2R\r\nDABIT=0C\r\nDTBIT=0C\r\n[JPART]\r\n[VPART]\r\n[COST]\r\nMCOST=${direct.m}\r\nLCOST=${direct.l}\r\nECOST=${direct.e}\r\nTCOST=${direct.m + direct.l + direct.e}\r\n`; + const files = new Map([ + ["Global.ini", cp949(global)], + ["Main.txt", cp949(["0", text(project.name, 80), "02"].join("\t") + "\x05\r\n")], + ]); + for (const [name, rows] of Object.entries(tables)) files.set(name, encode(name, rows)); + const fees = (project.tables.Wonga || []) + .map( + (r) => + [ + text(r.Gubun, 12), + text(r.Name), + text(r.Youl, 25), + text(r.VSusic, 200), + String(num(r.Cost)), + ].join("\t") + "\x05\r\n", + ) + .join(""); + files.set("CostAccount.txt", cp949(fees)); + warnings.unshift( + "JDL의 기본 표로 금액을 전개합니다. 산근·기계·일위의 원본 편집 산식과 관급/특수 업무 구조는 JSON/ESTX에 보관하세요. 원본 검사 DLL의 인증은 미확인입니다.", + ); + return { bytes: pack(files), warnings }; +} +function read(bytes, project) { + const files = new Map([...unpack(bytes)].map(([k, v]) => [k.toLowerCase(), v])); + if (!files.has("global.ini") || !files.has("main.txt")) + throw Error("Global.ini/Main.txt 없는 JDL입니다."); + const main = decoder + .decode(files.get("main.txt")) + .replace(/\x05\s*$/, "") + .split("\t"); + if (main.length !== 3) throw Error("JDL Main.txt 필드 오류"); + project.name = main[1].trim(); + project.options.AutoCalc = false; + project.options.SourceRefresh = false; + const rows = Object.fromEntries( + Object.keys(schema).map((name) => [name, parse(name, files.get(name))]), + ); + const byCode = new Map(); + for (const [file, table, kind] of [ + ["m.txt", "StuffcDat", 5], + ["l.txt", "LaborcDat", 6], + ["e.txt", "EstiMateDat", 101], + ["w.txt", "EstiMateDat", 101], + ["j.txt", "EstiMateDat", 101], + ["unit.txt", "HeadTitleDat", 103], + ["junit.txt", "MachTitleDat", 105], + ["danga.txt", "CostTitleDat", 104], + ]) + for (const r of rows[file]) { + if (byCode.has(r.F_CODE)) throw Error("JDL 자원 코드 중복: " + r.F_CODE); + const row = { + PkID: project.tables[table].length + 1, + Code: r.F_CODE, + Name: r.F_DES, + Size: r.F_SIZE, + Unite: r.F_UNIT, + GC: num(r.F_MPRICE), + NC: num(r.F_LPRICE), + JC: num(r.F_EPRICE), + SC: 0, + }; + row.TC = row.InputTC = row.GC + row.NC + row.JC; + row.ValueIndex = 4; + project.tables[table].push(row); + byCode.set(row.Code, { row, kind }); + } + const headings = new Set(); + let pk = 0; + for (const group of rows["gongjong.txt"]) { + if (headings.has(group.F_CODE)) throw Error("JDL 공종 코드 중복"); + headings.add(group.F_CODE); + project.tables.EstMainDat.push({ + PkID: ++pk, + Level: 1, + Code: group.F_CODE, + Name: group.F_DES, + GC: group.F_MPRICE, + NC: group.F_LPRICE, + JC: group.F_EPRICE, + SC: 0, + TC: group.F_MPRICE + group.F_LPRICE + group.F_EPRICE, + }); + for (const r of rows["mainpart.txt"].filter((r) => r.F_CODE === group.F_CODE)) { + const source = byCode.get(r.F_CODE1); + if (!source) throw Error("JDL 연결 자원 없음: " + r.F_CODE1); + const row = { + PkID: ++pk, + Code: r.F_CODE1, + Name: r.F_DES, + Size: r.F_SIZE, + Unite: r.F_UNIT, + GN: r.F_QQTY, + GU: r.F_MPRICE, + NU: r.F_LPRICE, + JU: r.F_EPRICE, + SU: 0, + LinkIndex: source.kind, + CalcIndex: 0, + }; + row.GC = Math.trunc(row.GN * row.GU); + row.NC = Math.trunc(row.GN * row.NU); + row.JC = Math.trunc(row.GN * row.JU); + row.SC = 0; + row.TC = row.GC + row.NC + row.JC; + row.TU = row.GU + row.NU + row.JU; + project.tables.EstMainDat.push(row); + } + } + if (rows["mainpart.txt"].some((r) => !headings.has(r.F_CODE))) throw Error("JDL 부모 공종 없음"); + const direct = project.tables.EstMainDat.filter((r) => !r.Level), + sum = (k) => direct.reduce((a, r) => a + num(r[k]), 0); + project.tables.EstMainDat.push({ + PkID: ++pk, + Name: "순공사비", + LinkIndex: 54, + GC: sum("GC"), + NC: sum("NC"), + JC: sum("JC"), + SC: 0, + TC: sum("TC"), + }); + if (files.has("costaccount.txt")) { + const lines = decoder + .decode(files.get("costaccount.txt")) + .split(/\r?\n/) + .filter((s) => s.trim()); + for (const line of lines) { + const f = line.replace(/\x05\s*$/, "").split("\t"); + if (f.length < 5) throw Error("JDL 원가 필드 오류"); + project.tables.Wonga.push({ + Gubun: f[0].trim(), + Name: f[1].trim(), + Youl: f[2].trim(), + VSusic: f[3].trim(), + Cost: num(f[4]), + }); + } + } + project.originalJdl = bytes.toString("base64"); + project.importWarnings = [ + "JDL 저장 금액을 보존하기 위해 자동계산을 껐습니다. 복잡한 단가산출식·손료/기성 확장 파일은 원문에 보존되며 자동 변환하지 않습니다.", + ]; + project.calculation = { + errors: [], + warnings: project.importWarnings, + direct: project.tables.EstMainDat.at(-1), + total: project.tables.Wonga.at(-1)?.Cost ?? sum("TC"), + }; + return project; +} +module.exports = { output, read, parse }; diff --git a/tools/est-test/native-actions.js b/tools/est-test/native-actions.js index eb6afee0e..ac8f5b35e 100644 --- a/tools/est-test/native-actions.js +++ b/tools/est-test/native-actions.js @@ -209,7 +209,53 @@ const EstActions = (() => { if (!selected().length) throw Error("레벨을 바꿀 행을 선택하세요."); const level = Number(node.dataset.level); EstApp.change(() => selected().forEach((i) => (activeRows()[i].Level = level))); - } else if (name === "print") EstReport.open(); + } else if (name === "export-xlsx") await EstExchange.exportExcel(); + else if (name === "export-estx") await EstExchange.exportNative(); + else if (name === "export-jdl") await EstExchange.exportJdl(); + else if (name === "import-jdl") EstApp.byId("jdl-file").click(); + else if (name === "export-xml") + form( + "조달청 XML 작성정보", + [ + { name: "company", label: "작성업체명", value: project.exportMeta?.company || "" }, + { + name: "kind", + label: "공사구분", + value: project.exportMeta?.kind || "", + choices: [ + ["", "선택하세요"], + ...[ + "토목공사", + "건축공사", + "설비공사", + "전기공사", + "통신공사", + "조경공사", + "소방공사", + "문화재수리공사", + "국가유산수리공사", + ].map((v) => [v, v]), + ], + }, + { name: "number", label: "공사관리번호", value: project.exportMeta?.number || "" }, + ], + (meta) => { + const output = EstC3r.output(project, meta); + if ( + output.warnings.length && + !confirm( + output.warnings.slice(0, 15).join("\n") + + `\n총 ${output.warnings.length}건. 단가 전개 결과를 XML로 저장할까요?`, + ) + ) + return; + EstApp.change(() => (project.exportMeta = meta), false); + EstApp.download(`${project.name}.xml`, output.xml, "application/xml;charset=utf-8"); + }, + ); + else if (name === "import-xml") EstApp.byId("xml-file").click(); + else if (name === "import-xlsx") EstApp.byId("excel-file").click(); + else if (name === "print") await EstReport.open(); else if (name === "print-document") EstReport.print(); else if (name === "csv") { const rows = activeRows(), @@ -430,6 +476,8 @@ const EstActions = (() => { else await EstBusinessActions.handle(name, node); } function install() { + EstSchedule.install(); + EstExchange.install(); EstBusinessActions.install(); document.addEventListener("click", async (event) => { try { diff --git a/tools/est-test/native-app.js b/tools/est-test/native-app.js index 42dcd10b5..2c7fb0a56 100644 --- a/tools/est-test/native-app.js +++ b/tools/est-test/native-app.js @@ -32,6 +32,7 @@ const EstApp = (() => { escalation: "물가변동", annual: "연차공사", resources: "동원 집계", + schedule: "예정공정표", drawing: "도면·문서", }; const esc = (value) => diff --git a/tools/est-test/native-c3r.js b/tools/est-test/native-c3r.js new file mode 100644 index 000000000..88a1ea2f1 --- /dev/null +++ b/tools/est-test/native-c3r.js @@ -0,0 +1,579 @@ +// Schema: knowledge/original/경쟁사 프로그램/STmate 분석/30_원자료/조달청_공사원가호환규정.xsd (1.0.5). +const EstC3r = (() => { + const kinds = { + 5: "자재", + 6: "노무", + 21: "표준시장단가", + 101: "경비항목", + 103: "일위대가", + 104: "중기단가산출", + 105: "기계경비", + }; + const esc = (value) => { + if (/[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(String(value ?? ""))) + throw Error("XML에 사용할 수 없는 제어문자가 있습니다."); + return String(value ?? "").replace( + /[&<>"']/g, + (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c], + ); + }; + const tag = (name, value) => `<${name}>${esc(value)}`; + const raw = (name, value) => `<${name}>${value}`; + const n = (value) => { + const number = Number(value || 0); + if (!Number.isFinite(number)) throw Error("XML 수치 오류"); + return number; + }; + function text(value, length, label) { + const s = String(value ?? "").replace(/[\r\n\t]/g, " "); + if (s.length > length) throw Error(`${label}: XML 길이 ${length}자 초과`); + return s; + } + function code(value, length = 17) { + const s = String(value || ""); + if (!s || s.length > length || !/^[A-Z0-9]+$/.test(s)) + throw Error(`XML 코드 형식 오류: ${s} (영문 대문자/숫자 ${length}자 이내)`); + return s; + } + const name = (row) => + tag("품명", text(row.Name, 50, "품명")) + + tag("규격", text(row.Size, 100, "규격")) + + tag("단위", text(row.Unite, 10, "단위")); + const amounts = (row) => + tag("재료비", n(row.GC)) + tag("노무비", n(row.NC)) + tag("경비", n(row.JC) + n(row.SC)); + const prices = (row) => + tag("재료비단가", n(row.GU)) + + tag("노무비단가", n(row.NU)) + + tag("경비단가", n(row.JU) + n(row.SU)); + const remarks = (row) => tag("비고", text(row.Bigo || row.Bigo1 || "", 50, "비고")); + const flag = (value) => (n(value) >= 2 ? "true" : "false"); + function output(project, meta) { + if (!meta?.company?.trim() || !meta?.kind) + throw Error("XML 작성업체명과 공사구분을 입력하세요."); + if ( + ![ + "토목공사", + "건축공사", + "설비공사", + "전기공사", + "통신공사", + "조경공사", + "소방공사", + "문화재수리공사", + "국가유산수리공사", + ].includes(meta.kind) + ) + throw Error("XML 공사구분 오류"); + const date = meta.date || new Date().toISOString().slice(0, 10); + if (!/^\d{4}-\d{2}-\d{2}$/.test(date) || Number.isNaN(Date.parse(date))) + throw Error("XML 제작일 오류"); + const source = project.tables, + generated = [], + warnings = []; + const available = new Map( + Object.keys(kinds).map((kind) => [ + Number(kind), + new Set((source[EstNative.sourceTables[kind]] || []).map((row) => row.Code)), + ]), + ); + function requireSource(kind, key) { + if (!available.get(n(kind))?.has(key)) throw Error(`XML 연결 자료 없음: ${kind}/${key}`); + } + const expenseCodes = new Set((source.EstiMateDat || []).map((r) => r.Code)); + let serial = 0; + function expense(row, unit = false) { + let value; + do { + value = "X" + String(++serial).padStart(10, "0"); + } while (expenseCodes.has(value)); + expenseCodes.add(value); + generated.push({ + Code: value, + Name: row.Name, + Size: row.Size, + Unite: row.Unite, + NC: unit ? n(row.NU) : n(row.NC), + GC: unit ? n(row.GU) : n(row.GC), + JC: unit ? n(row.JU) + n(row.SU) : n(row.JC) + n(row.SC), + }); + return value; + } + function detail(row, index) { + let kind = kinds[n(row.LinkIndex)], + key = row.Code, + gn = n(row.GN), + current = { ...row }; + if (!kind || !key || [1, 3].includes(n(row.CalcIndex))) { + kind = "경비항목"; + key = expense(row); + gn = 1; + current.GU = n(row.GC); + current.NU = n(row.NC); + current.JU = n(row.JC) + n(row.SC); + current.SU = 0; + warnings.push(`${row.Code || row.Name || index}: 독립 금액 항목으로 변환`); + } else requireSource(row.LinkIndex, key); + code(key); + return raw( + "세부공종", + tag("세부공종코드", key) + + tag("자원구분", kind) + + tag("계산제외", flag(row.CalcIndex)) + + tag("세부공종번호", index) + + tag("세부공종명", text(row.Name, 50, "세부공종명")) + + tag("규격", text(row.Size, 100, "규격")) + + tag("단위", text(row.Unite, 10, "단위")) + + tag("수량", gn) + + prices(current) + + amounts(row) + + tag("관급", "false") + + tag("자재구분", "구분없음") + + tag("세부공종구분", "일반내역") + + remarks(row), + ); + } + let active = null; + const groups = [], + stack = [], + siblings = new Map(); + function addGroup(name, level, header = null) { + while (stack.length && stack.at(-1).level <= level) stack.pop(); + const parent = stack.at(-1)?.code || "", + number = (siblings.get(parent) || 0) + 1; + if (number > 99 || parent.length >= 20) + throw Error("XML 공종 계층/동일 단계 공종 수 한도 초과"); + siblings.set(parent, number); + active = { name, level, header, code: parent + String(number).padStart(2, "0"), rows: [] }; + groups.push(active); + stack.push(active); + } + for (const row of source.EstMainDat) { + if (n(row.LinkIndex) === 54) break; + if (n(row.Level) > 0) { + addGroup(row.Name, n(row.Level), row); + continue; + } + if (!active) addGroup(project.name, 10); + active.rows.push(row); + } + if (!groups.length) addGroup(project.name, 10); + const boq = groups + .map((group, i) => + raw( + "내역서", + tag("내역서번호", group.code) + + tag("공종구분", "일반공종") + + tag("계산제외", "false") + + tag("공종번호", i + 1) + + tag("공종명", text(group.name, 50, "공종명")) + + amounts( + group.header || { + GC: group.rows.reduce((s, r) => s + (n(r.CalcIndex) < 2 ? n(r.GC) : 0), 0), + NC: group.rows.reduce((s, r) => s + (n(r.CalcIndex) < 2 ? n(r.NC) : 0), 0), + JC: group.rows.reduce( + (s, r) => s + (n(r.CalcIndex) < 2 ? n(r.JC) + n(r.SC) : 0), + 0, + ), + }, + ) + + tag("비고", "") + + group.rows.map((r, j) => detail(r, j + 1)).join(""), + ), + ) + .join(""); + const head = [...EstNative.groups(source.HeadMainDat || []).values()] + .map((group) => { + const title = + source.HeadTitleDat?.find((r) => r.Code === group.rows[0].Code) || group.rows[0]; + return raw( + "일위대가", + tag("일위대가코드", code(group.rows[0].Code, 12)) + + name(title) + + amounts(title) + + tag("비목구분", "합계") + + remarks(title) + + group.rows + .slice(1) + .map((row) => { + let kind = kinds[n(row.LinkIndex)], + key = row.Code, + current = { ...row }; + if (!kind || !key || [1, 3].includes(n(row.CalcIndex))) { + kind = "경비항목"; + key = expense(row); + current.GN = 1; + current.GU = n(row.GC); + current.NU = n(row.NC); + current.JU = n(row.JC) + n(row.SC); + current.SU = 0; + warnings.push(`${group.rows[0].Code}: 연결 없는 구성 항목을 독립 단가로 변환`); + } else requireSource(row.LinkIndex, key); + return raw( + "일위대가상세", + tag("자원구분", kind) + + tag("자원코드", code(key)) + + tag("계산제외", flag(row.CalcIndex)) + + name(row) + + tag("수량", n(current.GN)) + + prices(current) + + amounts(row) + + tag("관급", "false") + + remarks(row), + ); + }) + .join(""), + ); + }) + .join(""); + const costs = [...EstNative.groups(source.CostMainDat || []).values()] + .map((group) => { + const title = + source.CostTitleDat?.find((r) => r.Code === group.rows[0].Code) || group.rows[0]; + return raw( + "중기단가산출", + tag("중기단가산출코드", code(title.Code, 12)) + + name(title) + + amounts(title) + + tag("비목구분", "합계") + + remarks(title) + + tag( + "중기단가산출식", + group.rows + .slice(1) + .map((r) => EstText.clean(r.LineData || "")) + .join("\n"), + ), + ); + }) + .join(""); + const machines = [...EstNative.groups(source.MachMainDat || []).values()] + .map((group) => { + const title = + source.MachTitleDat?.find((r) => r.Code === group.rows[0].Code) || group.rows[0]; + warnings.push(`${title.Code}: 기계경비 상세는 경비항목 단가로 전개`); + return raw( + "기계경비", + tag("기계경비코드", code(title.Code)) + + name(title) + + tag("작업시간", "E0") + + tag("작업장소", "") + + amounts(title) + + remarks(title) + + group.rows + .slice(1) + .filter((r) => n(r.Index) === 0) + .map((row) => { + const key = expense(row); + return raw( + "기계경비상세", + tag("자원구분", "경비항목") + + tag("자원코드", key) + + tag("계산제외", flag(row.CalcIndex)) + + name(row) + + tag("수량", 1) + + prices({ GU: n(row.GC), NU: n(row.NC), JU: n(row.JC) + n(row.SC) }) + + amounts(row) + + remarks(row), + ); + }) + .join(""), + ); + }) + .join(""); + const materials = (source.StuffcDat || []) + .map((row) => + raw( + "자재", + tag("자재코드", code(row.Code, 16)) + + name(row) + + tag("재료비단가", n(row.TC)) + + remarks(row), + ), + ) + .join(""); + const labor = (source.LaborcDat || []) + .map((row) => + raw( + "노무", + tag("노무코드", code(row.Code, 16)) + + name(row) + + tag("노무비단가", n(row.TC)) + + remarks(row), + ), + ) + .join(""); + const expenses = [...(source.EstiMateDat || []), ...generated] + .map((row) => + raw( + "경비항목", + tag("경비항목코드", code(row.Code, 16)) + + tag("경비구분", "견적항목") + + name(row) + + tag("통화구분", "") + + tag("재료비단가", n(row.GC)) + + tag("노무비단가", n(row.NC)) + + tag("경비단가", n(row.JC) + n(row.SC)) + + remarks(row), + ), + ) + .join(""); + const market = (source.SilJukDat || []) + .map((row) => { + const key = code(row.Code, 12); + if (!/^[ACEMT]/.test(key)) throw Error(`표준시장단가 코드 접두어 오류: ${key}`); + return raw( + "실적단가", + tag("항목구분", "표준시장단가") + + tag("공종구분", meta.kind === "건축공사" ? "건축공사" : "토목공사") + + tag("실적공사비코드", key) + + name(row) + + tag("재료비단가", n(row.GC)) + + tag("노무비단가", n(row.NC)) + + tag("경비단가", n(row.JC) + n(row.SC)) + + remarks(row), + ); + }) + .join(""); + const fees = (source.Wonga || []) + .map((row) => + raw( + "제경비항목", + tag("세부공종코드", code(row.Gubun, 12)) + + tag("집계항목여부", row.BaseField ? "true" : "false") + + tag("비목명", text(row.Name, 50, "비목명")) + + tag("적용산식", text(row.VSusic, 200, "원가 수식")) + + tag("적용요율", n(String(row.Youl || 0).replace("%", ""))) + + tag("추가금액", 0) + + tag("산출금액", n(row.Cost)) + + tag("적용산식설명", ""), + ), + ) + .join(""); + const body = + raw("공사제경비", fees) + + raw( + "단일공사내역서", + raw("단일공사정보", tag("공사구분", meta.kind) + tag("공사설명", "")) + + boq + + head + + costs + + machines + + materials + + labor + + expenses + + market, + ); + return { + xml: + '' + + raw( + "공사원가호환규정", + tag("스키마버전", "1.0.5") + + raw( + "작성정보", + tag("작성업체명", text(meta.company, 50, "작성업체명")) + + tag("프로그램명", "EST 독립 웹앱") + + tag("프로그램버전", "est-web-2") + + tag("파일제작일", date) + + tag("파일검증여부", "false") + + tag("건축공사사업내용작성여부", "false"), + ) + + raw( + "공사정보", + tag("공사관리번호", text(meta.number || "", 20, "관리번호")) + + tag("주공사", meta.kind) + + tag("공사명", text(project.name, 100, "공사명")) + + tag("비고", ""), + ) + + raw("공사내역서", body), + ), + warnings: [...new Set(warnings)], + }; + } + function read(source) { + if (/ [...node.children].filter((c) => c.localName === name); + const value = (node, name) => children(node, name)[0]?.textContent || ""; + const amount = (node, name) => { + const raw = value(node, name); + if (raw.trim() === "" || !Number.isFinite(Number(raw))) throw Error(`XML 숫자 오류: ${name}`); + return Number(raw); + }; + const rows = (node, name) => [...node.getElementsByTagName(name)]; + const result = EstNative.empty(); + result.options.SourceRefresh = false; + result.options.AutoCalc = false; // Imported monetary values are authoritative until explicitly recalculated. + const info = children(doc.documentElement, "공사정보")[0]; + if (!info) throw Error("XML 공사정보 없음"); + result.name = value(info, "공사명"); + result.exportMeta = { + company: value(children(doc.documentElement, "작성정보")[0], "작성업체명"), + kind: value(info, "주공사"), + number: value(info, "공사관리번호"), + }; + const fields = (node) => ({ + Name: value(node, "품명"), + Size: value(node, "규격"), + Unite: value(node, "단위"), + Bigo: value(node, "비고"), + }); + const money = (node) => ({ + GC: amount(node, "재료비"), + NC: amount(node, "노무비"), + JC: amount(node, "경비"), + SC: 0, + TC: amount(node, "재료비") + amount(node, "노무비") + amount(node, "경비"), + }); + const unit = (node) => ({ + GU: amount(node, "재료비단가"), + NU: amount(node, "노무비단가"), + JU: amount(node, "경비단가"), + SU: 0, + TU: amount(node, "재료비단가") + amount(node, "노무비단가") + amount(node, "경비단가"), + }); + const linked = (node) => ({ + Code: code(value(node, "자원코드")), + LinkIndex: Number( + Object.entries(kinds).find(([, v]) => v === value(node, "자원구분"))?.[0] || 0, + ), + GN: amount(node, "수량"), + CalcIndex: ["true", "1"].includes(value(node, "계산제외")) ? 2 : 0, + ...fields(node), + ...money(node), + ...unit(node), + }); + let pk = 0; + const contracts = rows(doc.documentElement, "단일공사내역서"); + if (contracts.length !== 1) throw Error("복합공사 XML은 단일공사별 파일로 나눈 뒤 불러오세요."); + const contract = contracts[0]; + const contractGroups = children(contract, "내역서"), + maxDepth = Math.max( + 1, + ...contractGroups.map((group) => value(group, "내역서번호").length / 2), + ); + for (const group of contractGroups) { + result.tables.EstMainDat.push({ + PkID: ++pk, + Level: Math.max(1, maxDepth - value(group, "내역서번호").length / 2 + 1), + Code: value(group, "내역서번호"), + Name: value(group, "공종명"), + ...money(group), + }); + for (const row of children(group, "세부공종")) { + const next = { + PkID: ++pk, + Code: value(row, "세부공종코드"), + Name: value(row, "세부공종명"), + Size: value(row, "규격"), + Unite: value(row, "단위"), + GN: amount(row, "수량"), + ...money(row), + ...unit(row), + CalcIndex: ["true", "1"].includes(value(row, "계산제외")) ? 2 : 0, + LinkIndex: Number( + Object.entries(kinds).find(([, v]) => v === value(row, "자원구분"))?.[0] || 0, + ), + }; + result.tables.EstMainDat.push(next); + } + } + const direct = result.tables.EstMainDat.filter((r) => !r.Level && r.CalcIndex < 2); + result.tables.EstMainDat.push({ + PkID: ++pk, + Name: "순공사비", + LinkIndex: 54, + ...Object.fromEntries( + [...EstMath.components, "TC"].map((k) => [k, direct.reduce((s, r) => s + n(r[k]), 0)]), + ), + }); + for (const [element, table, key] of [ + ["자재", "StuffcDat", "자재코드"], + ["노무", "LaborcDat", "노무코드"], + ["경비항목", "EstiMateDat", "경비항목코드"], + ["실적단가", "SilJukDat", "실적공사비코드"], + ]) + children(contract, element).forEach((row, i) => { + const amounts = + element === "자재" + ? { GC: amount(row, "재료비단가") } + : element === "노무" + ? { NC: amount(row, "노무비단가") } + : { + GC: amount(row, "재료비단가"), + NC: amount(row, "노무비단가"), + JC: amount(row, "경비단가"), + }; + const tc = Object.values(amounts).reduce((a, b) => a + b, 0); + result.tables[table].push({ + PkID: i + 1, + Code: code(value(row, key)), + ...fields(row), + ...amounts, + TC: tc, + InputTC: tc, + ValueIndex: 4, + }); + }); + for (const [element, main, title, key] of [ + ["일위대가", "HeadMainDat", "HeadTitleDat", "일위대가코드"], + ["기계경비", "MachMainDat", "MachTitleDat", "기계경비코드"], + ]) + children(contract, element).forEach((row, i) => { + const header = { Code: code(value(row, key)), Index: i + 1, ...fields(row), ...money(row) }; + result.tables[title].push({ ...header }); + result.tables[main].push({ PkID: result.tables[main].length + 1, ...header }); + children(row, element + "상세").forEach((child) => + result.tables[main].push({ + PkID: result.tables[main].length + 1, + Index: 0, + TitleCode: header.Code, + ...linked(child), + }), + ); + }); + children(contract, "중기단가산출").forEach((row, i) => { + const header = { + Code: code(value(row, "중기단가산출코드")), + Index: i + 1, + ...fields(row), + ...money(row), + }; + result.tables.CostTitleDat.push({ ...header }); + result.tables.CostMainDat.push({ PkID: result.tables.CostMainDat.length + 1, ...header }); + result.tables.CostMainDat.push({ + PkID: result.tables.CostMainDat.length + 1, + Index: 0, + TitleCode: header.Code, + LineData: value(row, "중기단가산출식"), + ...money(row), + CalcIndex: 1, + }); + }); + const fees = children(doc.documentElement, "공사내역서")[0]; + for (const row of children(children(fees, "공사제경비")[0] || fees, "제경비항목")) + result.tables.Wonga.push({ + Gubun: value(row, "세부공종코드"), + Name: value(row, "비목명"), + Youl: value(row, "적용요율"), + VSusic: value(row, "적용산식"), + Cost: amount(row, "산출금액"), + }); + result.originalC3r = source; + result.importWarnings = [ + "XML의 저장 금액을 보존하기 위해 자동계산을 껐습니다. 재계산 전 원본 상세·산식을 확인하세요.", + "XML 원문은 프로젝트에 보존됩니다. 제출기관 인증/등록 검사는 별도입니다.", + ]; + result.calculation = { + errors: [], + warnings: result.importWarnings, + direct: result.tables.EstMainDat.at(-1), + total: result.tables.Wonga.at(-1)?.Cost ?? result.tables.EstMainDat.at(-1).TC, + }; + return EstModel.validate(result); + } + return { output, read }; +})(); +if (typeof module !== "undefined" && module.exports) module.exports = EstC3r; diff --git a/tools/est-test/native-engine.js b/tools/est-test/native-engine.js index cbb618737..27069f8e5 100644 --- a/tools/est-test/native-engine.js +++ b/tools/est-test/native-engine.js @@ -117,11 +117,28 @@ const EstNative = (() => { errors = [], warnings = []; for (const key of allTables) tables[key] ||= []; + function saveTitles(table, calculated) { + const byCode = new Map(calculated.map((row) => [row.Code, row])); + const existing = new Set(tables[table].map((row) => row.Code)); + tables[table] = tables[table] + .map((row) => { + const result = byCode.get(row.Code); + if (!result) return row; + const merged = { ...row, ...result }; + // Main-row identities belong to a different native table/index. + for (const key of ["PkID", "Index", "RecNo", "Flag"]) { + if (Object.hasOwn(row, key)) merged[key] = row[key]; + else delete merged[key]; + } + return merged; + }) + .concat(calculated.filter((row) => !existing.has(row.Code))); + } for (const kind of [5, 6, 21, 101]) for (const row of tables[sourceTables[kind]]) price(row, kind); const machine = EstMachine.calculate(tables.MachMainDat, tables.MachGijunDat, options); tables.MachMainDat = machine.rows; - tables.MachTitleDat = machine.titles; + saveTitles("MachTitleDat", machine.titles); errors.push(...machine.errors); const costs = groups(tables.CostMainDat), heads = groups(tables.HeadMainDat), @@ -219,9 +236,10 @@ const EstNative = (() => { errors.push(error.message); } } - tables.CostTitleDat = [...costs.keys()] - .map((code) => completed.get(`104:${code}`)) - .filter(Boolean); + saveTitles( + "CostTitleDat", + [...costs.keys()].map((code) => completed.get(`104:${code}`)).filter(Boolean), + ); for (const [code] of heads) { try { calculateGroup(103, code); @@ -229,9 +247,10 @@ const EstNative = (() => { errors.push(error.message); } } - tables.HeadTitleDat = [...heads.keys()] - .map((code) => completed.get(`103:${code}`)) - .filter(Boolean); + saveTitles( + "HeadTitleDat", + [...heads.keys()].map((code) => completed.get(`103:${code}`)).filter(Boolean), + ); const estimate = EstRows.estimate(tables.EstMainDat, options, resolve); tables.EstMainDat = estimate.rows; errors.push(...estimate.errors); diff --git a/tools/est-test/native-exchange.js b/tools/est-test/native-exchange.js new file mode 100644 index 000000000..49fbbcf9f --- /dev/null +++ b/tools/est-test/native-exchange.js @@ -0,0 +1,279 @@ +const EstExchange = (() => { + function xmlText(buffer) { + const bytes = new Uint8Array(buffer); + if (bytes[0] === 0xff && bytes[1] === 0xfe) + return new TextDecoder("utf-16le", { fatal: true }).decode(bytes); + if (bytes[0] === 0xfe && bytes[1] === 0xff) + return new TextDecoder("utf-16be", { fatal: true }).decode(bytes); + const header = new TextDecoder("ascii").decode(bytes.slice(0, 250)), + declared = header.match(/encoding\s*=\s*["']([^"']+)["']/i)?.[1]?.toLowerCase() || "utf-8", + encoding = ["cp949", "ks_c_5601-1987", "euc-kr", "ms949"].includes(declared) + ? "euc-kr" + : declared; + if (!["utf-8", "utf8", "euc-kr", "utf-16", "utf-16le", "utf-16be"].includes(encoding)) + throw Error("지원하지 않는 XML 인코딩: " + encoding); + return new TextDecoder(encoding, { fatal: true }).decode(bytes); + } + function parse(source) { + if (/ + [...si.getElementsByTagNameNS("*", "t")].map((t) => t.textContent).join(""), + ) + : []; + const tables = {}; + for (const sheet of workbook.getElementsByTagNameNS("*", "sheet")) { + const name = sheet.getAttribute("name"); + if (!EstNative.empty().tables[name] || name.startsWith("Cad")) continue; + const id = sheet.getAttributeNS( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "id", + ), + rel = [...rels.getElementsByTagNameNS("*", "Relationship")].find( + (r) => r.getAttribute("Id") === id, + ), + target = rel?.getAttribute("Target") || ""; + const path = target.startsWith("/") ? target.slice(1) : "xl/" + target; + if (!files[path]) throw Error(`Excel 시트 파일 없음: ${name}`); + const rows = [...parse(files[path]).getElementsByTagNameNS("*", "row")] + .map((row) => { + const values = []; + for (const cell of row.getElementsByTagNameNS("*", "c")) { + const address = cell.getAttribute("r") || "", + letters = address.match(/^[A-Z]+/)?.[0]; + if (!letters) throw Error("Excel 셀 주소 오류"); + const at = [...letters].reduce((n, c) => n * 26 + c.charCodeAt(0) - 64, 0) - 1; + if (at >= 16384) throw Error("Excel 열 범위 오류"); + if (cell.getElementsByTagNameNS("*", "f").length) + throw Error(`${name} ${address}: 수식은 Excel에서 값으로 붙여넣은 뒤 가져오세요.`); + const type = cell.getAttribute("t"), + v = cell.getElementsByTagNameNS("*", "v")[0]?.textContent; + let value; + if (type === "s") { + value = shared[Number(v)]; + if (value == null) throw Error("Excel 공유 문자열 오류"); + } else if (type === "inlineStr") + value = [...cell.getElementsByTagNameNS("*", "t")].map((t) => t.textContent).join(""); + else if (type === "b") value = v === "1"; + else if (type === "e") throw Error(`${name} ${address}: Excel 오류값`); + else if (type === "str") value = v || ""; + else if (v != null) { + value = Number(v); + if (!Number.isFinite(value)) throw Error("Excel 숫자 오류"); + } + values[at] = value; + } + return values; + }) + .filter((row) => row.some((v) => v != null)); + const headers = rows.shift() || []; + if ( + !headers.length || + headers.some( + (k) => + typeof k !== "string" || + !/^[A-Za-z][A-Za-z0-9_]*$/.test(k) || + ["constructor", "prototype"].includes(k), + ) || + new Set(headers).size !== headers.length + ) + throw Error(`${name}: 필드명 행이 올바르지 않습니다.`); + tables[name] = rows.map((values) => + Object.fromEntries(headers.map((k, i) => [k, values[i] ?? null])), + ); + } + if (!Object.keys(tables).length) + throw Error("가져올 EST 표 이름이 없습니다. 이 앱에서 내보낸 Excel 표를 사용하세요."); + return tables; + } + function apply(project, tables) { + const next = structuredClone(project); + for (const [table, rows] of Object.entries(tables)) { + const old = next.tables[table], + byId = new Map(old.filter((r) => r.PkID != null).map((r) => [r.PkID, r])); + const keys = rows[0] ? Object.keys(rows[0]) : []; + if ( + !keys.includes("PkID") && + old.some((r) => Object.values(r).some((v) => v && typeof v === "object")) + ) + throw Error(`${table}: 바이너리를 보존하려면 PkID 열이 필요합니다.`); + const seen = new Set(); + next.tables[table] = rows.map((row) => { + if (row.PkID != null) { + if (seen.has(row.PkID)) throw Error(`${table}: PkID 중복`); + seen.add(row.PkID); + } + const previous = byId.get(row.PkID); + // Keep original blobs and fields deliberately omitted from the workbook. + const merged = { ...previous, ...row }; + if (["StuffcDat", "LaborcDat", "SilJukDat", "EstiMateDat"].includes(table)) { + if ( + previous && + row.TC !== previous.TC && + (row.InputTC ?? previous.InputTC) === previous.InputTC + ) { + merged.InputTC = row.TC; + merged.ValueIndex = 4; + } + for (const component of ["NC", "GC", "JC", "SC"]) + if ( + previous && + row[component] !== previous[component] && + (row["Input" + component] ?? previous["Input" + component]) === + previous["Input" + component] + ) + merged["Input" + component] = row[component]; + if ( + !previous || + Object.keys(row).some((key) => (row[key] ?? null) !== (previous[key] ?? null)) + ) + next.options.SourceRefresh = true; + } + for (const key of keys) + if (previous?.[key] && typeof previous[key] === "object") merged[key] = previous[key]; + if (Object.hasOwn(row, "Code") && (previous?.Code ?? "") !== (row.Code ?? "") && previous) + throw Error("Excel에서 기존 코드를 바꿀 수 없습니다. 앱의 코드 편집을 사용하세요."); + return merged; + }); + } + return EstModel.validate(next); + } + async function exportExcel() { + document.activeElement?.blur(); + const response = await fetch("/api/export-xlsx", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(EstApp.state.project), + }); + if (!response.ok) throw Error((await response.json()).error); + EstApp.download( + `${EstApp.state.project.name}.xlsx`, + await response.blob(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ); + } + async function exportNative() { + document.activeElement?.blur(); + const response = await fetch("/api/export-estx", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(EstApp.state.project), + }); + if (!response.ok) throw Error((await response.json()).error); + EstApp.download( + `${EstApp.state.project.name}_edited.estx`, + await response.blob(), + "application/octet-stream", + ); + } + async function exportJdl() { + document.activeElement?.blur(); + const project = EstApp.state.project, + out = await EstApp.api("/api/export-jdl", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(project), + }); + if (!confirm(out.warnings.slice(0, 15).join("\n") + "\nJDL 기본 표로 내보낼까요?")) return; + EstApp.download( + `${project.name}.jdl`, + Uint8Array.from(atob(out.data), (c) => c.charCodeAt(0)), + "application/octet-stream", + ); + } + function install() { + const jdlInput = document.createElement("input"); + jdlInput.type = "file"; + jdlInput.accept = ".jdl"; + jdlInput.hidden = true; + jdlInput.id = "jdl-file"; + document.body.append(jdlInput); + jdlInput.addEventListener("change", async () => { + const file = jdlInput.files[0], + key = EstApp.state.documentKey, + revision = EstApp.state.revision; + if (!file) return; + try { + const next = await EstApp.api("/api/import-jdl", { + method: "POST", + body: await file.arrayBuffer(), + }); + if (key !== EstApp.state.documentKey || revision !== EstApp.state.revision) + throw Error("작업이 변경되었습니다. 다시 불러오세요."); + if (confirm("현재 작업 대신 JDL 내역서를 여시겠습니까?")) EstApp.setProject(next); + } catch (error) { + EstApp.state.notice = error.message; + EstApp.render(); + } finally { + jdlInput.value = ""; + } + }); + const xmlInput = document.createElement("input"); + xmlInput.type = "file"; + xmlInput.accept = ".xml"; + xmlInput.hidden = true; + xmlInput.id = "xml-file"; + document.body.append(xmlInput); + xmlInput.addEventListener("change", async () => { + const file = xmlInput.files[0], + key = EstApp.state.documentKey, + revision = EstApp.state.revision; + if (!file) return; + try { + const next = EstC3r.read(xmlText(await file.arrayBuffer())); + if (key !== EstApp.state.documentKey || revision !== EstApp.state.revision) + throw Error("작업이 변경되었습니다. 다시 불러오세요."); + if (confirm("현재 작업 대신 XML 내역서를 여시겠습니까?")) EstApp.setProject(next); + } catch (error) { + EstApp.state.notice = error.message; + EstApp.render(); + } finally { + xmlInput.value = ""; + } + }); + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".xlsx"; + input.hidden = true; + input.id = "excel-file"; + document.body.append(input); + input.addEventListener("change", async () => { + const file = input.files[0]; + if (!file) return; + const key = EstApp.state.documentKey, + revision = EstApp.state.revision; + try { + const files = await EstApp.api("/api/import-xlsx", { + method: "POST", + body: await file.arrayBuffer(), + }), + tables = read(files); + if (key !== EstApp.state.documentKey || revision !== EstApp.state.revision) + throw Error("작업이 변경되었습니다. Excel을 다시 불러오세요."); + const next = apply(EstApp.state.project, tables); + if ( + !confirm( + `${Object.entries(tables) + .map(([name, rows]) => `${EstApp.titles[name] || name}: ${rows.length}행`) + .join("\n")}\n현재 작업의 해당 표를 교체합니다.`, + ) + ) + return; + EstApp.change(() => (EstApp.state.project = next)); + } catch (error) { + EstApp.state.notice = error.message; + EstApp.render(); + } finally { + input.value = ""; + } + }); + } + return { read, apply, exportExcel, exportNative, exportJdl, install }; +})(); diff --git a/tools/est-test/native-extra.js b/tools/est-test/native-extra.js index 3d0c15b2b..1cbee51b4 100644 --- a/tools/est-test/native-extra.js +++ b/tools/est-test/native-extra.js @@ -12,6 +12,10 @@ const EstExtra = (() => { return `
${title}${tools}
${body}${note ? `

${note}

` : ""}`; } function render(state) { + if (state.view === "schedule") { + EstSchedule.render(); + return; + } const { project, view } = state, { esc, number, byId } = EstApp; let html = ""; diff --git a/tools/est-test/native-forms.js b/tools/est-test/native-forms.js new file mode 100644 index 000000000..08e6b35b1 --- /dev/null +++ b/tools/est-test/native-forms.js @@ -0,0 +1,139 @@ +const EstForms = (() => { + const common = ["Code", "Name", "Size", "Unite"]; + function data(id, project) { + if (id === 109) + return { + columns: ["Code", "Name", "start", "end", "weight", "progress"], + rows: EstSchedule.calculate(project).rows, + }; + const table = { + 1: "EstMainDat", + 117: "EstMainDat", + 2: "HeadMainDat", + 115: "HeadMainDat", + 3: "CostMainDat", + 4: "MachMainDat", + 119: "MachGijunDat", + 101: "EstiMateDat", + 21: "SilJukDat", + 5: "StuffcDat", + 6: "LaborcDat", + 7: "Wonga", + 22: "Wonga", + }[id]; + if (table) { + const columns = + table === "Wonga" + ? ["Gubun", "Name", "Youl", "VSusic", "Cost"] + : table === "CostMainDat" + ? ["Code", "LineData"] + : table === "HeadMainDat" + ? [...common, "GN", "NU", "GU", "JU", "SU", "TC"] + : [...common, "GN", "TU", "TC", "NC", "GC", "JC", "SC"]; + return { table, columns, rows: project.tables[table] || [] }; + } + const totals = { + 106: "EstMainDat", + 118: "EstMainDat", + 103: "HeadMainDat", + 116: "HeadMainDat", + 104: "CostMainDat", + 105: "MachMainDat", + }[id]; + if (totals) { + const rows = + totals === "EstMainDat" + ? (project.tables[totals] || []).filter((r) => Number(r.Level) > 0) + : [...EstNative.groups(project.tables[totals] || []).values()].map((group) => { + const row = { ...group.rows[0] }; + const titleTable = { + HeadMainDat: "HeadTitleDat", + CostMainDat: "CostTitleDat", + MachMainDat: "MachTitleDat", + }[totals]; + const title = project.tables[titleTable]?.find((item) => item.Code === row.Code); + if (title) return { ...row, ...title }; + for (const key of [...EstMath.components, "TC"]) + row[key] = group.rows + .slice(1) + .filter((r) => Number(r.CalcIndex || 0) < 2) + .reduce((n, r) => n + EstMath.num(r[key]), 0); + return row; + }); + return { table: totals, columns: [...common, "NC", "GC", "JC", "SC", "TC"], rows }; + } + if ([107, 108].includes(id)) + return { + columns: ["Code", "Name", "originalGN", "GN", "originalTC", "TC", "difference", "status"], + rows: EstBusiness.changes(project), + }; + const kinds = { + 13: 6, + 110: 5, + 111: 105, + 161: 21, + 162: 101, + 112: 6, + 113: 5, + 114: 105, + 163: 21, + 164: 101, + }; + if (kinds[id]) { + const output = EstResources.calculate(project, [112, 113, 114, 163, 164].includes(id)); + return { + columns: [ + ...([112, 113, 114, 163, 164].includes(id) ? ["WorkCode", "WorkName"] : []), + "Code", + "Name", + "Size", + "Unite", + "quantity", + "amount", + ], + errors: output.errors, + rows: output.rows.filter((r) => r.kind === kinds[id]), + }; + } + if (id === 14) + return { + columns: ["Code", "Name", "formula", "GN", "TargetCode"], + rows: (project.quantitySheets || []).flatMap((sheet) => + EstQuantity.calculate(sheet).items.map((item) => ({ + ...item, + Code: sheet.Code, + Name: item.Name || sheet.Name, + })), + ), + }; + return null; + } + const labels = { + originalGN: "당초 수량", + originalTC: "당초 금액", + difference: "증감", + status: "상태", + quantity: "소요량/시간", + amount: "금액", + start: "시작일", + end: "종료일", + weight: "가중치", + progress: "진도(%)", + WorkCode: "공종 코드", + WorkName: "공종명", + formula: "산식", + TargetCode: "연결 내역", + TU: "단가", + TC: "금액", + NC: "노무비", + GC: "재료비", + JC: "경비", + SC: "산출경비", + }; + function grid(output) { + const { esc } = EstApp, + number = (v) => new Intl.NumberFormat("ko-KR", { maximumFractionDigits: 12 }).format(v); + return `${output.columns.map((key) => ``).join("")}${output.rows.map((row) => `${output.columns.map((key) => ``).join("")}`).join("")}
${esc(labels[key] || EstModel.labels[key] || key)}
${esc(typeof row[key] === "number" ? number(row[key]) : key === "LineData" ? EstText.clean(row[key] || "") : (row[key] ?? ""))}
`; + } + return { data, grid }; +})(); diff --git a/tools/est-test/native-model.js b/tools/est-test/native-model.js index 21f6deaa8..f642b027a 100644 --- a/tools/est-test/native-model.js +++ b/tools/est-test/native-model.js @@ -298,6 +298,7 @@ const EstModel = (() => { } } project.options ||= {}; + EstSchedule.validate(project); if (typeof project.options !== "object" || Array.isArray(project.options)) throw Error("계산 옵션 형식 오류"); const validNumber = (value, label) => { @@ -312,6 +313,40 @@ const EstModel = (() => { validNumber(project.options.EscDesignTotal, "조사 도급액"); if (project.options.EscDesignTotal < 0) throw Error("조사 도급액은 0 이상입니다."); } + if (project.printSettings != null) { + const s = project.printSettings; + if (typeof s !== "object" || Array.isArray(s)) throw Error("인쇄 설정 형식 오류"); + for (const [key, min, max] of [ + ["margin", 0, 50], + ["font", 6, 20], + ["rowsPerPage", 1, 100], + ["startPage", 1, 99999], + ]) + if (s[key] != null) { + validNumber(s[key], key); + if ( + s[key] < min || + s[key] > max || + (["rowsPerPage", "startPage"].includes(key) && !Number.isInteger(s[key])) + ) + throw Error("인쇄 설정 범위 오류"); + } + if ( + (s.paper != null && !["A3", "A4"].includes(s.paper)) || + (s.orientation != null && !["landscape", "portrait"].includes(s.orientation)) + ) + throw Error("인쇄 용지/방향 오류"); + for (const key of ["form", "company", "notes"]) + if (s[key] != null && (typeof s[key] !== "string" || s[key].length > 100000)) + throw Error("인쇄 문구 형식 오류"); + } + if (project.documents != null) { + if (typeof project.documents !== "object" || Array.isArray(project.documents)) + throw Error("문서 본문 형식 오류"); + for (const [key, value] of Object.entries(project.documents)) + if (!/^(current|\d+)$/.test(key) || typeof value !== "string" || value.length > 100000) + throw Error("문서 본문 형식 오류"); + } for (const key of ["quantities", "progress", "annual", "drawings"]) { if (project[key] === undefined) project[key] = []; if (!Array.isArray(project[key])) throw Error(`${key} 형식 오류`); diff --git a/tools/est-test/native-report.js b/tools/est-test/native-report.js index 2580a9126..288b4bfb9 100644 --- a/tools/est-test/native-report.js +++ b/tools/est-test/native-report.js @@ -1,10 +1,34 @@ const EstReport = (() => { + let catalog = []; + function setting() { + const project = EstApp.state.project, + settings = project.printSettings || {}; + return { + form: "current", + paper: "A4", + orientation: "landscape", + margin: 12, + font: 9, + company: "", + startPage: 1, + rowsPerPage: 30, + notes: "", + ...settings, + }; + } function preview(all = false) { const { state, esc } = EstApp; + const settings = setting(), + form = catalog.find((row) => String(row.Index) === settings.form), + output = form ? EstForms.data(form.Index, state.project) : null; const number = (value) => new Intl.NumberFormat("ko-KR", { maximumFractionDigits: 12 }).format(Number(value) || 0); const root = document.createElement("div"); - if (state.project.tables[state.view]) { + if (output) root.innerHTML = EstForms.grid(output); + else if (form) { + const total = state.project.calculation?.total || 0; + root.innerHTML = `
${esc(settings.notes)}
${[8, 9].includes(form.Index) ? `

공사금액 ${number(total)}원

${esc(settings.company)}

` : ""}`; + } else if (state.project.tables[state.view]) { const rows = all ? state.project.tables[state.view].map((row, index) => ({ row, index })) : EstApp.visibleRows(); @@ -50,7 +74,7 @@ const EstReport = (() => { root .querySelectorAll("[contenteditable]") .forEach((node) => node.removeAttribute("contenteditable")); - const title = EstApp.titles[state.view] || state.view; + const title = form?.Name2 || EstApp.titles[state.view] || state.view; const detail = state.view === "quantity" ? state.project.quantitySheets?.find((sheet) => sheet.Code === state.quantitySheet)?.Name @@ -61,30 +85,118 @@ const EstReport = (() => { ?.Name : ""; const totals = state.project.calculation; - const errors = totals?.errors || []; + const errors = [...(totals?.errors || []), ...(output?.errors || [])]; const source = `${esc(state.project.name)} · ${esc(title)} -

${esc(state.project.name)}

${esc(title)}

${detail ? `

${esc(detail)}

` : ""}${totals ? `
순공사비 ${number(totals.direct?.TC)}원 · 도급액 ${number(totals.total)}원
` : ""} - ${root.innerHTML}${errors.length ? `

계산 오류 ${errors.length}건: ${esc(errors.join(" / "))}

` : ""}`; + ${paginate(root, settings)}${errors.length ? `

계산 오류 ${errors.length}건: ${esc(errors.join(" / "))}

` : ""}`; EstApp.byId("print-frame").srcdoc = source; } - function open() { + function paginate(root, settings) { + const table = root.querySelector("table"); + if (!table || root.querySelectorAll("table").length !== 1) return root.innerHTML; + const rows = [...table.querySelectorAll("tbody>tr")], + pages = []; + for (let at = 0; at < rows.length; at += settings.rowsPerPage) { + const copy = table.cloneNode(false); + copy.innerHTML = table.querySelector("thead")?.outerHTML || ""; + const body = document.createElement("tbody"); + rows.slice(at, at + settings.rowsPerPage).forEach((row) => body.append(row.cloneNode(true))); + copy.append(body); + pages.push( + `
${copy.outerHTML}
`, + ); + } + return pages.join(""); + } + async function open() { + catalog = await EstApp.api("/api/print-catalog"); const table = EstApp.state.project.tables[EstApp.state.view]; + const s = setting(), + { esc } = EstApp; EstApp.showDialog( "인쇄 미리보기", `
+ + + + ${[ + ["margin", "여백(mm)", 0, 50], + ["font", "글자(pt)", 6, 20], + ["rowsPerPage", "페이지당 행", 1, 100], + ["startPage", "시작 쪽", 1, 99999], + ] + .map( + ([key, label, min, max]) => + ``, + ) + .join("")} + ${table ? '' : ""} -
+ + `, ); + EstApp.byId("dialog-content") + .querySelectorAll("[data-print],#print-paper,#print-orientation,#print-form") + .forEach((node) => + node.addEventListener("change", () => { + const key = node.dataset.print || node.id.slice(6), + value = node.type === "number" ? Number(node.value) : node.value; + try { + if ( + node.type === "number" && + (!Number.isFinite(value) || + value < Number(node.min) || + value > Number(node.max) || + (["rowsPerPage", "startPage"].includes(key) && !Number.isInteger(value))) + ) + throw Error("인쇄 설정 범위 오류"); + EstApp.change(() => { + const next = { ...setting(), [key]: value }; + if (key === "form") { + const original = catalog.find((row) => String(row.Index) === value); + if (original) { + next.rowsPerPage = Math.max( + 1, + Math.min(100, Number(original.PrintLineCount) || 30), + ); + next.margin = Math.min(50, Number(original.PrintLeft) || 12); + } + } + if (key === "notes") { + EstApp.state.project.documents ||= {}; + EstApp.state.project.documents[next.form] = value; + } + if (key === "form") next.notes = EstApp.state.project.documents?.[value] || ""; + EstApp.state.project.printSettings = next; + }, false); + if (key === "form") + EstApp.byId("dialog-content").querySelector("[data-print=notes]").value = + EstApp.state.project.printSettings.notes; + preview(EstApp.byId("print-scope")?.value === "all"); + EstApp.byId("print-error").textContent = ""; + } catch (error) { + EstApp.byId("print-error").textContent = error.message; + node.value = setting()[key]; + } + }), + ); EstApp.byId("print-scope")?.addEventListener("change", (event) => preview(event.target.value === "all"), ); diff --git a/tools/est-test/native-resources.js b/tools/est-test/native-resources.js index 3624ff613..3189fe6a3 100644 --- a/tools/est-test/native-resources.js +++ b/tools/est-test/native-resources.js @@ -1,6 +1,6 @@ const EstResources = (() => { const { num, components } = EstMath; - function calculate(input) { + function calculate(input, byWork = false) { const project = EstNative.calculate({ ...structuredClone(input), options: { ...input.options, ResourceTracing: true }, @@ -10,14 +10,17 @@ const EstResources = (() => { costs = EstNative.groups(tables.CostMainDat), output = new Map(), errors = [...project.calculation.errors]; + let work = {}, + workKey = ""; function record(kind, code, quantity, amount = null) { const source = (tables[EstNative.sourceTables[kind]] || []).find((row) => row.Code === code); if (!source) { errors.push(`동원 원천 없음: ${kind}/${code}`); return; } - const id = `${kind}:${code}`; + const id = `${workKey}${kind}:${code}`; const row = output.get(id) || { + ...work, Code: code, Name: source.Name, Size: source.Size, @@ -63,8 +66,9 @@ const EstResources = (() => { const amount = num(row[key]) * multiplier * rates[key]; if (!amount) continue; const [kind, Code, Name] = labels[key], - id = `${kind}:${Code}`; + id = `${workKey}${kind}:${Code}`; const item = output.get(id) || { + ...work, Code, Name, Size: "금액 입력분", @@ -176,7 +180,7 @@ const EstResources = (() => { const source = tables[EstNative.sourceTables[kind]]?.find((row) => row.Code === code); record(kind, code, multiplier * weightedRate(source, rates)); } - for (const row of tables.EstMainDat) { + for (const [index, row] of tables.EstMainDat.entries()) { if (num(row.LinkIndex) === 54) break; if ( num(row.Level) > 0 || @@ -184,6 +188,10 @@ const EstResources = (() => { [51, 52, 68].includes(num(row.LinkIndex)) ) continue; + if (byWork) { + workKey = `${index}:`; + work = { WorkCode: row.Code || "", WorkName: row.Name || "" }; + } if (!EstNative.sourceTables[num(row.LinkIndex)]) { money(row, 1, { NC: 1, GC: 1, JC: 1, SC: 1 }); continue; diff --git a/tools/est-test/native-schedule.js b/tools/est-test/native-schedule.js new file mode 100644 index 000000000..f376386af --- /dev/null +++ b/tools/est-test/native-schedule.js @@ -0,0 +1,103 @@ +const EstSchedule = (() => { + function day(value) { + if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) + throw Error("공정 날짜는 YYYY-MM-DD 형식입니다."); + const time = Date.parse(value + "T00:00:00Z"); + if (!Number.isFinite(time) || new Date(time).toISOString().slice(0, 10) !== value) + throw Error("공정 날짜 오류"); + return time / 86400000; + } + function validate(project) { + if (project.schedule == null) return; + if (!Array.isArray(project.schedule) || project.schedule.length > 10000) + throw Error("공정표 형식/행 수 오류"); + const ids = new Set(); + for (const row of project.schedule) { + if (!row || typeof row.id !== "string" || !row.id || ids.has(row.id)) + throw Error("공정 항목 ID 오류"); + ids.add(row.id); + for (const key of ["Code", "Name", "start", "end"]) + if (typeof row[key] !== "string" || row[key].length > 1000) throw Error("공정 문자열 오류"); + for (const key of ["weight", "progress"]) + if ( + typeof row[key] !== "number" || + !Number.isFinite(row[key]) || + row[key] < 0 || + row[key] > Number.MAX_SAFE_INTEGER + ) + throw Error("공정 수치 오류"); + if (row.progress > 100) throw Error("공정 진도는 0~100%입니다."); + if (row.start) day(row.start); + if (row.end) day(row.end); + if (row.start && row.end && day(row.start) > day(row.end)) + throw Error("공정 종료일이 시작일보다 빠릅니다."); + } + } + function calculate(project) { + validate(project); + const rows = project.schedule || [], + dated = rows.filter((r) => r.start && r.end), + first = dated.length ? Math.min(...dated.map((r) => day(r.start))) : 0, + last = dated.length ? Math.max(...dated.map((r) => day(r.end))) : first, + span = last - first + 1, + weight = rows.reduce((sum, r) => sum + r.weight, 0); + return { + rows: rows.map((r) => ({ + ...r, + left: r.start && r.end ? ((day(r.start) - first) / span) * 100 : 0, + width: r.start && r.end ? ((day(r.end) - day(r.start) + 1) / span) * 100 : 0, + })), + weight, + progress: weight ? rows.reduce((sum, r) => sum + r.weight * r.progress, 0) / weight : 0, + start: dated.length ? new Date(first * 86400000).toISOString().slice(0, 10) : "", + end: dated.length ? new Date(last * 86400000).toISOString().slice(0, 10) : "", + }; + } + function render() { + const { esc, number, state } = EstApp, + output = calculate(state.project); + EstApp.byId("work-content").innerHTML = + `
시작 ${esc(output.start)} · 종료 ${esc(output.end)} · 가중치 합계 ${number(output.weight)} · 입력 진도 가중평균 ${number(output.progress)}%

기간과 진도는 직접 입력합니다. 가중치는 공종 금액 또는 사용자가 정한 값을 사용합니다.

${["코드", "공정명", "시작일", "종료일", "가중치", "진도(%)", "기간", "작업"].map((h) => ``).join("")}${output.rows.map((r, i) => `${["Code", "Name", "start", "end", "weight", "progress"].map((k) => ``).join("")}`).join("")}
${h}
`; + } + function install() { + document.addEventListener("click", (event) => { + const node = event.target.closest("[data-action]"); + if (!node) return; + const p = EstApp.state.project; + if (node.dataset.action === "schedule-add") + EstApp.change(() => { + p.schedule ||= []; + const row = p.tables.EstMainDat.find( + (r) => r.Code && !Number(r.Level) && !p.schedule.some((s) => s.Code === r.Code), + ); + p.schedule.push({ + id: crypto.randomUUID(), + Code: row?.Code || "", + Name: row?.Name || "새 공정", + start: "", + end: "", + weight: Math.max(0, Number(row?.TC) || 0), + progress: 0, + }); + }, false); + if (node.dataset.action === "schedule-delete") + EstApp.change(() => p.schedule.splice(Number(node.dataset.index), 1), false); + }); + document.addEventListener("change", (event) => { + const node = event.target; + if (!node.dataset.schedule) return; + const p = structuredClone(EstApp.state.project), + row = p.schedule[Number(node.dataset.index)], + key = node.dataset.schedule; + try { + row[key] = ["weight", "progress"].includes(key) ? Number(node.value) : node.value; + validate(p); + EstApp.change(() => (EstApp.state.project = p), false); + } catch (error) { + EstApp.state.notice = error.message; + EstApp.render(); + } + }); + } + return { validate, calculate, render, install }; +})(); diff --git a/tools/est-test/print-catalog.json b/tools/est-test/print-catalog.json new file mode 100644 index 000000000..ed102c8bf --- /dev/null +++ b/tools/est-test/print-catalog.json @@ -0,0 +1,4981 @@ +[ + { + "Index": 0, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 0, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "주요심사내용", + "Name2": "주요심사내용", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 5.5, + "PrintTitleMargin": 5.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 0, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 0.1, + "PrintTitleHorGab": 0.1, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.6, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.6, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.2, + "PrintNameWidth": 1.5, + "PrintNameHeight_": 4.2, + "PrintNameWidth_": 1.5, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.2, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.2, + "PrintCostWidth_": 1.5, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 20.0, + "Bottom": 10.0, + "Left": 25.0, + "Right": 10.0, + "Top_": 20.0, + "Bottom_": 10.0, + "Left_": 25.0, + "Right_": 10.0, + "PrintTop": 20.0, + "PrintBottom": 10.0, + "PrintLeft": 10.0, + "PrintRight": 10.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 10.0, + "PrintLineCount": 15, + "PrintColumnCount": 150, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 10.0, + "PrintLineCount_": 15, + "PrintColumnCount_": 10 + }, + { + "Index": 7, + "Level": 1, + "RecNo": 30, + "UsesRecNo": 1, + "PageNo": 1, + "CurrentPage": 1, + "TotalPage": 1, + "ProjectName": "다산소프트 EST main data", + "Name": "원가계산서", + "Name2": "원가계산서", + "EXPLEAN": 30, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 3,152B, SHA-256 9BB29AD9DD510337D5671575975DB7E6E2D30F262589A949AE92637C64BBF42D, head 06 19 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.1, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.1, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.1, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.1, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 80.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 7.6, + "PrintLineCount": 30, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 7.6, + "PrintLineCount_": 30, + "PrintColumnCount_": 10 + }, + { + "Index": 106, + "Level": 1, + "RecNo": 35, + "UsesRecNo": 1, + "PageNo": 2, + "CurrentPage": 1, + "TotalPage": 1, + "ProjectName": "다산소프트 EST main data", + "Name": "내역서총괄표", + "Name2": "내역서총괄표", + "EXPLEAN": 33, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 4,385B, SHA-256 7B96B3312D63324A9C7F0DCC2CE40B0ED558C9A0DF1D9FC4280C0C1C9EBEE1A8, head 06 1B 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 1, + "Level": 1, + "RecNo": 35, + "UsesRecNo": 22, + "PageNo": 3, + "CurrentPage": 2, + "TotalPage": 2, + "ProjectName": "다산소프트 EST main data", + "Name": "내역서", + "Name2": "내 역 서", + "EXPLEAN": 33, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 6,934B, SHA-256 CB306D9789C5ECA761BB1AA2626E1A28303F43520E128DFB96D34B14BE9B7E51, head 06 16 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 103, + "Level": 1, + "RecNo": 434, + "UsesRecNo": 1, + "PageNo": 5, + "CurrentPage": 21, + "TotalPage": 59, + "ProjectName": "다산소프트 EST main data", + "Name": "일위대가총괄표", + "Name2": "일위대가총괄표", + "EXPLEAN": 1230, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 4,417B, SHA-256 BD19E7FCDC0339AFC42A58C0EF6FEFAE14391994A86E4FE4E5017585D9203E70, head 06 1C 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 2, + "Level": 1, + "RecNo": 4009, + "UsesRecNo": 3991, + "PageNo": 64, + "CurrentPage": 149, + "TotalPage": 421, + "ProjectName": "다산소프트 EST main data", + "Name": "일위대가", + "Name2": "일 위 대 가", + "EXPLEAN": 8839, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 6,589B, SHA-256 26218A60B57B882EC2ACCA0481BEBB7A5F252DF9D2B66B68B8A850B238A1C4FA, head 06 17 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 0, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 2, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 104, + "Level": 1, + "RecNo": 635, + "UsesRecNo": 1, + "PageNo": 485, + "CurrentPage": 22, + "TotalPage": 44, + "ProjectName": "다산소프트 EST main data", + "Name": "단가산출총괄표", + "Name2": "단가산출총괄표", + "EXPLEAN": 1306, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 4,408B, SHA-256 67F6C2F99014D494CF2C6A5325C202169140D942DB571CEE44F509BE034660F3, head 06 1C 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 80.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 10.0, + "PrintLineHeight": 7.65, + "PrintLineCount": 30, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 10.0, + "PrintLineHeight_": 7.7, + "PrintLineCount_": 30, + "PrintColumnCount_": 10 + }, + { + "Index": 3, + "Level": 1, + "RecNo": 29748, + "UsesRecNo": 29697, + "PageNo": 529, + "CurrentPage": 504, + "TotalPage": 1226, + "ProjectName": "다산소프트 EST main data", + "Name": "단가산출", + "Name2": "단 가 산 출", + "EXPLEAN": 66084, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 2,618B, SHA-256 E3DFDD1BF4CD0F9837CB8FE59F33CF9B6C61C9CDF0DCD5C1DF9578C3A4BCC00B, head 06 17 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 0, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 2, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 80.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 10.0, + "PrintLineHeight": 4.2, + "PrintLineCount": 54, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 10.0, + "PrintLineHeight_": 7.1, + "PrintLineCount_": 32, + "PrintColumnCount_": 10 + }, + { + "Index": 119, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 1755, + "CurrentPage": 1, + "TotalPage": 1, + "ProjectName": "다산소프트 EST main data", + "Name": "기계경비적용기준", + "Name2": "기계경비 적용기준", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 3.7, + "PrintNameWidth_": 1.2, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 3.7, + "PrintCostWidth_": 1.2, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 7.0, + "PrintLineHeight": 6.5, + "PrintLineCount": 7, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 6.5, + "PrintLineCount_": 7, + "PrintColumnCount_": 10 + }, + { + "Index": 105, + "Level": 1, + "RecNo": 14, + "UsesRecNo": 1, + "PageNo": 1756, + "CurrentPage": 1, + "TotalPage": 47, + "ProjectName": "다산소프트 EST main data", + "Name": "기계경비총괄표", + "Name2": "기계경비총괄표", + "EXPLEAN": 984, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 4,044B, SHA-256 18E3837A953A2315B510EFFED5D4E0884F032228C115689DD7797430FD4193E0, head 06 1C 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 1, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 4, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 1803, + "CurrentPage": 1, + "TotalPage": 233, + "ProjectName": "다산소프트 EST main data", + "Name": "기계경비산출근거", + "Name2": "기계경비산출근거", + "EXPLEAN": 4886, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 4,026B, SHA-256 7715FA6BDC9349C4D313BC0740C7B952EC0A9FE09A036D172ADE4251CA4E0251, head 06 17 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 2, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 101, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 2036, + "CurrentPage": 1, + "TotalPage": 2, + "ProjectName": "다산소프트 EST main data", + "Name": "견적 및 기타단가", + "Name2": "견적 및 기타단가", + "EXPLEAN": 30, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 6,505B, SHA-256 0269661044C665442C3F122EF165601CFDCA8AD0D3447186B68C06043C16886C, head 06 1B 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 21, + "Level": 1, + "RecNo": 1850, + "UsesRecNo": 1, + "PageNo": 2038, + "CurrentPage": 89, + "TotalPage": 145, + "ProjectName": "다산소프트 EST main data", + "Name": "표준시장단가", + "Name2": "표준시장단가", + "EXPLEAN": 3038, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 5,578B, SHA-256 64A167E8F178D33E764FA3D4F4144D5308DA8E8ECA91587F42427F3CC07F2484, head 06 19 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 21, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 5, + "Level": 1, + "RecNo": 16495, + "UsesRecNo": 1, + "PageNo": 2183, + "CurrentPage": 1, + "TotalPage": 2793, + "ProjectName": "다산소프트 EST main data", + "Name": "자재단가", + "Name2": "자 재 단 가", + "EXPLEAN": 55857, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 9,591B, SHA-256 5E8FF3D5A26DE1B023310E9ED04FAAEE88839A9AEA5B4FD076B0E6D2939CE576, head 06 19 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.2, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.2, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 11.0, + "PrintLineHeight": 7.3, + "PrintLineCount": 20, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 11.0, + "PrintLineHeight_": 7.3, + "PrintLineCount_": 21, + "PrintColumnCount_": 10 + }, + { + "Index": 6, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 4976, + "CurrentPage": 1, + "TotalPage": 13, + "ProjectName": "다산소프트 EST main data", + "Name": "노임단가", + "Name2": "노 임 단 가", + "EXPLEAN": 381, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 1, + "PrintGrid": "BLOB 6,547B, SHA-256 5035A33BE5E95F0DD18865469C2DB930BFD7BF01720270F8382E157811DF6BB3, head 06 19 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 0, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 80.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 10.0, + "PrintLineHeight": 7.7, + "PrintLineCount": 30, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 10.0, + "PrintLineHeight_": 7.7, + "PrintLineCount_": 30, + "PrintColumnCount_": 10 + }, + { + "Index": 22, + "Level": 1, + "RecNo": 27, + "UsesRecNo": 1, + "PageNo": 11, + "CurrentPage": 1, + "TotalPage": 1, + "ProjectName": "다산소프트 EST main data", + "Name": "원가계산서(실적)", + "Name2": "원가계산서", + "EXPLEAN": 27, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.1, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.1, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.1, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.1, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 80.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 7.6, + "PrintLineCount": 30, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 7.6, + "PrintLineCount_": 30, + "PrintColumnCount_": 10 + }, + { + "Index": 107, + "Level": 1, + "RecNo": 2, + "UsesRecNo": 1, + "PageNo": 29, + "CurrentPage": 1, + "TotalPage": 2, + "ProjectName": "다산소프트 EST main data", + "Name": "공사비증감대비표", + "Name2": "공사비증감대비표", + "EXPLEAN": 58, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.3, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 10.0, + "PrintLineCount": 15, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 108, + "Level": 1, + "RecNo": 2, + "UsesRecNo": 1, + "PageNo": 31, + "CurrentPage": 1, + "TotalPage": 3, + "ProjectName": "다산소프트 EST main data", + "Name": "공사비,물량,증감대비표", + "Name2": "공사비,물량,증감대비표", + "EXPLEAN": 58, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintGrid": "BLOB 5,363B, SHA-256 BA12A5909EB0FD8DAFA12FCC0772374B64578F6C080A43017CC1B7C1AC828882, head 06 1B 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 109, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 7, + "CurrentPage": 1, + "TotalPage": 1, + "ProjectName": "다산소프트 EST main data", + "Name": "예정공정표", + "Name2": "예정공정표", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 0, + "PrintTitleOutline": 0, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 20.0, + "Bottom": 20.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 20.0, + "Bottom_": 20.0, + "Left_": 25.0, + "Right_": 15.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 25.0, + "PrintRight": 17.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 8, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 8, + "CurrentPage": 1, + "TotalPage": 2, + "ProjectName": "다산소프트 EST main data", + "Name": "갑지", + "Name2": "갑지", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 0, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.6, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.6, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.2, + "PrintNameWidth": 1.5, + "PrintNameHeight_": 4.2, + "PrintNameWidth_": 1.5, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.2, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.2, + "PrintCostWidth_": 1.5, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 20.0, + "Bottom": 20.0, + "Left": 25.0, + "Right": 15.0, + "Top_": 20.0, + "Bottom_": 20.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 9, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 1, + "CurrentPage": 1, + "TotalPage": 2, + "ProjectName": "다산소프트 EST main data", + "Name": "표지", + "Name2": "표지", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 0, + "PrintTitleOutline": 0, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 20.0, + "Bottom": 20.0, + "Left": 25.0, + "Right": 15.0, + "Top_": 20.0, + "Bottom_": 20.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 13, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 27, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원인원", + "Name2": "동원인원", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintGrid": "BLOB 2,991B, SHA-256 5F40014BC6198A161C6D312843747F955E946664BFF30D72E652D70F4EDA1463, head 06 1A 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 9.2, + "PrintLineCount": 25, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 9.2, + "PrintLineCount_": 25, + "PrintColumnCount_": 10 + }, + { + "Index": 110, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 28, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "소요자재", + "Name2": "소요자재", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.3, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 9.2, + "PrintLineCount": 25, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 9.2, + "PrintLineCount_": 25, + "PrintColumnCount_": 10 + }, + { + "Index": 111, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 30, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원장비", + "Name2": "동원장비", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 9.2, + "PrintLineCount": 25, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 9.2, + "PrintLineCount_": 25, + "PrintColumnCount_": 10 + }, + { + "Index": 161, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 31, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원실적", + "Name2": "동원실적", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.6, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.2, + "PrintNameWidth": 1.5, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.2, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 9.2, + "PrintLineCount": 25, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 9.2, + "PrintLineCount_": 25, + "PrintColumnCount_": 10 + }, + { + "Index": 162, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 32, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원견적", + "Name2": "동원견적", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.6, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.2, + "PrintNameWidth": 1.5, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.2, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 9.2, + "PrintLineCount": 25, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 9.2, + "PrintLineCount_": 25, + "PrintColumnCount_": 10 + }, + { + "Index": 112, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 33, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원인원(공종별)", + "Name2": "동원인원(공종별)", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 150 + }, + { + "Index": 113, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 35, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "소요자재(공종별)", + "Name2": "소요자재(공종별)", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 150 + }, + { + "Index": 114, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 38, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원장비(공종별)", + "Name2": "동원장비(공종별)", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.1, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 150 + }, + { + "Index": 163, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 40, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원실적(공종별)", + "Name2": "동원실적(공종별)", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 150 + }, + { + "Index": 164, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 41, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "동원견적(공종별)", + "Name2": "동원견적(공종별)", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.1, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 150 + }, + { + "Index": 115, + "Level": 1, + "RecNo": 1, + "UsesRecNo": 1, + "PageNo": 36, + "CurrentPage": 1, + "TotalPage": 4, + "ProjectName": "다산소프트 EST main data", + "Name": "일위대가(공람용)", + "Name2": "일위대가(공람용)", + "EXPLEAN": 71, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintGrid": "BLOB 6,650B, SHA-256 50C918B6FC828AF1DCEBDA2FD4B80F0FEDAC8E83898B12D7412419429E1D60AB, head 06 17 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 2, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 116, + "Level": 1, + "RecNo": 11, + "UsesRecNo": 1, + "PageNo": 35, + "CurrentPage": 1, + "TotalPage": 1, + "ProjectName": "다산소프트 EST main data", + "Name": "일위대가총괄표(공람용)", + "Name2": "일위대가총괄표(공람용)", + "EXPLEAN": 11, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintGrid": "BLOB 4,449B, SHA-256 42C37C8E5D7DA01FC47CAF509A5C0F19174D479CD079C5332BB17867EC938030, head 06 1C 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 1, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 117, + "Level": 1, + "RecNo": 2, + "UsesRecNo": 1, + "PageNo": 42, + "CurrentPage": 3, + "TotalPage": 4, + "ProjectName": "다산소프트 EST main data", + "Name": "내역서(공람용)", + "Name2": "내역서(공람용)", + "EXPLEAN": 58, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 118, + "Level": 1, + "RecNo": 40, + "UsesRecNo": 1, + "PageNo": 40, + "CurrentPage": 1, + "TotalPage": 2, + "ProjectName": "다산소프트 EST main data", + "Name": "내역서총괄표(공람용)", + "Name2": "내역서총괄표(공람용)", + "EXPLEAN": 58, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintGrid": "BLOB 4,386B, SHA-256 F2053D3D71BEDB797963A9A7EF1DA70D9CD438881F5EA99BB68359977D420EA8, head 06 1B 64 74 6D 45 73 74 44 61 74 61", + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 1, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 30.0, + "Bottom": 15.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 30.0, + "Bottom_": 15.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 11, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 1, + "CurrentPage": 1, + "TotalPage": 2, + "ProjectName": "다산소프트 EST main data", + "Name": "공사설명서", + "Name2": "공사설명서", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 0, + "PrintTitleOutline": 0, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 20.0, + "Bottom": 20.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 20.0, + "Bottom_": 20.0, + "Left_": 25.0, + "Right_": 15.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 25.0, + "PrintRight": 17.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 10, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 3, + "CurrentPage": 1, + "TotalPage": 4, + "ProjectName": "다산소프트 EST main data", + "Name": "시방서", + "Name2": "시방서", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 0, + "PrintTitleOutline": 0, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 0.1, + "PrintTitleHorGab": 0.1, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.4, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 20.0, + "Bottom": 20.0, + "Left": 20.0, + "Right": 15.0, + "Top_": 20.0, + "Bottom_": 20.0, + "Left_": 25.0, + "Right_": 15.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 25.0, + "PrintRight": 17.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 14, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 34, + "CurrentPage": 1, + "TotalPage": 1, + "ProjectName": "다산소프트 EST main data", + "Name": "수량산출서", + "Name2": "수량산출서", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.4, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.0, + "PrintNameWidth": 1.3, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.0, + "PrintCostWidth": 1.3, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 37.0, + "Bottom": 15.0, + "Left": 20.0, + "Right": 20.0, + "Top_": 37.0, + "Bottom_": 15.0, + "Left_": 20.0, + "Right_": 20.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 4.2, + "PrintLineCount": 54, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 4.2, + "PrintLineCount_": 54, + "PrintColumnCount_": 10 + }, + { + "Index": 151, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 0, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "도면", + "Name2": "도 면", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 4.5, + "PrintTitleMargin": 7.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 1, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 1.0, + "PrintTitleHorGab": 3.0, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 0, + "PrintOrint": 0, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.6, + "PrintHeaderHeight_": 4.4, + "PrintHeaderWidth_": 1.4, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.2, + "PrintNameWidth": 1.5, + "PrintNameHeight_": 4.0, + "PrintNameWidth_": 1.3, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.2, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.0, + "PrintCostWidth_": 1.3, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 0, + "Top": 20.0, + "Bottom": 20.0, + "Left": 25.0, + "Right": 17.0, + "Top_": 20.0, + "Bottom_": 20.0, + "Left_": 25.0, + "Right_": 17.0, + "PrintTop": 20.0, + "PrintBottom": 20.0, + "PrintLeft": 20.0, + "PrintRight": 20.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 8.0, + "PrintLineCount": 19, + "PrintColumnCount": 10, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 8.0, + "PrintLineCount_": 19, + "PrintColumnCount_": 10 + }, + { + "Index": 171, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 0, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "심사결과요약서", + "Name2": "심사결과요약서", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 5.5, + "PrintTitleMargin": 5.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 0, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 0.1, + "PrintTitleHorGab": 0.1, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.6, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.6, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.2, + "PrintNameWidth": 1.5, + "PrintNameHeight_": 4.2, + "PrintNameWidth_": 1.5, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.2, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.2, + "PrintCostWidth_": 1.5, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 20.0, + "Bottom": 10.0, + "Left": 25.0, + "Right": 10.0, + "Top_": 20.0, + "Bottom_": 10.0, + "Left_": 25.0, + "Right_": 10.0, + "PrintTop": 20.0, + "PrintBottom": 10.0, + "PrintLeft": 10.0, + "PrintRight": 10.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 10.0, + "PrintLineCount": 15, + "PrintColumnCount": 150, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 10.0, + "PrintLineCount_": 15, + "PrintColumnCount_": 10 + }, + { + "Index": 173, + "Level": 1, + "RecNo": 0, + "UsesRecNo": 0, + "PageNo": 0, + "CurrentPage": 0, + "TotalPage": 0, + "ProjectName": "다산소프트 EST main data", + "Name": "주요심사내용", + "Name2": "주요심사내용", + "EXPLEAN": 0, + "CompanyName": "(주)다산소프트", + "FontName": "굴림체", + "Checked": 0, + "PrintAutoFix": 0, + "Modifyed": 0, + "All": 1, + "Selected": 0, + "HeadMerge": 0, + "PrintViewSync": 1, + "PrintTitleFont": "굴림체", + "PrintTitleHeight": 8.0, + "PrintTitleWidth": 5.5, + "PrintTitleMargin": 5.0, + "PrintTitleShow": 1, + "PrintTitleOutline": 0, + "PrintTitleShadow": 1, + "PrintTitleUnderline": 0, + "PrintTitleBold": 1, + "PrintTitleFirstShow": 0, + "PrintTitleVerGab": 0.1, + "PrintTitleHorGab": 0.1, + "PrintTitleLineWidth": 0.3, + "PrintMethod": 1, + "PrintOrint": 1, + "PrintGongMethod": 0, + "PrintApplyField": 0, + "PrintSumBottom": 0, + "PrintProjectFont": "굴림체", + "PrintProjectHeight": 4.0, + "PrintProjectWidth": 1.5, + "PrintProjectMargin": 1.0, + "PrintProjectShow": 1, + "PrintProjectPos": 0, + "PrintProjectUnderline": 0, + "PrintProjectBold": 0, + "PrintCompanyFont": "굴림체", + "PrintCompanyHeight": 3.0, + "PrintCompanyWidth": 1.2, + "PrintCompanyMargin": 1.0, + "PrintCompanyShow": 1, + "PrintCompanyPos": 1, + "PrintCompanyUnderline": 0, + "PrintCompanyBold": 0, + "PrintPageFont": "굴림체", + "PrintPageKind": 1, + "PrintPageHeight": 3.0, + "PrintPageWidth": 1.2, + "PrintPageMargin": 2.0, + "PrintPageShow": 1, + "PrintPagePos": 0, + "PrintPageBold": 0, + "PrintPageAuto": 1, + "PrintChangeIndexFont": "굴림체", + "PrintChangeIndexHeight": 3.0, + "PrintChangeIndexWidth": 1.2, + "PrintChangeIndexMargin": 2.0, + "PrintChangeIndexShow": 1, + "PrintHeaderFont": "굴림체", + "PrintHeaderHeight": 4.5, + "PrintHeaderWidth": 1.6, + "PrintHeaderHeight_": 4.5, + "PrintHeaderWidth_": 1.6, + "PrintHeaderBold": 0, + "PrintNameFont": "굴림체", + "PrintNameHeight": 4.2, + "PrintNameWidth": 1.5, + "PrintNameHeight_": 4.2, + "PrintNameWidth_": 1.5, + "PrintCostFont": "굴림체", + "PrintCostHeight": 4.2, + "PrintCostWidth": 1.5, + "PrintCostHeight_": 4.2, + "PrintCostWidth_": 1.5, + "PrintLineWidthOut": 0.5, + "PrintLineWidthIn": 0.2, + "PrintGangiFont": "굴림체", + "PrintGangiHeight": 15.0, + "PrintGangiWidth": 10.0, + "PrintGangiMargin": 60.0, + "PrintGangiShow": 1, + "PrintGangiOutLine": 0, + "PrintGangiOutLineShow": 1, + "PrintGangiShadow": 0, + "PrintGangiUnderline": 0, + "PrintGangiBold": 0, + "PrintGangiOrint": 1, + "Top": 20.0, + "Bottom": 10.0, + "Left": 25.0, + "Right": 10.0, + "Top_": 20.0, + "Bottom_": 10.0, + "Left_": 25.0, + "Right_": 10.0, + "PrintTop": 20.0, + "PrintBottom": 10.0, + "PrintLeft": 10.0, + "PrintRight": 10.0, + "PrintHeaderLineHeight": 12.0, + "PrintLineHeight": 10.0, + "PrintLineCount": 15, + "PrintColumnCount": 150, + "PrintZoomRatio": 100, + "PrintAutoZoom": 1, + "PrintHeaderLineHeight_": 12.0, + "PrintLineHeight_": 10.0, + "PrintLineCount_": 15, + "PrintColumnCount_": 10 + } +] diff --git a/tools/est-test/server.cjs b/tools/est-test/server.cjs index ce0173a90..eda2797d9 100644 --- a/tools/est-test/server.cjs +++ b/tools/est-test/server.cjs @@ -5,6 +5,9 @@ const http = require("node:http"), vm = require("node:vm"), crypto = require("node:crypto"); const { importEstx } = require("./import-estx.cjs"); +const exchange = require("./exchange.cjs"); +const { exportEstx } = require("./export-estx.cjs"); +const jdl = require("./jdl.cjs"); const base = __dirname, port = Number(process.env.EST_TEST_PORT || 5175); const dataDirectory = process.env.EST_TEST_DATA_DIR || path.join(base, "data"); @@ -21,6 +24,7 @@ const scripts = [ "native-progress.js", "native-business.js", "native-resources.js", + "native-schedule.js", "native-model.js", "native-verify.js", ]; @@ -51,6 +55,9 @@ const allowed = new Set([ "native-extra.js", "native-settings.js", "native-report.js", + "native-forms.js", + "native-c3r.js", + "native-exchange.js", ...scripts, ]); function reply(res, status, data, type = "application/json; charset=utf-8") { @@ -103,6 +110,10 @@ const server = http.createServer(async (req, res) => { reply(res, 200, JSON.parse(fs.readFileSync(path.join(base, "sample.json"), "utf8"))); return; } + if (req.method === "GET" && url.pathname === "/api/print-catalog") { + reply(res, 200, JSON.parse(fs.readFileSync(path.join(base, "print-catalog.json"), "utf8"))); + return; + } if (req.method === "GET" && url.pathname === "/api/parity") { engine.sample = JSON.parse(fs.readFileSync(path.join(base, "sample.json"), "utf8")); engine.expected = JSON.parse(fs.readFileSync(path.join(base, "parity.json"), "utf8")); @@ -138,6 +149,36 @@ const server = http.createServer(async (req, res) => { reply(res, 200, importEstx(bytes, name)); return; } + if (req.method === "POST" && url.pathname === "/api/import-xlsx") { + reply(res, 200, exchange.readWorkbook(await body(req))); + return; + } + if (req.method === "POST" && url.pathname === "/api/import-jdl") { + const empty = vm.runInContext("EstNative.empty()", engine); + reply(res, 200, calculate(jdl.read(await body(req), empty))); + return; + } + if (req.method === "POST" && url.pathname === "/api/export-jdl") { + const project = calculate(JSON.parse((await body(req)).toString("utf8"))), + out = jdl.output(project); + reply(res, 200, { data: out.bytes.toString("base64"), warnings: out.warnings }); + return; + } + if (req.method === "POST" && url.pathname === "/api/export-xlsx") { + const project = calculate(JSON.parse((await body(req)).toString("utf8"))); + reply( + res, + 200, + exchange.workbook(project), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ); + return; + } + if (req.method === "POST" && url.pathname === "/api/export-estx") { + const project = calculate(JSON.parse((await body(req)).toString("utf8"))); + reply(res, 200, exportEstx(project), "application/octet-stream"); + return; + } if (req.method === "POST" && ["/api/calculate", "/api/save"].includes(url.pathname)) { if (!req.headers["content-type"]?.startsWith("application/json")) { reply(res, 415, { error: "JSON 요청이 필요합니다." });