Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Api.ts
T

212 lines
6.5 KiB
TypeScript

/* =============================================================================
* 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 type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
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];
/** 일위대가 시트 행 [시작, 끝](엑셀 1부터 · 329 · 옛 파일 없음) */
행?: [number, number];
}
/** 엑셀 보기 창 목록 한 줄 — 호표 · 단가산출 블록 행 범위 */
export interface HopyoSpan {
번: number;
이름: string;
행: [number, number];
}
/** 엑셀 보기 창 격자(329) — 시트 「일위대가」 · 「단가산출」 */
export interface HopyoGrid {
책: Workbook;
호표: HopyoSpan[];
단산: HopyoSpan[];
}
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, unknown>;
못읽음: 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, unknown>;
못읽음: 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<HopyoBrief, "금액"> {
층: Layer;
주인: string | null;
키: string;
파일: string;
}
/** 확정 한 줄 — 로직 · 자재 · 빼기 · null(지움) */
export type HopyoMemory =
| { 로직: string; 입력값: Record<string, unknown>; 환산: number }
| { 자재: string; 입력값: Record<string, unknown>; 환산: 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<HopyoFile[]> =>
(await call<{ 줄: HopyoFile[] }>(at(scope, "/hopyo"))).줄;
export const readHopyo = (scope: HopyoScope, key: string): Promise<HopyoView> =>
call(at(scope, `/hopyo/${enc(key)}`));
/** 격자 — 없으면 404 「격자 없음 — 다시 올리면 보임」(옛 파일) */
export const readHopyoGrid = (scope: HopyoScope, key: string): Promise<HopyoGrid> =>
call(at(scope, `/hopyo-grid/${enc(key)}`));
export const readHopyoOne = (scope: HopyoScope, key: string, no: number): Promise<HopyoOne> =>
call(at(scope, `/hopyo/${enc(key)}/${no}`));
export const guessHopyo = (
scope: HopyoScope,
key: string,
no: number,
차례: number,
대상: HopyoTarget,
): Promise<HopyoGuess> =>
call(at(scope, `/hopyo/${enc(key)}/${no}/guess`), {
method: "POST",
body: JSON.stringify({ 차례, 대상 }),
});
export const saveHopyoMemory = (
scope: HopyoScope,
줄: Record<string, HopyoMemory>,
): Promise<unknown> =>
call(at(scope, "/hopyo-memory"), { method: "POST", body: JSON.stringify({ 줄 }) });
export const deleteHopyo = (scope: HopyoScope, key: string): Promise<unknown> =>
call(at(scope, `/hopyo/${enc(key)}`), { method: "DELETE" });
/** 실무 호표 → `target` 층 사전조합(파일 층 = `from`) */
export const buildHopyo = (
target: HopyoScope,
from: { 층: Layer; 주인: string | null; 키: string; 번: number },
새로 = false,
): Promise<HopyoBuilt> =>
call(at(target, "/hopyo-build"), {
method: "POST",
body: JSON.stringify({ ...from, 새로 }),
});
export const listHopyoPicks = async (scope: HopyoScope): Promise<HopyoPickRow[]> =>
(await call<{ 줄: HopyoPickRow[] }>(at(scope, "/hopyo-picks"))).줄;
/** 엑셀 올리기 — 서버 까닭 글 그대로(시트 없음 · 20 MB · 엉터리) */
export async function uploadHopyo(scope: HopyoScope, file: File): Promise<HopyoFile> {
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;
}