Files
Aislo/M01_MasterData/M01_MasterData_UI_Combo_Api.ts
eomsangdonandClaude Sonnet 5 0fb69c053b feat(M01): 일위대가 조합에 칸 「본뜬키」 더함 · 목록 · 상세에 보임 (계약 더함)
- 열 한 벌에 「본뜬키」(소유 다음 · 비고 앞) — 비면 원본 · 있으면 본뜬 원본 조합 키
- 서버가 없는 키 · 자기 키를 400 으로 막고 검사(check_master)도 같은 규칙
- 정본 조합 37 줄에 본뜬키 null 채움 · 목록 「본뜬키」 열 · 상세 「원본 / 본뜬키」 줄
- _틀.md 10장 · _화면_계약.md 9장 갱신 · 시험 하나 더함

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoyUwuTFiDJfvCfHpvKKzp
2026-09-28 18:56:28 +09:00

109 lines
3.4 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_Combo_Api.ts
* 일위대가 조합 화면이 부르는 서버 길 — 조합 마스터 `일위대가조합.json`(키 UA)
* 길 = `_화면_계약.md` 9장
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import { ApiError } from "./M01_MasterData_UI_Logic_Api";
/** 담은 로직 한 줄 — 로직 키와 메모뿐(수량·계산은 마스터에 두지 않음 · 계약 9장) */
export interface ComboLogic {
로직: string;
메모: string | null;
}
/** 조합 한 줄 통째 — `_틀.md` 10장 · 키·소유는 서버 것이 이김 */
export interface Combo {
키: string;
이름: string;
구분: string | null;
상세구분: string | null;
단위: string | null;
담은로직: ComboLogic[];
출처: string | null;
소유: string | null;
/** 이 조합을 본떠 만든 원본 키 — 비면 원본(M02 가 갈라 낸 조합에 채워짐) */
본뜬키: string | null;
비고: string | null;
}
export interface ComboSummary extends Combo {
count: number;
blocked: boolean;
reasons: string[];
}
/** `GET /combo` 의 `줄` — 담은 로직에 이름을 붙인 것 */
export interface ComboLine {
차례: number;
로직: string;
메모: string | null;
ok: boolean;
까닭?: string;
이름?: string;
구분?: string;
상세구분?: string;
결과단위?: string;
}
export interface ComboOne {
file: string;
version: string;
combo: Combo;
줄: ComboLine[];
}
export interface PreviewLine {
차례: number;
로직: string;
이름: string;
ok: boolean;
까닭?: string;
갈래?: string;
결과?: number;
노무비?: number;
재료비?: number;
경비?: number;
계?: number;
}
export interface PreviewAnswer {
줄: PreviewLine[];
노무비: number;
재료비: number;
경비: number;
계: number;
멈춤: string[];
}
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;
}
export const fetchCombos = (): Promise<ComboSummary[]> =>
request<{ combos: ComboSummary[] }>("/combos").then((d) => d.combos);
export const fetchCombo = (key: string): Promise<ComboOne> =>
request(`/combo?key=${encodeURIComponent(key)}`);
export const newCombo = (combo: Combo): Promise<{ file: string; version: string; key: string }> =>
request("/combo/new", { combo, owner: "현장" });
export const editCombo = (
key: string,
version: string,
combo: Combo,
): Promise<{ file: string; version: string }> => request("/combo/edit", { key, version, combo });
export const deleteCombo = (key: string, version: string): Promise<unknown> =>
request("/combo/delete", { key, version });
/** 보기만 — 수량(inputs)은 이 부름에만 실림 · 저장 안 함 */
export const previewCombo = (
combo: Combo | string,
inputs: Record<string, Record<string, unknown>>,
): Promise<PreviewAnswer> => request("/combo/preview", { combo, inputs });