Files
Aislo/tools/stmate-test/scr_report_opts.js
T

196 lines
7.7 KiB
JavaScript

/* -*- coding: utf-8 -*- */
/**
* 보고서 인쇄 선택 (scr_report_opts.js) — PM_Prt 인쇄 메뉴 옵션 · PM_Prn_Ord 인쇄순서 · PM_Sel_St 집계수준 · wM_Sel_Prn 인쇄영역
* 옵션(F1-51) — 글꼴 · 글자 크기 · 코드필드(SH_TYP1 코드/호표) · 비고(SH_TYP3 코드/호표/가격지) · 번호 자리 맞춤(CH_PRN_COD) · 방향
* 순서·수준(F1-52) — 내보낼 시트 차례(위·아래) · 총괄·집계 공종 단(1~6)
* 영역(F1-53) — 차수(당초 · 변경 1~8차) · 변경분만 · 분할 비율 · 구.시도 시기금 비율 · 엑셀 수식 넣기
* 근거: 지식DB STmate/old/20_분석 20 §2.4(Start8x.Ini 출력 설정) · 34(화면 구조 PM_Prt · wM_Sel_Prn) · 24 §3(Prn_FMLA_INFO)
* 화면 상태 = ctx.uiState.reportOpts · 보고서 묶음은 effective() 로 설정을 덮어 만든 dbf 로 다시 짬
*/
(function (global) {
"use strict";
const esc = (s) =>
String(s ?? "").replace(
/[&<>"]/g,
(c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c],
);
const FONTS = ["굴림체", "돋움체", "바탕체", "맑은 고딕", "궁서체"];
// 시작값 — 공사 설정(Start8x.Ini)에서
function defaults(ini) {
return {
font: String(ini.FONT || "굴림체"),
size: 10,
codeField: String(ini.SH_TYP1 ?? "1"),
remark: String(ini.SH_TYP3 ?? "2"),
padNo: String(ini.CH_PRN_COD ?? "0"),
orient: "auto",
order: null,
level: 2,
changeOnly: false,
splitRate: 100,
fundRate: 100,
formulas: false,
};
}
function state(ctx) {
const ini = (ctx.dbfData && ctx.dbfData.start_ini) || {};
const ui = ctx.uiState;
ui.reportOpts = Object.assign(defaults(ini), ui.reportOpts || {});
return ui.reportOpts;
}
/**
* 설정을 덮은 dbf + 묶음 opts — 출력 설정(SH_TYP1·SH_TYP3·CH_PRN_COD)은 Start8x.Ini 값만 바꿔 같은 짜기 코드를 씀
*/
function effective(ctx) {
const o = state(ctx);
const dbf = Object.assign({}, ctx.dbfData);
dbf.start_ini = Object.assign({}, ctx.dbfData.start_ini || {}, {
SH_TYP1: o.codeField,
SH_TYP3: o.remark,
CH_PRN_COD: o.padNo,
});
const today = new Date();
const pad = (n) => String(n).padStart(2, "0");
const opts = {
order: o.order,
minLevel: Number(o.level) || 2,
level: Number(o.level) || 2,
changeOnly: !!o.changeOnly,
splitRate: Number(o.splitRate),
fundRate: Number(o.fundRate),
date: `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())} ${pad(today.getHours())}:${pad(today.getMinutes())}`,
};
const print = { font: o.font, size: Number(o.size) || 10 };
if (o.orient !== "auto") print.orient = o.orient;
return { dbf, opts, print, formulas: !!o.formulas, key: JSON.stringify(o) };
}
// 인쇄순서 — 저장된 차례 + 새로 생긴 시트(뒤에)
function orderOf(ctx, ids) {
const o = state(ctx);
const base = (o.order || []).filter((id) => ids.includes(id));
return base.concat(ids.filter((id) => !base.includes(id)));
}
function html(ctx, ids, stages) {
const o = state(ctx);
const sel = (name, list, cur) =>
`<select data-ro="${name}">${list.map(([v, l]) => `<option value="${esc(v)}" ${String(cur) === String(v) ? "selected" : ""}>${esc(l)}</option>`).join("")}</select>`;
const ord = orderOf(ctx, ids)
.map(
(id, i) =>
`<div style="display:flex;gap:4px;align-items:center"><span style="width:22px;text-align:right">${i + 1}</span><span style="flex:1">${esc(id)}</span><button class="win-btn" data-ro-up="${esc(id)}">▲</button><button class="win-btn" data-ro-dn="${esc(id)}">▼</button></div>`,
)
.join("");
const st = stages.map((n) => [n, n === 1 ? "당초" : `변경 ${n - 1}차`]);
return `<details id="ro-box" ${ctx.uiState.reportOptsOpen ? "open" : ""} style="margin:4px 0;border:1px solid #ccc;padding:4px">
<summary>인쇄 선택 — 옵션 · 순서 · 영역</summary>
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start">
<fieldset><legend>인쇄 메뉴 옵션</legend>
<div>글꼴 ${sel(
"font",
FONTS.map((f) => [f, f]),
o.font,
)} 크기 ${sel(
"size",
[8, 9, 10, 11, 12].map((s) => [s, s]),
o.size,
)}</div>
<div>코드필드 ${sel(
"codeField",
[
["0", "코드번호"],
["1", "호표"],
],
o.codeField,
)}
비고 ${sel(
"remark",
[
["0", "코드"],
["1", "호표"],
["2", "가격지 위치"],
],
o.remark,
)}</div>
<div>번호 자리 맞춤 ${sel(
"padNo",
[
["0", "안 함"],
["1", "함"],
],
o.padNo,
)}
방향 ${sel(
"orient",
[
["auto", "자동(열 수)"],
["portrait", "세로"],
["landscape", "가로"],
],
o.orient,
)}</div>
</fieldset>
<fieldset><legend>인쇄영역</legend>
<div>차수 ${sel("stage", st, ctx.uiState.reportStage || 1)}
<label><input type="checkbox" data-ro="changeOnly" ${o.changeOnly ? "checked" : ""}> 변경분만(집계표)</label></div>
<div>공종 집계수준 ${sel(
"level",
[1, 2, 3, 4, 5, 6].map((n) => [n, `${n} 단 이상`]),
o.level,
)}</div>
<div>분할 비율 <input data-ro="splitRate" value="${esc(o.splitRate)}" size="4">% · 시기금 비율 <input data-ro="fundRate" value="${esc(o.fundRate)}" size="4">%</div>
<div><label><input type="checkbox" data-ro="formulas" ${o.formulas ? "checked" : ""}> 엑셀 수식 넣기(내역·일위대가·집계 금액)</label></div>
</fieldset>
<fieldset style="min-width:260px"><legend>인쇄순서</legend><div style="max-height:180px;overflow:auto">${ord}</div>
<button class="win-btn" data-ro-reset="1">기본 순서</button></fieldset>
</div></details>`;
}
function bind(ctx, api, ids) {
const o = state(ctx);
const ui = ctx.uiState;
const box = document.getElementById("ro-box");
if (box) box.ontoggle = () => (ui.reportOptsOpen = box.open);
const redo = () => {
ui._reportBook = null;
api.renderWorkspace();
};
document.querySelectorAll("[data-ro]").forEach((el) => {
el.onchange = () => {
const k = el.getAttribute("data-ro");
const v = el.type === "checkbox" ? el.checked : el.value;
if (k === "stage") ui.reportStage = Number(v);
else o[k] = v;
redo();
};
});
const move = (id, d) => {
const list = orderOf(ctx, ids);
const i = list.indexOf(id);
const j = i + d;
if (i < 0 || j < 0 || j >= list.length) return;
[list[i], list[j]] = [list[j], list[i]];
o.order = list;
redo();
};
document
.querySelectorAll("[data-ro-up]")
.forEach((el) => (el.onclick = () => move(el.getAttribute("data-ro-up"), -1)));
document
.querySelectorAll("[data-ro-dn]")
.forEach((el) => (el.onclick = () => move(el.getAttribute("data-ro-dn"), 1)));
document.querySelectorAll("[data-ro-reset]").forEach(
(el) =>
(el.onclick = () => {
o.order = null;
redo();
}),
);
}
global.STMATE_REPORT_OPTS = { defaults, state, effective, orderOf, html, bind };
})(typeof window !== "undefined" ? window : globalThis);