- 새 M02_MasterTemplete_Combo_Store · Router_Combos — /api/m02/layers/{layer}/combos 목록 · 하나 · 새로 · 고침 · 지움 · 나무 · 알약 · 미리 보기 · /api/m02/combos/all(세 층)
- 키 장부 UC{회사}-###### · UP{사용자}-######(파일 머리 다음 · 지운 번호 안 씀) · 마스터 UA 는 M01 저장 길 그대로
- 나무 = 폴더 [구분, 상세구분] 깊이 2 · 빈 폴더는 머리 폴더 · 쓰는 조합 든 폴더 지우기 409
- 옛 /api/m01/combo* 길 뺌(logic/combos 만 남김) · 조합 화면 Api 는 M02 마스터 층 길로
- use-combo · 갈라 내기 · 항목 이름 · 면적 알약 · 스냅샷 원본이 세 층 조합을 읽음 · 남의 층 조합 403 · 갈라 낸 조합은 그 층에(시스템 관리자 한정 뺌)
- 시험 옮김(test_m01_combo · test_m01_combo_inputs) · 새 test_m02_combo_layers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
/* =============================================================================
|
|
* M01_MasterData_UI_Combo_Api.ts
|
|
* 일위대가 조합 화면이 부르는 서버 길 — 조합 마스터 `일위대가조합.json`(키 UA)
|
|
* 길 = M02 층 길(`/api/m02/layers/system/combos` · PLAN 27-2a) · 모양 = `_화면_계약.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[];
|
|
}
|
|
|
|
/** 조합 길 = M02 층 길 마스터(`/api/m02/layers/system/combos` · PLAN 27-2a) */
|
|
async function request<T>(path: string, method = "GET", body?: unknown): Promise<T> {
|
|
const response = await fetch(`${API_BASE_URL}/m02/layers/system/combos${path}`, {
|
|
method,
|
|
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 FILE = "일위대가조합.json";
|
|
const q = (key: string): string => `/${encodeURIComponent(key)}`;
|
|
|
|
export const fetchCombos = (): Promise<ComboSummary[]> =>
|
|
request<{ 줄: ComboSummary[] }>("").then((d) => d.줄);
|
|
|
|
export const fetchCombo = (key: string): Promise<ComboOne> =>
|
|
request<Omit<ComboOne, "file" | "version"> & { 판: string }>(q(key)).then((d) => ({
|
|
...d,
|
|
file: FILE,
|
|
version: d.판,
|
|
}));
|
|
|
|
export const newCombo = (combo: Combo): Promise<{ file: string; version: string; key: string }> =>
|
|
request<{ 키: string; 판: string }>("", "POST", { combo: { ...combo, 소유: "현장" } }).then(
|
|
(d) => ({ file: FILE, version: d.판, key: d.키 }),
|
|
);
|
|
|
|
export const editCombo = (
|
|
key: string,
|
|
version: string,
|
|
combo: Combo,
|
|
): Promise<{ file: string; version: string }> =>
|
|
request<{ 판: string }>(q(key), "PUT", { combo, 판: version }).then((d) => ({
|
|
file: FILE,
|
|
version: d.판,
|
|
}));
|
|
|
|
export const deleteCombo = (key: string, version: string): Promise<unknown> =>
|
|
request(`${q(key)}?판=${encodeURIComponent(version)}`, "DELETE");
|
|
|
|
/** 보기만 — 수량(inputs)은 이 부름에만 실림 · 저장 안 함 */
|
|
export const previewCombo = (
|
|
combo: Combo | string,
|
|
inputs: Record<string, Record<string, unknown>>,
|
|
): Promise<PreviewAnswer> => request("/preview", "POST", { combo, inputs });
|