diff options
| author | joonhoekim <26rote@gmail.com> | 2025-07-21 05:56:55 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-07-21 05:56:55 +0000 |
| commit | 8f19c063aeb3df1eed9cab58f4bf7cac22ab13dc (patch) | |
| tree | f7c8d17cec162e37f74c3f9ecca71c1fc7a55364 /app/api/(S-ERP) | |
| parent | 89de030f3fc74f4d665517ad62141328d1702e5a (diff) | |
ECC PO_INFORMATION 수신 라우트 구성 및 응답 메시지를 위한 유틸리티 함수 확장
Diffstat (limited to 'app/api/(S-ERP)')
| -rw-r--r-- | app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts | 199 |
1 files changed, 199 insertions, 0 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 new file mode 100644 index 00000000..3b7636f9 --- /dev/null +++ b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts @@ -0,0 +1,199 @@ +import { NextRequest } from 'next/server'; +import db from '@/db/db'; + +import { + ToXMLFields, + serveWsdl, + createXMLParser, + extractRequestData, + convertXMLToDBData, + processNestedArray, + createErrorResponse, + createSuccessResponse, + createSoapResponse, + replaceSubTableData, + withSoapLogging, +} from '@/lib/soap/mdg/utils'; + +import { + PR_INFORMATION_T_BID_HEADER, + PR_INFORMATION_T_BID_ITEM, +} from '@/db/schema/ECC/ecc'; + +// 스키마에서 타입 추론 +type BidHeaderData = typeof PR_INFORMATION_T_BID_HEADER.$inferInsert; +type BidItemData = typeof PR_INFORMATION_T_BID_ITEM.$inferInsert; + +// XML 구조 타입 정의 +type BidHeaderXML = ToXMLFields<Omit<BidHeaderData, 'id' | 'createdAt' | 'updatedAt'>>; +type BidItemXML = ToXMLFields<Omit<BidItemData, 'id' | 'createdAt' | 'updatedAt'>>; + +// 처리된 데이터 구조 +interface ProcessedPRData { + bidHeader: BidHeaderData; + bidItems: BidItemData[]; +} + +export async function GET(request: NextRequest) { + const url = new URL(request.url); + if (url.searchParams.has('wsdl')) { + return serveWsdl('IF_ECC_EVCP_PR_INFORMATION.wsdl'); + } + + return new Response('Method Not Allowed', { status: 405 }); +} + +export async function POST(request: NextRequest) { + const url = new URL(request.url); + if (url.searchParams.has('wsdl')) { + return serveWsdl('IF_ECC_EVCP_PR_INFORMATION.wsdl'); + } + + const body = await request.text(); + + // SOAP 로깅 래퍼 함수 사용 + return withSoapLogging( + 'INBOUND', + 'S-ERP', + 'IF_ECC_EVCP_PR_INFORMATION', + body, + async () => { + console.log('🚀 PR_INFORMATION 수신 시작, 데이터 길이:', body.length); + + // 1) XML 파싱 + const parser = createXMLParser(['T_BID_HEADER', 'T_BID_ITEM']); + const parsedData = parser.parse(body); + + // 2) SOAP Body 또는 루트에서 요청 데이터 추출 + const requestData = extractRequestData(parsedData, 'IF_ECC_EVCP_PR_INFORMATIONReq'); + if (!requestData) { + console.error('유효한 요청 데이터를 찾을 수 없습니다'); + throw new Error('Missing request data - IF_ECC_EVCP_PR_INFORMATIONReq not found'); + } + + // 3) XML 데이터를 DB 삽입 가능한 형태로 변환 + const processedData = transformPRData(requestData as PRRequestXML); + + // 4) 필수 필드 검증 + for (const prData of processedData) { + if (!prData.bidHeader.ANFNR) { + throw new Error('Missing required field: ANFNR in Bid Header'); + } + for (const item of prData.bidItems) { + if (!item.ANFNR || !item.ANFPS) { + throw new Error('Missing required fields in Bid Item: ANFNR, ANFPS'); + } + } + } + + // 5) 데이터베이스 저장 + await saveToDatabase(processedData); + + console.log(`🎉 처리 완료: ${processedData.length}개 PR 데이터`); + + // 6) 성공 응답 반환 + return createSoapResponse('http://60.101.108.100/', { + 'tns:IF_ECC_EVCP_PR_INFORMATIONRes': { + EV_TYPE: 'S', + }, + }); + } + ).catch((error) => { + // withSoapLogging에서 이미 에러 로그를 처리하므로, 여기서는 응답만 생성 + return createSoapResponse('http://60.101.108.100/', { + 'tns:IF_ECC_EVCP_PR_INFORMATIONRes': { + EV_TYPE: 'E', + EV_MESSAGE: + error instanceof Error ? error.message.slice(0, 100) : 'Unknown error', + }, + }); + }); +} + +// ----------------------------------------------------------------------------- +// 데이터 변환 및 저장 관련 유틸리티 +// ----------------------------------------------------------------------------- + +// Root XML Request 타입 +type PRRequestXML = { + CHG_GB?: string; + T_BID_HEADER?: BidHeaderXML[]; + T_BID_ITEM?: BidItemXML[]; +}; + +// XML -> DB 데이터 변환 함수 +function transformPRData(requestData: PRRequestXML): ProcessedPRData[] { + const headers = requestData.T_BID_HEADER || []; + const items = requestData.T_BID_ITEM || []; + + return headers.map((header) => { + const headerKey = header.ANFNR || ''; + const fkData = { ANFNR: headerKey }; + + // Header 변환 + const bidHeaderConverted = convertXMLToDBData<BidHeaderData>( + header as Record<string, string | undefined>, + undefined // Header는 자체 필드만 사용 + ); + + // 해당 Header의 Item들 필터 후 변환 + const relatedItems = items.filter((item) => item.ANFNR === headerKey); + + const bidItemsConverted = processNestedArray( + relatedItems, + (item) => + convertXMLToDBData<BidItemData>(item as Record<string, string | undefined>, fkData), + fkData + ); + + return { + bidHeader: bidHeaderConverted, + bidItems: bidItemsConverted, + }; + }); +} + +// 데이터베이스 저장 함수 +async function saveToDatabase(processedPRs: ProcessedPRData[]) { + console.log(`데이터베이스 저장 시작: ${processedPRs.length}개 PR 데이터`); + + try { + await db.transaction(async (tx) => { + for (const prData of processedPRs) { + const { bidHeader, bidItems } = prData; + + if (!bidHeader.ANFNR) { + console.warn('ANFNR가 없는 헤더 발견, 건너뜁니다.'); + continue; + } + + // 1. 헤더 테이블 Upsert (ANFNR 기준) + await tx + .insert(PR_INFORMATION_T_BID_HEADER) + .values(bidHeader) + .onConflictDoUpdate({ + target: PR_INFORMATION_T_BID_HEADER.ANFNR, + set: { + ...bidHeader, + updatedAt: new Date(), + }, + }); + + // 2. 아이템 테이블 전체 교체 (ANFNR FK 기준) + await replaceSubTableData( + tx, + PR_INFORMATION_T_BID_ITEM, + bidItems, + 'ANFNR', + bidHeader.ANFNR + ); + } + }); + + console.log(`데이터베이스 저장 완료: ${processedPRs.length}개 PR`); + return true; + } catch (error) { + console.error('데이터베이스 저장 중 오류 발생:', error); + throw error; + } +}
\ No newline at end of file |
