- 모든 요소·표·로직 줄의 열쇠 → 키(LB000123 꼴) · 옛 열쇠·원문 번호는 원문번호 칸 · 대장 _키대장.json (키 36,840 · 다음 번호)
- 변수 적는 법 키로 — 로직 식·연결·준용·통합·후보·조달 연결 일괄 변환 · {이름} 낀 참조는 ID:원문번호
- 인력 8 파일 → 인력.json(줄마다 조사 · 머리 조사 묶음) · 기계 2 파일 → 기계.json(세부분류)
- 소요량·계수·로직 파일 이름 = 그룹_원문_NN장_장 제목 · 머리 부문·차례
- 엔진(값 찾기 · 알림에 키 옆 이름) · check_master(키 모양·겹침·대장·끊긴 키 · 기계 요소 줄도 본문 결손 대조) · M01 서버(새 줄 키는 대장 다음 번호 · 키 못 고침) · 화면 칸 이름만 맞춤
- 로직 일괄 시험 계산 1,351 줄 — 옮기기 전후 줄마다 결과·금액 같음
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
170 lines
4.4 KiB
TypeScript
170 lines
4.4 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;
|
|
book: string;
|
|
chapter: 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 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 fetchLogic = (book: string, key: string): Promise<LogicOne> =>
|
|
request(`/logic?${query({ book, 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 runCalc = (body: {
|
|
book: string;
|
|
key: string;
|
|
inputs: Record<string, unknown>;
|
|
row?: LogicRow;
|
|
file?: string;
|
|
}): Promise<CalcAnswer> => request("/calc", body);
|
|
|
|
export const saveFiles = (
|
|
files: SaveFile[],
|
|
): Promise<{ files: { file: string; version: string }[] }> => request("/save", { files });
|