// src/lib/cron/syncTagFormMappings.ts import db from "@/db/db"; import { projects, tagTypes, tagClasses, tagTypeClassFormMappings, formMetas } from '@/db/schema'; import { eq, and, inArray } from 'drizzle-orm'; import { getSEDPToken } from "./sedp-token"; // 환경 변수 const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/dev/api'; // 인터페이스 정의 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: LinkAttribute[]; JOIN_TABLS: any[]; DELETED: boolean; CRTER_NO: string; CRTE_DTM: string; CHGER_NO: string | null; CHGE_DTM: string | null; _id: string; } interface LinkAttribute { ATT_ID: string; CPY_DESC: string; JOIN_KEY_ATT_ID: string | null; JOIN_VAL_ATT_ID: string | null; KEY_YN: boolean; EDIT_YN: boolean; PUB_YN: boolean; VND_YN: boolean; DEF_VAL: string | null; UOM_ID: string | null; } interface Attribute { PROJ_NO: string; ATT_ID: string; DESC: string; GROUP: string | null; REMARK: string | null; VAL_TYPE: string; IGN_LIST_VAL: boolean; CL_ID: string | null; UOM_ID: string | null; DEF_VAL: string | null; MIN_VAL: number; MAX_VAL: number; ESS_YN: boolean; SEQ: number; FORMAT: string | null; REG_EXPS: string | null; ATTRIBUTES: any[]; DELETED: boolean; CRTER_NO: string; CRTE_DTM: string; CHGER_NO: string | null; CHGE_DTM: string | null; _id: string; } interface CodeList { PROJ_NO: string; CL_ID: string; DESC: string; REMARK: string | null; PRNT_CD_ID: string | null; REG_TYPE_ID: string | null; VAL_ATT_ID: string | null; VALUES: CodeValue[]; LNK_ATT: any[]; DELETED: boolean; CRTER_NO: string; CRTE_DTM: string; CHGER_NO: string | null; CHGE_DTM: string | null; _id: string; } interface CodeValue { PRNT_VALUE: string | null; VALUE: string; DESC: string; REMARK: string; USE_YN: boolean; SEQ: number; ATTRIBUTES: any[]; } interface UOM { PROJ_NO: string; UOM_ID: string; DESC: string; SYMBOL: string; CONV_RATE: number; DELETED: boolean; CRTER_NO: string; CRTE_DTM: string; CHGER_NO: string | null; CHGE_DTM: string | null; _id: string; } interface Project { id: number; code: string; name: string; type?: string; createdAt?: Date; updatedAt?: Date; } interface SyncResult { project: string; success: boolean; count?: number; error?: string; } interface FormColumn { key: string; label: string; type: string; options?: string[]; uom?: string; uomId?: string; } // 레지스터 데이터 가져오기 async function getRegisters(projectCode: string): Promise { try { // 토큰(API 키) 가져오기 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({ ContainDeleted: true }) } ); if (!response.ok) { throw new Error(`레지스터 요청 실패: ${response.status} ${response.statusText}`); } const data = await response.json(); // 결과가 배열인지 확인 if (Array.isArray(data)) { return data; } else { // 단일 객체인 경우 배열로 변환 return [data]; } } catch (error) { console.error(`프로젝트 ${projectCode}의 레지스터 가져오기 실패:`, error); throw error; } } // 특정 속성 가져오기 async function getAttributeById(projectCode: string, attributeId: string): Promise { try { // 토큰(API 키) 가져오기 const apiKey = await getSEDPToken(); const response = await fetch( `${SEDP_API_BASE_URL}/Attributes/GetByID`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': '*/*', 'ApiKey': apiKey, 'ProjectNo': projectCode }, body: JSON.stringify({ ATT_ID: attributeId }) } ); if (!response.ok) { if (response.status === 404) { console.warn(`속성 ID ${attributeId}를 찾을 수 없음`); return null; } throw new Error(`속성 요청 실패: ${response.status} ${response.statusText}`); } return response.json(); } catch (error) { console.error(`속성 ID ${attributeId} 가져오기 실패:`, error); return null; } } // 특정 코드 리스트 가져오기 async function getCodeListById(projectCode: string, codeListId: string): Promise { try { // 토큰(API 키) 가져오기 const apiKey = await getSEDPToken(); const response = await fetch( `${SEDP_API_BASE_URL}/CodeList/GetByID`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': '*/*', 'ApiKey': apiKey, 'ProjectNo': projectCode }, body: JSON.stringify({ CL_ID: codeListId }) } ); if (!response.ok) { if (response.status === 404) { console.warn(`코드 리스트 ID ${codeListId}를 찾을 수 없음`); return null; } throw new Error(`코드 리스트 요청 실패: ${response.status} ${response.statusText}`); } return response.json(); } catch (error) { console.error(`코드 리스트 ID ${codeListId} 가져오기 실패:`, error); return null; } } // UOM 가져오기 async function getUomById(projectCode: string, uomId: string): Promise { try { // 토큰(API 키) 가져오기 const apiKey = await getSEDPToken(); const response = await fetch( `${SEDP_API_BASE_URL}/UOM/GetByID`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': '*/*', 'ApiKey': apiKey, 'ProjectNo': projectCode }, body: JSON.stringify({ UOM_ID: uomId }) } ); if (!response.ok) { if (response.status === 404) { console.warn(`UOM ID ${uomId}를 찾을 수 없음`); return null; } throw new Error(`UOM 요청 실패: ${response.status} ${response.statusText}`); } return response.json(); } catch (error) { console.error(`UOM ID ${uomId} 가져오기 실패:`, error); return null; } } // 데이터베이스에 태그 타입 클래스 폼 매핑 및 폼 메타 저장 async function saveFormMappingsAndMetas( projectId: number, projectCode: string, registers: Register[] ): Promise { try { // 프로젝트와 관련된 태그 타입 및 클래스 가져오기 const tagTypeRecords = await db.select() .from(tagTypes) .where(eq(tagTypes.projectId, projectId)); const tagClassRecords = await db.select() .from(tagClasses) .where(eq(tagClasses.projectId, projectId)); // 태그 타입과 클래스를 매핑 const tagTypeMap = new Map(tagTypeRecords.map(type => [type.code, type])); const tagClassMap = new Map(tagClassRecords.map(cls => [cls.code, cls])); // 저장할 매핑 목록과 폼 메타 정보 const mappingsToSave = []; const formMetasToSave = []; // 각 레지스터 처리 for (const register of registers) { // 삭제된 레지스터는 건너뜀 if (register.DELETED) continue; // 폼 메타 데이터를 위한 컬럼 정보 구성 const columns: FormColumn[] = []; // 각 속성 정보 수집 for (const linkAtt of register.LNK_ATT) { // 속성 가져오기 const attribute = await getAttributeById(projectCode, linkAtt.ATT_ID); if (!attribute) continue; // 기본 컬럼 정보 const column: FormColumn = { key: linkAtt.ATT_ID, label: linkAtt.CPY_DESC, type: attribute.VAL_TYPE || 'STRING' }; // 리스트 타입인 경우 옵션 추가 if ((attribute.VAL_TYPE === 'LIST' || attribute.VAL_TYPE === 'DYNAMICLIST') && attribute.CL_ID) { const codeList = await getCodeListById(projectCode, attribute.CL_ID); if (codeList && codeList.VALUES) { // 유효한 옵션만 필터링 const options = codeList.VALUES .filter(value => value.USE_YN) .map(value => value.DESC); if (options.length > 0) { column.options = options; } } } // UOM 정보 추가 if (linkAtt.UOM_ID) { const uom = await getUomById(projectCode, linkAtt.UOM_ID); if (uom) { column.uom = uom.SYMBOL; column.uomId = uom.UOM_ID; } } columns.push(column); } // 폼 메타 정보 저장 formMetasToSave.push({ projectId, formCode: register.TYPE_ID, formName: register.DESC, columns: JSON.stringify(columns), createdAt: new Date(), updatedAt: new Date() }); // 관련된 클래스 매핑 처리 for (const classId of register.MAP_CLS_ID) { // 해당 클래스와 태그 타입 확인 const tagClass = tagClassMap.get(classId); if (!tagClass) { console.warn(`클래스 ID ${classId}를 프로젝트 ID ${projectId}에서 찾을 수 없음`); continue; } const tagTypeCode = tagClass.tagTypeCode; const tagType = tagTypeMap.get(tagTypeCode); if (!tagType) { console.warn(`태그 타입 ${tagTypeCode}를 프로젝트 ID ${projectId}에서 찾을 수 없음`); continue; } // 매핑 정보 저장 mappingsToSave.push({ projectId, tagTypeLabel: tagType.description, classLabel: tagClass.label, formCode: register.TYPE_ID, formName: register.DESC, createdAt: new Date(), updatedAt: new Date() }); } } // 기존 데이터 삭제 후 새로 저장 await db.delete(tagTypeClassFormMappings).where(eq(tagTypeClassFormMappings.projectId, projectId)); await db.delete(formMetas).where(eq(formMetas.projectId, projectId)); let totalSaved = 0; // 매핑 정보 저장 if (mappingsToSave.length > 0) { await db.insert(tagTypeClassFormMappings).values(mappingsToSave); totalSaved += mappingsToSave.length; console.log(`프로젝트 ID ${projectId}에 ${mappingsToSave.length}개의 태그 타입-클래스-폼 매핑 저장 완료`); } // 폼 메타 정보 저장 if (formMetasToSave.length > 0) { await db.insert(formMetas).values(formMetasToSave); totalSaved += formMetasToSave.length; console.log(`프로젝트 ID ${projectId}에 ${formMetasToSave.length}개의 폼 메타 정보 저장 완료`); } return totalSaved; } catch (error) { console.error(`폼 매핑 및 메타 저장 실패 (프로젝트 ID: ${projectId}):`, error); throw error; } } // 메인 동기화 함수 export async function syncTagFormMappings() { try { console.log('태그 폼 매핑 동기화 시작:', new Date().toISOString()); // 모든 프로젝트 가져오기 const allProjects = await db.select().from(projects); // 각 프로젝트에 대해 폼 매핑 동기화 const results = await Promise.allSettled( allProjects.map(async (project: Project) => { try { // 레지스터 데이터 가져오기 const registers = await getRegisters(project.code); // 데이터베이스에 저장 const count = await saveFormMappingsAndMetas(project.id, project.code, registers); return { project: project.code, success: true, count } as SyncResult; } catch (error) { console.error(`프로젝트 ${project.code} 폼 매핑 동기화 실패:`, error); return { project: project.code, success: false, error: error instanceof Error ? error.message : String(error) } as SyncResult; } }) ); // 결과 처리를 위한 배열 준비 const successfulResults: SyncResult[] = []; const failedResults: SyncResult[] = []; // 결과 분류 results.forEach((result) => { if (result.status === 'fulfilled') { if (result.value.success) { successfulResults.push(result.value); } else { failedResults.push(result.value); } } else { // 거부된 프로미스는 실패로 간주 failedResults.push({ project: 'unknown', success: false, error: result.reason?.toString() || 'Unknown error' }); } }); const successCount = successfulResults.length; const failCount = failedResults.length; // 이제 안전하게 count 속성에 접근 가능 const totalItems = successfulResults.reduce((sum, result) => sum + (result.count || 0), 0 ); console.log(`태그 폼 매핑 동기화 완료: ${successCount}개 프로젝트 성공 (총 ${totalItems}개 항목), ${failCount}개 프로젝트 실패`); return { success: successCount, failed: failCount, items: totalItems, timestamp: new Date().toISOString() }; } catch (error) { console.error('태그 폼 매핑 동기화 중 오류 발생:', error); throw error; } }