ERP-node/frontend/lib/api/dynamicForm.ts

754 lines
23 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { apiClient, ApiResponse } from "./client";
// 동적 폼 데이터 타입
export interface DynamicFormData {
screenId: number;
tableName: string;
data: Record<string, any>;
}
// 폼 데이터 저장 응답 타입
export interface SaveFormDataResponse {
id: number;
success: boolean;
message: string;
data?: Record<string, any>;
}
// 폼 데이터 조회 응답 타입
export interface FormDataResponse {
id: number;
screenId: number;
tableName: string;
data: Record<string, any>;
createdAt: string;
updatedAt: string;
}
// 동적 폼 API 클래스
export class DynamicFormApi {
/**
* 폼 데이터 저장 (기존 버전 - 레거시 지원)
* @param formData 저장할 폼 데이터
* @returns 저장 결과
*/
static async saveFormData(formData: DynamicFormData): Promise<ApiResponse<SaveFormDataResponse>> {
try {
const response = await apiClient.post("/dynamic-form/save", formData);
return {
success: true,
data: response.data,
message: "데이터가 성공적으로 저장되었습니다.",
};
} catch (error: any) {
console.error("❌ 폼 데이터 저장 실패:", error);
const errorMessage = error.response?.data?.message || error.message || "데이터 저장 중 오류가 발생했습니다.";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 폼 데이터 저장 (개선된 버전)
* @param formData 저장할 폼 데이터
* @returns 저장 결과 (상세한 검증 정보 포함)
*/
static async saveData(formData: DynamicFormData): Promise<ApiResponse<SaveFormDataResponse>> {
try {
const response = await apiClient.post("/dynamic-form/save-enhanced", formData);
return response.data;
} catch (error: any) {
console.error("❌ 개선된 폼 데이터 저장 실패:", error);
// 개선된 오류 처리
const errorResponse = error.response?.data;
if (errorResponse && !errorResponse.success) {
return errorResponse; // 서버에서 온 구조화된 오류 응답 그대로 반환
}
const errorMessage = error.response?.data?.message || error.message || "데이터 저장 중 오류가 발생했습니다.";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 폼 데이터 업데이트
* @param id 레코드 ID
* @param formData 업데이트할 폼 데이터
* @returns 업데이트 결과
*/
static async updateFormData(
id: string | number,
formData: Partial<DynamicFormData>,
): Promise<ApiResponse<SaveFormDataResponse>> {
try {
console.log("🔄 폼 데이터 업데이트 요청:", { id, formData });
console.log("🌐 API URL:", `/dynamic-form/${id}`);
console.log("📦 요청 본문:", JSON.stringify(formData, null, 2));
const response = await apiClient.put(`/dynamic-form/${id}`, formData);
console.log("✅ 폼 데이터 업데이트 성공:", response.data);
console.log("📊 응답 상태:", response.status);
console.log("📋 응답 헤더:", response.headers);
return {
success: true,
data: response.data,
message: "데이터가 성공적으로 업데이트되었습니다.",
};
} catch (error: any) {
console.error("❌ 폼 데이터 업데이트 실패:", error);
console.error("📊 에러 응답:", error.response?.data);
console.error("📊 에러 상태:", error.response?.status);
const errorMessage = error.response?.data?.message || error.message || "데이터 업데이트 중 오류가 발생했습니다.";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 폼 데이터 부분 업데이트 (변경된 필드만)
* @param id 레코드 ID
* @param originalData 원본 데이터
* @param newData 변경할 데이터
* @param tableName 테이블명
* @returns 업데이트 결과
*/
static async updateFormDataPartial(
id: string | number, // 🔧 UUID 문자열도 지원
originalData: Record<string, any>,
newData: Record<string, any>,
tableName: string,
): Promise<ApiResponse<SaveFormDataResponse>> {
try {
console.log("🔄 폼 데이터 부분 업데이트 요청:", {
id,
originalData,
newData,
tableName,
});
const response = await apiClient.patch(`/dynamic-form/${id}/partial`, {
tableName,
originalData,
newData,
});
console.log(" :", response.data);
return {
success: true,
data: response.data,
message: " .",
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 폼 데이터 삭제
* @param id 레코드 ID
* @returns 삭제 결과
*/
static async deleteFormData(id: number): Promise<ApiResponse<void>> {
try {
console.log("🗑 :", id);
await apiClient.delete(`/dynamic-form/${id}`);
console.log(" ");
return {
success: true,
message: " .",
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 실제 테이블에서 폼 데이터 삭제
* @param id 레코드 ID
* @param tableName 테이블명
* @param screenId 화면 ID (제어관리 실행용, 선택사항)
* @returns 삭제 결과
*/
static async deleteFormDataFromTable(
id: string | number,
tableName: string,
screenId?: number
): Promise<ApiResponse<void>> {
try {
console.log("🗑 :", { id, tableName, screenId });
await apiClient.delete(`/dynamic-form/${id}`, {
data: { tableName, screenId },
});
console.log(" ");
return {
success: true,
message: " .",
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 폼 데이터 목록 조회
* @param screenId 화면 ID
* @param params 검색 파라미터
* @returns 폼 데이터 목록
*/
static async getFormDataList(
screenId: number,
params?: {
page?: number;
size?: number;
search?: string;
sortBy?: string;
sortOrder?: "asc" | "desc";
},
): Promise<
ApiResponse<{
content: FormDataResponse[];
totalElements: number;
totalPages: number;
currentPage: number;
size: number;
}>
> {
try {
console.log("📋 :", { screenId, params });
const response = await apiClient.get(`/dynamic-form/screen/${screenId}`, { params });
console.log(" :", response.data);
return {
success: true,
data: response.data,
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 특정 폼 데이터 조회
* @param id 레코드 ID
* @returns 폼 데이터
*/
static async getFormData(id: number): Promise<ApiResponse<FormDataResponse>> {
try {
console.log("📄 :", id);
const response = await apiClient.get(`/dynamic-form/${id}`);
console.log(" :", response.data);
return {
success: true,
data: response.data,
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 테이블의 컬럼 정보 조회 (폼 검증용)
* @param tableName 테이블명
* @returns 컬럼 정보
*/
static async getTableColumns(tableName: string): Promise<
ApiResponse<{
tableName: string;
columns: Array<{
columnName: string;
dataType: string;
nullable: boolean;
primaryKey: boolean;
maxLength?: number;
defaultValue?: any;
}>;
}>
> {
try {
console.log("📊 :", tableName);
const response = await apiClient.get(`/dynamic-form/table/${tableName}/columns`);
console.log(" :", response.data);
return {
success: true,
data: response.data,
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 폼 데이터 검증
* @param tableName 테이블명
* @param data 검증할 데이터
* @returns 검증 결과
*/
static async validateFormData(
tableName: string,
data: Record<string, any>,
): Promise<
ApiResponse<{
valid: boolean;
errors: Array<{
field: string;
message: string;
code: string;
}>;
}>
> {
try {
console.log(" :", { tableName, data });
const response = await apiClient.post(`/dynamic-form/validate`, {
tableName,
data,
});
console.log(" :", response.data);
return {
success: true,
data: response.data,
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 테이블의 기본키 조회
* @param tableName 테이블명
* @returns 기본키 컬럼명 배열
*/
static async getTablePrimaryKeys(tableName: string): Promise<ApiResponse<string[]>> {
try {
const response = await apiClient.get(`/dynamic-form/table/${tableName}/primary-keys`);
return {
success: true,
data: response.data.data,
message: " .",
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 테이블 데이터 조회 (페이징 + 검색)
* @param tableName 테이블명
* @param params 검색 파라미터
* @returns 테이블 데이터
*/
static async getTableData(
tableName: string,
params?: {
page?: number;
pageSize?: number;
search?: string;
sortBy?: string;
sortOrder?: "asc" | "desc";
filters?: Record<string, any>;
autoFilter?: {
enabled: boolean;
filterColumn?: string;
userField?: string;
};
},
): Promise<ApiResponse<any[]>> {
try {
console.log("📊 :", { tableName, params });
// autoFilter가 없으면 기본값으로 멀티테넌시 필터 적용
// pageSize를 size로 변환 (백엔드 파라미터명 호환)
const requestParams = {
...params,
size: params?.pageSize || params?.size || 100, // 기본값 100
autoFilter: params?.autoFilter ?? {
enabled: true,
filterColumn: "company_code",
userField: "companyCode",
},
};
const response = await apiClient.post(`/table-management/tables/${tableName}/data`, requestParams);
console.log(" ():", response.data);
console.log("🔍 response.data :", {
type: typeof response.data,
isArray: Array.isArray(response.data),
keys: response.data ? Object.keys(response.data) : [],
hasData: response.data?.data !== undefined,
dataType: response.data?.data ? typeof response.data.data : "N/A",
dataIsArray: response.data?.data ? Array.isArray(response.data.data) : false,
dataLength: response.data?.data ? (Array.isArray(response.data.data) ? response.data.data.length : "not array") : "no data",
// 중첩 구조 확인
dataDataExists: response.data?.data?.data !== undefined,
dataDataIsArray: response.data?.data?.data ? Array.isArray(response.data.data.data) : false,
dataDataLength: response.data?.data?.data ? (Array.isArray(response.data.data.data) ? response.data.data.data.length : "not array") : "no nested data",
});
// API 응답 구조: { data: [...], total, page, size, totalPages }
// 또는 중첩: { success: true, data: { data: [...], total, ... } }
// data 배열만 추출
let tableData: any[] = [];
if (Array.isArray(response.data)) {
// 케이스 1: 응답이 배열이면 그대로 사용
console.log(" 1: 응답이 ");
tableData = response.data;
} else if (response.data && Array.isArray(response.data.data)) {
// 케이스 2: 응답이 { data: [...] } 구조면 data 배열 추출
console.log(" 2: 응답이 { data: [...] } ");
tableData = response.data.data;
} else if (response.data?.data?.data && Array.isArray(response.data.data.data)) {
// 케이스 2-1: 중첩 구조 { success: true, data: { data: [...] } }
console.log(" 2-1: 중첩 { data: { data: [...] } }");
tableData = response.data.data.data;
} else if (response.data && typeof response.data === "object") {
// 케이스 3: 응답이 객체면 배열로 감싸기 (최후의 수단)
console.log(" 3: 응답이 ( )");
tableData = [response.data];
}
console.log(" :", {
originalType: typeof response.data,
isArray: Array.isArray(response.data),
hasDataProperty: response.data?.data !== undefined,
extractedCount: tableData.length,
firstRow: tableData[0],
allRows: tableData,
});
return {
success: true,
data: tableData,
message: " .",
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* 엑셀 업로드 (대량 데이터 삽입/업데이트)
* @param payload 업로드 데이터
* @returns 업로드 결과
*/
static async uploadExcelData(payload: {
tableName: string;
data: any[];
uploadMode: "insert" | "update" | "upsert";
keyColumn?: string;
}): Promise<ApiResponse<any>> {
try {
console.log("📤 :", payload);
const response = await apiClient.post(`/dynamic-form/excel-upload`, payload);
console.log(" :", response.data);
return {
success: true,
data: response.data,
message: " .",
};
} catch (error: any) {
console.error(" :", error);
const errorMessage = error.response?.data?.message || error.message || " .";
return {
success: false,
message: errorMessage,
errorCode: error.response?.data?.errorCode,
};
}
}
// ================================
// 마스터-디테일 엑셀 API
// ================================
/**
* 마스터-디테일 관계 정보 조회
* @param screenId 화면 ID
* @returns 마스터-디테일 관계 정보 (null이면 마스터-디테일 구조 아님)
*/
static async getMasterDetailRelation(screenId: number): Promise<ApiResponse<MasterDetailRelation | null>> {
try {
console.log("🔍 - :", screenId);
const response = await apiClient.get(`/data/master-detail/relation/${screenId}`);
return {
success: true,
data: response.data?.data || null,
message: response.data?.message || " ",
};
} catch (error: any) {
console.error(" - :", error);
return {
success: false,
data: null,
message: error.response?.data?.message || error.message,
};
}
}
/**
* 마스터-디테일 엑셀 다운로드 데이터 조회
* @param screenId 화면 ID
* @param filters 필터 조건
* @returns JOIN된 플랫 데이터
*/
static async getMasterDetailDownloadData(
screenId: number,
filters?: Record<string, any>
): Promise<ApiResponse<MasterDetailDownloadData>> {
try {
console.log("📥 - :", { screenId, filters });
const response = await apiClient.post(`/data/master-detail/download`, {
screenId,
filters,
});
return {
success: true,
data: response.data?.data,
message: " ",
};
} catch (error: any) {
console.error(" - :", error);
return {
success: false,
message: error.response?.data?.message || error.message,
};
}
}
/**
* 마스터-디테일 엑셀 업로드
* @param screenId 화면 ID
* @param data 엑셀에서 읽은 플랫 데이터
* @returns 업로드 결과
*/
static async uploadMasterDetailData(
screenId: number,
data: Record<string, any>[]
): Promise<ApiResponse<MasterDetailUploadResult>> {
try {
console.log("📤 - :", { screenId, rowCount: data.length });
const response = await apiClient.post(`/data/master-detail/upload`, {
screenId,
data,
});
return {
success: response.data?.success,
data: response.data?.data,
message: response.data?.message,
};
} catch (error: any) {
console.error(" - :", error);
return {
success: false,
message: error.response?.data?.message || error.message,
};
}
}
/**
* 마스터-디테일 간단 모드 엑셀 업로드
* - 마스터 정보는 UI에서 선택
* - 디테일 정보만 엑셀에서 업로드
* - 채번 규칙을 통해 마스터 키 자동 생성
* @param screenId 화면 ID
* @param detailData 디테일 데이터 배열
* @param masterFieldValues UI에서 선택한 마스터 필드 값
* @param numberingRuleId 채번 규칙 ID (optional)
* @param afterUploadFlowId 업로드 후 실행할 제어 ID (optional, 하위 호환성)
* @param afterUploadFlows 업로드 후 실행할 제어 목록 (optional)
* @returns 업로드 결과
*/
static async uploadMasterDetailSimple(
screenId: number,
detailData: Record<string, any>[],
masterFieldValues: Record<string, any>,
numberingRuleId?: string,
afterUploadFlowId?: string,
afterUploadFlows?: Array<{ flowId: string; order: number }>
): Promise<ApiResponse<MasterDetailSimpleUploadResult>> {
try {
console.log("📤 - :", {
screenId,
detailRowCount: detailData.length,
masterFieldValues,
numberingRuleId,
afterUploadFlows: afterUploadFlows?.length || 0,
});
const response = await apiClient.post(`/data/master-detail/upload-simple`, {
screenId,
detailData,
masterFieldValues,
numberingRuleId,
afterUploadFlowId,
afterUploadFlows,
});
return {
success: response.data?.success,
data: response.data?.data,
message: response.data?.message,
};
} catch (error: any) {
console.error(" - :", error);
return {
success: false,
message: error.response?.data?.message || error.message,
};
}
}
}
// 마스터-디테일 관계 타입
export interface MasterDetailRelation {
masterTable: string;
detailTable: string;
masterKeyColumn: string;
detailFkColumn: string;
masterColumns: Array<{ name: string; label: string; inputType: string; isFromMaster: boolean }>;
detailColumns: Array<{ name: string; label: string; inputType: string; isFromMaster: boolean }>;
}
// 마스터-디테일 다운로드 데이터 타입
export interface MasterDetailDownloadData {
headers: string[];
columns: string[];
data: Record<string, any>[];
masterColumns: string[];
detailColumns: string[];
joinKey: string;
}
// 마스터-디테일 업로드 결과 타입
export interface MasterDetailUploadResult {
success: boolean;
masterInserted: number;
masterUpdated: number;
detailInserted: number;
detailDeleted: number;
errors: string[];
}
// 🆕 마스터-디테일 간단 모드 업로드 결과 타입
export interface MasterDetailSimpleUploadResult {
success: boolean;
masterInserted: number;
detailInserted: number;
generatedKey: string; // 생성된 마스터 키
errors?: string[];
}
// 편의를 위한 기본 export
export const dynamicFormApi = DynamicFormApi;