wip(802-4b-3): 중지 — 펼친 단가산출 블록 격자 전체 폭(정의 영역 전체 폭 · 기호 줄 오른쪽 칸 자리) · 금액 줄 비목 합 참조 값(moneyScope = 앞 줄 누계) · 빨강 헬퍼 틀(시험 · ORCA 안 돌림)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -218,8 +218,9 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 기호 정의 — 값 식 아래 한 줄씩 */
|
||||
/* 기호 정의 — 값 식 아래 한 줄씩(오른쪽 칸 자리) · 펼친 단가산출 블록만 격자 전체 폭 */
|
||||
.fdoc__defs {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
@@ -254,6 +255,13 @@
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.fdoc__defs > .fdoc__colhead,
|
||||
.fdoc__def,
|
||||
.fdoc__defedit,
|
||||
.fdoc__unused > summary {
|
||||
margin-left: calc(50% + var(--spacing-24) / 2);
|
||||
}
|
||||
|
||||
.fdoc__defedit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -294,6 +302,13 @@
|
||||
.fdoc__grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.fdoc__defs > .fdoc__colhead,
|
||||
.fdoc__def,
|
||||
.fdoc__defedit,
|
||||
.fdoc__unused > summary {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fdoc__wrap,
|
||||
|
||||
@@ -15,6 +15,7 @@ import { el } from "@ui/ui_template_elements";
|
||||
import { createSelectField } from "@ui/ui_template_input";
|
||||
import type { DocVar, FormulaDoc, MoneyLine, Node } from "../common_util/common_util_formula_doc";
|
||||
import type { DocAnswer } from "../common_util/common_util_formula_doc_calc";
|
||||
import { moneyScope } from "../common_util/common_util_formula_doc_view";
|
||||
import {
|
||||
lineMap,
|
||||
litsOf,
|
||||
@@ -74,8 +75,15 @@ function vals(h: LinesHooks): Values {
|
||||
return got;
|
||||
}
|
||||
|
||||
/** 식 글 → 그림(못 풀면 글 그대로 빨강) */
|
||||
function expr(text: unknown, h: LinesHooks, side: "sym" | "val", inline = false, cost?: string) {
|
||||
/** 식 글 → 그림(못 풀면 글 그대로 빨강) · scope = 금액 줄이 부르는 비목 합(앞 줄 누계) */
|
||||
function expr(
|
||||
text: unknown,
|
||||
h: LinesHooks,
|
||||
side: "sym" | "val",
|
||||
inline = false,
|
||||
cost?: string,
|
||||
scope?: Values,
|
||||
) {
|
||||
const n = tryParse(text);
|
||||
if (!n) {
|
||||
const raw = span("fdoc__raw", String(text ?? ""));
|
||||
@@ -84,7 +92,14 @@ function expr(text: unknown, h: LinesHooks, side: "sym" | "val", inline = false,
|
||||
}
|
||||
const lits = side === "val" ? litsOf(n) : undefined;
|
||||
return {
|
||||
node: renderNode(n, { side, vals: vals(h), vars: h.vars, inline, cost, lits }),
|
||||
node: renderNode(n, {
|
||||
side,
|
||||
vals: scope ? { ...vals(h), ...scope } : vals(h),
|
||||
vars: h.vars,
|
||||
inline,
|
||||
cost,
|
||||
lits,
|
||||
}),
|
||||
tree: n,
|
||||
};
|
||||
}
|
||||
@@ -256,15 +271,16 @@ function moneyRow(i: number, h: LinesHooks): HTMLElement[] {
|
||||
}
|
||||
const right = el("div", { className: "fdoc__cell fdoc__valcell fdoc__money" });
|
||||
const costs = line.비목 === "묶음" ? [...COSTS] : [null];
|
||||
const scope = moneyScope(h.answer.줄, i);
|
||||
for (const cost of costs) {
|
||||
const row = span("fdoc__mrow");
|
||||
if (cost) row.append(span("fdoc__cost", cost), span("ui-eq__op", ":"));
|
||||
const pv = span("fdoc__edit");
|
||||
pv.append(expr(line.단가, h, "val", true, cost ?? undefined).node);
|
||||
pv.append(expr(line.단가, h, "val", true, cost ?? undefined, scope).node);
|
||||
row.append(pv);
|
||||
if (hasQty) {
|
||||
const qv = span("fdoc__edit");
|
||||
qv.append(expr(line.수량, h, "val", true).node);
|
||||
qv.append(expr(line.수량, h, "val", true, undefined, scope).node);
|
||||
row.append(span("ui-eq__op", "×"), qv);
|
||||
if (h.admin)
|
||||
qv.addEventListener("click", (ev) => onValueClick(ev, qv, h, "qty", i, () => undefined));
|
||||
|
||||
@@ -521,7 +521,6 @@ export function createDocScreen(opt: DocScreenOptions) {
|
||||
el("div", { className: "fdoc__colhead", text: ft("Sym_Head") }),
|
||||
el("div", { className: "fdoc__colhead", text: ft("Val_Head") }),
|
||||
...lines,
|
||||
el("div"),
|
||||
defs,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -137,3 +137,16 @@ export function takenOf(n: Node, vals: Values): 0 | 1 | 2 {
|
||||
const t = evalNode(n[2][0], vals);
|
||||
return t === undefined ? 0 : t ? 1 : 2;
|
||||
}
|
||||
|
||||
/** 금액 줄 i 가 부르는 비목 합(노무비 · 재료비 · 경비 = 앞 줄 누계 · 합밖 · 제외 단계 줄 뺌)
|
||||
* — 계산기 `money` 의 running 과 같은 값(셈 답 `줄` 의 비목에서 다시 모음 · 802-4b-3) */
|
||||
export function moneyScope(rows: unknown, i: number): Values {
|
||||
const acc: Record<string, number> = { 노무비: 0, 재료비: 0, 경비: 0 };
|
||||
((rows ?? []) as { 비목?: Record<string, unknown>; 합밖?: unknown }[])
|
||||
.slice(0, i)
|
||||
.forEach((r) => {
|
||||
if (r.합밖) return;
|
||||
for (const k of Object.keys(acc)) if (isNum(r.비목?.[k])) acc[k] += r.비목![k] as number;
|
||||
});
|
||||
return acc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/* 802-4b-3 금액 줄 값 식 빨강 헬퍼 — 화면 `formula_doc_lines.ts` 와 같은 값 목록(기호 값 + 금액 줄 기호 + 비목 합)으로
|
||||
* 금액 줄 단가 · 수량의 기호마다 값이 있는지 봄. stdin = [{문서, 값, 줄}] · stdout = [{빨강: [[줄, 기호]], 합: [[줄, 셈, 금액]]}] */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const cache = new Map();
|
||||
function load(at) {
|
||||
if (cache.has(at)) return cache.get(at);
|
||||
const js = ts.transpileModule(fs.readFileSync(at, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(at, box.exports);
|
||||
const need = (p) =>
|
||||
p.startsWith(".") ? load(path.resolve(path.dirname(at), `${p}.ts`)) : require(p);
|
||||
new Function("exports", "module", "require", js)(box.exports, box, need);
|
||||
return box.exports;
|
||||
}
|
||||
const view = load(
|
||||
path.join(__dirname, "..", "..", "common_util", "common_util_formula_doc_view.ts"),
|
||||
);
|
||||
const COSTS = new Set(["노무비", "재료비", "경비"]);
|
||||
const names = (n, out = []) => {
|
||||
if (!n) return out;
|
||||
if (n[0] === "var") out.push(n[1]);
|
||||
else if (n[0] === "field") names(n[1], out);
|
||||
else if (n[0] === "neg" || n[0] === "pct") names(n[1], out);
|
||||
else if (n[0] === "bin") names(n[2], out) && names(n[3], out);
|
||||
else if (n[0] === "call") n[2].forEach((x) => names(x, out));
|
||||
return out;
|
||||
};
|
||||
const num = (v) => (typeof v === "string" && v !== "" && !isNaN(Number(v)) ? Number(v) : v);
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const out = input.map(({ 문서, 값, 줄 }) => {
|
||||
const vals = { ...값 };
|
||||
const lines = 문서.금액 || [];
|
||||
lines.forEach((l, i) => {
|
||||
const a = num((줄[i] || {}).금액);
|
||||
if (l.기호 && typeof a === "number") vals[String(l.기호)] = a;
|
||||
});
|
||||
const red = [];
|
||||
const sums = [];
|
||||
lines.forEach((l, i) => {
|
||||
const amount = num((줄[i] || {}).금액);
|
||||
const raw = view.moneyScope(줄, i);
|
||||
const scope = { ...vals, ...raw };
|
||||
let cost = false;
|
||||
for (const t of [l.단가, l.수량]) {
|
||||
for (const name of names(view.tryParse(t))) {
|
||||
if (COSTS.has(name)) cost = true;
|
||||
const v = scope[name];
|
||||
const miss = v === null || v === undefined || (typeof v === "object" && l.비목 !== "묶음");
|
||||
if (miss && typeof amount === "number") red.push([i, name]);
|
||||
}
|
||||
}
|
||||
if (cost && typeof amount === "number") {
|
||||
const p = view.evalNode(view.tryParse(l.단가), scope);
|
||||
const q = l.수량 === undefined || l.수량 === null || l.수량 === "" ? 1 : view.evalNode(view.tryParse(l.수량), scope);
|
||||
sums.push([i, typeof p === "number" && typeof q === "number" ? p * q : null, amount]);
|
||||
}
|
||||
});
|
||||
return { 빨강: red, 합: sums };
|
||||
});
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
Reference in New Issue
Block a user