'use server' import { revalidatePath } from "next/cache"; import db from "@/db/db"; import { biddings, biddingCompanies } from "@/db/schema"; import { eq, and, inArray } from "drizzle-orm"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { cancelRFQ } from "@/lib/soap/ecc/send/delete-rfq"; /** * 입찰(Bidding) 삭제 서버 액션 * 조건: * 1. SAP 연동 건(ANFNR 존재)이어야 함 (필수) * 2. 입찰 상태가 '입찰생성(bidding_generated)'이어야 함 * * 동작: * 1. SAP 취소 I/F 전송 (필수) * 2. 성공 시 입찰 상태를 '삭제(deleted)'로 변경 * 3. 연관된 데이터(업체 응답 등) 상태 변경 */ export async function deleteBidding(biddingIds: number[], deleteReason?: string): Promise<{ success: boolean; message: string; results?: Array<{ biddingId: number; success: boolean; error?: string }>; }> { try { const session = await getServerSession(authOptions); if (!session?.user?.id) { return { success: false, message: "인증이 필요합니다." }; } const userName = session.user.name || session.user.email || "Unknown"; // 1. Bidding 정보 조회 const targets = await db.select({ id: biddings.id, biddingNumber: biddings.biddingNumber, ANFNR: biddings.ANFNR, status: biddings.status, title: biddings.title }) .from(biddings) .where(inArray(biddings.id, biddingIds)); // 2. 유효성 검증 // 조건 1: ANFNR이 있어야 함 // 조건 2: 상태가 'bidding_generated'(입찰생성) 이어야 함 const validTargets = targets.filter(b => b.status === 'bidding_generated' && b.ANFNR && b.ANFNR.trim() !== "" ); if (validTargets.length === 0) { // 실패 사유 분석 const noAnfnr = targets.filter(b => !b.ANFNR || b.ANFNR.trim() === ""); const wrongStatus = targets.filter(b => b.status !== 'bidding_generated'); let errorMsg = "삭제 가능한 입찰이 없습니다."; if (noAnfnr.length > 0) errorMsg += " (SAP 연동 건(ANFNR)이 아님)"; if (wrongStatus.length > 0) errorMsg += " ('입찰생성' 상태가 아님)"; return { success: false, message: errorMsg }; } const results: Array<{ biddingId: number; success: boolean; error?: string }> = []; // 3. 각 Bidding에 대해 처리 for (const bidding of validTargets) { try { // 3-1. SAP 취소 요청 전송 (ANFNR 필수이므로 바로 호출) const cancelResult = await cancelRFQ(bidding.ANFNR!); if (!cancelResult.success) { results.push({ biddingId: bidding.id, success: false, error: `SAP 전송 실패: ${cancelResult.message}` }); continue; } // 3-2. DB 상태 변경 및 연관 데이터 정리 await db.transaction(async (tx) => { // 입찰 상태 변경 await tx .update(biddings) .set({ status: 'deleted', // 삭제 상태 처리 remarks: deleteReason ? `[삭제사유] ${deleteReason}` : `[삭제됨] 사용자에 의한 삭제`, updatedBy: userName, updatedAt: new Date() }) .where(eq(biddings.id, bidding.id)); }); results.push({ biddingId: bidding.id, success: true }); } catch (error) { console.error(`입찰 삭제 실패 (ID: ${bidding.id}, ANFNR: ${bidding.ANFNR}):`, error); results.push({ biddingId: bidding.id, success: false, error: error instanceof Error ? error.message : "알 수 없는 오류" }); } } const successCount = results.filter(r => r.success).length; const failCount = results.length - successCount; // 캐시 갱신 revalidatePath("/evcp/bid"); // 결과 메시지 조합 let message = ""; const invalidStatusCount = targets.filter(b => b.status !== 'bidding_generated').length; const noAnfnrCount = targets.filter(b => !b.ANFNR || b.ANFNR.trim() === "").length; if (invalidStatusCount > 0) { message += `'입찰생성' 상태가 아닌 건(${invalidStatusCount}건) 제외. `; } if (noAnfnrCount > 0) { message += `SAP 미연동 건(${noAnfnrCount}건) 제외. `; } if (failCount === 0 && successCount > 0) { return { success: true, message: message + `입찰 삭제가 완료되었습니다. (${successCount}건)`, results }; } else if (successCount > 0) { return { success: false, // 부분 성공 message: message + `일부 삭제 실패 (성공: ${successCount}건, 실패: ${failCount}건)`, results }; } else { return { success: false, message: message + (failCount > 0 ? "삭제 처리에 실패했습니다." : "처리할 대상이 없습니다."), results }; } } catch (error) { console.error("입찰 삭제 처리 중 오류:", error); return { success: false, message: error instanceof Error ? error.message : "입찰 삭제 처리 중 오류가 발생했습니다." }; } }