feat(est-test): 기성 환경설정(공통·기성고·원가계산 칸, 적용·취소, Ctrl+U)
- 기성고 화면을 native-progress-view.js 로 옮김 - 코드·레벨·사정금액·노재경 구분(비목순서·실적)·비고란·집계공종 진하게·수량/금액 0 숨김·품명정렬·두줄·바탕색 반영 - Enter 뒤 셀 이동 방향, 취소면 연 때 값으로 되돌림 - 주 메뉴 환경설정은 기성 화면에서 이 창을 엶 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ for (const file of [
|
||||
"native-block-actions.js",
|
||||
"native-extra.js",
|
||||
"native-applied-cost.js",
|
||||
"native-progress-view.js",
|
||||
"native-business-actions.js",
|
||||
"native-forms.js",
|
||||
"native-excel-to-est.js",
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""기성 환경설정: 옵션 기본값·비목 열 순서·표 머리 반영."""
|
||||
|
||||
from test_est_rea_parity import run
|
||||
|
||||
|
||||
def test_defaults_and_part_order():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const d=EstProgressView.get({});
|
||||
const o=(x)=>EstProgressView.parts(EstProgressView.get({Display:{order:x.order},AccomOption:x}));
|
||||
return {code:d.code,sajung:d.sajung,gubun:d.gubun,
|
||||
a:o({order:0}),b:o({order:2,siljuk:true}),c:o({order:3}),d:o({order:5})};
|
||||
})()""",
|
||||
)
|
||||
assert result == {
|
||||
"code": True,
|
||||
"sajung": True,
|
||||
"gubun": False,
|
||||
"a": ["T", "N", "G", "J"],
|
||||
"b": ["N", "G", "J", "S", "T"],
|
||||
"c": ["G", "N", "J", "T"],
|
||||
"d": ["T", "N", "G", "J"],
|
||||
}
|
||||
|
||||
|
||||
def test_view_headers_follow_options_and_zero_hiding():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{Code:'A',Name:'a',GN:10,GU:100},{LinkIndex:54}];
|
||||
const q=EstNative.calculate(p);q.progress=[{id:'r1',name:'1회',inputs:{},locked:false}];
|
||||
const g=globalThis;g.EstApp={...(g.EstApp||{}),esc:String,number:String};
|
||||
g.EstProgressWonga={render:()=>''};
|
||||
const box=(t,tools,body)=>body,button=()=>'',table=(h,rows)=>JSON.stringify({h,rows});
|
||||
const st={progressPage:0,selection:new Set()};
|
||||
const head=(o)=>{q.options.Display=o.d;q.options.AccomOption=o.a;
|
||||
return JSON.parse(EstProgressView.view({...st},q,{box,button,table}).split('<p')[0]);};
|
||||
const plain=head({});
|
||||
const custom=head({d:{code:false,gnZero:false},a:{level:true,sajung:false,gubun:true,gungu:true}});
|
||||
return {plain:plain.h,custom:custom.h,jun:custom.rows[0][3]};
|
||||
})()""",
|
||||
)
|
||||
assert result["plain"][0] == "코드" and "사정금액" in result["plain"]
|
||||
assert result["custom"] == [
|
||||
"레벨",
|
||||
"공종",
|
||||
"도급수량",
|
||||
"전회수량",
|
||||
"금회 입력",
|
||||
"입력방법",
|
||||
"금회금액",
|
||||
"금회 노무",
|
||||
"금회 재료",
|
||||
"금회 경비",
|
||||
"누계수량",
|
||||
"누계금액",
|
||||
"비고",
|
||||
]
|
||||
assert result["jun"] == ""
|
||||
@@ -22,6 +22,7 @@
|
||||
<script defer src="native-excel-to-est.js"></script>
|
||||
<script defer src="native-other-load.js"></script>
|
||||
<script defer src="native-applied-cost.js"></script>
|
||||
<script defer src="native-progress-view.js"></script>
|
||||
<script defer src="native-actions-more.js"></script>
|
||||
<script defer src="native-wonga.js"></script>
|
||||
<script defer src="native-jungsang.js"></script>
|
||||
|
||||
@@ -8,6 +8,7 @@ const EstActionsMore = (() => {
|
||||
typeof EstExcelToEst !== "undefined" && EstExcelToEst,
|
||||
typeof EstOtherLoad !== "undefined" && EstOtherLoad,
|
||||
typeof EstAppliedCost !== "undefined" && EstAppliedCost,
|
||||
typeof EstProgressView !== "undefined" && EstProgressView,
|
||||
].filter(Boolean);
|
||||
function handle(name, state, node) {
|
||||
for (const module of modules()) if (module.handle(name, state, node)) return true;
|
||||
|
||||
@@ -333,7 +333,8 @@ const EstActions = (() => {
|
||||
"REA 샘플 대조",
|
||||
`<p><b>${result.checked}개 중 ${result.passed}개 일치 · 불일치 ${result.failed}개</b></p><p>보관된 EST 샘플의 저장값과 계산 결과를 비교했습니다. 원본 실행 비교는 포함하지 않습니다.</p>${result.errors.length ? `<p>${esc(result.errors.join(" / "))}</p>` : ""}<table><thead><tr><th>영역</th><th>검사 수</th><th>일치</th></tr></thead><tbody>${[...new Set(result.results.map((r) => r.group))].map((group) => `<tr><td>${esc(group)}</td><td>${result.results.filter((r) => r.group === group).length}</td><td>${result.results.filter((r) => r.group === group && r.pass).length}</td></tr>`).join("")}</tbody></table>`,
|
||||
);
|
||||
} else if (name === "settings") EstSettings.show();
|
||||
} else if (name === "settings")
|
||||
EstApp.state.view === "progress" ? EstProgressView.dialog(EstApp.state) : EstSettings.show();
|
||||
else if (name === "raw-line") {
|
||||
const row = activeRows()[Number(node.dataset.row)];
|
||||
showDialog(
|
||||
|
||||
@@ -126,108 +126,7 @@ const EstExtra = (() => {
|
||||
: "현재 작업을 저장한 뒤 당초 내역을 확정하세요.",
|
||||
);
|
||||
} else if (view === "progress") {
|
||||
state.roundId ||= project.progress[0]?.id;
|
||||
const round = project.progress.find((r) => r.id === state.roundId);
|
||||
let body = '<p class="empty">회차를 추가하세요.</p>',
|
||||
tools =
|
||||
button("add-round", "기성 회차 추가") +
|
||||
'<label>원본 기성 표 확인 <input type="file" accept=".estx" data-accom-inspect></label>' +
|
||||
(project.progress.length ? button("clear-progress", "기성 전체 초기화") : "");
|
||||
if (round) {
|
||||
tools +=
|
||||
`<select id="round-choice">${project.progress.map((r) => `<option value="${esc(r.id)}" ${r.id === round.id ? "selected" : ""}>${esc(r.name)}${r.locked ? " (확정)" : ""}</option>`).join("")}</select>` +
|
||||
button(
|
||||
round.locked ? "unlock-round" : "lock-round",
|
||||
round.locked ? "확정 해제(이후 포함)" : "회차 확정·저장",
|
||||
) +
|
||||
button("fill-progress", "전량·비율 일괄 입력") +
|
||||
button("rename-round", "회차 이름") +
|
||||
button("delete-round", "회차 삭제") +
|
||||
button("reset-round-inputs", "회차 입력 초기화") +
|
||||
`<label><input id="progress-components" type="checkbox" ${round.separateComponents !== false ? "checked" : ""} ${round.locked ? "disabled" : ""}> 비목별 계산</label><label>금액 소수자리 <input id="progress-digits" type="number" min="0" max="4" value="${round.amountDigits ?? 0}" ${round.locked ? "disabled" : ""}></label>` +
|
||||
`<label><input id="use-approved" type="checkbox" ${round.approved ? "checked" : ""} ${round.locked ? "disabled" : ""}> 누계에 사정 사용</label>`;
|
||||
let output;
|
||||
try {
|
||||
output =
|
||||
round.locked && round.result ? round.result : EstBusiness.progress(project, round.id);
|
||||
} catch (e) {
|
||||
output = { rows: [], errors: [e.message] };
|
||||
}
|
||||
state.business = output;
|
||||
const page = EstBlockActions.progressPage(state, output.rows);
|
||||
tools +=
|
||||
button("progress-prev", "이전", state.progressPage === 0 ? "disabled" : "") +
|
||||
`<span>${state.progressPage + 1} / ${page.pages}쪽 · ${page.total}행</span>` +
|
||||
button("progress-next", "다음", state.progressPage + 1 >= page.pages ? "disabled" : "");
|
||||
body =
|
||||
table(
|
||||
[
|
||||
"코드",
|
||||
"공종",
|
||||
"도급수량",
|
||||
"전회수량",
|
||||
"금회 입력",
|
||||
"입력방법",
|
||||
"사정 입력",
|
||||
"입력방법",
|
||||
"금회금액",
|
||||
"사정금액",
|
||||
"누계수량",
|
||||
"누계금액",
|
||||
],
|
||||
page.rows.map(({ row, index }) => {
|
||||
const editable = ![54, 55, 58, 59, 62, 67].includes(Number(row.LinkIndex));
|
||||
const key = EstBusiness.rowKey(row, index),
|
||||
input = round.inputs?.[key] || {};
|
||||
const edit = (prefix) =>
|
||||
`<input data-progress="${prefix}" data-key="${esc(key)}" type="number" step="any" value="${input[prefix] ?? row[prefix + "GN"] ?? 0}" ${round.locked || !editable || input[prefix + "Mode"] === "components" ? "disabled" : ""}>` +
|
||||
(input[prefix + "Mode"] === "components"
|
||||
? EstMath.components
|
||||
.map(
|
||||
(field, i) =>
|
||||
`<label>${["노무", "재료", "경비", "실적"][i]}<input data-progress="${prefix + field}" data-key="${esc(key)}" type="number" step="any" value="${input[prefix + field] ?? 0}" ${round.locked ? "disabled" : ""}></label>`,
|
||||
)
|
||||
.join("")
|
||||
: "");
|
||||
const method = (prefix) =>
|
||||
!editable
|
||||
? "자동계산"
|
||||
: `<select data-progress="${prefix}Mode" data-key="${esc(key)}" ${round.locked ? "disabled" : ""}>${[
|
||||
["quantity", "수량"],
|
||||
["amount", "금액"],
|
||||
["ratio", "비율 %"],
|
||||
["components", "비목 금액"],
|
||||
]
|
||||
.map(
|
||||
([v, label]) =>
|
||||
`<option value="${v}" ${v === (input[`${prefix}Mode`] || "quantity") ? "selected" : ""}>${label}</option>`,
|
||||
)
|
||||
.join("")}</select>`;
|
||||
return [
|
||||
esc(row.Code),
|
||||
esc(row.Name),
|
||||
number(row.GN),
|
||||
number(row.JunGN),
|
||||
edit("Gum"),
|
||||
method("Gum"),
|
||||
edit("Sa"),
|
||||
method("Sa"),
|
||||
number(row.GumTC),
|
||||
number(row.SaTC),
|
||||
number(row.TotalGN),
|
||||
number(row.TotalTC),
|
||||
];
|
||||
}),
|
||||
) +
|
||||
`<p class="business-note">${esc((output.errors || []).join(" / "))} 금회 도급액 ${number(output.total)} · 기성비율 ${number(output.ratio)}%</p>`;
|
||||
body += EstProgressWonga.render(state, round, output);
|
||||
}
|
||||
html = box(
|
||||
"기성고",
|
||||
tools,
|
||||
body,
|
||||
"전회는 직전 확정 회차 누계를 읽습니다. 금회·사정은 수량·금액·비율 중 하나를 입력해 계산합니다.",
|
||||
);
|
||||
html = EstProgressView.view(state, project, { box, button, table });
|
||||
} else if (view === "escalation") {
|
||||
html = EstAppliedCost.view(state, project, { box, button, table });
|
||||
} else if (view === "annual") {
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
// 기성고 화면(native-extra.js 에서 옮김)과 원본 기성 「환경설정」(TfrmAccomOption — spec_pb1 §4: 주 메뉴 환경 설정 0x10ccddc 가
|
||||
// 기성 문서일 때 · 기성 원가계산서 팝업 popupOptision 0xd479f4 Ctrl+U). 칸을 누르면 바로 옵션에 쓰고 「취소」면 연 때 값으로 되돌림(d45e1c).
|
||||
// 공통적용(코드·수량 0·금액 0·비목순서·품명정렬·품명 두줄·Enter 이동·바탕색)은 다른 표와 같은 계산 환경 Display,
|
||||
// 기성고(레벨·비고란 발췌근거·집계공종 진하게·사정금액·노재경 구분·실적 비목)는 AccomOption. 원본도 처리기가 없는
|
||||
// 자동저장 분·가로세로 계산 확인·가로 계산방법·재료비 처리·이윤 사사오입은 값만 담는다(화면·계산 불변).
|
||||
const EstProgressView = (() => {
|
||||
const num = (v) => Number(v) || 0;
|
||||
const COMMON = {
|
||||
code: true,
|
||||
gnZero: true,
|
||||
costZero: true,
|
||||
order: 0,
|
||||
align: 0,
|
||||
twoLine: false,
|
||||
enter: 0,
|
||||
color: "",
|
||||
autoSave: false,
|
||||
saveTime: 10,
|
||||
};
|
||||
const ACCOM = {
|
||||
level: false,
|
||||
gungu: false,
|
||||
sumBold: false,
|
||||
calcCheck: false,
|
||||
horCalc: 0,
|
||||
sajung: true,
|
||||
gubun: false,
|
||||
siljuk: false,
|
||||
gcTo: 0,
|
||||
eyunRound: false,
|
||||
};
|
||||
const get = (options) => ({
|
||||
...COMMON,
|
||||
...(options?.Display || {}),
|
||||
...ACCOM,
|
||||
...(options?.AccomOption || {}),
|
||||
});
|
||||
const ORDERS = ["TNGJ", "TGNJ", "NGJT", "GNJT"];
|
||||
const PART = { T: "합계", N: "노무", G: "재료", J: "경비", S: "실적" };
|
||||
|
||||
// 비목 금액 열(금회): 비목순서대로, 실적은 실적 비목 표시 때만 경비 뒤에
|
||||
function parts(o) {
|
||||
const seq = [...(ORDERS[o.order] || ORDERS[0])];
|
||||
if (o.siljuk) seq.splice(seq.indexOf("J") + 1, 0, "S");
|
||||
return seq;
|
||||
}
|
||||
|
||||
function view(state, project, { box, button, table }) {
|
||||
const { esc } = EstApp,
|
||||
o = get(project.options);
|
||||
const shown = (v, zero) => (!zero && !num(v) ? "" : EstApp.number(v));
|
||||
const qty = (v) => shown(v, o.gnZero),
|
||||
cost = (v) => shown(v, o.costZero);
|
||||
state.roundId ||= project.progress[0]?.id;
|
||||
const round = project.progress.find((r) => r.id === state.roundId);
|
||||
let body = '<p class="empty">회차를 추가하세요.</p>',
|
||||
tools =
|
||||
button("add-round", "기성 회차 추가") +
|
||||
'<label>원본 기성 표 확인 <input type="file" accept=".estx" data-accom-inspect></label>' +
|
||||
(project.progress.length ? button("clear-progress", "기성 전체 초기화") : "") +
|
||||
button("accom-option", "환경 설정");
|
||||
if (round) {
|
||||
tools +=
|
||||
`<select id="round-choice">${project.progress.map((r) => `<option value="${esc(r.id)}" ${r.id === round.id ? "selected" : ""}>${esc(r.name)}${r.locked ? " (확정)" : ""}</option>`).join("")}</select>` +
|
||||
button(
|
||||
round.locked ? "unlock-round" : "lock-round",
|
||||
round.locked ? "확정 해제(이후 포함)" : "회차 확정·저장",
|
||||
) +
|
||||
button("fill-progress", "전량·비율 일괄 입력") +
|
||||
button("rename-round", "회차 이름") +
|
||||
button("delete-round", "회차 삭제") +
|
||||
button("reset-round-inputs", "회차 입력 초기화") +
|
||||
`<label><input id="progress-components" type="checkbox" ${round.separateComponents !== false ? "checked" : ""} ${round.locked ? "disabled" : ""}> 비목별 계산</label><label>금액 소수자리 <input id="progress-digits" type="number" min="0" max="4" value="${round.amountDigits ?? 0}" ${round.locked ? "disabled" : ""}></label>` +
|
||||
`<label><input id="use-approved" type="checkbox" ${round.approved ? "checked" : ""} ${round.locked ? "disabled" : ""}> 누계에 사정 사용</label>`;
|
||||
let output;
|
||||
try {
|
||||
output =
|
||||
round.locked && round.result ? round.result : EstBusiness.progress(project, round.id);
|
||||
} catch (e) {
|
||||
output = { rows: [], errors: [e.message] };
|
||||
}
|
||||
state.business = output;
|
||||
const page = EstBlockActions.progressPage(state, output.rows);
|
||||
tools +=
|
||||
button("progress-prev", "이전", state.progressPage === 0 ? "disabled" : "") +
|
||||
`<span>${state.progressPage + 1} / ${page.pages}쪽 · ${page.total}행</span>` +
|
||||
button("progress-next", "다음", state.progressPage + 1 >= page.pages ? "disabled" : "");
|
||||
const seq = o.gubun ? parts(o) : ["T"];
|
||||
const nameStyle = [
|
||||
["", "text-align:right", "text-align:center"][o.align] || "",
|
||||
o.twoLine ? "white-space:normal;max-width:16em" : "white-space:nowrap",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(";");
|
||||
const headers = [
|
||||
...(o.code ? ["코드"] : []),
|
||||
...(o.level ? ["레벨"] : []),
|
||||
"공종",
|
||||
"도급수량",
|
||||
"전회수량",
|
||||
"금회 입력",
|
||||
"입력방법",
|
||||
...(o.sajung ? ["사정 입력", "입력방법"] : []),
|
||||
...seq.map((p) => (p === "T" ? "금회금액" : `금회 ${PART[p]}`)),
|
||||
...(o.sajung ? ["사정금액"] : []),
|
||||
"누계수량",
|
||||
"누계금액",
|
||||
...(o.gungu ? ["비고"] : []),
|
||||
];
|
||||
body =
|
||||
table(
|
||||
headers,
|
||||
page.rows.map(({ row, index }) => {
|
||||
const editable = ![54, 55, 58, 59, 62, 67].includes(Number(row.LinkIndex));
|
||||
const key = EstBusiness.rowKey(row, index),
|
||||
input = round.inputs?.[key] || {};
|
||||
const edit = (prefix) =>
|
||||
`<input data-progress="${prefix}" data-key="${esc(key)}" type="number" step="any" value="${input[prefix] ?? row[prefix + "GN"] ?? 0}" ${round.locked || !editable || input[prefix + "Mode"] === "components" ? "disabled" : ""}>` +
|
||||
(input[prefix + "Mode"] === "components"
|
||||
? EstMath.components
|
||||
.map(
|
||||
(field, i) =>
|
||||
`<label>${["노무", "재료", "경비", "실적"][i]}<input data-progress="${prefix + field}" data-key="${esc(key)}" type="number" step="any" value="${input[prefix + field] ?? 0}" ${round.locked ? "disabled" : ""}></label>`,
|
||||
)
|
||||
.join("")
|
||||
: "");
|
||||
const method = (prefix) =>
|
||||
!editable
|
||||
? "자동계산"
|
||||
: `<select data-progress="${prefix}Mode" data-key="${esc(key)}" ${round.locked ? "disabled" : ""}>${[
|
||||
["quantity", "수량"],
|
||||
["amount", "금액"],
|
||||
["ratio", "비율 %"],
|
||||
["components", "비목 금액"],
|
||||
]
|
||||
.map(
|
||||
([v, label]) =>
|
||||
`<option value="${v}" ${v === (input[`${prefix}Mode`] || "quantity") ? "selected" : ""}>${label}</option>`,
|
||||
)
|
||||
.join("")}</select>`;
|
||||
const bold = o.sumBold && num(row.Level) > 0 ? ";font-weight:bold" : "";
|
||||
return [
|
||||
...(o.code ? [esc(row.Code)] : []),
|
||||
...(o.level ? [num(row.Level) || ""] : []),
|
||||
`<span style="display:block;${nameStyle}${bold}">${esc(row.Name)}</span>`,
|
||||
qty(row.GN),
|
||||
qty(row.JunGN),
|
||||
edit("Gum"),
|
||||
method("Gum"),
|
||||
...(o.sajung ? [edit("Sa"), method("Sa")] : []),
|
||||
...seq.map((p) => cost(row[`Gum${p}C`])),
|
||||
...(o.sajung ? [cost(row.SaTC)] : []),
|
||||
qty(row.TotalGN),
|
||||
cost(row.TotalTC),
|
||||
...(o.gungu ? [esc(row.Bigo ?? "")] : []),
|
||||
];
|
||||
}),
|
||||
) +
|
||||
`<p class="business-note">${esc((output.errors || []).join(" / "))} 금회 도급액 ${EstApp.number(output.total)} · 기성비율 ${EstApp.number(output.ratio)}%</p>`;
|
||||
body += EstProgressWonga.render(state, round, output);
|
||||
}
|
||||
const html = box(
|
||||
"기성고",
|
||||
tools,
|
||||
body,
|
||||
"전회는 직전 확정 회차 누계를 읽습니다. 금회·사정은 수량·금액·비율 중 하나를 입력해 계산합니다.",
|
||||
);
|
||||
return o.color ? `<div style="background:${esc(o.color)}">${html}</div>` : html;
|
||||
}
|
||||
|
||||
// 환경설정 창: 칸을 바꾸면 바로 옵션에 쓰고(화면에 즉시), 「취소」 는 연 때 값으로 되돌림
|
||||
let opened = null;
|
||||
function dialog(state) {
|
||||
const { esc } = EstApp,
|
||||
o = get(state.project.options);
|
||||
opened = {
|
||||
Display: structuredClone(state.project.options.Display),
|
||||
AccomOption: structuredClone(state.project.options.AccomOption),
|
||||
};
|
||||
const check = (name, label, extra = "") =>
|
||||
`<label><input type="checkbox" data-ao="${name}" ${o[name] ? "checked" : ""} ${extra}> ${esc(label)}</label>`;
|
||||
const pick = (name, label, choices, extra = "") =>
|
||||
`<label>${esc(label)} <select data-ao="${name}" ${extra}>${choices.map((c, i) => `<option value="${i}" ${num(o[name]) === i ? "selected" : ""}>${esc(c)}</option>`).join("")}</select></label>`;
|
||||
EstApp.showDialog(
|
||||
"환경설정",
|
||||
`<h4>공통적용</h4><div class="form-fields">${check("code", "코드표시")}${check("gnZero", '수량 "0" 표시')}${check("costZero", '금액 "0" 표시')}${check("autoSave", "데이타 자동저장")}<label>데이타 입력이 없으면 <input type="number" min="5" max="30" step="5" data-ao="saveTime" value="${num(o.saveTime)}" ${o.autoSave ? "" : "disabled"}> 분후 자동저장</label>${pick("order", "비목순서", ["합/노/재/경", "합/재/노/경", "노/재/경/합", "재/노/경/합"], o.gubun ? "" : "disabled")}${pick("align", "품명정렬", ["좌정렬", "우정렬", "중앙정렬"])}${pick("twoLine", "품명표기", ["한줄로 표시", "두줄로 표시"])}${pick("enter", "Enter 뒤 셀 이동", ["이동안함", "좌", "우", "위", "아래"])}<label>바탕색 <input type="color" data-ao="color" value="${esc(o.color || "#ffffff")}"></label></div>` +
|
||||
`<h4>기성고</h4><div class="form-fields">${check("level", "레벨 표시")}${check("gungu", "비고란에 발췌근거 표시")}${check("sumBold", "집계공종 진하게 표시")}${check("calcCheck", "가로,세로 계산 확인")}${pick("horCalc", "데이타(가로) 계산방법", ["절하", "절상"])}${check("sajung", "사정금액 표시")}${check("gubun", "노,재,경 구분 표시")}${check("siljuk", "실적 비목 표시", o.gubun ? "" : "disabled")}</div>` +
|
||||
`<h4>원가계산</h4><div class="form-fields">${pick("gcTo", "순직접공사비의 재료비 처리", ["간접재료비", "직접재료비"])}${check("eyunRound", "이윤 비율 사사오입")}</div>` +
|
||||
`<button data-action="accom-option-ok">적 용</button> <button data-action="accom-option-cancel">취 소</button>`,
|
||||
);
|
||||
}
|
||||
function set(name, value) {
|
||||
const options = EstApp.state.project.options,
|
||||
bucket = name in COMMON ? "Display" : "AccomOption";
|
||||
const flag = typeof COMMON[name] === "boolean" || typeof ACCOM[name] === "boolean";
|
||||
const typed = flag
|
||||
? typeof value === "string"
|
||||
? value === "1"
|
||||
: Boolean(value)
|
||||
: name === "color"
|
||||
? value
|
||||
: num(value);
|
||||
options[bucket] = { ...(options[bucket] || {}), [name]: typed };
|
||||
const box = document.getElementById("dialog-content");
|
||||
if (name === "autoSave") box.querySelector('[data-ao="saveTime"]').disabled = !typed;
|
||||
if (name === "gubun") {
|
||||
box.querySelector('[data-ao="order"]').disabled = !typed;
|
||||
box.querySelector('[data-ao="siljuk"]').disabled = !typed;
|
||||
}
|
||||
EstApp.render();
|
||||
}
|
||||
function close(apply) {
|
||||
const options = EstApp.state.project.options;
|
||||
if (opened) {
|
||||
const now = { Display: options.Display, AccomOption: options.AccomOption };
|
||||
for (const k of ["Display", "AccomOption"])
|
||||
if (opened[k] === undefined) delete options[k];
|
||||
else options[k] = opened[k];
|
||||
// 적용: 연 때 값으로 돌린 뒤 바뀐 값을 한 번에 기록(되돌리기 한 단계)
|
||||
if (apply) EstApp.change((project) => Object.assign(project.options, now), false);
|
||||
}
|
||||
opened = null;
|
||||
EstApp.byId("dialog").close();
|
||||
EstApp.render();
|
||||
}
|
||||
|
||||
// Enter 뒤 셀 이동(ComboBoxEnter 62c+0x60): 표 안 입력칸에서 Enter 면 좌·우·위·아래 칸으로
|
||||
function move(input, direction) {
|
||||
const cell = input.closest("td"),
|
||||
tr = cell?.parentElement;
|
||||
if (!tr) return null;
|
||||
const col = [...tr.children].indexOf(cell);
|
||||
let target = null;
|
||||
if (direction === 1 || direction === 2) {
|
||||
const cells = [...tr.children];
|
||||
for (
|
||||
let c = col + (direction === 1 ? -1 : 1);
|
||||
c >= 0 && c < cells.length;
|
||||
c += direction === 1 ? -1 : 1
|
||||
)
|
||||
if ((target = cells[c].querySelector("input:not([disabled]),select:not([disabled])")))
|
||||
break;
|
||||
} else if (direction === 3 || direction === 4) {
|
||||
for (
|
||||
let r = direction === 3 ? tr.previousElementSibling : tr.nextElementSibling;
|
||||
r;
|
||||
r = direction === 3 ? r.previousElementSibling : r.nextElementSibling
|
||||
)
|
||||
if (
|
||||
(target = r.children[col]?.querySelector("input:not([disabled]),select:not([disabled])"))
|
||||
)
|
||||
break;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
function install() {
|
||||
document.addEventListener("change", (event) => {
|
||||
const t = event.target;
|
||||
if (t.dataset?.ao === undefined) return;
|
||||
set(t.dataset.ao, t.type === "checkbox" ? t.checked : t.value);
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
const state = EstApp.state;
|
||||
if (state?.view !== "progress") return;
|
||||
if (event.ctrlKey && event.key.toLowerCase() === "u") {
|
||||
event.preventDefault();
|
||||
dialog(state);
|
||||
return;
|
||||
}
|
||||
const t = event.target;
|
||||
if (event.key !== "Enter" || !t.matches?.("table.sheet input, table.sheet select")) return;
|
||||
const direction = num(get(state.project.options).enter);
|
||||
if (!direction) return;
|
||||
const target = move(t, direction);
|
||||
if (target) {
|
||||
event.preventDefault();
|
||||
t.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
setTimeout(() =>
|
||||
(
|
||||
document.querySelector(
|
||||
`[data-key="${CSS.escape(target.dataset.key || "")}"][data-progress="${target.dataset.progress}"]`,
|
||||
) || target
|
||||
).focus(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (typeof document !== "undefined" && document.addEventListener) install();
|
||||
|
||||
function handle(name, state) {
|
||||
if (name === "accom-option") dialog(state);
|
||||
else if (name === "accom-option-ok") close(true);
|
||||
else if (name === "accom-option-cancel") close(false);
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
return { view, get, parts, move, handle, dialog };
|
||||
})();
|
||||
@@ -119,6 +119,7 @@ const EstProgressWonga = (() => {
|
||||
b("pw-total", "공사비역산 자리수 맞추기"),
|
||||
b("pw-init", "원가계산서 초기화"),
|
||||
b("pw-undo", "되살리기"),
|
||||
'<button data-action="accom-option" title="Ctrl+U">환경 설정</button>',
|
||||
`<label><input type="checkbox" data-pw-sname ${lockFormula ? "checked" : ""}> 산출근거 약어로 표시</label>`,
|
||||
round.wonga ? "<span>회차 서식 사용 중</span>" : "",
|
||||
round.wongaTotalDigits ? `<span>역산 ${round.wongaTotalDigits}자리</span>` : "",
|
||||
|
||||
@@ -105,6 +105,7 @@ const allowed = new Set([
|
||||
"native-excel-to-est.js",
|
||||
"native-other-load.js",
|
||||
"native-applied-cost.js",
|
||||
"native-progress-view.js",
|
||||
"native-actions-more.js",
|
||||
"native-wonga-tools.js",
|
||||
"native-estimate-print.js",
|
||||
|
||||
Reference in New Issue
Block a user