- 가져온 식 만들 때 SK 가져온.근거후보 = 793-3a 블록 제안 근거후보(San_Basis_Store.block · 못 셈 = 빈 칸) - 머리 판 후보 = 원문번호 · 옵션 · 품검산 · 입력필요 n · 까닭 [표지 글] · [우리 기초로 바꾸기] 입력 필요 = 바인딩 + 후보 입력필요 + 환산 - 환산 계수 = 가져온 식에서 to-base 다시(바꿈 전 · 후 서버 정본) · 계수 없을 땐 시트 안 엶(셈 길 400 없음) - 단산 줄 고른 것 층 · 주인 뺌 — 층을 주면 옮기기가 SK 를 못 찾아 고정 줄로 옮겨짐 - 시험 test_793_3b 9(근거후보 채움 · SK 참조 옮기기) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
5.6 KiB
TypeScript
146 lines
5.6 KiB
TypeScript
/* =============================================================================
|
|
* logic/M02_Hopyo_Imported_Model.ts
|
|
* 불러오기 단산 줄 = 가져온 식 단가산출(PLAN 793-3b · 요구 646 · 647) — 꼴 · 읽기 · 바꾸기만.
|
|
* · SK `가져온` 덧칸 꼴(793-2 `M02_Sangeun_Imported.py` 머리) · 근거 제안 세 단(확정 · 근접 · 사람 고름 · 793-3a 가 채움)
|
|
* · 단산 줄 고른 것 = {갈래 단가산출, 키 SK-…, 필요(입력 필요), 표지(결과 칩)} — 층 = 옮길 층 — 필요가 비어야 확인 · 옮김
|
|
* · 줄 결과 칩 = 가져온 식 있음 · 근거 확정 · 근접 · 사람 고름 · 입력 필요 n(「2차 필요」 대신)
|
|
* 화면 · fetch · sessionStorage 없음(`CLAUDE.md` 4장).
|
|
* ========================================================================== */
|
|
|
|
import type { DraftPick } from "./M02_MasterTemplete_Hopyo_Picks";
|
|
|
|
export type Money4 = Record<"노무비" | "재료비" | "경비" | "계", number>;
|
|
export type Tier = "확정" | "근접" | "사람";
|
|
export const TIERS: Tier[] = ["확정", "근접", "사람"];
|
|
|
|
/** 근거 후보 하나(793-3a `제안.근거후보`) — 까닭 = [[표지(판 차이 · 현장 값 · 감률 · 할증 배수 · 단위 기준 차이 · 그 밖), 글]] */
|
|
export interface BasisCand {
|
|
기초: string;
|
|
이름?: string;
|
|
원문번호?: string;
|
|
옵션?: Record<string, unknown>;
|
|
/** 글에 없는 칸(감률 · 할증 포함) — 사용자 입력 */
|
|
입력필요?: string[];
|
|
품검산?: "같음" | "다름" | "짝 없음" | null;
|
|
단: Tier;
|
|
까닭?: [string, string][];
|
|
}
|
|
|
|
/** 근거 후보 까닭 글(「표지 글」 · 줄마다) */
|
|
export const reasonText = (c: BasisCand): string =>
|
|
(c.까닭 ?? []).map(([tag, text]) => `${tag} ${text}`.trim()).join(" · ");
|
|
|
|
export interface CheckLine {
|
|
자리: string;
|
|
행?: number | null;
|
|
셈?: number | null;
|
|
엑셀?: number | null;
|
|
같음: boolean;
|
|
까닭?: string;
|
|
}
|
|
|
|
/** SK 문서 덧칸 `가져온` */
|
|
export interface ImportedMeta {
|
|
로직?: { 이름?: string; 호표?: { 이름?: string; 이음?: string | null }[] };
|
|
단가기준?: "엑셀" | "우리";
|
|
원본?: {
|
|
파일?: string;
|
|
시트?: string;
|
|
행?: number[];
|
|
갈래?: string;
|
|
엑셀금액?: Money4;
|
|
글줄?: { 행?: number | null; 글?: string; 자리?: string; 값?: number | null }[];
|
|
};
|
|
검산?: {
|
|
같음: boolean;
|
|
까닭?: string | null;
|
|
셈?: Money4 | null;
|
|
엑셀?: Money4;
|
|
줄: CheckLine[];
|
|
};
|
|
근거후보?: BasisCand[];
|
|
바꿈?: { 전?: Money4 | null; 후?: Money4 | null; 차이?: Money4 | null };
|
|
}
|
|
|
|
export interface ImportedDoc {
|
|
이름?: string;
|
|
단위?: string;
|
|
바탕?: { 로직?: string } | null;
|
|
입력값?: Record<string, unknown>;
|
|
/** SK 단위 환산(기초 결과단위 성격이 다를 때 · 728-2) */
|
|
환산?: { 계수?: number | string };
|
|
가져온?: ImportedMeta;
|
|
[k: string]: unknown;
|
|
}
|
|
|
|
/** 가져온 식(아직 우리 기초로 안 바꿈) */
|
|
export const isImported = (d: ImportedDoc | null | undefined): boolean => !!d?.가져온?.로직;
|
|
|
|
/** 근거 후보 세 단 */
|
|
export function tiers(list: BasisCand[] | undefined): Record<Tier, BasisCand[]> {
|
|
const out: Record<Tier, BasisCand[]> = { 확정: [], 근접: [], 사람: [] };
|
|
for (const c of list ?? []) if (TIERS.includes(c.단)) out[c.단].push(c);
|
|
return out;
|
|
}
|
|
|
|
/** 검산 다른 줄 */
|
|
export const wrongLines = (d: ImportedDoc): CheckLine[] =>
|
|
(d.가져온?.검산?.줄 ?? []).filter((x) => !x.같음);
|
|
|
|
const blank = (v: unknown): boolean => v === undefined || v === null || String(v).trim() === "";
|
|
|
|
/** 입력 필요 칸 값 — 입력값 + 「환산」 = 환산 계수 */
|
|
export const needValues = (d: ImportedDoc): Record<string, unknown> => ({
|
|
...(d.입력값 ?? {}),
|
|
환산: d.환산?.계수,
|
|
});
|
|
|
|
/** 입력 필요 중 아직 빈 칸 */
|
|
export const stillNeed = (need: string[], values: Record<string, unknown> | undefined): string[] =>
|
|
need.filter((k) => blank(values?.[k]));
|
|
|
|
/** 단산 줄 고른 것 — 그 SK(필요 = 빈 입력 필요 칸 · `missing` 이 막음) · 층 · 주인 안 둠 = 옮길 층(SK 를 만든 층)의 SK */
|
|
export function skPick(
|
|
sk: string,
|
|
doc: ImportedDoc,
|
|
need: string[],
|
|
marks: string[],
|
|
): DraftPick & { 표지: string[] } {
|
|
const values = needValues(doc);
|
|
return {
|
|
갈래: "단가산출",
|
|
키: sk,
|
|
이름: String(doc.이름 ?? sk),
|
|
단위: String(doc.단위 ?? ""),
|
|
입력값: Object.fromEntries(need.map((k) => [k, values[k] ?? ""])),
|
|
출처: {},
|
|
몫: null,
|
|
필요: need,
|
|
표지: marks,
|
|
};
|
|
}
|
|
|
|
/** 결과 표지 — 가져온 식 있음 | 우리 기초(+ 근거 단) */
|
|
export function marksOf(doc: ImportedDoc, tier: Tier | null): string[] {
|
|
if (isImported(doc)) return ["가져온 식 있음"];
|
|
return tier ? [`근거 ${tier}`] : ["사람 고름"];
|
|
}
|
|
|
|
/** 단산 줄 결과 칩 글 — SK 고른 것이 있으면 그 표지 + 입력 필요 n · 없으면 null(옛 판정 칩) */
|
|
export function rowChips(picks: (DraftPick & { 표지?: string[] })[]): string[] | null {
|
|
const sk = picks.find((p) => p.갈래 === "단가산출" && /^SK-/.test(String(p.키 ?? "")));
|
|
if (!sk) return null;
|
|
const need = stillNeed(sk.필요 ?? [], sk.입력값);
|
|
return [...(sk.표지 ?? ["가져온 식 있음"]), ...(need.length ? [`입력 필요 ${need.length}`] : [])];
|
|
}
|
|
|
|
/** 금액 차이 글(후 − 전) */
|
|
export function gapText(
|
|
before: Money4 | null | undefined,
|
|
after: Money4 | null | undefined,
|
|
): string {
|
|
if (!before || !after) return "—";
|
|
const d = after.계 - before.계;
|
|
return `${d > 0 ? "+" : ""}${d.toLocaleString("ko-KR")}`;
|
|
}
|