summaryrefslogtreecommitdiff
path: root/lib/bidding/handlers.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/handlers.ts')
-rw-r--r--lib/bidding/handlers.ts86
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() : '';