diff options
Diffstat (limited to 'lib/vendor-document-list/sync-service.ts')
| -rw-r--r-- | lib/vendor-document-list/sync-service.ts | 270 |
1 files changed, 35 insertions, 235 deletions
diff --git a/lib/vendor-document-list/sync-service.ts b/lib/vendor-document-list/sync-service.ts index 4c1f5786..e058803b 100644 --- a/lib/vendor-document-list/sync-service.ts +++ b/lib/vendor-document-list/sync-service.ts @@ -42,7 +42,7 @@ class SyncService { * 변경사항을 change_logs에 기록 */ async logChange( - contractId: number, + projectId: number, entityType: 'document' | 'revision' | 'attachment', entityId: number, action: 'CREATE' | 'UPDATE' | 'DELETE', @@ -56,7 +56,7 @@ class SyncService { const changedFields = this.detectChangedFields(oldValues, newValues) await db.insert(changeLogs).values({ - contractId, + projectId, entityType, entityId, action, @@ -99,7 +99,7 @@ class SyncService { * 동기화할 변경사항 조회 (증분) */ async getPendingChanges( - contractId: number, + projectId: number, targetSystem: string = 'DOLCE', limit?: number ): Promise<ChangeLog[]> { @@ -107,7 +107,7 @@ class SyncService { .select() .from(changeLogs) .where(and( - eq(changeLogs.contractId, contractId), + eq(changeLogs.projectId, projectId), eq(changeLogs.isSynced, false), lt(changeLogs.syncAttempts, 3), sql`(${changeLogs.targetSystems} IS NULL OR ${changeLogs.targetSystems} @> ${JSON.stringify([targetSystem])})` @@ -136,14 +136,14 @@ class SyncService { * 동기화 배치 생성 */ async createSyncBatch( - contractId: number, + projectId: number, targetSystem: string, changeLogIds: number[] ): Promise<number> { const [batch] = await db .insert(syncBatches) .values({ - contractId, + projectId, targetSystem, batchSize: changeLogIds.length, changeLogIds, @@ -158,7 +158,7 @@ class SyncService { * 메인 동기화 실행 함수 (청크 처리 포함) */ async syncToExternalSystem( - contractId: number, + projectId: number, targetSystem: string = 'DOLCE', manualTrigger: boolean = false ): Promise<SyncResult> { @@ -169,7 +169,7 @@ class SyncService { } // 2. 대기 중인 변경사항 조회 (전체) - const pendingChanges = await this.getPendingChanges(contractId, targetSystem) + const pendingChanges = await this.getPendingChanges(projectId, targetSystem) if (pendingChanges.length === 0) { return { @@ -182,7 +182,7 @@ class SyncService { // 3. 배치 생성 const batchId = await this.createSyncBatch( - contractId, + projectId, targetSystem, pendingChanges.map(c => c.id) ) @@ -214,10 +214,10 @@ class SyncService { // 시스템별로 다른 동기화 메서드 호출 switch (targetSystem.toUpperCase()) { case 'DOLCE': - chunkResult = await this.performSyncDOLCE(chunk, contractId) + chunkResult = await this.performSyncDOLCE(chunk, projectId) break case 'SWP': - chunkResult = await this.performSyncSWP(chunk, contractId) + chunkResult = await this.performSyncSWP(chunk, projectId) break default: throw new Error(`Unsupported target system: ${targetSystem}`) @@ -296,7 +296,7 @@ class SyncService { */ private async performSyncDOLCE( changes: ChangeLog[], - contractId: number + projectId: number ): Promise<{ success: boolean; successCount: number; failureCount: number; errors?: string[]; endpointResults?: Record<string, any> }> { const errors: string[] = [] const endpointResults: Record<string, any> = {} @@ -325,7 +325,7 @@ class SyncService { // DOLCE 업로드 실행 const uploadResult = await dolceUploadService.uploadToDoLCE( - contractId, + projectId, revisionIds, 'system_user', // 시스템 사용자 ID 'System Upload' @@ -374,216 +374,16 @@ class SyncService { */ private async performSyncSWP( changes: ChangeLog[], - contractId: number + projectId: number ): Promise<{ success: boolean; successCount: number; failureCount: number; errors?: string[]; endpointResults?: Record<string, any> }> { - const errors: string[] = [] - const endpointResults: Record<string, any> = {} - let overallSuccess = true - - // 변경사항을 SWP 시스템 형태로 변환 - const syncData = await this.transformChangesForSWP(changes) - - // 1. SWP 메인 엔드포인트 (XML 전송) - const mainUrl = process.env.SYNC_SWP_URL - if (mainUrl) { - try { - console.log(`Sending to SWP main: ${mainUrl}`) - - const transformedData = this.convertToXML({ - contractId, - systemType: 'SWP', - changes: syncData, - batchSize: changes.length, - timestamp: new Date().toISOString(), - source: 'EVCP' - }) - - const response = await fetch(mainUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/xml', - 'Authorization': `Basic ${Buffer.from(`${process.env.SYNC_SWP_USER}:${process.env.SYNC_SWP_PASSWORD}`).toString('base64')}`, - 'X-System': 'SWP' - }, - body: transformedData - }) - - if (!response.ok) { - const errorText = await response.text() - throw new Error(`SWP main: HTTP ${response.status} - ${errorText}`) - } - - let result - const contentType = response.headers.get('content-type') - if (contentType?.includes('application/json')) { - result = await response.json() - } else { - result = await response.text() - } - - endpointResults['swp_main'] = result - console.log(`✅ SWP main sync successful`) - - } catch (error) { - const errorMessage = `SWP main: ${error instanceof Error ? error.message : 'Unknown error'}` - errors.push(errorMessage) - overallSuccess = false - - console.error(`❌ SWP main sync failed:`, error) - } - } - - // 2. SWP 알림 엔드포인트 (선택사항) - const notificationUrl = process.env.SYNC_SWP_NOTIFICATION_URL - if (notificationUrl) { - try { - console.log(`Sending to SWP notification: ${notificationUrl}`) - - const notificationData = { - event: 'swp_sync_notification', - itemCount: syncData.length, - syncTime: new Date().toISOString(), - system: 'SWP' - } - - const response = await fetch(notificationUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(notificationData) - }) - - if (!response.ok) { - const errorText = await response.text() - throw new Error(`SWP notification: HTTP ${response.status} - ${errorText}`) - } - - const result = await response.json() - endpointResults['swp_notification'] = result - console.log(`✅ SWP notification sync successful`) - - } catch (error) { - const errorMessage = `SWP notification: ${error instanceof Error ? error.message : 'Unknown error'}` - errors.push(errorMessage) - // 알림은 실패해도 전체 동기화는 성공으로 처리 - console.error(`❌ SWP notification sync failed:`, error) - } - } - - if (!mainUrl) { - throw new Error('No SWP main endpoint configured') - } - - console.log(`SWP sync completed with ${errors.length} errors`) - + // SWP 동기화 로직 구현 + // 현재는 플레이스홀더 return { - success: overallSuccess && errors.length === 0, - successCount: overallSuccess ? changes.length : 0, - failureCount: overallSuccess ? 0 : changes.length, - errors: errors.length > 0 ? errors : undefined, - endpointResults - } - } - - /** - * SWP 시스템용 데이터 변환 - */ - private async transformChangesForSWP(changes: ChangeLog[]): Promise<SyncableEntity[]> { - const syncData: SyncableEntity[] = [] - - for (const change of changes) { - try { - let entityData = null - - // 엔티티 타입별로 현재 데이터 조회 - switch (change.entityType) { - case 'document': - if (change.action !== 'DELETE') { - const [document] = await db - .select() - .from(documents) - .where(eq(documents.id, change.entityId)) - .limit(1) - entityData = document - } - break - - case 'revision': - if (change.action !== 'DELETE') { - const [revision] = await db - .select() - .from(revisions) - .where(eq(revisions.id, change.entityId)) - .limit(1) - entityData = revision - } - break - - case 'attachment': - if (change.action !== 'DELETE') { - const [attachment] = await db - .select() - .from(documentAttachments) - .where(eq(documentAttachments.id, change.entityId)) - .limit(1) - entityData = attachment - } - break - } - - // SWP 특화 데이터 구조 - syncData.push({ - entityType: change.entityType as any, - entityId: change.entityId, - action: change.action as any, - data: entityData || change.oldValues, - metadata: { - changeId: change.id, - changedAt: change.createdAt, - changedBy: change.userName, - changedFields: change.changedFields, - // SWP 전용 메타데이터 - swpFormat: 'legacy', - batchSequence: syncData.length + 1, - needsValidation: change.entityType === 'document', - legacyId: `SWP_${change.entityId}_${Date.now()}` - } - }) - - } catch (error) { - console.error(`Failed to transform change ${change.id} for SWP:`, error) - } + success: true, + successCount: changes.length, + failureCount: 0, + endpointResults: { message: 'SWP sync placeholder' } } - - return syncData - } - - /** - * 간단한 XML 변환 헬퍼 (SWP용) - */ - private convertToXML(data: any): string { - const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>' - const xmlBody = ` - <SyncRequest> - <ContractId>${data.contractId}</ContractId> - <SystemType>${data.systemType}</SystemType> - <BatchSize>${data.batchSize}</BatchSize> - <Timestamp>${data.timestamp}</Timestamp> - <Source>${data.source}</Source> - <Changes> - ${data.changes.map((change: SyncableEntity) => ` - <Change> - <EntityType>${change.entityType}</EntityType> - <EntityId>${change.entityId}</EntityId> - <Action>${change.action}</Action> - <Data>${JSON.stringify(change.data)}</Data> - </Change> - `).join('')} - </Changes> - </SyncRequest>` - - return xmlHeader + xmlBody } /** @@ -638,13 +438,13 @@ class SyncService { /** * 동기화 상태 조회 */ - async getSyncStatus(contractId: number, targetSystem: string = 'DOLCE') { + async getSyncStatus(projectId: number, targetSystem: string = 'DOLCE') { try { // 대기 중인 변경사항 수 조회 const pendingCount = await db.$count( changeLogs, and( - eq(changeLogs.contractId, contractId), + eq(changeLogs.projectId, projectId), eq(changeLogs.isSynced, false), lt(changeLogs.syncAttempts, 3), sql`(${changeLogs.targetSystems} IS NULL OR ${changeLogs.targetSystems} @> ${JSON.stringify([targetSystem])})` @@ -655,7 +455,7 @@ class SyncService { const syncedCount = await db.$count( changeLogs, and( - eq(changeLogs.contractId, contractId), + eq(changeLogs.projectId, projectId), eq(changeLogs.isSynced, true), sql`(${changeLogs.targetSystems} IS NULL OR ${changeLogs.targetSystems} @> ${JSON.stringify([targetSystem])})` ) @@ -665,7 +465,7 @@ class SyncService { const failedCount = await db.$count( changeLogs, and( - eq(changeLogs.contractId, contractId), + eq(changeLogs.projectId, projectId), eq(changeLogs.isSynced, false), sql`${changeLogs.syncAttempts} >= 3`, sql`(${changeLogs.targetSystems} IS NULL OR ${changeLogs.targetSystems} @> ${JSON.stringify([targetSystem])})` @@ -677,7 +477,7 @@ class SyncService { .select() .from(syncBatches) .where(and( - eq(syncBatches.contractId, contractId), + eq(syncBatches.projectId, projectId), eq(syncBatches.targetSystem, targetSystem), eq(syncBatches.status, 'SUCCESS') )) @@ -685,7 +485,7 @@ class SyncService { .limit(1) return { - contractId, + projectId, targetSystem, totalChanges: pendingCount + syncedCount + failedCount, pendingChanges: pendingCount, @@ -703,13 +503,13 @@ class SyncService { /** * 최근 동기화 배치 목록 조회 */ - async getRecentSyncBatches(contractId: number, targetSystem: string = 'DOLCE', limit: number = 10) { + async getRecentSyncBatches(projectId: number, targetSystem: string = 'DOLCE', limit: number = 10) { try { const batches = await db .select() .from(syncBatches) .where(and( - eq(syncBatches.contractId, contractId), + eq(syncBatches.projectId, projectId), eq(syncBatches.targetSystem, targetSystem) )) .orderBy(desc(syncBatches.createdAt)) @@ -718,7 +518,7 @@ class SyncService { // Date 객체를 문자열로 변환 return batches.map(batch => ({ id: Number(batch.id), - contractId: batch.contractId, + projectId: batch.projectId, targetSystem: batch.targetSystem, batchSize: batch.batchSize, status: batch.status, @@ -742,7 +542,7 @@ export const syncService = new SyncService() // 편의 함수들 (기본 타겟 시스템을 DOLCE로 변경) export async function logDocumentChange( - contractId: number, + projectId: number, documentId: number, action: 'CREATE' | 'UPDATE' | 'DELETE', newValues?: any, @@ -751,11 +551,11 @@ export async function logDocumentChange( userName?: string, targetSystems: string[] = ["DOLCE", "SWP"] ) { - return syncService.logChange(contractId, 'document', documentId, action, newValues, oldValues, userId, userName, targetSystems) + return syncService.logChange(projectId, 'document', documentId, action, newValues, oldValues, userId, userName, targetSystems) } export async function logRevisionChange( - contractId: number, + projectId: number, revisionId: number, action: 'CREATE' | 'UPDATE' | 'DELETE', newValues?: any, @@ -764,11 +564,11 @@ export async function logRevisionChange( userName?: string, targetSystems: string[] = ["DOLCE", "SWP"] ) { - return syncService.logChange(contractId, 'revision', revisionId, action, newValues, oldValues, userId, userName, targetSystems) + return syncService.logChange(projectId, 'revision', revisionId, action, newValues, oldValues, userId, userName, targetSystems) } export async function logAttachmentChange( - contractId: number, + projectId: number, attachmentId: number, action: 'CREATE' | 'UPDATE' | 'DELETE', newValues?: any, @@ -777,5 +577,5 @@ export async function logAttachmentChange( userName?: string, targetSystems: string[] = ["DOLCE", "SWP"] ) { - return syncService.logChange(contractId, 'attachment', attachmentId, action, newValues, oldValues, userId, userName, targetSystems) + return syncService.logChange(projectId, 'attachment', attachmentId, action, newValues, oldValues, userId, userName, targetSystems) }
\ No newline at end of file |
