diff options
Diffstat (limited to 'lib/sedp/sync-object-class.ts')
| -rw-r--r-- | lib/sedp/sync-object-class.ts | 442 |
1 files changed, 273 insertions, 169 deletions
diff --git a/lib/sedp/sync-object-class.ts b/lib/sedp/sync-object-class.ts index 5fd3ebff..1a325407 100644 --- a/lib/sedp/sync-object-class.ts +++ b/lib/sedp/sync-object-class.ts @@ -6,6 +6,12 @@ import { getSEDPToken } from "./sedp-token"; // 환경 변수 const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api'; +// 서브클래스 타입 정의 +interface SubclassInfo { + id: string; + desc: string; +} + // ObjectClass 인터페이스 정의 interface ObjectClass { PROJ_NO: string; @@ -55,6 +61,60 @@ interface LinkAttribute { SEQ: number; } +interface SubClassCodeValue { + PRNT_VALUE: string; + VALUE: string; + DESC: string; + REMARK: string; + USE_YN: boolean; + SEQ: number; + ATTRIBUTES: Array<{ + ATT_ID: string; + VALUE: string; + }>; +} + +/** + * 프로젝트별 CodeList(클래스 코드 값) 조회 + */ +export async function getCodeListsByID(projectCode: string): Promise<SubClassCodeValue[]> { + try { + // 1) 토큰(API 키) 발급 + const apiKey = await getSEDPToken(); + + // 2) API 호출 + 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({ + ProjectNo: projectCode, + CL_ID: 'EVCP_TAG_FUNC', + ContainDeleted: false, + }), + }); + + if (!response.ok) { + // 네트워크·인증 오류 등 + throw new Error(`코드 리스트 요청 실패: ${response.status} ${response.statusText}`); + } + + // 3) 응답 파싱 + const { VALUES = [] } = (await response.json()) as { VALUES?: SubClassCodeValue[] }; + + return VALUES; // 정상 반환 + } catch (err) { + // 4) 파싱 오류·기타 예외 + console.error(`프로젝트 ${projectCode}의 코드 리스트 가져오기 실패:`, err); + + return []; + } +} + // 태그 클래스 속성 저장 함수 async function saveTagClassAttributes( tagClassId: number, @@ -366,197 +426,237 @@ async function getAllTagTypes(projectCode: string, token: string): Promise<TagTy } } -// LNK_ATT 속성 처리가 포함된 오브젝트 클래스 저장 함수 +// 수정된 함수: ID와 DESC을 함께 반환 +function findSubclasses(parentCode: string, allClasses: ObjectClass[]): SubclassInfo[] { + return allClasses + .filter(cls => cls.PRT_CLS_ID === parentCode) + .map(cls => ({ + id: cls.CLS_ID, + desc: cls.DESC + })); +} + +// 서브클래스별 리마크 가져오기 (수정됨) +async function getSubclassRemarks( + subclasses: SubclassInfo[], + projectCode: string +): Promise<Record<string, string>> { + try { + if (subclasses.length === 0) { + return {}; + } + + // getCodeListsByID로 코드 리스트 가져오기 + const codeValues = await getCodeListsByID(projectCode); + + if (!Array.isArray(codeValues)) { + console.log(`프로젝트 ${projectCode}의 코드 리스트가 배열이 아닙니다.`); + return {}; + } + + // 서브클래스별 리마크 매핑 + const remarkMap: Record<string, string> = {}; + + for (const subclass of subclasses) { + // VALUE가 서브클래스 ID와 일치하는 항목 찾기 + const matchedValue = codeValues.find(value => value.VALUE === subclass.id); + if (matchedValue && matchedValue.REMARK) { + remarkMap[subclass.id] = matchedValue.REMARK; + } else { + // REMARK가 없는 경우 빈 문자열 또는 기본값 + remarkMap[subclass.id] = ''; + } + } + + return remarkMap; + } catch (error) { + console.error(`서브클래스 리마크 가져오기 실패 (프로젝트: ${projectCode}):`, error); + return {}; + } +} + +// LNK_ATT 속성 처리가 포함된 오브젝트 클래스 저장 함수 (수정됨) async function saveObjectClassesToDatabase( projectId: number, classes: ObjectClass[], projectCode: string, token: string, - skipTagTypeSync: boolean = false // 태그 타입 동기화를 건너뛸지 여부 + skipTagTypeSync: boolean = false ): Promise<number> { try { - // null이 아닌 TAG_TYPE_ID만 필터링 - const validClasses = classes.filter(cls => cls.TAG_TYPE_ID !== null && cls.TAG_TYPE_ID !== "") ; - - if (validClasses.length === 0) { - console.log(`프로젝트 ID ${projectId}에 저장할 유효한 오브젝트 클래스가 없습니다.`); - return 0; - } - - // 모든 태그 타입 ID 목록 추출 - const tagTypeCodes = validClasses.map(cls => cls.TAG_TYPE_ID!); - - // skipTagTypeSync가 true인 경우 태그 타입 동기화 단계 건너뜀 - if (!skipTagTypeSync) { - // 태그 타입이 없는 경우를 대비해 태그 타입 정보 먼저 가져와서 저장 - console.log(`프로젝트 ID ${projectId}의 태그 타입 동기화 시작...`); + // null이 아닌 TAG_TYPE_ID만 필터링 + const validClasses = classes.filter(cls => cls.TAG_TYPE_ID !== null && cls.TAG_TYPE_ID !== ""); - try { - // 프로젝트의 모든 태그 타입 가져오기 - const allTagTypes = await getAllTagTypes(projectCode, token); - - // 태그 타입 저장 - await saveTagTypesToDatabase(allTagTypes, projectCode); - } catch (error) { - console.error(`프로젝트 ${projectCode}의 태그 타입 동기화 실패:`, error); - // 에러가 발생해도 계속 진행 + if (validClasses.length === 0) { + console.log(`프로젝트 ID ${projectId}에 저장할 유효한 오브젝트 클래스가 없습니다.`); + return 0; } - console.log(`프로젝트 ID ${projectId}의 태그 타입 동기화 완료`); - } - - // 존재하는 태그 타입 확인 - 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 (!skipTagTypeSync) { + console.log(`프로젝트 ID ${projectId}의 태그 타입 동기화 시작...`); + try { + const allTagTypes = await getAllTagTypes(projectCode, token); + await saveTagTypesToDatabase(allTagTypes, projectCode); + } catch (error) { + console.error(`프로젝트 ${projectCode}의 태그 타입 동기화 실패:`, error); + } + console.log(`프로젝트 ID ${projectId}의 태그 타입 동기화 완료`); + } - // 이미 존재하는 코드인지 확인 - if (existingClassMap.has(cls.CLS_ID)) { - // 업데이트 항목에 추가 - toUpdate.push(record); - } else { - // 새로 추가할 항목에 추가 (createdAt 필드 추가) - toInsert.push({ - ...record, - createdAt: new Date() - }); + // 존재하는 태그 타입 확인 + 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; } - } - - // 트랜잭션 실행 - let totalChanged = 0; - - // 1. 새 항목 삽입 및 속성 처리 - if (toInsert.length > 0) { - // returning을 사용하여 삽입된 레코드의 ID와 code를 가져옴 - const insertedClasses = await db.insert(tagClasses) - .values(toInsert) - .returning({ id: tagClasses.id, code: tagClasses.code }); - - totalChanged += toInsert.length; - console.log(`프로젝트 ID ${projectId}에 ${toInsert.length}개의 새 오브젝트 클래스 추가 완료`); - // 새로 삽입된 각 클래스의 LNK_ATT 속성 처리 - for (const insertedClass of insertedClasses) { - const originalClass = classesToSave.find(cls => cls.CLS_ID === insertedClass.code); - if (originalClass && originalClass.LNK_ATT && originalClass.LNK_ATT.length > 0) { - try { - await saveTagClassAttributes(insertedClass.id, originalClass.LNK_ATT); - } catch (error) { - console.error(`태그 클래스 ${insertedClass.code}의 속성 저장 실패:`, error); - // 속성 저장 실패해도 계속 진행 + // 현재 프로젝트의 모든 오브젝트 클래스 가져오기 + 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) { + // 서브클래스 찾기 (이제 {id, desc} 형태로 반환) + const subclasses = findSubclasses(cls.CLS_ID, classes); + + // 서브클래스별 리마크 가져오기 + const subclassRemark = await getSubclassRemarks(subclasses, projectCode); + + const record = { + code: cls.CLS_ID, + projectId: projectId, + label: cls.DESC, + tagTypeCode: cls.TAG_TYPE_ID!, + subclasses: subclasses, // 이제 {id, desc}[] 형태 + subclassRemark: subclassRemark, + updatedAt: new Date() + }; + + if (existingClassMap.has(cls.CLS_ID)) { + toUpdate.push(record); + } else { + toInsert.push({ + ...record, + createdAt: new Date() + }); } - } } - } - - // 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) - ) - ); - // 업데이트된 클래스의 ID 조회 - const updatedClass = await db.select({ id: tagClasses.id }) - .from(tagClasses) - .where( - and( - eq(tagClasses.code, item.code), - eq(tagClasses.projectId, item.projectId) - ) - ) - .limit(1); + let totalChanged = 0; - if (updatedClass.length > 0) { - const originalClass = classesToSave.find(cls => cls.CLS_ID === item.code); - if (originalClass && originalClass.LNK_ATT) { - try { - await saveTagClassAttributes(updatedClass[0].id, originalClass.LNK_ATT); - } catch (error) { - console.error(`태그 클래스 ${item.code}의 속성 업데이트 실패:`, error); - // 속성 업데이트 실패해도 계속 진행 + // 새 항목 삽입 + if (toInsert.length > 0) { + const insertedClasses = await db.insert(tagClasses) + .values(toInsert) + .returning({ id: tagClasses.id, code: tagClasses.code }); + + totalChanged += toInsert.length; + console.log(`프로젝트 ID ${projectId}에 ${toInsert.length}개의 새 오브젝트 클래스 추가 완료`); + + // 새로 삽입된 각 클래스의 LNK_ATT 속성 처리 + for (const insertedClass of insertedClasses) { + const originalClass = classesToSave.find(cls => cls.CLS_ID === insertedClass.code); + if (originalClass && originalClass.LNK_ATT && originalClass.LNK_ATT.length > 0) { + try { + await saveTagClassAttributes(insertedClass.id, originalClass.LNK_ATT); + } catch (error) { + console.error(`태그 클래스 ${insertedClass.code}의 속성 저장 실패:`, error); + } + } } - } } - totalChanged += 1; - } - - if (toUpdate.length > 0) { - console.log(`프로젝트 ID ${projectId}의 ${toUpdate.length}개 오브젝트 클래스 업데이트 완료`); - } - - // 3. 더 이상 존재하지 않는 항목 삭제 (CASCADE로 속성도 자동 삭제됨) - if (codesToDelete.length > 0) { - for (const code of codesToDelete) { - await db.delete(tagClasses) - .where( - and( - eq(tagClasses.code, code), - eq(tagClasses.projectId, projectId) - ) - ); + // 기존 항목 업데이트 + for (const item of toUpdate) { + await db.update(tagClasses) + .set({ + label: item.label, + tagTypeCode: item.tagTypeCode, + subclasses: item.subclasses, + subclassRemark: item.subclassRemark, + updatedAt: item.updatedAt + }) + .where( + and( + eq(tagClasses.code, item.code), + eq(tagClasses.projectId, item.projectId) + ) + ); + + // 업데이트된 클래스의 속성 처리 + const updatedClass = await db.select({ id: tagClasses.id }) + .from(tagClasses) + .where( + and( + eq(tagClasses.code, item.code), + eq(tagClasses.projectId, item.projectId) + ) + ) + .limit(1); + + if (updatedClass.length > 0) { + const originalClass = classesToSave.find(cls => cls.CLS_ID === item.code); + if (originalClass && originalClass.LNK_ATT) { + try { + await saveTagClassAttributes(updatedClass[0].id, originalClass.LNK_ATT); + } catch (error) { + console.error(`태그 클래스 ${item.code}의 속성 업데이트 실패:`, error); + } + } + } + + totalChanged += 1; } - console.log(`프로젝트 ID ${projectId}에서 ${codesToDelete.length}개의 오브젝트 클래스 삭제 완료`); - totalChanged += codesToDelete.length; - } - - return totalChanged; + + if (toUpdate.length > 0) { + console.log(`프로젝트 ID ${projectId}의 ${toUpdate.length}개 오브젝트 클래스 업데이트 완료`); + } + + // 더 이상 존재하지 않는 항목 삭제 + 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; + console.error(`오브젝트 클래스 저장 실패 (프로젝트 ID: ${projectId}):`, error); + throw error; } } @@ -717,6 +817,8 @@ export async function getProjectTagClassesWithAttributes(projectId: number) { code: tagClasses.code, label: tagClasses.label, tagTypeCode: tagClasses.tagTypeCode, + subclasses: tagClasses.subclasses, + subclassRemark: tagClasses.subclassRemark, createdAt: tagClasses.createdAt, updatedAt: tagClasses.updatedAt, // 속성 정보 @@ -741,6 +843,8 @@ export async function getProjectTagClassesWithAttributes(projectId: number) { code: row.code, label: row.label, tagTypeCode: row.tagTypeCode, + subclasses: row.subclasses, // 이제 {id, desc}[] 형태 + subclassRemark: row.subclassRemark, createdAt: row.createdAt, updatedAt: row.updatedAt, attributes: [] |
