summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/api/cron/tags-plant/start/route.ts38
-rw-r--r--lib/forms-plant/services.ts161
-rw-r--r--lib/sedp/get-tags-plant.ts1013
-rw-r--r--lib/tags-plant/queries.ts2
-rw-r--r--lib/tags-plant/service.ts158
-rw-r--r--lib/tags-plant/table/add-tag-dialog.tsx2
-rw-r--r--lib/tags-plant/table/tag-table-column.tsx19
-rw-r--r--lib/tags-plant/table/tag-table.tsx3
8 files changed, 682 insertions, 714 deletions
diff --git a/app/api/cron/tags-plant/start/route.ts b/app/api/cron/tags-plant/start/route.ts
index 83e06935..17a96ed7 100644
--- a/app/api/cron/tags-plant/start/route.ts
+++ b/app/api/cron/tags-plant/start/route.ts
@@ -88,8 +88,6 @@ async function processTagImport(syncId: string) {
const jobInfo = syncJobs.get(syncId)!;
const projectCode = jobInfo.projectCode;
const packageCode = jobInfo.packageCode;
- const mode = jobInfo.mode; // 모드 정보 추출
-
// 상태 업데이트: 처리 중
syncJobs.set(syncId, {
@@ -102,23 +100,40 @@ async function processTagImport(syncId: string) {
throw new Error('Package is required');
}
- // 여기서 실제 태그 가져오기 로직 import
const { importTagsFromSEDP } = await import('@/lib/sedp/get-tags-plant');
- // 진행 상황 업데이트를 위한 콜백 함수
- const updateProgress = (progress: number) => {
+ // ENG 모드 실행 (0~50%)
+ const updateProgressENG = (progress: number) => {
+ syncJobs.set(syncId, {
+ ...syncJobs.get(syncId)!,
+ progress: Math.floor(progress * 0.5)
+ });
+ };
+
+ const engResult = await importTagsFromSEDP(projectCode, packageCode, updateProgressENG, 'ENG');
+
+ // IM 모드 실행 (50~100%)
+ const updateProgressIM = (progress: number) => {
syncJobs.set(syncId, {
...syncJobs.get(syncId)!,
- progress
+ progress: 50 + Math.floor(progress * 0.5)
});
};
- // 실제 태그 가져오기 실행
- const result = await importTagsFromSEDP(projectCode, packageCode,updateProgress, mode);
+ const imResult = await importTagsFromSEDP(projectCode, packageCode, updateProgressIM, 'IM');
- // 명시적으로 캐시 무효화
+ // 캐시 무효화
revalidateTag(`tags-${packageCode}`);
- revalidateTag(`forms-${packageCode}-${mode}`);
+ revalidateTag(`forms-${packageCode}-ENG`);
+ revalidateTag(`forms-${packageCode}-IM`);
+
+ // 결과 합산
+ const result = {
+ processedCount: engResult.processedCount + imResult.processedCount,
+ excludedCount: engResult.excludedCount + imResult.excludedCount,
+ totalEntries: engResult.totalEntries + imResult.totalEntries,
+ errors: [...(engResult.errors || []), ...(imResult.errors || [])].filter(Boolean)
+ };
// 상태 업데이트: 완료
syncJobs.set(syncId, {
@@ -131,7 +146,6 @@ async function processTagImport(syncId: string) {
return result;
} catch (error: any) {
- // 에러 발생 시 상태 업데이트
syncJobs.set(syncId, {
...syncJobs.get(syncId)!,
status: 'failed',
@@ -139,7 +153,7 @@ async function processTagImport(syncId: string) {
error: error.message || 'Unknown error occurred',
});
- throw error; // 에러 다시 던지기
+ throw error;
}
}
diff --git a/lib/forms-plant/services.ts b/lib/forms-plant/services.ts
index 3f50bd47..64d353de 100644
--- a/lib/forms-plant/services.ts
+++ b/lib/forms-plant/services.ts
@@ -21,7 +21,7 @@ import {
VendorDataReportTempsPlant,
} from "@/db/schema/vendorData";
import { eq, and, desc, sql, DrizzleError, inArray, or, type SQL, type InferSelectModel } from "drizzle-orm";
-import { unstable_cache } from "next/cache";
+import { unstable_cache ,unstable_noStore } from "next/cache";
import { revalidateTag } from "next/cache";
import { getErrorMessage } from "../handle-error";
import { DataTableColumnJSON } from "@/components/form-data/form-data-table-columns";
@@ -234,9 +234,10 @@ export async function getEditableFieldsByTag(
* 그리고 이 로직 전체를 unstable_cache로 감싸 캐싱.
*/
export async function getFormData(formCode: string, projectCode: string, packageCode:string) {
+ unstable_noStore();
try {
- console.log(formCode,projectCode, packageCode)
+ // console.log(formCode,projectCode, packageCode)
const project = await db.query.projects.findFirst({
where: eq(projects.code, projectCode),
@@ -329,83 +330,84 @@ export async function getFormData(formCode: string, projectCode: string, package
console.error(`[getFormData] Cache operation failed:`, cacheError);
// Fallback logic (기존과 동일하게 editableFieldsMap 추가)
- try {
- console.log(`[getFormData] Fallback DB query for (${formCode}, ${packageCode})`);
-
- const project = await db.query.projects.findFirst({
- where: eq(projects.code, projectCode),
- columns: {
- id: true
- }
- });
-
- const projectId = project.id;
-
- const metaRows = await db
- .select()
- .from(formMetas)
- .where(
- and(
- eq(formMetas.formCode, formCode),
- eq(formMetas.projectId, projectId)
- )
- )
- .orderBy(desc(formMetas.updatedAt))
- .limit(1);
-
- const meta = metaRows[0] ?? null;
- if (!meta) {
- console.warn(`[getFormData] Fallback: No form meta found for formCode: ${formCode} and projectId: ${projectId}`);
- return { columns: null, data: [], editableFieldsMap: new Map() };
- }
-
- const entryRows = await db
- .select()
- .from(formEntriesPlant)
- .where(
- and(
- eq(formEntriesPlant.formCode, formCode),
- eq(formEntriesPlant.projectCode, projectCode),
- eq(formEntriesPlant.packageCode, packageCode)
- )
- )
- .orderBy(desc(formEntriesPlant.updatedAt))
- .limit(1);
-
- const entry = entryRows[0] ?? null;
-
- let columns = meta.columns as DataTableColumnJSON[];
- const excludeKeys = ['BF_TAG_NO', 'TAG_TYPE_ID', 'PIC_NO'];
- columns = columns.filter(col => !excludeKeys.includes(col.key));
-
- columns.forEach((col) => {
- if (!col.displayLabel) {
- if (col.uom) {
- col.displayLabel = `${col.label} (${col.uom})`;
- } else {
- col.displayLabel = col.label;
- }
- }
- });
-
- let data: Array<Record<string, any>> = [];
- if (entry) {
- if (Array.isArray(entry.data)) {
- data = entry.data;
- } else {
- console.warn("formEntries data was not an array. Using empty array (fallback).");
- }
- }
-
- // Fallback에서도 편집 가능 필드 정보 계산
- const editableFieldsMap = await getEditableFieldsByTag(projectCode, packageCode, projectId);
-
- return { columns, data, projectId, editableFieldsMap };
- } catch (dbError) {
- console.error(`[getFormData] Fallback DB query failed:`, dbError);
- return { columns: null, data: [], editableFieldsMap: new Map() };
- }
- }
+ // try {
+ // console.log(`[getFormData] Fallback DB query for (${formCode}, ${packageCode})`);
+
+ // const project = await db.query.projects.findFirst({
+ // where: eq(projects.code, projectCode),
+ // columns: {
+ // id: true
+ // }
+ // });
+
+ // const projectId = project.id;
+
+ // const metaRows = await db
+ // .select()
+ // .from(formMetas)
+ // .where(
+ // and(
+ // eq(formMetas.formCode, formCode),
+ // eq(formMetas.projectId, projectId)
+ // )
+ // )
+ // .orderBy(desc(formMetas.updatedAt))
+ // .limit(1);
+
+ // const meta = metaRows[0] ?? null;
+ // if (!meta) {
+ // console.warn(`[getFormData] Fallback: No form meta found for formCode: ${formCode} and projectId: ${projectId}`);
+ // return { columns: null, data: [], editableFieldsMap: new Map() };
+ // }
+
+ // const entryRows = await db
+ // .select()
+ // .from(formEntriesPlant)
+ // .where(
+ // and(
+ // eq(formEntriesPlant.formCode, formCode),
+ // eq(formEntriesPlant.projectCode, projectCode),
+ // eq(formEntriesPlant.packageCode, packageCode)
+ // )
+ // )
+ // .orderBy(desc(formEntriesPlant.updatedAt))
+ // .limit(1);
+
+ // const entry = entryRows[0] ?? null;
+
+ // let columns = meta.columns as DataTableColumnJSON[];
+ // const excludeKeys = ['BF_TAG_NO', 'TAG_TYPE_ID', 'PIC_NO'];
+ // columns = columns.filter(col => !excludeKeys.includes(col.key));
+
+ // columns.forEach((col) => {
+ // if (!col.displayLabel) {
+ // if (col.uom) {
+ // col.displayLabel = `${col.label} (${col.uom})`;
+ // } else {
+ // col.displayLabel = col.label;
+ // }
+ // }
+ // });
+
+ // let data: Array<Record<string, any>> = [];
+ // if (entry) {
+ // if (Array.isArray(entry.data)) {
+ // data = entry.data;
+ // } else {
+ // console.warn("formEntries data was not an array. Using empty array (fallback).");
+ // }
+ // }
+
+ // // Fallback에서도 편집 가능 필드 정보 계산
+ // const editableFieldsMap = await getEditableFieldsByTag(projectCode, packageCode, projectId);
+
+ // return { columns, data, projectId, editableFieldsMap };
+ // } catch (dbError) {
+ // console.error(`[getFormData] Fallback DB query failed:`, dbError);
+ // return { columns: null, data: [], editableFieldsMap: new Map() };
+ // }
+ // }
+}
}
/**
* contractId와 formCode(itemCode)를 사용하여 contractItemId를 찾는 서버 액션
@@ -1052,6 +1054,7 @@ type GetReportFileList = (
}>;
export const getFormId: GetReportFileList = async (projectCode, packageCode, formCode, mode) => {
+ unstable_noStore();
const result: { formId: number } = {
formId: 0,
};
diff --git a/lib/sedp/get-tags-plant.ts b/lib/sedp/get-tags-plant.ts
index be0e398b..f804ebe9 100644
--- a/lib/sedp/get-tags-plant.ts
+++ b/lib/sedp/get-tags-plant.ts
@@ -3,651 +3,578 @@ import {
tagsPlant,
formsPlant,
formEntriesPlant,
- items,
- tagTypeClassFormMappings,
projects,
tagTypes,
tagClasses,
} from "@/db/schema";
-import { eq, and, like, inArray } from "drizzle-orm";
-import { revalidateTag } from "next/cache"; // 추가
+import { eq, and } from "drizzle-orm";
+import { revalidatePath } from "next/cache";
import { getSEDPToken } from "./sedp-token";
-/**
- * 태그 가져오기 서비스 함수
- * contractItemId(packageId)를 기반으로 외부 시스템에서 태그 데이터를 가져와 DB에 저장
- * TAG_IDX를 기준으로 태그를 식별합니다.
- *
- * @param projectCode 계약 아이템 ID (contractItemId)
- * @param packageCode 계약 아이템 ID (contractItemId)
- * @param progressCallback 진행 상황을 보고하기 위한 콜백 함수
- * @returns 처리 결과 정보 (처리된 태그 수, 오류 목록 등)
- */
+const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
+
+// ============ 타입 정의 ============
+interface newRegister {
+ PROJ_NO: string;
+ MAP_ID: string;
+ EP_ID: string;
+ DESC: string;
+ CATEGORY: string;
+ BYPASS: boolean;
+ REG_TYPE_ID: string;
+ TOOL_ID: string;
+ TOOL_TYPE: string;
+ SCOPES: string[];
+ MAP_CLS: {
+ TOOL_ATT_NAME: string;
+ ITEMS: any[];
+ };
+ MAP_ATT: any[];
+ MAP_TMPLS: string[];
+ CRTER_NO: string;
+ CRTE_DTM: string;
+ CHGER_NO: string;
+ _id: string;
+}
+
+interface Register {
+ PROJ_NO: string;
+ TYPE_ID: string;
+ EP_ID: string;
+ DESC: string;
+ REMARK: string | null;
+ NEW_TAG_YN: boolean;
+ ALL_TAG_YN: boolean;
+ VND_YN: boolean;
+ SEQ: number;
+ CMPLX_YN: boolean;
+ CMPL_SETT: any | null;
+ MAP_ATT: any[];
+ MAP_CLS_ID: string[];
+ MAP_OPER: any | null;
+ LNK_ATT: any[];
+ JOIN_TABLS: any[];
+ DELETED: boolean;
+ CRTER_NO: string;
+ CRTE_DTM: string;
+ CHGER_NO: string | null;
+ CHGE_DTM: string | null;
+ _id: string;
+}
+
+interface FormInfo {
+ formCode: string;
+ formName: string;
+ im: boolean;
+ eng: boolean;
+}
+
+// ============ API 호출 함수들 ============
+
+async function getNewRegisters(projectCode: string): Promise<newRegister[]> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/AdapterDataMapping/GetByToolID`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ TOOL_ID: "eVCP"
+ })
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(`새 레지스터 요청 실패: ${response.status} ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ const registers: newRegister[] = Array.isArray(data) ? data : [data];
+
+ console.log(`[getNewRegisters] 프로젝트 ${projectCode}에서 ${registers.length}개의 레지스터를 가져왔습니다.`);
+ return registers;
+}
+
+async function getRegisters(projectCode: string): Promise<Register[]> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Register/Get`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ ContainDeleted: false
+ })
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(`레지스터 요청 실패: ${response.status} ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ const registers: Register[] = Array.isArray(data) ? data : [data];
+
+ console.log(`[getRegisters] 프로젝트 ${projectCode}에서 ${registers.length}개의 레지스터를 가져왔습니다.`);
+ return registers;
+}
+
+async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise<any> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Data/GetPubData`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ REG_TYPE_ID: formCode,
+ ContainDeleted: false
+ })
+ }
+ );
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`SEDP API 요청 실패: ${response.status} ${response.statusText} - ${errorText}`);
+ }
+
+ return await response.json();
+}
+
+async function getRegisterDetail(projectCode: string, formCode: string): Promise<Register | null> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Register/GetByID`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ TYPE_ID: formCode,
+ ContainDeleted: false
+ })
+ }
+ );
+
+ if (!response.ok) {
+ console.error(`Register detail 요청 실패: ${formCode}`);
+ return null;
+ }
+
+ return await response.json();
+}
+
+// ============ 메인 함수 ============
+
export async function importTagsFromSEDP(
projectCode: string,
packageCode: string,
- progressCallback?: (progress: number) => void,
- mode?: string
+ progressCallback?: (progress: number) => void
): Promise<{
processedCount: number;
excludedCount: number;
totalEntries: number;
errors?: string[];
}> {
+ const allErrors: string[] = [];
+ let totalProcessedCount = 0;
+ let totalExcludedCount = 0;
+ let totalEntriesCount = 0;
+
try {
- // 진행 상황 보고
if (progressCallback) progressCallback(5);
+ // Step 1: 프로젝트 ID 조회
const project = await db.query.projects.findFirst({
where: eq(projects.code, projectCode),
- columns: {
- id: true
- }
+ columns: { id: true }
});
+ if (!project) {
+ throw new Error(`Project not found: ${projectCode}`);
+ }
+ const projectId = project.id;
+
+ if (progressCallback) progressCallback(10);
- // 프로젝트 ID 획득
- const projectId = project?.id;
+ // Step 2: 두 API 동시 호출
+ const [newRegisters, registers] = await Promise.all([
+ getNewRegisters(projectCode),
+ getRegisters(projectCode)
+ ]);
- // Step 1-2: Get the item using itemId from contractItem
- const item = await db.query.items.findFirst({
- where: and(eq(items.ProjectNo, projectCode), eq(items.packageCode, packageCode))
+ if (progressCallback) progressCallback(20);
+
+ // ======== 서브클래스 매핑을 위한 태그 클래스 로드 ========
+ const allTagClasses = await db.query.tagClasses.findMany({
+ where: eq(tagClasses.projectId, projectId)
});
- if (!item) {
- throw new Error(`Item with ID ${item?.id} not found`);
+ // 클래스 코드로 빠른 조회를 위한 Map
+ const tagClassByCode = new Map(allTagClasses.map(tc => [tc.code, tc]));
+
+ // 서브클래스 코드로 부모 클래스 찾기 위한 Map
+ const parentBySubclassCode = new Map<string, typeof allTagClasses[0]>();
+ for (const tc of allTagClasses) {
+ if (tc.subclasses && Array.isArray(tc.subclasses)) {
+ for (const sub of tc.subclasses as { id: string; desc: string }[]) {
+ parentBySubclassCode.set(sub.id, tc);
+ }
+ }
}
- const itemCode = item.itemCode;
+ console.log(`[importTagsFromSEDP] 태그 클래스 ${allTagClasses.length}개 로드, 서브클래스 매핑 ${parentBySubclassCode.size}개 생성`);
+ // ======== 서브클래스 매핑 준비 완료 ========
- // 진행 상황 보고
- if (progressCallback) progressCallback(10);
+ // Step 3: packageCode에 해당하는 폼 정보 추출
+ const formsToProcess: FormInfo[] = [];
- // 기본 매핑 검색 - 모든 모드에서 사용
- const baseMappings = await db.query.tagTypeClassFormMappings.findMany({
- where: and(
- like(tagTypeClassFormMappings.remark, `%${itemCode}%`),
- eq(tagTypeClassFormMappings.projectId, projectId)
- )
- });
-
- if (baseMappings.length === 0) {
- throw new Error(`No mapping found for item code ${itemCode}`);
+ // Register 정보를 Map으로 변환 (TYPE_ID로 빠른 조회)
+ const registerMap = new Map<string, Register>();
+ for (const reg of registers) {
+ registerMap.set(reg.TYPE_ID, reg);
}
- // Step 2: Find the mapping entries - 모드에 따라 다른 조건 적용
- let mappings = [];
-
- if (mode === 'IM') {
- // IM 모드일 때는 먼저 SEDP에서 태그 데이터를 가져와 TAG_TYPE_ID 리스트 확보
-
- // 프로젝트 코드 가져오기
- const project = await db.query.projects.findFirst({
- where: eq(projects.id, projectId)
- });
-
- if (!project) {
- throw new Error(`Project with ID ${projectId} not found`);
- }
-
- // 각 매핑의 formCode에 대해 태그 데이터 조회
- const tagTypeIds = new Set<string>();
-
- for (const mapping of baseMappings) {
- try {
- // SEDP에서 태그 데이터 가져오기
- const tagData = await fetchTagDataFromSEDP(project.code, mapping.formCode);
-
- // 첫 번째 키를 테이블 이름으로 사용
- const tableName = Object.keys(tagData)[0];
- const tagEntries = tagData[tableName];
-
- if (Array.isArray(tagEntries)) {
- // 모든 태그에서 TAG_TYPE_ID 수집
- for (const entry of tagEntries) {
- if (entry.TAG_TYPE_ID && entry.TAG_TYPE_ID !== "") {
- tagTypeIds.add(entry.TAG_TYPE_ID);
- }
- }
- }
- } catch (error) {
- console.error(`Error fetching tag data for formCode ${mapping.formCode}:`, error);
- }
- }
-
- if (tagTypeIds.size === 0) {
- throw new Error('No valid TAG_TYPE_ID found in SEDP tag data');
- }
-
- // 수집된 TAG_TYPE_ID로 tagTypes에서 정보 조회
- const tagTypeInfo = await db.query.tagTypes.findMany({
- where: and(
- inArray(tagTypes.code, Array.from(tagTypeIds)),
- eq(tagTypes.projectId, projectId)
- )
- });
-
- if (tagTypeInfo.length === 0) {
- throw new Error('No matching tag types found for the collected TAG_TYPE_IDs');
- }
-
- // 태그 타입 설명 수집
- const tagLabels = tagTypeInfo.map(tt => tt.description);
-
- // IM 모드에 맞는 매핑 조회 - ep가 "IMEP"인 항목만
- mappings = await db.query.tagTypeClassFormMappings.findMany({
- where: and(
- inArray(tagTypeClassFormMappings.tagTypeLabel, tagLabels),
- eq(tagTypeClassFormMappings.projectId, projectId),
- eq(tagTypeClassFormMappings.ep, "IMEP")
- )
- });
-
- } else {
- // ENG 모드 또는 기본 모드일 때 - 기본 매핑 사용
- mappings = [...baseMappings];
-
- // ENG 모드에서는 ep 필드가 "IMEP"가 아닌 매핑만 필터링
- if (mode === 'ENG') {
- mappings = mappings.filter(mapping => mapping.ep !== "IMEP");
+ // newRegisters에서 packageCode가 SCOPES에 포함된 것 필터링
+ for (const newReg of newRegisters) {
+ if (newReg.SCOPES && newReg.SCOPES.includes(packageCode)) {
+ const formCode = newReg.REG_TYPE_ID;
+ const formName = newReg.DESC;
+
+ // Register에서 EP_ID 확인하여 im/eng 결정
+ const register = registerMap.get(formCode);
+ const isIM = register?.EP_ID === "IMEP";
+
+ formsToProcess.push({
+ formCode,
+ formName,
+ im: isIM,
+ eng: !isIM
+ });
}
}
- // 매핑이 없는 경우 모드에 따라 다른 오류 메시지 사용
- if (mappings.length === 0) {
- if (mode === 'IM') {
- throw new Error('No suitable mappings found for IM mode');
- } else {
- throw new Error(`No mapping found for item code ${itemCode}`);
- }
+ if (formsToProcess.length === 0) {
+ throw new Error(`No forms found for packageCode: ${packageCode}`);
}
-
- // 진행 상황 보고
- if (progressCallback) progressCallback(15);
-
- // 결과 누적을 위한 변수들 초기화
- let totalProcessedCount = 0;
- let totalExcludedCount = 0;
- let totalEntriesCount = 0;
- const allErrors: string[] = [];
-
- // 각 매핑에 대해 처리
- for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) {
- const mapping = mappings[mappingIndex];
-
- // Step 3: Get the project code
- const project = await db.query.projects.findFirst({
- where: eq(projects.id, mapping.projectId)
- });
-
- if (!project) {
- allErrors.push(`Project with ID ${mapping.projectId} not found`);
- continue; // 다음 매핑으로 진행
- }
- // IM 모드에서는 baseMappings에서 같은 formCode를 가진 매핑을 찾음
- let formCode = mapping.formCode;
- if (mode === 'IM') {
- // baseMapping에서 동일한 formCode를 가진 매핑 찾기
- const originalMapping = baseMappings.find(
- baseMapping => baseMapping.formCode === mapping.formCode
- );
-
- // 찾았으면 해당 formCode 사용, 못 찾았으면 현재 매핑의 formCode 유지
- if (originalMapping) {
- formCode = originalMapping.formCode;
- }
- }
+ console.log(`[importTagsFromSEDP] ${formsToProcess.length}개의 폼을 처리합니다.`);
- // 진행 상황 보고 - 매핑별 진행률 조정
- if (progressCallback) {
- const baseProgress = 15;
- const mappingProgress = Math.floor(15 * (mappingIndex + 1) / mappings.length);
- progressCallback(baseProgress + mappingProgress);
- }
+ if (progressCallback) progressCallback(25);
- // Step 4: Find the form ID
- const form = await db.query.formsPlant.findFirst({
- where: and(
- eq(formsPlant.projectCode, projectCode),
- eq(formsPlant.formCode, formCode),
- eq(formsPlant.packageCode, packageCode)
- )
- });
-
- let formId;
-
- // If form doesn't exist, create it
- if (!form) {
- // 폼이 없는 경우 새로 생성 - 모드에 따른 필드 설정
- const insertValues: any = {
- projectCode,
- packageCode,
- formCode: formCode,
- formName: mapping.formName
- };
-
- // 모드 정보가 있으면 해당 필드 설정
- if (mode) {
- if (mode === "ENG") {
- insertValues.eng = true;
- } else if (mode === "IM") {
- insertValues.im = true;
- if (mapping.remark && mapping.remark.includes("VD_")) {
- insertValues.eng = true;
- }
- }
- }
+ // Step 4: 각 폼에 대해 처리
+ for (let i = 0; i < formsToProcess.length; i++) {
+ const formInfo = formsToProcess[i];
+ const { formCode, formName, im, eng } = formInfo;
- const insertResult = await db.insert(formsPlant)
- .values(insertValues)
- .onConflictDoUpdate({
- target: [formsPlant.projectCode, formsPlant.formCode],
- set: {
- packageCode: insertValues.packageCode,
- formName: insertValues.formName,
- eng: insertValues.eng ?? false,
- im: insertValues.im ?? false,
- updatedAt: new Date()
- }
- })
- .returning({ id: formsPlant.id });
+ try {
+ // 진행률 계산
+ const baseProgress = 25;
+ const progressPerForm = 70 / formsToProcess.length;
- if (insertResult.length === 0) {
- allErrors.push(`Failed to create form record for formCode ${formCode}`);
- continue; // 다음 매핑으로 진행
- }
-
- formId = insertResult[0].id;
- } else {
- // 폼이 이미 존재하는 경우 - 필요시 모드 필드 업데이트
- formId = form.id;
-
- if (mode) {
- let shouldUpdate = false;
- const updateValues: any = {};
-
- if (mode === "ENG" && form.eng !== true) {
- updateValues.eng = true;
- shouldUpdate = true;
- } else if (mode === "IM" && form.im !== true) {
- updateValues.im = true;
- shouldUpdate = true;
- }
-
- if (shouldUpdate) {
- await db.update(formsPlant)
- .set({
- ...updateValues,
- updatedAt: new Date()
- })
- .where(eq(formsPlant.id, formId));
+ // Step 4-1: formsPlant upsert
+ const existingForm = await db.query.formsPlant.findFirst({
+ where: and(
+ eq(formsPlant.projectCode, projectCode),
+ eq(formsPlant.packageCode, packageCode),
+ eq(formsPlant.formCode, formCode)
+ )
+ });
+
+ let formId: number;
+
+ if (existingForm) {
+ // 기존 폼 업데이트
+ await db.update(formsPlant)
+ .set({
+ formName,
+ im,
+ eng,
+ updatedAt: new Date()
+ })
+ .where(eq(formsPlant.id, existingForm.id));
+
+ formId = existingForm.id;
+ console.log(`[formsPlant] Updated form: ${formCode}`);
+ } else {
+ // 새 폼 생성
+ const insertResult = await db.insert(formsPlant)
+ .values({
+ projectCode,
+ packageCode,
+ formCode,
+ formName,
+ im,
+ eng
+ })
+ .returning({ id: formsPlant.id });
- console.log(`Updated form ${formId} with ${mode} mode enabled`);
- }
+ formId = insertResult[0].id;
+ console.log(`[formsPlant] Created form: ${formCode}`);
}
- }
-
- // 진행 상황 보고 - 매핑별 진행률 조정
- if (progressCallback) {
- const baseProgress = 30;
- const mappingProgress = Math.floor(20 * (mappingIndex + 1) / mappings.length);
- progressCallback(baseProgress + mappingProgress);
- }
- try {
- // Step 5: Call the external API to get tag data
- const tagData = await fetchTagDataFromSEDP(projectCode, baseMappings[0].formCode);
-
- // 진행 상황 보고
if (progressCallback) {
- const baseProgress = 50;
- const mappingProgress = Math.floor(10 * (mappingIndex + 1) / mappings.length);
- progressCallback(baseProgress + mappingProgress);
+ progressCallback(baseProgress + progressPerForm * (i + 0.2));
}
- // Step 6: Process the data and insert into the tags table
- let processedCount = 0;
- let excludedCount = 0;
-
- // Get the first key from the response as the table name
+ // Step 4-2: SEDP에서 태그 데이터 가져오기
+ const tagData = await fetchTagDataFromSEDP(projectCode, formCode);
const tableName = Object.keys(tagData)[0];
const tagEntries = tagData[tableName];
if (!Array.isArray(tagEntries) || tagEntries.length === 0) {
- allErrors.push(`No tag data found in the API response for formCode ${baseMappings[0].formCode}`);
- continue; // 다음 매핑으로 진행
+ console.log(`[importTagsFromSEDP] No tag data for formCode: ${formCode}`);
+ continue;
}
- const entriesCount = tagEntries.length;
- totalEntriesCount += entriesCount;
-
- // formEntries를 위한 데이터 수집
- const newTagsForFormEntry: Array<{
- TAG_IDX: string; // 변경: TAG_NO → TAG_IDX
- TAG_NO?: string; // TAG_NO도 함께 저장 (편집 가능한 필드)
- TAG_DESC: string | null;
- status: string;
- [key: string]: any;
- }> = [];
- const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
- const apiKey = await getSEDPToken();
-
- const registerResponse = await fetch(
- `${SEDP_API_BASE_URL}/Register/GetByID`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'accept': '*/*',
- 'ApiKey': apiKey,
- 'ProjectNo': projectCode
- },
- body: JSON.stringify({
- ProjectNo: projectCode,
- TYPE_ID: baseMappings[0].formCode, // 또는 mapping.formCode
- ContainDeleted: false
- })
- }
- )
-
- if (!registerResponse.ok) {
- allErrors.push(`Failed to fetch register details for ${baseMappings[0].formCode}`)
- continue
+ totalEntriesCount += tagEntries.length;
+
+ if (progressCallback) {
+ progressCallback(baseProgress + progressPerForm * (i + 0.4));
}
-
- const registerDetail: Register = await registerResponse.json()
+
+ // Step 4-3: Register detail에서 허용된 ATT_ID 추출
+ const registerDetail = await getRegisterDetail(projectCode, formCode);
+ const allowedAttIds = new Set<string>();
- // ✅ MAP_ATT에서 허용된 ATT_ID 목록 추출
- const allowedAttIds = new Set<string>()
- if (Array.isArray(registerDetail.MAP_ATT)) {
+ if (registerDetail?.MAP_ATT && Array.isArray(registerDetail.MAP_ATT)) {
for (const mapAttr of registerDetail.MAP_ATT) {
if (mapAttr.ATT_ID) {
- allowedAttIds.add(mapAttr.ATT_ID)
+ allowedAttIds.add(mapAttr.ATT_ID);
}
}
}
-
- // Process each tag entry
- for (let i = 0; i < tagEntries.length; i++) {
- try {
- const entry = tagEntries[i];
-
- // TAG_IDX가 없는 경우 제외 (변경: TAG_NO → TAG_IDX 체크)
- if (!entry.TAG_IDX) {
- excludedCount++;
- totalExcludedCount++;
-
- // 주기적으로 진행 상황 보고 (건너뛰어도 진행률은 업데이트)
- if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
- const baseProgress = 60;
- const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
- progressCallback(baseProgress + entryProgress);
- }
+ // Step 4-4: 태그 처리
+ const newTagsForFormEntry: Array<Record<string, any>> = [];
+ let processedCount = 0;
+ let excludedCount = 0;
- continue; // 이 항목은 건너뜀
- }
+ for (const entry of tagEntries) {
+ // TAG_IDX 없으면 제외
+ if (!entry.TAG_IDX) {
+ excludedCount++;
+ continue;
+ }
+
+ // TAG_TYPE_ID 없으면 제외
+ if (!entry.TAG_TYPE_ID || entry.TAG_TYPE_ID === "") {
+ excludedCount++;
+ continue;
+ }
- const attributes: Record<string, string> = {}
- if (Array.isArray(entry.ATTRIBUTES)) {
- for (const attr of entry.ATTRIBUTES) {
- // MAP_ATT에 정의된 ATT_ID만 포함
- if (attr.ATT_ID && allowedAttIds.has(attr.ATT_ID)) {
- if (attr.VALUE !== null && attr.VALUE !== undefined) {
- attributes[attr.ATT_ID] = String(attr.VALUE)
- }
+ // attributes 추출 (허용된 ATT_ID만)
+ const attributes: Record<string, string> = {};
+ if (Array.isArray(entry.ATTRIBUTES)) {
+ for (const attr of entry.ATTRIBUTES) {
+ if (attr.ATT_ID && allowedAttIds.has(attr.ATT_ID)) {
+ if (attr.VALUE !== null && attr.VALUE !== undefined) {
+ attributes[attr.ATT_ID] = String(attr.VALUE);
}
}
}
-
-
- // TAG_TYPE_ID가 null이거나 빈 문자열인 경우 제외
- if (entry.TAG_TYPE_ID === null || entry.TAG_TYPE_ID === "") {
- excludedCount++;
- totalExcludedCount++;
-
- // 주기적으로 진행 상황 보고 (건너뛰어도 진행률은 업데이트)
- if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
- const baseProgress = 60;
- const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
- progressCallback(baseProgress + entryProgress);
- }
+ }
- continue; // 이 항목은 건너뜀
+ // tagType 조회
+ const tagType = await db.query.tagTypes.findFirst({
+ where: and(
+ eq(tagTypes.code, entry.TAG_TYPE_ID),
+ eq(tagTypes.projectId, projectId)
+ )
+ });
+
+ // ======== 클래스 및 서브클래스 결정 로직 ========
+ let classLabel: string;
+ let subclassValue: string | null = null;
+ let tagClassId: number | null = null;
+
+ // 1. 먼저 CLS_ID로 직접 tagClass 찾기
+ const tagClass = tagClassByCode.get(entry.CLS_ID);
+
+ if (tagClass) {
+ // 직접 찾은 경우 - 이게 메인 클래스
+ classLabel = tagClass.label || entry.CLS_ID;
+ tagClassId = tagClass.id;
+ } else {
+ // 2. 서브클래스인지 확인 (부모 클래스의 subclasses 배열에 있는지)
+ const parentClass = parentBySubclassCode.get(entry.CLS_ID);
+
+ if (parentClass) {
+ // 서브클래스인 경우
+ classLabel = parentClass.label || parentClass.code;
+ subclassValue = entry.CLS_ID;
+ tagClassId = parentClass.id;
+
+ console.log(`[importTagsFromSEDP] 서브클래스 발견: ${entry.CLS_ID} -> 부모: ${parentClass.code}`);
+ } else {
+ // 어디에도 없는 경우 - 원본 값 사용
+ classLabel = entry.CLS_ID;
+ console.log(`[importTagsFromSEDP] 클래스를 찾을 수 없음: ${entry.CLS_ID}`);
}
-
- // Get tag type description
- const tagType = await db.query.tagTypes.findFirst({
- where: and(
- eq(tagTypes.code, entry.TAG_TYPE_ID),
- eq(tagTypes.projectId, mapping.projectId)
- )
- });
-
- // Get tag class label
- const tagClass = await db.query.tagClasses.findFirst({
- where: and(
- eq(tagClasses.code, entry.CLS_ID),
- eq(tagClasses.projectId, mapping.projectId)
- )
- });
-
- // Insert or update the tag - tagIdx 필드 추가
- await db.insert(tagsPlant).values({
- projectCode,
- packageCode,
- formId: formId,
- tagIdx: entry.TAG_IDX,
+ }
+ // ======== 클래스/서브클래스 결정 완료 ========
+
+ // tagsPlant upsert (subclass 필드 추가)
+ await db.insert(tagsPlant).values({
+ projectCode,
+ packageCode,
+ formId,
+ tagIdx: entry.TAG_IDX,
+ tagNo: entry.TAG_NO || entry.TAG_IDX,
+ tagType: tagType?.description || entry.TAG_TYPE_ID,
+ tagClassId: tagClassId,
+ class: classLabel,
+ subclass: subclassValue,
+ description: entry.TAG_DESC,
+ attributes,
+ }).onConflictDoUpdate({
+ target: [tagsPlant.projectCode, tagsPlant.packageCode, tagsPlant.tagIdx],
+ set: {
+ formId,
tagNo: entry.TAG_NO || entry.TAG_IDX,
tagType: tagType?.description || entry.TAG_TYPE_ID,
- tagClassId: tagClass?.id,
- class: tagClass?.label || entry.CLS_ID,
+ tagClassId: tagClassId,
+ class: classLabel,
+ subclass: subclassValue,
description: entry.TAG_DESC,
- attributes: attributes, // JSONB로 저장
- }).onConflictDoUpdate({
- target: [tagsPlant.projectCode, tagsPlant.packageCode, tagsPlant.tagIdx],
- set: {
- formId: formId,
- tagNo: entry.TAG_NO || entry.TAG_IDX,
- tagType: tagType?.description || entry.TAG_TYPE_ID,
- class: tagClass?.label || entry.CLS_ID,
- description: entry.TAG_DESC,
- attributes: attributes, // JSONB 업데이트
- updatedAt: new Date()
- }
- })
- // formEntries용 데이터 수집
- const tagDataForFormEntry = {
- TAG_IDX: entry.TAG_IDX, // 변경: TAG_NO → TAG_IDX
- TAG_NO: entry.TAG_NO || entry.TAG_IDX, // TAG_NO도 함께 저장
- TAG_DESC: entry.TAG_DESC || null,
- status: "From S-EDP", // SEDP에서 가져온 데이터임을 표시
- source: "S-EDP" // 태그 출처 (불변) - S-EDP에서 가져옴
- };
-
- // ATTRIBUTES가 있으면 추가 (SHI 필드들)
- if (Array.isArray(entry.ATTRIBUTES)) {
- for (const attr of entry.ATTRIBUTES) {
- if (attr.ATT_ID && attr.VALUE !== null && attr.VALUE !== undefined) {
- tagDataForFormEntry[attr.ATT_ID] = attr.VALUE;
- }
+ attributes,
+ updatedAt: new Date()
+ }
+ });
+
+ // formEntriesPlant용 데이터 준비
+ const tagDataForFormEntry: Record<string, any> = {
+ TAG_IDX: entry.TAG_IDX,
+ TAG_NO: entry.TAG_NO || entry.TAG_IDX,
+ TAG_DESC: entry.TAG_DESC || null,
+ status: "From S-EDP",
+ source: "S-EDP"
+ };
+
+ // ATTRIBUTES 추가
+ if (Array.isArray(entry.ATTRIBUTES)) {
+ for (const attr of entry.ATTRIBUTES) {
+ if (attr.ATT_ID && attr.VALUE !== null && attr.VALUE !== undefined) {
+ tagDataForFormEntry[attr.ATT_ID] = attr.VALUE;
}
}
+ }
- newTagsForFormEntry.push(tagDataForFormEntry);
+ newTagsForFormEntry.push(tagDataForFormEntry);
+ processedCount++;
+ }
- processedCount++;
- totalProcessedCount++;
+ totalProcessedCount += processedCount;
+ totalExcludedCount += excludedCount;
- // 주기적으로 진행 상황 보고
- if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
- const baseProgress = 60;
- const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
- progressCallback(baseProgress + entryProgress);
- }
- } catch (error: any) {
- console.error(`Error processing tag entry:`, error);
- allErrors.push(error.message || 'Unknown error');
- }
+ if (progressCallback) {
+ progressCallback(baseProgress + progressPerForm * (i + 0.8));
}
- // Step 7: formEntries 업데이트 - TAG_IDX 기준으로 변경
+ // Step 4-5: formEntriesPlant upsert
if (newTagsForFormEntry.length > 0) {
- try {
- // 기존 formEntry 가져오기
- const existingEntry = await db.query.formEntriesPlant.findFirst({
- where: and(
- eq(formEntriesPlant.formCode, formCode),
- eq(formEntriesPlant.projectCode, projectCode),
- eq(formEntriesPlant.packageCode, packageCode)
- )
- });
-
- if (existingEntry && existingEntry.id) {
- // 기존 formEntry가 있는 경우
- let existingData: Array<{
- TAG_IDX?: string; // 추가: TAG_IDX 필드
- TAG_NO?: string;
- TAG_DESC?: string;
- status?: string;
- [key: string]: any;
- }> = [];
-
- if (Array.isArray(existingEntry.data)) {
- existingData = existingEntry.data;
- }
+ const existingEntry = await db.query.formEntriesPlant.findFirst({
+ where: and(
+ eq(formEntriesPlant.formCode, formCode),
+ eq(formEntriesPlant.projectCode, projectCode),
+ eq(formEntriesPlant.packageCode, packageCode)
+ )
+ });
+
+ if (existingEntry) {
+ // 기존 데이터 병합
+ let existingData: Array<Record<string, any>> = [];
+ if (Array.isArray(existingEntry.data)) {
+ existingData = existingEntry.data;
+ }
- // 기존 TAG_IDX들 추출 (변경: TAG_NO → TAG_IDX)
- const existingTagIdxs = new Set(
- existingData
- .map(item => item.TAG_IDX)
- .filter(tagIdx => tagIdx !== undefined && tagIdx !== null)
- );
+ const existingTagIdxs = new Set(
+ existingData.map(item => item.TAG_IDX).filter(Boolean)
+ );
- // 중복되지 않은 새 태그들만 필터링 (변경: TAG_NO → TAG_IDX)
- const newUniqueTagsData = newTagsForFormEntry.filter(
- tagData => !existingTagIdxs.has(tagData.TAG_IDX)
+ // 기존 데이터 업데이트 + 새 데이터 추가
+ const updatedData = existingData.map(existingItem => {
+ const newData = newTagsForFormEntry.find(
+ n => n.TAG_IDX === existingItem.TAG_IDX
);
+ return newData ? { ...existingItem, ...newData } : existingItem;
+ });
- // 기존 태그들의 status와 ATTRIBUTES 업데이트 (변경: TAG_NO → TAG_IDX)
- const updatedExistingData = existingData.map(existingItem => {
- const newTagData = newTagsForFormEntry.find(
- newItem => newItem.TAG_IDX === existingItem.TAG_IDX
- );
-
- if (newTagData) {
- // 기존 태그가 있으면 SEDP 데이터로 업데이트
- return {
- ...existingItem,
- ...newTagData,
- TAG_IDX: existingItem.TAG_IDX // TAG_IDX는 유지
- };
- }
-
- return existingItem;
- });
-
- const finalData = [...updatedExistingData, ...newUniqueTagsData];
+ const newUniqueData = newTagsForFormEntry.filter(
+ n => !existingTagIdxs.has(n.TAG_IDX)
+ );
- await db
- .update(formEntriesPlant)
- .set({
- data: finalData,
- updatedAt: new Date()
- })
- .where(eq(formEntriesPlant.id, existingEntry.id));
+ await db.update(formEntriesPlant)
+ .set({
+ data: [...updatedData, ...newUniqueData],
+ updatedAt: new Date()
+ })
+ .where(eq(formEntriesPlant.id, existingEntry.id));
- console.log(`[IMPORT SEDP] Updated formEntry with ${newUniqueTagsData.length} new tags, updated ${updatedExistingData.length - newUniqueTagsData.length} existing tags for form ${formCode}`);
- } else {
- // formEntry가 없는 경우 새로 생성
- await db.insert(formEntriesPlant).values({
- formCode: formCode,
- projectCode,
- packageCode,
- data: newTagsForFormEntry,
- createdAt: new Date(),
- updatedAt: new Date(),
- });
-
- console.log(`[IMPORT SEDP] Created new formEntry with ${newTagsForFormEntry.length} tags for form ${formCode}`);
- }
+ console.log(`[formEntriesPlant] Updated: ${formCode} (${newUniqueData.length} new, ${updatedData.length} updated)`);
+ } else {
+ // 새로 생성
+ await db.insert(formEntriesPlant).values({
+ formCode,
+ projectCode,
+ packageCode,
+ data: newTagsForFormEntry,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ });
- // 캐시 무효화
- // revalidateTag(`form-data-${formCode}-${packageId}`);
- } catch (formEntryError) {
- console.error(`[IMPORT SEDP] Error updating formEntry for form ${formCode}:`, formEntryError);
- allErrors.push(`Error updating formEntry for form ${formCode}: ${formEntryError}`);
+ console.log(`[formEntriesPlant] Created: ${formCode} (${newTagsForFormEntry.length} tags)`);
}
}
+ if (progressCallback) {
+ progressCallback(baseProgress + progressPerForm * (i + 1));
+ }
+
} catch (error: any) {
- console.error(`Error processing mapping for formCode ${formCode}:`, error);
- allErrors.push(`Error with formCode ${formCode}: ${error.message || 'Unknown error'}`);
+ console.error(`Error processing form ${formCode}:`, error);
+ allErrors.push(`Form ${formCode}: ${error.message}`);
}
}
- // 모든 매핑 처리 완료 - 진행률 100%
- if (progressCallback) {
- progressCallback(100);
- }
+ if (progressCallback) progressCallback(100);
- // 최종 결과 반환
return {
processedCount: totalProcessedCount,
excludedCount: totalExcludedCount,
totalEntries: totalEntriesCount,
errors: allErrors.length > 0 ? allErrors : undefined
};
+
} catch (error: any) {
console.error("Tag import error:", error);
throw error;
}
-}
-
-/**
- * SEDP API에서 태그 데이터 가져오기
- *
- * @param projectCode 프로젝트 코드
- * @param formCode 양식 코드
- * @returns API 응답 데이터
- */
-async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise<any> {
- try {
- // Get the token
- const apiKey = await getSEDPToken();
-
- // Define the API base URL
- const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
-
- // Make the API call
- const response = await fetch(
- `${SEDP_API_BASE_URL}/Data/GetPubData`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'accept': '*/*',
- 'ApiKey': apiKey,
- 'ProjectNo': projectCode
- },
- body: JSON.stringify({
- ProjectNo: projectCode,
- REG_TYPE_ID: formCode,
- ContainDeleted: false
- })
- }
- );
-
- if (!response.ok) {
- const errorText = await response.text();
- throw new Error(`SEDP API request failed: ${response.status} ${response.statusText} - ${errorText}`);
- }
-
- const data = await response.json();
- return data;
- } catch (error: any) {
- console.error('Error calling SEDP API:', error);
- throw new Error(`Failed to fetch data from SEDP API: ${error.message || 'Unknown error'}`);
- }
} \ No newline at end of file
diff --git a/lib/tags-plant/queries.ts b/lib/tags-plant/queries.ts
index a0d28b1e..c7ad43e0 100644
--- a/lib/tags-plant/queries.ts
+++ b/lib/tags-plant/queries.ts
@@ -5,6 +5,7 @@ import db from "@/db/db"
import { tagsPlant } from "@/db/schema/vendorData"
import { eq, and } from "drizzle-orm"
+import { revalidateTag, unstable_noStore } from "next/cache";
/**
* 모든 태그 가져오기 (클라이언트 렌더링용)
@@ -13,6 +14,7 @@ export async function getAllTagsPlant(
projectCode: string,
packageCode: string
) {
+ unstable_noStore();
try {
const tags = await db
.select()
diff --git a/lib/tags-plant/service.ts b/lib/tags-plant/service.ts
index 9e9d9ebf..27cc207b 100644
--- a/lib/tags-plant/service.ts
+++ b/lib/tags-plant/service.ts
@@ -25,6 +25,14 @@ interface CreatedOrExistingForm {
isNewlyCreated: boolean;
}
+interface FormInfo {
+ formCode: string;
+ formName: string;
+ im: boolean;
+ eng: boolean;
+}
+
+
/**
* 16진수 24자리 고유 식별자 생성
* @returns 24자리 16진수 문자열 (예: "a1b2c3d4e5f6789012345678")
@@ -280,6 +288,7 @@ export async function createTag(
tagIdx: generatedTagIdx, // 🆕 생성된 16진수 24자리 추가
tagNo: validated.data.tagNo,
class: validated.data.class,
+ subclass: validated.data.subclass,
tagType: validated.data.tagType,
description: validated.data.description ?? null,
})
@@ -1790,13 +1799,11 @@ export async function getIMForms(
return existingForms
}
- // 2. DB에 없으면 SEDP API에서 가져오기
+ // 2. DB에 없으면 두 API 동시 호출
const apiKey = await getSEDPToken()
- // 2-1. GetByToolID로 레지스터 매핑 정보 가져오기
- const mappingResponse = await fetch(
- `${SEDP_API_BASE_URL}/AdapterDataMapping/GetByToolID`,
- {
+ const [newRegistersResponse, registersResponse] = await Promise.all([
+ fetch(`${SEDP_API_BASE_URL}/AdapterDataMapping/GetByToolID`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -1808,95 +1815,94 @@ export async function getIMForms(
ProjectNo: projectCode,
TOOL_ID: "eVCP"
})
- }
- )
+ }),
+ fetch(`${SEDP_API_BASE_URL}/Register/Get`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ ContainDeleted: false
+ })
+ })
+ ])
- if (!mappingResponse.ok) {
- throw new Error(
- `레지스터 매핑 요청 실패: ${mappingResponse.status} ${mappingResponse.statusText}`
- )
+ if (!newRegistersResponse.ok) {
+ throw new Error(`새 레지스터 요청 실패: ${newRegistersResponse.status}`)
}
- const mappingData = await mappingResponse.json()
- const registers: NewRegister[] = Array.isArray(mappingData)
- ? mappingData
- : [mappingData]
+ if (!registersResponse.ok) {
+ throw new Error(`레지스터 요청 실패: ${registersResponse.status}`)
+ }
- // 2-2. packageCode가 SCOPES에 포함된 레지스터 필터링
- const matchingRegisters = registers.filter(register =>
- register.SCOPES.includes(packageCode)
- )
+ const newRegistersData = await newRegistersResponse.json()
+ const registersData = await registersResponse.json()
- if (matchingRegisters.length === 0) {
- console.log(`패키지 ${packageCode}에 해당하는 레지스터가 없습니다.`)
- return []
+ const newRegisters: NewRegister[] = Array.isArray(newRegistersData)
+ ? newRegistersData
+ : [newRegistersData]
+
+ const registers: RegisterDetail[] = Array.isArray(registersData)
+ ? registersData
+ : [registersData]
+
+ // 3. Register를 Map으로 변환 (TYPE_ID로 빠른 조회)
+ const registerMap = new Map<string, RegisterDetail>()
+ for (const reg of registers) {
+ registerMap.set(reg.TYPE_ID, reg)
}
- // 2-3. 각 레지스터의 상세 정보 가져오기
+ // 4. packageCode가 SCOPES에 포함되고, EP_ID가 "IMEP"인 것만 필터링
const formInfos: FormInfo[] = []
const formsToInsert: typeof formsPlant.$inferInsert[] = []
- for (const register of matchingRegisters) {
- try {
- const detailResponse = await fetch(
- `${SEDP_API_BASE_URL}/Register/GetByID`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'accept': '*/*',
- 'ApiKey': apiKey,
- 'ProjectNo': projectCode
- },
- body: JSON.stringify({
- ProjectNo: projectCode,
- TYPE_ID: register.REG_TYPE_ID,
- ContainDeleted: false
- })
- }
- )
-
- if (!detailResponse.ok) {
- console.error(
- `레지스터 상세 정보 요청 실패 (${register.REG_TYPE_ID}): ${detailResponse.status}`
- )
- continue
- }
-
- const detail: RegisterDetail = await detailResponse.json()
+ for (const newReg of newRegisters) {
+ // packageCode가 SCOPES에 없으면 스킵
+ if (!newReg.SCOPES || !newReg.SCOPES.includes(packageCode)) {
+ continue
+ }
- // DELETED가 true이거나 DESC가 없으면 스킵
- if (detail.DELETED || !detail.DESC) {
- continue
- }
+ const formCode = newReg.REG_TYPE_ID
+ const register = registerMap.get(formCode)
- formInfos.push({
- formCode: detail.TYPE_ID,
- formName: detail.DESC
- })
+ // Register에서 EP_ID가 "IMEP"가 아니면 스킵 (IM 폼만 처리)
+ if (!register || register.EP_ID !== "IMEP") {
+ continue
+ }
- // DB 삽입용 데이터 준비
- formsToInsert.push({
- projectCode: projectCode,
- packageCode: packageCode,
- formCode: detail.TYPE_ID,
- formName: detail.DESC,
- eng: false,
- im: true
- })
- } catch (error) {
- console.error(
- `레지스터 ${register.REG_TYPE_ID} 상세 정보 가져오기 실패:`,
- error
- )
+ // DELETED면 스킵
+ if (register.DELETED) {
continue
}
+
+ const formName = newReg.DESC || register.DESC || formCode
+
+ formInfos.push({
+ formCode,
+ formName
+ })
+
+ formsToInsert.push({
+ projectCode,
+ packageCode,
+ formCode,
+ formName,
+ eng: false,
+ im: true
+ })
}
- // 2-4. DB에 저장
+ // 5. DB에 저장
if (formsToInsert.length > 0) {
- await db.insert(formsPlant).values(formsToInsert).onConflictDoNothing()
- console.log(`${formsToInsert.length}개의 IM 폼을 DB에 저장했습니다.`)
+ await db.insert(formsPlant)
+ .values(formsToInsert)
+ .onConflictDoNothing()
+
+ console.log(`[getIMForms] ${formsToInsert.length}개의 IM 폼을 DB에 저장했습니다.`)
}
return formInfos
diff --git a/lib/tags-plant/table/add-tag-dialog.tsx b/lib/tags-plant/table/add-tag-dialog.tsx
index de5d2bf8..1bfb0703 100644
--- a/lib/tags-plant/table/add-tag-dialog.tsx
+++ b/lib/tags-plant/table/add-tag-dialog.tsx
@@ -329,7 +329,7 @@ export function AddTagDialog({ projectCode, packageCode }: AddTagDialogProps) {
const tagData: CreateTagSchema = {
tagType: data.tagType,
class: data.class,
- // subclass: data.subclass, // 서브클래스 정보 추가
+ subclass: data.subclass, // 서브클래스 정보 추가
tagNo: row.tagNo,
description: row.description,
...Object.fromEntries(
diff --git a/lib/tags-plant/table/tag-table-column.tsx b/lib/tags-plant/table/tag-table-column.tsx
index 80c25464..30bdacc3 100644
--- a/lib/tags-plant/table/tag-table-column.tsx
+++ b/lib/tags-plant/table/tag-table-column.tsx
@@ -82,14 +82,27 @@ export function getColumns({
minSize: 150,
size: 240,
},
- {
+ {
accessorKey: "class",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="Tag Class" />
+ <DataTableColumnHeaderSimple column={column} title="Class" />
),
cell: ({ row }) => <div>{row.getValue("class")}</div>,
meta: {
- excelHeader: "Tag Class"
+ excelHeader: "Class"
+ },
+ enableResizing: true,
+ minSize: 100,
+ size: 150,
+ },
+ {
+ accessorKey: "subclass",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Item Class" />
+ ),
+ cell: ({ row }) => <div>{row.getValue("subclass")}</div>,
+ meta: {
+ excelHeader: "Item Class"
},
enableResizing: true,
minSize: 100,
diff --git a/lib/tags-plant/table/tag-table.tsx b/lib/tags-plant/table/tag-table.tsx
index 2fdcd5fc..70bfc4e4 100644
--- a/lib/tags-plant/table/tag-table.tsx
+++ b/lib/tags-plant/table/tag-table.tsx
@@ -78,6 +78,9 @@ export function TagsTable({
const [isLoading, setIsLoading] = React.useState(true)
const [rowAction, setRowAction] = React.useState<DataTableRowAction<Tag> | null>(null)
+
+ console.log(tableData,"tableData")
+
// 선택된 행 관리
const [selectedRowsData, setSelectedRowsData] = React.useState<Tag[]>([])
const [clearSelection, setClearSelection] = React.useState(false)