feat(799-2): M01 로직 상세 · M02 단가산출 상세 옛 탭 넷(시트 · 인력/재료/경비 · 시험 계산 · 텍스트 수식) 걷음 — 보기는 수식 · 단가산출 상세 · 셈 구동 따로
- 셈 구동 떼기: 새 M01_MasterData_UI_Logic_Drive.ts(driveRun · previewText · autoFill · createDrive) — M01 상세는 숨은 시험 계산 칸 단추 대신 구동 함수 · 시험 계산 칸(Logic_Calc)은 로직 복사 화면이 쓰므로 남기고 같은 구동을 부름 · 재료 고름은 rerun 고리 - M02: 새 _Sangeun_Tabs.ts(수식 · 단가산출 상세 보기 고르기) · Detail 의 시험 계산 · 인력/재료/경비 걷음 - 걷은 파일(git rm): M01 LogicLab_Sheet · Logic_Sheet_Model · LogicLab_Formula(.ts .css) · FormulaTokens · LogicLab_Est / M02 Sangeun_Sheet(.ts .css) · SheetEdit · SheetFormula · logic Sheet_Model · Const_Model · Formula_Model · 부품 시트 줄(createCalcSheetRow) · Cover_Model.sheetGroups · Calc 의 M02 고리(answer · shown · held) · 죽은 CSS · 글 - 서버 길 · 문서 칸(변수줄 · 글줄 · 덮개)은 남김 — 금액 그대로 - 낡은 시험: test_790_1(시트 화면 전부) · test_714_3 · test_748_2(+ helper)는 화면이 없어져 지움 · test_794_1 · test_790_2 는 화면 · TS 거울만 덜고 서버 시험 남김 · test_714_2 · 717_2b · 721 · 722(원래 틀린 한 줄 고침) · 734 · 737 · 750_2 · 793_3b · 796_2 · 796_2b 는 보기 둘 · 새 파일에 맞춤 · 새 test_799_2_old_tabs_gone Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,163 +0,0 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData/logic/M01_Logic_Sheet_Model.ts
|
||||
* M01 로직 줄 식 시트 꼴(PLAN 794-1 · 요구 650~652 · 요구서_652 1 장 6) — M02 단가산출 시트와 같은 꼴
|
||||
* 「이름 | 식 | 값 | 비목」 · 줄 갈래 = 근거(출처 · 개정 · 비고 · 끝수) · 옵션(입력) · 계산(중간) · 자원(호표) ·
|
||||
* 율(덧줄) · 단계 · 소계 · 합계 · 식 글 = 서버 이름 꼴(`/logic/sheet` · 없으면 엔진 식 「*」 → 「×」)
|
||||
* 값 = 시험 계산 답(줄 금액 = 호표 → 덧줄 차례 · 중간 값 · 비목 소계)
|
||||
* 고침 = 줄 사본 식 칸 하나(`setLabFormula`) — 저장은 화면이 기존 M01 [저장] 길로
|
||||
* 짝 = 서버 `M01_MasterData_Sheet.py`(자리 = 갈래 · 차례 꼴만 같음) · 화면 · fetch · sessionStorage 없음
|
||||
* ========================================================================== */
|
||||
|
||||
import type {
|
||||
CalcAnswer,
|
||||
CalcLine,
|
||||
LogicInput,
|
||||
LogicRow,
|
||||
NamedFormula,
|
||||
} from "../M01_MasterData_UI_Logic_Api";
|
||||
|
||||
export type LabPart = "중간" | "호표" | "덧줄";
|
||||
export type LabKind = "근거" | "옵션" | "계산" | "자원" | "율" | "단계" | "소계" | "합계";
|
||||
|
||||
/** 줄마다 식 칸 이름(갈래 → 칸) — 서버 `FIELD` 와 같음 */
|
||||
export const FIELD: Record<LabPart, "식" | "수량"> = { 중간: "식", 호표: "수량", 덧줄: "식" };
|
||||
|
||||
export interface LabNames {
|
||||
중간: string[];
|
||||
호표: string[];
|
||||
덧줄: string[];
|
||||
이름표?: { 글: string; 갈래: string }[];
|
||||
}
|
||||
|
||||
export interface LabItem {
|
||||
kind: LabKind;
|
||||
이름: string;
|
||||
/** 이름 꼴 식 글 · 빈 글 = 식 없음 */
|
||||
식: string;
|
||||
값: number | string | null;
|
||||
/** 비목 줄임(노 · 재 · 경) */
|
||||
비목: string;
|
||||
/** 식 칸 자리(중간 · 호표 · 덧줄) */
|
||||
part?: LabPart;
|
||||
at?: number;
|
||||
input?: LogicInput;
|
||||
/** 금액번지(791-1) */
|
||||
번지?: string;
|
||||
/** 저장본과 식이 다름 */
|
||||
changed: boolean;
|
||||
}
|
||||
|
||||
const SHORT: Record<string, string> = { 노무비: "노", 재료비: "재", 경비: "경" };
|
||||
const short = (v: unknown): string => (typeof v === "string" ? (SHORT[v] ?? v) : "");
|
||||
|
||||
/** 엔진 식 → 보기 글(이름 꼴이 아직 없을 때) */
|
||||
export const plain = (s: string): string => s.replace(/\s*\*\s*/g, " × ");
|
||||
|
||||
export const spotKey = (part: LabPart, at: number): string => `${part}#${at}`;
|
||||
|
||||
/** 줄 식 칸 값(엔진 식) */
|
||||
export function formulaAt(row: LogicRow, part: LabPart, at: number): string {
|
||||
const list = (row[part] ?? []) as unknown as Record<string, unknown>[];
|
||||
return String(list[at]?.[FIELD[part]] ?? "");
|
||||
}
|
||||
|
||||
/** 줄 식 칸 하나를 엔진 식으로 바꿈(줄 객체 그대로 고침) · 없는 자리 = false */
|
||||
export function setLabFormula(row: LogicRow, part: LabPart, at: number, engine: string): boolean {
|
||||
const list = row[part] as unknown as Record<string, unknown>[] | undefined;
|
||||
if (!list || at < 0 || at >= list.length) return false;
|
||||
list[at][FIELD[part]] = engine;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 끝수 글 — 글자면 그대로 · 묶음이면 「대상 방법 자리」 */
|
||||
export function cutText(rule: unknown): string {
|
||||
if (rule == null || rule === "") return "";
|
||||
if (typeof rule === "string") return rule;
|
||||
const r = rule as Record<string, unknown>;
|
||||
return [r.대상, r.방법, r.자리 != null ? `${String(r.자리)} 자리` : ""]
|
||||
.filter((x) => x !== undefined && x !== "")
|
||||
.join(" · ");
|
||||
}
|
||||
|
||||
const valueOf = (v: unknown): number | string | null =>
|
||||
typeof v === "number" || typeof v === "string" ? v : null;
|
||||
|
||||
/** 원문 절 머리 개정 연도 글 — [2008, 2020, 2025] → 「'08 · '20 · '25」(794-2b · 없으면 빈 글) */
|
||||
export const revisionText = (row: LogicRow): string =>
|
||||
(row.개정 ?? []).map((y) => `'${String(y).slice(-2)}`).join(" · ");
|
||||
|
||||
/** 시트 줄 — 근거 · 옵션 · 계산 · 자원 · 율 · 단계 · 소계 · 합계 */
|
||||
export function labItems(o: {
|
||||
row: LogicRow;
|
||||
/** 저장본 줄(새 로직 = null) — 식이 다르면 고친 줄 */
|
||||
original: LogicRow | null;
|
||||
values: Record<string, string>;
|
||||
names: LabNames | null;
|
||||
answer: CalcAnswer | null;
|
||||
}): LabItem[] {
|
||||
const { row, original } = o;
|
||||
const out: LabItem[] = [];
|
||||
const item = (x: Partial<LabItem> & { kind: LabKind; 이름: string }): void =>
|
||||
void out.push({ 식: "", 값: null, 비목: "", changed: false, ...x });
|
||||
if (row.출처) item({ kind: "근거", 이름: row.원문번호 || "", 식: row.출처 });
|
||||
if (row.개정?.length) item({ kind: "근거", 이름: "개정", 식: revisionText(row) });
|
||||
for (const line of (row.비고 ?? "").split(/\r?\n/).filter((x) => x.trim()))
|
||||
item({ kind: "근거", 이름: "", 식: line.trim() });
|
||||
for (const input of row.입력 ?? [])
|
||||
item({ kind: "옵션", 이름: input.이름, 값: o.values[input.이름] ?? "", input });
|
||||
const ok = o.answer?.ok ? o.answer : null;
|
||||
const mid = ok?.middle ?? {};
|
||||
const shown = (part: LabPart, at: number): string =>
|
||||
o.names?.[part]?.[at] ?? plain(formulaAt(row, part, at));
|
||||
const changed = (part: LabPart, at: number): boolean =>
|
||||
!!original && formulaAt(original, part, at) !== formulaAt(row, part, at);
|
||||
const spot = (part: LabPart, at: number) => ({
|
||||
part,
|
||||
at,
|
||||
식: shown(part, at),
|
||||
changed: changed(part, at),
|
||||
});
|
||||
(row.중간 ?? []).forEach((f: NamedFormula, at) =>
|
||||
item({ kind: "계산", 이름: f.이름, 값: valueOf(mid[f.이름]), ...spot("중간", at) }),
|
||||
);
|
||||
const ho = row.호표 ?? [];
|
||||
const extra = row.덧줄 ?? [];
|
||||
const lines: (CalcLine | undefined)[] =
|
||||
ok?.lines && ok.lines.length === ho.length + extra.length ? ok.lines : [];
|
||||
ho.forEach((h, at) => {
|
||||
const b = (h as { 번지?: string }).번지;
|
||||
item({
|
||||
kind: "자원",
|
||||
이름: h.단위 ? `${h.이름 ?? ""} (${h.단위})` : (h.이름 ?? ""),
|
||||
값: lines[at]?.금액 ?? null,
|
||||
비목: short(h.비목),
|
||||
...(b ? { 번지: b } : {}),
|
||||
...spot("호표", at),
|
||||
});
|
||||
});
|
||||
extra.forEach((x, at) => {
|
||||
const b = (x as { 번지?: string }).번지;
|
||||
item({
|
||||
kind: "율",
|
||||
이름: x.이름,
|
||||
값: lines[ho.length + at]?.금액 ?? null,
|
||||
비목: short(x.비목),
|
||||
...(b ? { 번지: b } : {}),
|
||||
...spot("덧줄", at),
|
||||
});
|
||||
});
|
||||
const steps = (row.단계 ?? []) as { 이름?: string; 줄?: unknown }[];
|
||||
for (const s of Array.isArray(steps) ? steps : [])
|
||||
item({
|
||||
kind: "단계",
|
||||
이름: String(s.이름 ?? ""),
|
||||
식: Array.isArray(s.줄) ? s.줄.map(String).join(" + ") : "",
|
||||
});
|
||||
const cut = cutText(row.끝수);
|
||||
if (cut) item({ kind: "근거", 이름: "끝수", 식: cut });
|
||||
const sums = ok?.sums ?? null;
|
||||
for (const k of Object.keys(SHORT))
|
||||
if (sums && k in sums) item({ kind: "소계", 이름: k, 값: sums[k], 비목: SHORT[k] });
|
||||
if (sums && "계" in sums) item({ kind: "합계", 이름: "계", 값: sums.계 });
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user