diff options
Diffstat (limited to 'lib/bidding/handlers.ts')
| -rw-r--r-- | lib/bidding/handlers.ts | 86 |
1 files changed, 75 insertions, 11 deletions
diff --git a/lib/bidding/handlers.ts b/lib/bidding/handlers.ts index a5cc72ae..760d7900 100644 --- a/lib/bidding/handlers.ts +++ b/lib/bidding/handlers.ts @@ -418,7 +418,7 @@ export async function requestBiddingClosureInternal(payload: { await db .update(biddings) .set({ - status: 'closed', + status: 'bid_closure', updatedBy: payload.currentUserId.toString(), updatedAt: new Date(), remarks: payload.description, // 폐찰 사유를 remarks에 저장 @@ -490,6 +490,12 @@ export async function requestBiddingClosureInternal(payload: { export async function requestBiddingAwardInternal(payload: { biddingId: number; selectionReason: string; + awardedCompanies: Array<{ + companyId: number; + companyName: string | null; + finalQuoteAmount: number; + awardRatio: number; + }>; // ✅ 결재 상신 시점의 낙찰 대상 정보 currentUserId: number; // ✅ 결재 상신한 사용자 ID }) { debugLog('[BiddingAwardHandler] 낙찰 핸들러 시작', { @@ -506,25 +512,83 @@ export async function requestBiddingAwardInternal(payload: { } try { - // 기존 awardBidding 함수 로직을 재구성하여 실행 - const { awardBidding } = await import('@/lib/bidding/detail/service'); - - const result = await awardBidding(payload.biddingId, payload.selectionReason, payload.currentUserId.toString()); + // ✅ 결재 상신 시점의 낙찰 대상 정보를 직접 사용하여 처리 + const { default: db } = await import('@/db/db'); + const { biddings, vendorSelectionResults } = await import('@/db/schema'); + const { eq } = await import('drizzle-orm'); - if (!result.success) { - debugError('[BiddingAwardHandler] 낙찰 처리 실패', result.error); - throw new Error(result.error || '낙찰 처리에 실패했습니다.'); + // 1. 최종입찰가 계산 (낙찰된 업체의 견적금액 * 발주비율의 합) + let finalBidPrice = 0; + for (const company of payload.awardedCompanies) { + finalBidPrice += company.finalQuoteAmount * (company.awardRatio / 100); } + await db.transaction(async (tx) => { + // 1. 입찰 상태를 낙찰로 변경하고 최종입찰가 업데이트 + await tx + .update(biddings) + .set({ + status: 'vendor_selected', + finalBidPrice: finalBidPrice.toString(), + updatedAt: new Date() + }) + .where(eq(biddings.id, payload.biddingId)); + + // 2. 선정 사유 저장 (첫 번째 낙찰 업체 기준으로 저장) + const firstAwardedCompany = payload.awardedCompanies[0]; + + // 기존 선정 결과 확인 + const existingResult = await tx + .select() + .from(vendorSelectionResults) + .where(eq(vendorSelectionResults.biddingId, payload.biddingId)) + .limit(1); + + if (existingResult.length > 0) { + // 업데이트 + await tx + .update(vendorSelectionResults) + .set({ + selectedCompanyId: firstAwardedCompany.companyId, + selectionReason: payload.selectionReason, + selectedBy: payload.currentUserId.toString(), + selectedAt: new Date(), + updatedAt: new Date() + }) + .where(eq(vendorSelectionResults.biddingId, payload.biddingId)); + } else { + // 삽입 + await tx + .insert(vendorSelectionResults) + .values({ + biddingId: payload.biddingId, + selectedCompanyId: firstAwardedCompany.companyId, + selectionReason: payload.selectionReason, + selectedBy: payload.currentUserId.toString(), + selectedAt: new Date(), + createdAt: new Date(), + updatedAt: new Date() + }); + } + }); + + // 캐시 무효화 (API를 통한 방식) + const { revalidateViaCronJob } = await import('@/lib/revalidation-utils'); + await revalidateViaCronJob({ + tags: [`bidding-${payload.biddingId}`, 'quotation-vendors', 'quotation-details'] + }); + debugSuccess('[BiddingAwardHandler] 낙찰 완료', { biddingId: payload.biddingId, selectionReason: payload.selectionReason, + awardedCompaniesCount: payload.awardedCompanies.length, + finalBidPrice, }); return { success: true, biddingId: payload.biddingId, - message: `입찰이 낙찰 처리되었습니다.`, + message: `입찰이 낙찰 처리되었습니다. 최종입찰가: ${finalBidPrice.toLocaleString()}원`, }; } catch (error) { debugError('[BiddingAwardHandler] 낙찰 중 에러', error); @@ -562,7 +626,7 @@ export async function mapBiddingAwardToTemplateVariables(payload: { bidPicName: biddings.bidPicName, supplyPicName: biddings.supplyPicName, targetPrice: biddings.targetPrice, - winnerCount: biddings.winnerCount, + awardCount: biddings.awardCount, }) .from(biddings) .where(eq(biddings.id, biddingId)) @@ -606,7 +670,7 @@ export async function mapBiddingAwardToTemplateVariables(payload: { const title = bidding.title || '낙찰'; const biddingTitle = bidding.title || ''; const biddingNumber = bidding.biddingNumber || ''; - const winnerCount = (bidding.winnerCount || 1).toString(); + const winnerCount = (bidding.awardCount === 'single' ? 1 : bidding.awardCount === 'multiple' ? 2 : 1).toString(); const contractType = bidding.biddingType || ''; const budget = bidding.targetPrice ? bidding.targetPrice.toLocaleString() : ''; const targetPrice = bidding.targetPrice ? bidding.targetPrice.toLocaleString() : ''; |
