From 284f9f40d9494168f3e68eedd9af067c38362eea Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Thu, 30 Oct 2025 10:35:26 +0900 Subject: (김준회) refactor: POS: 온디맨드로 다운로드받도록 변경, 매핑로직에선 pos 관련 로직 제거 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/soap/ecc/mapper/bidding-and-pr-mapper.ts | 180 +++------------------------ lib/soap/ecc/mapper/rfq-and-pr-mapper.ts | 42 ++----- 2 files changed, 25 insertions(+), 197 deletions(-) (limited to 'lib/soap/ecc') diff --git a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts index 5f3c7e78..af4cecdc 100644 --- a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts +++ b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts @@ -1,7 +1,13 @@ /** - * pr 발행 후, pr을 묶어서 rfq, bidding 을 sap ecc에서 생성한 경우 - * ZBSART = AB인 경우, 즉 bidding인 경우 해당 케이스를 soap으로 수신한 뒤 이 함수에서 헤더는 biddings 테이블에, 아이템은 prItemsForBidding 테이블에 매핑 - * ZBSART = AN인 경우, 즉 rfq인 경우 해당 케이스를 soap으로 수신한 뒤 rfq-and-pr-mapper.ts 파일에서 매핑 + * SAP ECC에서 PR을 묶어 Bidding을 생성한 경우 SOAP으로 수신하여 처리하는 매퍼 + * + * ZBSART = AB (Bidding)인 경우 이 파일에서 처리하여 biddings, prItemsForBidding 테이블에 매핑합니다. + * ZBSART = AN (RFQ)인 경우는 rfq-and-pr-mapper.ts 파일에서 매핑합니다. + * + * 주요 변경사항: + * - POS 파일 자동 다운로드 로직 제거됨 (온디맨드 방식으로 변경) + * - syncBiddingPosFiles 함수 제거됨 + * - 사용자가 필요할 때만 POS 파일을 다운로드하도록 개선 */ import { debugLog, debugSuccess, debugError } from '@/lib/debug-utils'; @@ -23,11 +29,8 @@ import { parseSAPDateTime, parseSAPDateToString, } from './common-mapper-utils'; -import { - getDcmtmIdByMaterialCode, - getEncryptDocumentumFile, - downloadPosFile -} from '@/lib/pos'; +// Note: POS 파일은 온디맨드 방식으로 다운로드됩니다. +// 자동 동기화 관련 import는 제거되었습니다. // ECC 데이터 타입 정의 export type ECCBidHeader = typeof PR_INFORMATION_T_BID_HEADER.$inferInsert; @@ -37,116 +40,8 @@ export type ECCBidItem = typeof PR_INFORMATION_T_BID_ITEM.$inferInsert; export type BiddingData = typeof biddings.$inferInsert; export type PrItemForBiddingData = typeof prItemsForBidding.$inferInsert; -/** - * Bidding용 POS 파일 동기화 함수 - * 자재코드 기준으로 POS 파일을 찾아서 prDocuments 테이블에 저장 - */ -async function syncBiddingPosFiles( - biddingId: number, - materialCodes: string[], - userId: string = '1' -): Promise<{ - success: boolean; - successCount: number; - failedCount: number; - errors: string[]; -}> { - debugLog('Bidding POS 파일 동기화 시작', { biddingId, materialCodes }); - - let successCount = 0; - let failedCount = 0; - const errors: string[] = []; - - // 중복 제거된 자재코드로 처리 - const uniqueMaterialCodes = [...new Set(materialCodes.filter(code => code && code.trim() !== ''))]; - - for (const materialCode of uniqueMaterialCodes) { - try { - debugLog(`자재코드 ${materialCode} POS 파일 조회 시작`); - - // 1. 자재코드로 DCMTM_ID 조회 - const dcmtmResult = await getDcmtmIdByMaterialCode({ materialCode }); - - if (!dcmtmResult.success || !dcmtmResult.files || dcmtmResult.files.length === 0) { - debugLog(`자재코드 ${materialCode}: POS 파일 없음`); - continue; // 에러로 카운트하지 않고 스킵 - } - - // 첫 번째 파일만 처리 - const posFile = dcmtmResult.files[0]; - - // 2. POS API로 파일 경로 가져오기 - const posResult = await getEncryptDocumentumFile({ - objectID: posFile.dcmtmId - }); - - if (!posResult.success || !posResult.result) { - errors.push(`${materialCode}: POS 파일 경로 조회 실패`); - failedCount++; - continue; - } - - // 3. 파일 다운로드 - const downloadResult = await downloadPosFile({ - relativePath: posResult.result - }); - - if (!downloadResult.success || !downloadResult.fileBuffer) { - errors.push(`${materialCode}: 파일 다운로드 실패`); - failedCount++; - continue; - } - - // 4. 서버에 파일 저장 (uploads/bidding-pos 디렉토리) - const path = await import('path'); - const fs = await import('fs/promises'); - - const uploadDir = path.join(process.cwd(), 'uploads', 'bidding-pos'); - try { - await fs.access(uploadDir); - } catch { - await fs.mkdir(uploadDir, { recursive: true }); - } - - const timestamp = Date.now(); - const sanitizedFileName = (downloadResult.fileName || `${materialCode}.pdf`).replace(/[^a-zA-Z0-9.-]/g, '_'); - const fileName = `${timestamp}_${sanitizedFileName}`; - const filePath = path.join(uploadDir, fileName); - - await fs.writeFile(filePath, downloadResult.fileBuffer); - - // 5. prDocuments 테이블에 저장 - await db.insert(prDocuments).values({ - biddingId, - documentName: `${materialCode} 설계문서`, - fileName, - originalFileName: posFile.fileName, - fileSize: downloadResult.fileBuffer.length, - mimeType: downloadResult.mimeType || 'application/pdf', - filePath: `uploads/bidding-pos/${fileName}`, - registeredBy: userId, - description: `POS 시스템에서 자동 동기화됨 (DCMTM_ID: ${posFile.dcmtmId}, 자재코드: ${materialCode})`, - version: 'Rev.0' - }); - - successCount++; - debugSuccess(`자재코드 ${materialCode} POS 파일 동기화 완료`); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류'; - errors.push(`${materialCode}: ${errorMessage}`); - failedCount++; - debugError(`자재코드 ${materialCode} POS 파일 동기화 실패`, error); - } - } - - return { - success: successCount > 0, - successCount, - failedCount, - errors - }; -} +// Note: syncBiddingPosFiles 함수는 제거되었습니다. +// POS 파일은 온디맨드 방식으로 사용자가 필요할 때 다운로드됩니다. /** @@ -501,53 +396,8 @@ export async function mapAndSaveECCBiddingData( processedCount: result.processedCount, }); - // 7) 각 Bidding에 대해 POS 파일 자동 동기화 (비동기로 실행하여 메인 플로우 블록하지 않음) - debugLog('Bidding POS 파일 자동 동기화 시작', { biddingCount: result.insertedBiddings.length }); - - // 비동기로 각 Bidding의 POS 파일 동기화 실행 (결과를 기다리지 않음) - result.insertedBiddings.forEach(async (bidding) => { - try { - // 해당 Bidding과 관련된 모든 자재코드 추출 - const relatedMaterialCodes = result.allEccItems - .filter(item => item.ANFNR === bidding.ANFNR) - .map(item => item.MATNR) - .filter(Boolean) as string[]; - - if (relatedMaterialCodes.length === 0) { - debugLog(`Bidding ${bidding.biddingNumber}: 자재코드 없음`); - return; - } - - debugLog(`Bidding ${bidding.biddingNumber} POS 파일 동기화 시작`, { - biddingId: bidding.id, - materialCodes: relatedMaterialCodes - }); - - const syncResult = await syncBiddingPosFiles( - bidding.id, - relatedMaterialCodes, - bidding.createdBy || '1' - ); - - if (syncResult.success) { - debugSuccess(`Bidding ${bidding.biddingNumber} POS 파일 동기화 완료`, { - biddingId: bidding.id, - successCount: syncResult.successCount, - failedCount: syncResult.failedCount - }); - } else { - debugError(`Bidding ${bidding.biddingNumber} POS 파일 동기화 실패`, { - biddingId: bidding.id, - errors: syncResult.errors - }); - } - } catch (error) { - debugError(`Bidding ${bidding.biddingNumber} POS 파일 동기화 중 예외 발생`, { - biddingId: bidding.id, - error: error instanceof Error ? error.message : '알 수 없는 오류' - }); - } - }); + // Note: POS 파일은 온디맨드 방식으로 사용자가 필요할 때 다운로드됩니다. + // 자동 다운로드는 더 이상 수행하지 않습니다. return { success: true, diff --git a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts index f2683213..cc241aa6 100644 --- a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts +++ b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts @@ -1,7 +1,12 @@ /** - * pr 발행 후, pr을 묶어서 rfq, bidding 을 sap ecc에서 생성한 경우 - * ZBSART = AN인 경우, 즉 rfq인 경우 해당 케이스를 soap으로 수신한 뒤 이 함수에서 rqfLast, rfqPrItems 테이블에 매핑 - * bidding인 경우는 bidding-and-pr-mapper.ts 파일에서 매핑 + * SAP ECC에서 PR을 묶어 RFQ를 생성한 경우 SOAP으로 수신하여 처리하는 매퍼 + * + * ZBSART = AN (RFQ)인 경우 이 파일에서 처리하여 rfqsLast, rfqPrItems 테이블에 매핑합니다. + * ZBSART = AB (Bidding)인 경우는 bidding-and-pr-mapper.ts 파일에서 매핑합니다. + * + * 주요 변경사항: + * - POS 파일 자동 다운로드 로직 제거됨 (온디맨드 방식으로 변경) + * - 사용자가 필요할 때만 POS 파일을 다운로드하도록 개선 */ import { debugLog, debugSuccess, debugError } from '@/lib/debug-utils'; @@ -20,7 +25,6 @@ import { findProjectInfoByPSPID, parseSAPDateTime, } from './common-mapper-utils'; -import { syncRfqPosFiles } from '@/lib/pos'; // ECC 데이터 타입 정의 export type ECCBidHeader = typeof PR_INFORMATION_T_BID_HEADER.$inferInsert; @@ -374,34 +378,8 @@ export async function mapAndSaveECCRfqDataToRfqLast( processedCount: result.processedCount, }); - // 6) 각 RFQ에 대해 POS 파일 자동 동기화 (비동기로 실행하여 메인 플로우 블록하지 않음) - debugLog('RFQ POS 파일 자동 동기화 시작', { rfqCount: result.insertedRfqs.length }); - - // 비동기로 각 RFQ의 POS 파일 동기화 실행 (결과를 기다리지 않음) - result.insertedRfqs.forEach(async (rfq) => { - try { - debugLog(`RFQ ${rfq.rfqCode} POS 파일 동기화 시작`, { rfqId: rfq.id }); - const syncResult = await syncRfqPosFiles(rfq.id, 1); // 시스템 사용자 ID = 1 - - if (syncResult.success) { - debugSuccess(`RFQ ${rfq.rfqCode} POS 파일 동기화 완료`, { - rfqId: rfq.id, - successCount: syncResult.successCount, - failedCount: syncResult.failedCount - }); - } else { - debugError(`RFQ ${rfq.rfqCode} POS 파일 동기화 실패`, { - rfqId: rfq.id, - errors: syncResult.errors - }); - } - } catch (error) { - debugError(`RFQ ${rfq.rfqCode} POS 파일 동기화 중 예외 발생`, { - rfqId: rfq.id, - error: error instanceof Error ? error.message : '알 수 없는 오류' - }); - } - }); + // Note: POS 파일은 온디맨드 방식으로 사용자가 필요할 때 다운로드됩니다. + // 자동 다운로드는 더 이상 수행하지 않습니다. return { success: true, -- cgit v1.2.3