diff options
| -rw-r--r-- | app/api/cron/tags-plant/start/route.ts | 38 | ||||
| -rw-r--r-- | lib/forms-plant/services.ts | 161 | ||||
| -rw-r--r-- | lib/sedp/get-tags-plant.ts | 1013 | ||||
| -rw-r--r-- | lib/tags-plant/queries.ts | 2 | ||||
| -rw-r--r-- | lib/tags-plant/service.ts | 158 | ||||
| -rw-r--r-- | lib/tags-plant/table/add-tag-dialog.tsx | 2 | ||||
| -rw-r--r-- | lib/tags-plant/table/tag-table-column.tsx | 19 | ||||
| -rw-r--r-- | lib/tags-plant/table/tag-table.tsx | 3 |
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) |
