summaryrefslogtreecommitdiff
path: root/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/soap/ecc/mapper/bidding-and-pr-mapper.ts')
-rw-r--r--lib/soap/ecc/mapper/bidding-and-pr-mapper.ts178
1 files changed, 176 insertions, 2 deletions
diff --git a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts
index 4db8d451..99373555 100644
--- a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts
+++ b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts
@@ -9,6 +9,7 @@ import db from '@/db/db';
import {
biddings,
prItemsForBidding,
+ prDocuments,
} from '@/db/schema/bidding';
import {
PR_INFORMATION_T_BID_HEADER,
@@ -22,6 +23,11 @@ import {
parseSAPDateTime,
parseSAPDateToString,
} from './common-mapper-utils';
+import {
+ getDcmtmIdByMaterialCode,
+ getEncryptDocumentumFile,
+ downloadPosFile
+} from '@/lib/pos';
// ECC 데이터 타입 정의
export type ECCBidHeader = typeof PR_INFORMATION_T_BID_HEADER.$inferInsert;
@@ -31,6 +37,117 @@ 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
+ };
+}
+
/**
* Bidding 코드 생성 함수 (배치 처리용)
@@ -333,7 +450,12 @@ export async function mapAndSaveECCBiddingData(
const inserted = await tx
.insert(biddings)
.values(biddingRecords)
- .returning({ id: biddings.id, biddingNumber: biddings.biddingNumber });
+ .returning({
+ id: biddings.id,
+ biddingNumber: biddings.biddingNumber,
+ ANFNR: biddings.ANFNR,
+ createdBy: biddings.createdBy
+ });
const biddingNumberToId = new Map<string, number>();
for (const row of inserted) {
@@ -368,13 +490,65 @@ export async function mapAndSaveECCBiddingData(
await tx.insert(prItemsForBidding).values(chunk);
}
- return { processedCount: biddingRecords.length };
+ return {
+ processedCount: biddingRecords.length,
+ insertedBiddings: inserted as Array<{ id: number; biddingNumber: string; ANFNR: string | null; createdBy: string | null }>,
+ allEccItems: eccItems // POS 동기화를 위해 필요
+ };
});
debugSuccess('ECC Bidding 데이터 일괄 처리 완료', {
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 : '알 수 없는 오류'
+ });
+ }
+ });
+
return {
success: true,
message: `${result.processedCount}개의 Bidding 데이터가 성공적으로 처리되었습니다.`,