diff options
| -rw-r--r-- | app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts | 197 | ||||
| -rw-r--r-- | lib/soap/ecc/mapper/bidding-and-pr-mapper.ts | 85 | ||||
| -rw-r--r-- | lib/soap/ecc/mapper/rfq-and-pr-mapper.ts | 83 |
3 files changed, 329 insertions, 36 deletions
diff --git a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts index 012f47fc..3cee4b42 100644 --- a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts +++ b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts @@ -1,9 +1,12 @@ import { NextRequest } from 'next/server'; import db from '@/db/db'; +import { eq } from 'drizzle-orm'; import { PR_INFORMATION_T_BID_HEADER, PR_INFORMATION_T_BID_ITEM, } from '@/db/schema/ECC/ecc'; +import { rfqsLast, rfqPrItems } from '@/db/schema/rfqLast'; +import { biddings, prItemsForBidding, prDocuments } from '@/db/schema/bidding'; import { ToXMLFields, serveWsdl, @@ -19,10 +22,12 @@ import { bulkReplaceSubTableData } from "@/lib/soap/batch-utils"; import { - mapAndSaveECCRfqData + mapAndSaveECCRfqData, + deleteECCRfqData } from "@/lib/soap/ecc/mapper/rfq-and-pr-mapper"; import { - mapAndSaveECCBiddingData + mapAndSaveECCBiddingData, + deleteECCBiddingData } from "@/lib/soap/ecc/mapper/bidding-and-pr-mapper"; @@ -94,45 +99,165 @@ export async function POST(request: NextRequest) { } } - // 5) 원본 ECC 데이터 저장 (기존 로직 유지) - await saveToDatabase(processedData); + // 5) CHG_GB에 따라 Create/Delete 분기 처리 + // 전체 처리를 하나의 트랜잭션으로 묶어서 하나라도 실패하면 전부 롤백 + const chgGb = requestData.CHG_GB || 'C'; // 기본값은 Create + + await db.transaction(async (tx) => { + if (chgGb === 'D') { + // Delete 처리 + console.log('삭제 모드로 데이터 처리 시작 (트랜잭션)'); + + // ZBSART에 따라 삭제할 데이터 분류 + const anHeaders: BidHeaderData[] = []; + const abHeaders: BidHeaderData[] = []; + + for (const prData of processedData) { + if (prData.bidHeader.ZBSART === 'AN') { + anHeaders.push(prData.bidHeader); + } else if (prData.bidHeader.ZBSART === 'AB') { + abHeaders.push(prData.bidHeader); + } + } - // 6) ZBSART에 따라 비즈니스 테이블 분기 처리 - const anHeaders: BidHeaderData[] = []; - const abHeaders: BidHeaderData[] = []; - const anItems: BidItemData[] = []; - const abItems: BidItemData[] = []; + let deletedRfqCount = 0; + let deletedBiddingCount = 0; + + // AN (RFQ) 데이터 삭제 - 트랜잭션 내에서 직접 처리 + if (anHeaders.length > 0) { + for (const eccHeader of anHeaders) { + const anfnr = eccHeader.ANFNR; + if (!anfnr) { + console.error('삭제할 ANFNR이 없음', { eccHeader }); + continue; + } + + // 해당 ANFNR의 RFQ 찾기 + const existingRfq = await tx + .select({ id: rfqsLast.id, rfqCode: rfqsLast.rfqCode }) + .from(rfqsLast) + .where(eq(rfqsLast.ANFNR, anfnr)) + .limit(1); + + if (existingRfq.length === 0) { + console.log(`ANFNR ${anfnr}에 해당하는 RFQ가 존재하지 않음`); + continue; + } + + const rfqId = existingRfq[0].id; + const rfqCode = existingRfq[0].rfqCode; + + if (!rfqCode) { + console.error('RFQ 코드가 없는 데이터는 건너뜀', { rfqId }); + continue; + } + + // rfqPrItems 삭제 + await tx + .delete(rfqPrItems) + .where(eq(rfqPrItems.rfqsLastId, rfqId)); + + // rfqsLast 삭제 + await tx + .delete(rfqsLast) + .where(eq(rfqsLast.id, rfqId)); + + deletedRfqCount++; + console.log(`RFQ ${rfqCode} 삭제 완료`); + } + } - // ZBSART에 따라 데이터 분류 - for (const prData of processedData) { - if (prData.bidHeader.ZBSART === 'AN') { - anHeaders.push(prData.bidHeader); - anItems.push(...prData.bidItems); - } else if (prData.bidHeader.ZBSART === 'AB') { - abHeaders.push(prData.bidHeader); - abItems.push(...prData.bidItems); - } - } + // AB (Bidding) 데이터 삭제 - 트랜잭션 내에서 직접 처리 + if (abHeaders.length > 0) { + for (const eccHeader of abHeaders) { + const anfnr = eccHeader.ANFNR; + if (!anfnr) { + console.error('삭제할 ANFNR이 없음', { eccHeader }); + continue; + } + + // 해당 ANFNR의 Bidding 찾기 + const existingBidding = await tx + .select({ id: biddings.id, biddingNumber: biddings.biddingNumber }) + .from(biddings) + .where(eq(biddings.ANFNR, anfnr)) + .limit(1); + + if (existingBidding.length === 0) { + console.log(`ANFNR ${anfnr}에 해당하는 Bidding이 존재하지 않음`); + continue; + } + + const biddingId = existingBidding[0].id; + const biddingNumber = existingBidding[0].biddingNumber; + + // prItemsForBidding 삭제 + await tx + .delete(prItemsForBidding) + .where(eq(prItemsForBidding.biddingId, biddingId)); + + // prDocuments 삭제 + await tx + .delete(prDocuments) + .where(eq(prDocuments.biddingId, biddingId)); + + // biddings 삭제 + await tx + .delete(biddings) + .where(eq(biddings.id, biddingId)); + + deletedBiddingCount++; + console.log(`Bidding ${biddingNumber} 삭제 완료`); + } + } - // AN (RFQ) 데이터 처리 - rfqsLast 테이블 - let rfqMappingResult: { success: boolean; message: string; processedCount: number } | null = null; - if (anHeaders.length > 0) { - rfqMappingResult = await mapAndSaveECCRfqData(anHeaders, anItems); - if (!rfqMappingResult.success) { - throw new Error(`RFQ 비즈니스 테이블 매핑 실패: ${rfqMappingResult.message}`); - } - } + console.log(`🗑️ 삭제 완료 (트랜잭션): ${processedData.length}개 PR 데이터, ${deletedRfqCount}개 RFQ 삭제, ${deletedBiddingCount}개 Bidding 삭제`); + + } else { + // Create/Update 처리 (기존 로직) + console.log('생성/업데이트 모드로 데이터 처리 시작 (트랜잭션)'); + + // 5) 원본 ECC 데이터 저장 (기존 로직 유지) + await saveToDatabase(processedData); + + // 6) ZBSART에 따라 비즈니스 테이블 분기 처리 + const anHeaders: BidHeaderData[] = []; + const abHeaders: BidHeaderData[] = []; + const anItems: BidItemData[] = []; + const abItems: BidItemData[] = []; + + // ZBSART에 따라 데이터 분류 + for (const prData of processedData) { + if (prData.bidHeader.ZBSART === 'AN') { + anHeaders.push(prData.bidHeader); + anItems.push(...prData.bidItems); + } else if (prData.bidHeader.ZBSART === 'AB') { + abHeaders.push(prData.bidHeader); + abItems.push(...prData.bidItems); + } + } - // AB (Bidding) 데이터 처리 - let biddingMappingResult: { success: boolean; message: string; processedCount: number } | null = null; - if (abHeaders.length > 0) { - biddingMappingResult = await mapAndSaveECCBiddingData(abHeaders, abItems); - if (!biddingMappingResult.success) { - throw new Error(`Bidding 비즈니스 테이블 매핑 실패: ${biddingMappingResult.message}`); - } - } + // AN (RFQ) 데이터 처리 - rfqsLast 테이블 + let rfqMappingResult: { success: boolean; message: string; processedCount: number } | null = null; + if (anHeaders.length > 0) { + rfqMappingResult = await mapAndSaveECCRfqData(anHeaders, anItems); + if (!rfqMappingResult.success) { + throw new Error(`RFQ 비즈니스 테이블 매핑 실패: ${rfqMappingResult.message}`); + } + } - console.log(`🎉 처리 완료: ${processedData.length}개 PR 데이터, ${rfqMappingResult?.processedCount || 0}개 RFQ 매핑, ${biddingMappingResult?.processedCount || 0}개 Bidding 매핑`); + // AB (Bidding) 데이터 처리 + let biddingMappingResult: { success: boolean; message: string; processedCount: number } | null = null; + if (abHeaders.length > 0) { + biddingMappingResult = await mapAndSaveECCBiddingData(abHeaders, abItems); + if (!biddingMappingResult.success) { + throw new Error(`Bidding 비즈니스 테이블 매핑 실패: ${biddingMappingResult.message}`); + } + } + + console.log(`🎉 처리 완료 (트랜잭션): ${processedData.length}개 PR 데이터, ${rfqMappingResult?.processedCount || 0}개 RFQ 매핑, ${biddingMappingResult?.processedCount || 0}개 Bidding 매핑`); + } + }); // 6) 성공 응답 반환 return createSoapResponse('http://60.101.108.100/', { diff --git a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts index a02ef9bf..5f3c7e78 100644 --- a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts +++ b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts @@ -624,4 +624,89 @@ export function validateECCBiddingData( isValid: errors.length === 0, errors, }; +} + +/** + * ECC Bidding 데이터를 삭제 (biddings/prItemsForBidding 테이블에서 삭제) + */ +export async function deleteECCBiddingData( + eccHeaders: ECCBidHeader[] +): Promise<{ success: boolean; message: string; deletedCount: number }> { + debugLog('ECC Bidding 데이터 삭제 시작', { + headerCount: eccHeaders.length, + }); + + try { + const result = await db.transaction(async (tx) => { + const deletedBiddings: string[] = []; + + for (const eccHeader of eccHeaders) { + const anfnr = eccHeader.ANFNR; + if (!anfnr) { + debugError('삭제할 ANFNR이 없음', { eccHeader }); + continue; + } + + // 1) 해당 ANFNR의 Bidding 찾기 + const existingBidding = await tx + .select({ id: biddings.id, biddingNumber: biddings.biddingNumber }) + .from(biddings) + .where(eq(biddings.ANFNR, anfnr)) + .limit(1); + + if (existingBidding.length === 0) { + debugLog(`ANFNR ${anfnr}에 해당하는 Bidding이 존재하지 않음`); + continue; + } + + const biddingId = existingBidding[0].id; + const biddingNumber = existingBidding[0].biddingNumber; + + // 2) prItemsForBidding 삭제 + const deletedItems = await tx + .delete(prItemsForBidding) + .where(eq(prItemsForBidding.biddingId, biddingId)); + + debugLog(`Bidding ${biddingNumber}의 PR 아이템 ${deletedItems.rowCount}개 삭제 완료`); + + // 3) prDocuments 삭제 (POS 파일 관련 문서) + const deletedDocuments = await tx + .delete(prDocuments) + .where(eq(prDocuments.biddingId, biddingId)); + + debugLog(`Bidding ${biddingNumber}의 문서 ${deletedDocuments.rowCount}개 삭제 완료`); + + // 4) biddings 삭제 + await tx + .delete(biddings) + .where(eq(biddings.id, biddingId)); + + deletedBiddings.push(biddingNumber); + debugLog(`Bidding ${biddingNumber} 삭제 완료`); + } + + return { deletedBiddings }; + }); + + debugSuccess('ECC Bidding 데이터 삭제 완료', { + deletedCount: result.deletedBiddings.length, + deletedBiddings: result.deletedBiddings, + }); + + return { + success: true, + message: `${result.deletedBiddings.length}개의 Bidding 데이터가 성공적으로 삭제되었습니다.`, + deletedCount: result.deletedBiddings.length, + }; + } catch (error) { + debugError('ECC Bidding 데이터 삭제 중 오류 발생', error); + return { + success: false, + message: + error instanceof Error + ? error.message + : '알 수 없는 오류가 발생했습니다.', + deletedCount: 0, + }; + } }
\ No newline at end of file diff --git a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts index 0ca808ad..9180b4e2 100644 --- a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts +++ b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts @@ -479,3 +479,86 @@ export function validateECCRfqData( errors, }; } + +/** + * ECC RFQ 데이터를 삭제 (rfqsLast/rfqPrItems 테이블에서 삭제) + */ +export async function deleteECCRfqData( + eccHeaders: ECCBidHeader[] +): Promise<{ success: boolean; message: string; deletedCount: number }> { + debugLog('ECC RFQ 데이터 삭제 시작', { + headerCount: eccHeaders.length, + }); + + try { + const result = await db.transaction(async (tx) => { + const deletedRfqs: (string | null)[] = []; + + for (const eccHeader of eccHeaders) { + const anfnr = eccHeader.ANFNR; + if (!anfnr) { + debugError('삭제할 ANFNR이 없음', { eccHeader }); + continue; + } + + // 1) 해당 ANFNR의 RFQ 찾기 + const existingRfq = await tx + .select({ id: rfqsLast.id, rfqCode: rfqsLast.rfqCode }) + .from(rfqsLast) + .where(eq(rfqsLast.ANFNR, anfnr)) + .limit(1); + + if (existingRfq.length === 0) { + debugLog(`ANFNR ${anfnr}에 해당하는 RFQ가 존재하지 않음`); + continue; + } + + const rfqId = existingRfq[0].id; + const rfqCode = existingRfq[0].rfqCode; + + if (!rfqCode) { + debugError('RFQ 코드가 없는 데이터는 건너뜀', { rfqId }); + continue; + } + + // 2) rfqPrItems 삭제 + const deletedItems = await tx + .delete(rfqPrItems) + .where(eq(rfqPrItems.rfqsLastId, rfqId)); + + debugLog(`RFQ ${rfqCode}의 PR 아이템 ${deletedItems.rowCount}개 삭제 완료`); + + // 3) rfqsLast 삭제 + await tx + .delete(rfqsLast) + .where(eq(rfqsLast.id, rfqId)); + + deletedRfqs.push(rfqCode); + debugLog(`RFQ ${rfqCode} 삭제 완료`); + } + + return { deletedRfqs }; + }); + + debugSuccess('ECC RFQ 데이터 삭제 완료', { + deletedCount: result.deletedRfqs.length, + deletedRfqs: result.deletedRfqs, + }); + + return { + success: true, + message: `${result.deletedRfqs.length}개의 RFQ 데이터가 성공적으로 삭제되었습니다.`, + deletedCount: result.deletedRfqs.length, + }; + } catch (error) { + debugError('ECC RFQ 데이터 삭제 중 오류 발생', error); + return { + success: false, + message: + error instanceof Error + ? error.message + : '알 수 없는 오류가 발생했습니다.', + deletedCount: 0, + }; + } +} |
