feat(m01): 흐름 로직 왼쪽 「지금 내용」 — 단가산출 상세 · 텍스트 수식(buildFlowView) · Detail 흐름 칸
- FlowRows(순수): 출력 상자를 비용 종류별 줄 · 소계 · 계(합계 끝수 1원 미만 버림) · 함수 상자 결과 - FlowView: 화면 · sub2 식 보기가 있으면 식 두 줄 - Detail: DetailContext.flowHost 가 있으면 호표 · 읽는 식 대신 이 칸 - 시험 test_m01_logic_flowview.py Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EauA2ATmqNRbeV48s9CcBr
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_LogicLab_FlowRows.ts
|
||||
* 흐름 로직의 왼쪽 「단가산출 상세」 줄 만들기(DOM 없음 · 순수) — 흐름도 출력 상자를 비용 종류별로 묶어
|
||||
* 줄(이름 · 단위 · 값) · 소계 · 계 · 함수 상자별 결과 값. 값은 DataFlow 계산 결과(`CfEvalResult`)를 읽기만 함.
|
||||
* 모양은 docs/raw/M01_쉬운단가/흐름도_적용_정함.md 7. 시험 = resources/tester/test_m01_logic_flowview.py
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CfEvalResult } from "../M02_MasterTemplete/DataFlow/M02_DF_Eval";
|
||||
import type { CfBox, CfCost, CfLogic, CfRound } from "../M02_MasterTemplete/DataFlow/M02_DF_Model";
|
||||
import { roundBy, showRounded } from "../M02_MasterTemplete/DataFlow/M02_DF_Round";
|
||||
|
||||
export type FlowGroupKey = "labor" | "material" | "cost" | "plain";
|
||||
|
||||
/** 비용 종류 → 묶음(인력 = 노무비 · 재료 = 재료비 · 경비) · 값(비용 아님)은 따로 */
|
||||
export const GROUP_OF: Record<CfCost, FlowGroupKey> = {
|
||||
노무비: "labor",
|
||||
재료비: "material",
|
||||
경비: "cost",
|
||||
값: "plain",
|
||||
};
|
||||
export const GROUP_ORDER: FlowGroupKey[] = ["labor", "material", "cost", "plain"];
|
||||
const COST_OF: Record<Exclude<FlowGroupKey, "plain">, "노무비" | "재료비" | "경비"> = {
|
||||
labor: "노무비",
|
||||
material: "재료비",
|
||||
cost: "경비",
|
||||
};
|
||||
|
||||
export interface FlowLine {
|
||||
box: string;
|
||||
name: string;
|
||||
unit: string;
|
||||
value: number | string | boolean | null;
|
||||
/** 보이는 글 — 숫자는 그 출력의 끝수 자리대로 */
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface FlowGroup {
|
||||
key: FlowGroupKey;
|
||||
lines: FlowLine[];
|
||||
/** 비용 묶음만 — 합계 끝수를 적용한 소계 */
|
||||
sub: number | null;
|
||||
subText: string;
|
||||
}
|
||||
|
||||
export interface FlowFunc {
|
||||
box: string;
|
||||
name: string;
|
||||
unit: string;
|
||||
value: unknown;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface FlowRows {
|
||||
groups: FlowGroup[];
|
||||
/** 합계 끝수(기본 1원 미만 버림)를 적용한 계 */
|
||||
total: number;
|
||||
totalText: string;
|
||||
funcs: FlowFunc[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
const plain = (v: unknown): string =>
|
||||
v === null || v === undefined || Array.isArray(v)
|
||||
? "—"
|
||||
: typeof v === "number"
|
||||
? v.toLocaleString("ko-KR", { maximumFractionDigits: 6 })
|
||||
: String(v);
|
||||
|
||||
/** 줄 모으기 — round.item = 출력 기본 끝수 · round.total = 합계 기본 끝수 */
|
||||
export function flowRows(
|
||||
lg: CfLogic,
|
||||
ev: CfEvalResult,
|
||||
round: { item: CfRound; total: CfRound },
|
||||
): FlowRows {
|
||||
const outs: CfBox[] = lg.boxes.filter((b) => b.kind === "output");
|
||||
const groups = GROUP_ORDER.map((key): FlowGroup => {
|
||||
const mine = outs.filter((b) => GROUP_OF[b.output?.cost ?? "값"] === key);
|
||||
const lines = mine.map((b): FlowLine => {
|
||||
const v = ev.outputs[b.id] ?? null;
|
||||
const r = b.output?.round ?? (key === "plain" ? undefined : round.item);
|
||||
return {
|
||||
box: b.id,
|
||||
name: b.name,
|
||||
unit: b.output?.unit ?? b.unit,
|
||||
value: v,
|
||||
text: typeof v === "number" ? showRounded(v, r) : plain(v),
|
||||
};
|
||||
});
|
||||
const raw = key === "plain" ? null : ev.cost[COST_OF[key]];
|
||||
const sub = raw === null ? null : roundBy(round.total, raw);
|
||||
return {
|
||||
key,
|
||||
lines,
|
||||
sub,
|
||||
subText: sub === null ? "" : showRounded(sub, round.total),
|
||||
};
|
||||
}).filter((g) => g.lines.length);
|
||||
const total = roundBy(round.total, ev.cost.계);
|
||||
const funcs = lg.boxes
|
||||
.filter((b) => b.kind === "function" && b.func)
|
||||
.map((b): FlowFunc => {
|
||||
const out = b.func!.output;
|
||||
const v = ev.ports[b.id]?.[out.name];
|
||||
return {
|
||||
box: b.id,
|
||||
name: b.name,
|
||||
unit: out.unit || b.unit,
|
||||
value: v,
|
||||
text:
|
||||
typeof v === "number"
|
||||
? v.toLocaleString("ko-KR", { maximumFractionDigits: 6 })
|
||||
: plain(v),
|
||||
};
|
||||
});
|
||||
return {
|
||||
groups,
|
||||
total,
|
||||
totalText: showRounded(total, round.total),
|
||||
funcs,
|
||||
errors: ev.errors,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user