diff options
Diffstat (limited to 'lib/sedp/sync-object-class.ts')
| -rw-r--r-- | lib/sedp/sync-object-class.ts | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/lib/sedp/sync-object-class.ts b/lib/sedp/sync-object-class.ts new file mode 100644 index 00000000..1cf0c23b --- /dev/null +++ b/lib/sedp/sync-object-class.ts @@ -0,0 +1,304 @@ +import db from "@/db/db"; +import { projects, tagClasses, tagTypes } from '@/db/schema'; +import { eq, and } 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'; + +// ObjectClass 인터페이스 정의 +interface ObjectClass { + PROJ_NO: string; + CLS_ID: string; + DESC: string; + TAG_TYPE_ID: string | null; + PRT_CLS_ID: string | null; + LNK_ATT: any[]; + DELETED: boolean; + DEL_USER: string | null; + DEL_DTM: string | null; + 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; +} + +// 오브젝트 클래스 데이터 가져오기 +async function getObjectClasses(projectCode: string, token:string): Promise<ObjectClass[]> { + try { + const response = await fetch( + `${SEDP_API_BASE_URL}/ObjectClass/Get`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'accept': '*/*', + 'ApiKey': token, + '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 verifyTagTypes(projectId: number, tagTypeCodes: string[]): Promise<Set<string>> { + try { + // 프로젝트에 있는 태그 타입 코드 조회 + const existingTagTypes = await db.select({ code: tagTypes.code }) + .from(tagTypes) + .where(eq(tagTypes.projectId, projectId)); + + // 존재하는 태그 타입 코드 Set으로 반환 + return new Set(existingTagTypes.map(type => type.code)); + } catch (error) { + console.error(`프로젝트 ID ${projectId}의 태그 타입 확인 실패:`, error); + throw error; + } +} + +// 데이터베이스에 오브젝트 클래스 저장 (upsert 사용) +async function saveObjectClassesToDatabase(projectId: number, classes: ObjectClass[]): Promise<number> { + try { + // null이 아닌 TAG_TYPE_ID만 필터링 + const validClasses = classes.filter(cls => cls.TAG_TYPE_ID !== null); + + if (validClasses.length === 0) { + console.log(`프로젝트 ID ${projectId}에 저장할 유효한 오브젝트 클래스가 없습니다.`); + return 0; + } + + // 모든 태그 타입 ID 목록 추출 + const tagTypeCodes = validClasses.map(cls => cls.TAG_TYPE_ID!); + + // 존재하는 태그 타입 확인 + const existingTagTypeCodes = await verifyTagTypes(projectId, tagTypeCodes); + + // 태그 타입이 존재하는 오브젝트 클래스만 필터링 + const classesToSave = validClasses.filter(cls => + cls.TAG_TYPE_ID !== null && existingTagTypeCodes.has(cls.TAG_TYPE_ID) + ); + + if (classesToSave.length === 0) { + console.log(`프로젝트 ID ${projectId}에 저장할 유효한 오브젝트 클래스가 없습니다 (태그 타입 존재하지 않음).`); + return 0; + } + + // 현재 프로젝트의 오브젝트 클래스 코드 가져오기 + const existingClasses = await db.select() + .from(tagClasses) + .where(eq(tagClasses.projectId, projectId)); + + // 코드 기준으로 맵 생성 + const existingClassMap = new Map( + existingClasses.map(cls => [cls.code, cls]) + ); + + // 새로 추가할 항목 + const toInsert = []; + + // 업데이트할 항목 + const toUpdate = []; + + // API에 있는 코드 목록 + const apiClassCodes = new Set(classesToSave.map(cls => cls.CLS_ID)); + + // 삭제할 코드 목록 + const codesToDelete = existingClasses + .map(cls => cls.code) + .filter(code => !apiClassCodes.has(code)); + + // 클래스 데이터 처리 + for (const cls of classesToSave) { + // 데이터베이스 레코드 준비 + const record = { + code: cls.CLS_ID, + projectId: projectId, + label: cls.DESC, + tagTypeCode: cls.TAG_TYPE_ID!, + updatedAt: new Date() + }; + + // 이미 존재하는 코드인지 확인 + if (existingClassMap.has(cls.CLS_ID)) { + // 업데이트 항목에 추가 + toUpdate.push(record); + } else { + // 새로 추가할 항목에 추가 (createdAt 필드 추가) + toInsert.push({ + ...record, + createdAt: new Date() + }); + } + } + + // 트랜잭션 실행 + let totalChanged = 0; + + // 1. 새 항목 삽입 + if (toInsert.length > 0) { + await db.insert(tagClasses).values(toInsert); + totalChanged += toInsert.length; + console.log(`프로젝트 ID ${projectId}에 ${toInsert.length}개의 새 오브젝트 클래스 추가 완료`); + } + + // 2. 기존 항목 업데이트 + for (const item of toUpdate) { + await db.update(tagClasses) + .set({ + label: item.label, + tagTypeCode: item.tagTypeCode, + updatedAt: item.updatedAt + }) + .where( + and( + eq(tagClasses.code, item.code), + eq(tagClasses.projectId, item.projectId) + ) + ); + totalChanged += 1; + } + + if (toUpdate.length > 0) { + console.log(`프로젝트 ID ${projectId}의 ${toUpdate.length}개 오브젝트 클래스 업데이트 완료`); + } + + // 3. 더 이상 존재하지 않는 항목 삭제 + if (codesToDelete.length > 0) { + for (const code of codesToDelete) { + await db.delete(tagClasses) + .where( + and( + eq(tagClasses.code, code), + eq(tagClasses.projectId, projectId) + ) + ); + } + console.log(`프로젝트 ID ${projectId}에서 ${codesToDelete.length}개의 오브젝트 클래스 삭제 완료`); + totalChanged += codesToDelete.length; + } + + return totalChanged; + } catch (error) { + console.error(`오브젝트 클래스 저장 실패 (프로젝트 ID: ${projectId}):`, error); + throw error; + } +} + +// 메인 동기화 함수 +export async function syncObjectClasses() { + try { + console.log('오브젝트 클래스 동기화 시작:', new Date().toISOString()); + + // 1. 토큰 가져오기 + const token = await getSEDPToken(); + + // 2. 모든 프로젝트 가져오기 + const allProjects = await db.select().from(projects); + + // 3. 각 프로젝트에 대해 오브젝트 클래스 동기화 + const results = await Promise.allSettled( + allProjects.map(async (project: Project) => { + try { + // 오브젝트 클래스 데이터 가져오기 + const objectClasses = await getObjectClasses(project.code, token); + + // 데이터베이스에 저장 + const count = await saveObjectClassesToDatabase(project.id, objectClasses); + 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; + } +}
\ No newline at end of file |
