Files
Aislo/M01_MasterData/M01_MasterData_UI_Logic_Api.ts
T

337 lines
9.0 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_Logic_Api.ts
* 로직 화면이 부르는 서버 길 — 계약 `resources/master_data/_화면_계약.md` · `/api/m01`
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
/** 로직 한 줄 — 파일 그대로(한글 칸) · 틀 `_틀.md` 6장 */
export interface LogicInput {
이름: string;
단위?: string;
고르기?: (string | number)[];
범위?: [number, number];
}
export interface HoLine {
종류: string;
요소: string;
이름?: string;
규격?: string;
단위?: string;
수량: string;
비목?: string;
}
export interface NamedFormula {
이름: string;
: string;
비목?: string;
출처?: string;
}
export interface LogicRow {
/** 서버가 주는 키 — 새 로직은 "" (저장 때 대장의 다음 번호) */
: string;
원문번호: string;
이름: string;
결과단위: string;
출처: string;
소유?: string;
입력: LogicInput[];
중간: NamedFormula[];
호표?: HoLine[];
결과?: { : string };
덧줄?: NamedFormula[];
끝수?: string | null;
비고?: string;
[extra: string]: unknown;
}
export interface LogicSummary {
file: string;
구분: string;
상세구분: string;
: string;
원문번호: string;
이름: string;
결과단위: string;
출처: string;
blocked: boolean;
reasons: string[];
}
export interface ElementBrief {
ref: string;
file?: string;
이름?: string;
규격?: string;
단위?: unknown;
값?: unknown;
값칸?: Record<string, string> | null;
}
export interface LogicOne {
file: string;
version: string;
logic: LogicRow;
blocked: boolean;
reasons: string[];
prices: Record<string, ElementBrief | null>;
}
export interface CalcLine {
이름: string;
단위: string;
수량: number;
단가: number;
금액: number;
비목: Record<string, number>;
/** 값 없는 요소를 다른 값으로 셈 — 「준용: <직종>」 · 「산정: …」 */
출처?: string;
}
export type CalcAnswer =
| {
ok: true;
lines?: CalcLine[];
sums?: Record<string, number>;
result?: unknown;
middle: Record<string, unknown>;
}
| { ok: false; reason: string };
export interface TextLine {
이름: string;
: string;
/** 같은 줄을 변수 이름으로 적은 식 — 왼쪽 칸 */
이름글?: string;
/** 줄 번호(답 전체에서 하나씩) — 두 칸 줄 맞춤 · 밝히기 */
짝?: number;
금액: number;
까닭?: string;
}
/** `POST /text` 답 — 서버가 만든 읽는 식 줄(화면은 그대로 찍음) */
export type TextAnswer =
| {
ok: true;
groups: { 비목: string; : TextLine[]; 소계: number; 끝수: string }[];
: number;
}
| { ok: false; reason: string };
export interface SubBrief {
name: string;
book: string | null;
details: string[];
}
export interface LogicFile {
file: string;
book: string;
chapter: string;
version: string;
}
export interface SaveChange {
op: "edit" | "add" | "delete";
key?: string;
row?: LogicRow;
}
export interface SaveFile {
file: string;
version: string;
changes: SaveChange[];
}
/** 서버가 준 몸과 상태 번호 — 409·422 는 몸(`detail`)을 화면이 풀어 보임 */
export class ApiError extends Error {
constructor(
public status: number,
public detail: unknown,
) {
super(typeof detail === "string" ? detail : JSON.stringify(detail));
}
}
async function request<T>(path: string, body?: unknown): Promise<T> {
const response = await fetch(`${API_BASE_URL}/m01${path}`, {
method: body === undefined ? "GET" : "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
});
const data = (await response.json().catch(() => ({}))) as { detail?: unknown } & T;
if (!response.ok) throw new ApiError(response.status, data.detail ?? response.statusText);
return data;
}
const query = (params: Record<string, string>): string => new URLSearchParams(params).toString();
export const fetchLogics = (): Promise<LogicSummary[]> =>
request<{ logics: LogicSummary[] }>("/logics").then((d) => d.logics);
/** 왼쪽 트리 목록 — 서버가 준 구분·상세구분(장 차례대로) */
export const fetchLogicSubs = (): Promise<SubBrief[]> =>
request<{ subs: SubBrief[] }>(`/subs?${query({ group: "로직" })}`).then((d) => d.subs);
export const fetchLogic = (key: string): Promise<LogicOne> => request(`/logic?${query({ key })}`);
export const fetchLogicFiles = (): Promise<LogicFile[]> =>
request<{ files: LogicFile[] }>(`/groups/${encodeURIComponent("로직")}/files`).then(
(d) => d.files,
);
export const searchElements = (
group: string,
q: string,
): Promise<{ total: number; items: ElementBrief[] }> =>
request(`/elements?${query({ group, q, limit: "100" })}`);
export const searchPrice = (q: string): Promise<{ total: number; items: ElementBrief[] }> =>
request(`/pick?${query({ kind: "price", q, limit: "100" })}`);
export const fetchMaterials = (cond: {
sub: string;
detail?: string;
spec?: string;
region?: string;
}): Promise<{ total: number; 기본: string | null; items: ElementBrief[] }> =>
request(`/materials?${query({ ...cond, limit: "100" })}`);
export const runCalc = (body: {
key: string;
inputs: Record<string, unknown>;
row?: LogicRow;
file?: string;
}): Promise<CalcAnswer> => request("/calc", body);
export const runText = (body: {
key: string;
inputs: Record<string, unknown>;
row?: LogicRow;
file?: string;
}): Promise<TextAnswer> => request("/text", body);
export const saveFiles = (
files: SaveFile[],
): Promise<{ files: { file: string; version: string }[] }> => request("/save", { files });
/** 본떠 만들기 — 정본·자체 둘 다 본뜸 · 새 키(`GX…`)는 서버가 줌(계약 5장) */
export const copyLogic = (
key: string,
): Promise<{ file: string; version: string; key: string; logic: LogicRow }> =>
request("/logic/copy", { key });
/** 복사해서 만들기(계약 7장) — 찾기·로직 부르기 한 덩이를 이름 하나(별칭)로 보임 */
export interface FormulaAlias {
이름: string;
종류: "찾기" | "로직";
참조: string;
/** 되돌릴 원문 부르기 글 — 저장 때 이 글로 갈아 끼움 */
원문: string;
밑이름: string;
값칸: Record<string, string>;
결과단위: string;
자리: string[];
}
export interface FormulaLine {
자리: string;
갈래: "중간" | "호표" | "덧줄" | "결과";
차례: number;
이름: string;
단위: string;
비목: string;
: string;
출처: string;
종류?: string;
요소?: unknown;
}
export interface LogicFormula {
file: string;
version: string;
key: string;
소유: string;
prices: Record<string, ElementBrief | null>;
로직: LogicRow;
별칭: FormulaAlias[];
: FormulaLine[];
}
export const fetchLogicFormula = (key: string): Promise<LogicFormula> =>
request(`/logic/formula?${query({ key })}`);
export const saveLogicFormula = (body: {
로직: LogicRow;
별칭: FormulaAlias[];
owner: string;
이름?: string;
본뜬키?: string;
}): Promise<{ file: string; version: string; key: string; logic: LogicRow }> =>
request("/logic/formula/save", body);
/** 저장 전 시험 계산용 — 별칭을 원문으로 되돌린 로직 줄만(저장 안 함) */
export const previewLogicFormula = (
로직: LogicRow,
별칭: FormulaAlias[],
): Promise<{ logic: LogicRow }> => request("/logic/formula/preview", { 로직, 별칭 });
/* ── 식으로 새 로직 만들기(계약 7장) ── */
export interface DraftFind {
: string;
값칸: string;
조건: Record<string, string>;
}
/** 변수마다 정한 것 */
export interface DraftDecided {
무엇?: string;
단위?: string;
비목?: string;
요소?: string;
찾기?: DraftFind;
값?: string;
설명?: string;
}
export interface DraftVar {
이름: string;
처음: number;
갈래: "정의됨" | "미정" | "비목합";
정의줄: number | null;
쓰임: number[];
무엇: string | null;
단위: string | null;
비목: string | null;
정할것: string[];
}
export interface DraftProblem {
: number | null;
자리: number | null;
갈래: string;
: string;
}
export interface DraftAnswer {
: { : number; 이름: string; : string; : number }[];
변수: DraftVar[];
결과: string;
문제: DraftProblem[];
다됨: boolean;
logic: LogicRow | null;
/** 저장했을 때만 */
file?: string;
version?: string;
key?: string;
}
export interface DraftHead {
이름: string;
결과단위: string;
원문번호?: string;
출처?: string;
비고?: string;
끝수?: string;
}
/** 검사·미리보기 · `save` 면 자체 로직(키 `GX`)으로 저장 — 문제가 있으면 422 */
export const draftLogic = (body: {
text: string;
decided: Record<string, DraftDecided>;
head?: DraftHead;
save?: boolean;
owner?: string;
}): Promise<DraftAnswer> => request("/logic/draft", body);