/* ============================================================================= * M02_MasterTemplete_Hopyo_Api.ts * 일위대가 실무자료(PLAN 324) 서버 길 — `/api/m02/layers/{층}/hopyo…` · 층 = 화면 층(`Api_Fetch` 지금 층) · * 올리기 = FormData(숨은 file 입력) · 그 밖 = 공용 `call`(서버 까닭 글 그대로). * ========================================================================== */ import { API_BASE_URL } from "@config/config_frontend"; import { call, getActiveLayer, getActiveOwner, type Layer } from "./M02_MasterTemplete_Api_Fetch"; import type { LaborRef } from "./M02_MasterTemplete_Labor_Calc"; /** 층 · 주인(null = 자기 층) */ export interface HopyoScope { layer: Layer; owner: string | null; } export interface HopyoFile { 키: string; 파일: string; 올린때: string; 올린이: { id: number; 이름: string | null } | null; 시트: string; 호표수: number; 어긋남: number; } export type Money = Record<"계" | "노무비" | "재료비" | "경비", number | null>; export interface HopyoBrief { 번: number; 이름: string; 규격: string; 단위: string; 금액: Money; /** [들어감 수, 줄 수] */ 맞춤: [number, number]; } export interface HopyoView extends HopyoFile { 검산: { 어긋남: string[]; 총괄표: number }; 호표: HopyoBrief[]; 맞춤: [number, number]; } /** 맞춤 대상 — 로직 · 자재 · 다른 호표 */ export interface HopyoTarget { 로직?: string; 자재?: string; 호표?: number; 이름?: string; 규격?: string; 단위?: string | null; 점수?: number; } export interface HopyoMatch { 열쇠: string; 상태: "기억" | "같음" | "후보" | "없음" | "빼기" | "호표" | "%"; 대상: HopyoTarget | null; 입력값: Record; 못읽음: string[]; 환산: { 배: number | null; 갈래: string } | null; 들어감: boolean; 후보?: HopyoTarget[]; } export interface HopyoLine { 차례: number; 이름: string; 규격: string; 수량: number | null; 단위: string; 갈래: string; 단가: Money; 금액: Money; 비고: string; 맞춤: HopyoMatch; } export interface HopyoOne extends HopyoFile { 호표: HopyoBrief & { 줄: HopyoLine[] }; } export interface HopyoGuess { 대상: HopyoTarget; 입력값: Record; 못읽음: string[]; 환산: { 배: number | null; 갈래: string }; } export interface HopyoMissing { 차례: number; 이름: string; 규격: string; 수량: number | null; 단위: string; 금액: number | null; 까닭: string; } export interface HopyoBuilt { 참조: LaborRef; 이름: string; 다시씀: boolean; 빠진줄: HopyoMissing[]; 비교: { 엑셀: number | null; 우리: number | null; 차이: number | null; 까닭: string | null }; } /** [로직 추가] 「실무 호표」 줄 하나 */ export interface HopyoPickRow extends Omit { 층: Layer; 주인: string | null; 키: string; 파일: string; } /** 확정 한 줄 — 로직 · 자재 · 빼기 · null(지움) */ export type HopyoMemory = | { 로직: string; 입력값: Record; 환산: number } | { 자재: string; 입력값: Record; 환산: number } | { 빼기: true } | null; const enc = encodeURIComponent; /** 지금 화면 층(구조물 조합 층) */ export const pageScope = (): HopyoScope => ({ layer: getActiveLayer(), owner: getActiveOwner() }); const at = (scope: HopyoScope, path: string): string => { const who = scope.owner && scope.layer !== "system" ? `owner=${enc(scope.owner)}` : ""; const join = path.includes("?") ? "&" : "?"; return `/layers/${scope.layer}${path}${who ? join + who : ""}`; }; export const listHopyo = async (scope: HopyoScope): Promise => (await call<{ 줄: HopyoFile[] }>(at(scope, "/hopyo"))).줄; export const readHopyo = (scope: HopyoScope, key: string): Promise => call(at(scope, `/hopyo/${enc(key)}`)); export const readHopyoOne = (scope: HopyoScope, key: string, no: number): Promise => call(at(scope, `/hopyo/${enc(key)}/${no}`)); export const guessHopyo = ( scope: HopyoScope, key: string, no: number, 차례: number, 대상: HopyoTarget, ): Promise => call(at(scope, `/hopyo/${enc(key)}/${no}/guess`), { method: "POST", body: JSON.stringify({ 차례, 대상 }), }); export const saveHopyoMemory = ( scope: HopyoScope, 줄: Record, ): Promise => call(at(scope, "/hopyo-memory"), { method: "POST", body: JSON.stringify({ 줄 }) }); export const deleteHopyo = (scope: HopyoScope, key: string): Promise => call(at(scope, `/hopyo/${enc(key)}`), { method: "DELETE" }); /** 실무 호표 → `target` 층 사전조합(파일 층 = `from`) */ export const buildHopyo = ( target: HopyoScope, from: { 층: Layer; 주인: string | null; 키: string; 번: number }, 새로 = false, ): Promise => call(at(target, "/hopyo-build"), { method: "POST", body: JSON.stringify({ ...from, 새로 }), }); export const listHopyoPicks = async (scope: HopyoScope): Promise => (await call<{ 줄: HopyoPickRow[] }>(at(scope, "/hopyo-picks"))).줄; /** 엑셀 올리기 — 서버 까닭 글 그대로(시트 없음 · 20 MB · 엉터리) */ export async function uploadHopyo(scope: HopyoScope, file: File): Promise { const form = new FormData(); form.append("file", file); const response = await fetch(`${API_BASE_URL}/m02${at(scope, "/hopyo")}`, { method: "POST", credentials: "include", body: form, }); const body = (await response.json().catch(() => ({}))) as { detail?: unknown } & HopyoFile; if (!response.ok) throw new Error(typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`); return body; }