From ee52c983423fbc63373ce1dacb041d973da502df Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 24 Sep 2025 07:56:53 +0000 Subject: (최겸) 구매 입찰 발주비율 로직 수정(이시원 프로) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bidding/actions.ts | 52 ++++++++++++++++++++-- lib/bidding/detail/service.ts | 30 ++++++++++++- .../detail/table/bidding-invitation-dialog.tsx | 2 +- lib/bidding/service.ts | 2 +- 4 files changed, 80 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/bidding/actions.ts b/lib/bidding/actions.ts index 3eadaace..df9d0dad 100644 --- a/lib/bidding/actions.ts +++ b/lib/bidding/actions.ts @@ -67,6 +67,17 @@ export async function transmitToContract(biddingId: number, userId: number) { throw new Error("낙찰된 업체가 없습니다.") } + // 일반/매각 입찰의 경우 비율 합계 100% 검증 + const contractType = biddingData.contractType + if (contractType === 'general' || contractType === 'sale') { + const totalRatio = winnerCompaniesData.reduce((sum, company) => + sum + (Number(company.awardRatio) || 0), 0) + + if (totalRatio !== 100) { + throw new Error(`일반/매각 입찰의 경우 비율 합계가 100%여야 합니다. 현재 합계: ${totalRatio}%`) + } + } + for (const winnerCompany of winnerCompaniesData) { // winnerCompany에서 직접 정보 사용 const awardRatio = (Number(winnerCompany.awardRatio) || 100) / 100 @@ -116,7 +127,7 @@ export async function transmitToContract(biddingId: number, userId: number) { name: biddingData.title, vendorId: winnerCompany.companyId, linkedBidNumber: biddingData.biddingNumber, - contractAmount: totalContractAmount || null, // 발주비율 계산된 최종 금액 사용 + contractAmount: totalContractAmount ? totalContractAmount.toString() as any : null, // 발주비율 계산된 최종 금액 사용 startDate: biddingData.contractStartDate || null, endDate: biddingData.contractEndDate || null, currency: biddingData.currency || 'KRW', @@ -154,7 +165,7 @@ export async function transmitToContract(biddingId: number, userId: number) { weightUnit: '', // 중량 단위 제외 contractDeliveryDate: bid.proposedDeliveryDate || null, contractUnitPrice: bid.bidUnitPrice || null, - contractAmount: finalAmount || null, + contractAmount: finalAmount ? finalAmount.toString() as any : null, contractCurrency: bid.currency || biddingData.currency || 'KRW', }) } @@ -221,6 +232,17 @@ export async function transmitToPO(biddingId: number) { throw new Error("낙찰된 업체가 없습니다.") } + // 일반/매각 입찰의 경우 비율 합계 100% 검증 + const contractType = bidding.contractType + if (contractType === 'general' || contractType === 'sale') { + const totalRatio = winnerCompaniesRaw.reduce((sum, company) => + sum + (Number(company.awardRatio) || 0), 0) + + if (totalRatio !== 100) { + throw new Error(`일반/매각 입찰의 경우 비율 합계가 100%여야 합니다. 현재 합계: ${totalRatio}%`) + } + } + // 4. 낙찰된 업체들의 입찰 데이터 조회 (발주비율 적용) type POItem = { prItemId: number @@ -339,7 +361,19 @@ export async function transmitToPO(biddingId: number) { // 낙찰된 업체들의 상세 정보 조회 (발주비율에 따른 계산 포함) export async function getWinnerDetails(biddingId: number) { try { - // 1. 낙찰된 업체들 조회 + // 1. 입찰 정보 조회 (contractType 포함) + const biddingInfo = await db.select({ + contractType: biddings.contractType, + }) + .from(biddings) + .where(eq(biddings.id, biddingId)) + .limit(1) + + if (!biddingInfo || biddingInfo.length === 0) { + return { success: false, error: '입찰 정보를 찾을 수 없습니다.' } + } + + // 2. 낙찰된 업체들 조회 const winnerCompanies = await db.select({ id: biddingCompanies.id, companyId: biddingCompanies.companyId, @@ -347,6 +381,7 @@ export async function getWinnerDetails(biddingId: number) { awardRatio: biddingCompanies.awardRatio, vendorName: vendors.vendorName, vendorCode: vendors.vendorCode, + contractType: biddingInfo[0].contractType, }) .from(biddingCompanies) .leftJoin(vendors, eq(biddingCompanies.companyId, vendors.id)) @@ -361,6 +396,17 @@ export async function getWinnerDetails(biddingId: number) { return { success: false, error: '낙찰된 업체가 없습니다.' } } + // 일반/매각 입찰의 경우 비율 합계 100% 검증 + const contractType = biddingInfo[0].contractType + if (contractType === 'general' || contractType === 'sale') { + const totalRatio = winnerCompanies.reduce((sum, company) => + sum + (Number(company.awardRatio) || 0), 0) + + if (totalRatio !== 100) { + return { success: false, error: `일반/매각 입찰의 경우 비율 합계가 100%여야 합니다. 현재 합계: ${totalRatio}%` } + } + } + // 2. 각 낙찰 업체의 입찰 품목 정보 조회 const winnerDetails = [] diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts index 9fb3d87f..645ebeac 100644 --- a/lib/bidding/detail/service.ts +++ b/lib/bidding/detail/service.ts @@ -1158,6 +1158,23 @@ export async function deleteAwardDocument(documentId: number, biddingId: number, export async function awardBidding(biddingId: number, selectionReason: string, userId: string) { try { const userName = await getUserNameById(userId) + + // 입찰 정보 조회 (contractType 포함) + const biddingInfo = await db + .select({ + contractType: biddings.contractType, + status: biddings.status + }) + .from(biddings) + .where(eq(biddings.id, biddingId)) + .limit(1) + + if (biddingInfo.length === 0) { + return { success: false, error: '입찰 정보를 찾을 수 없습니다.' } + } + + const bidding = biddingInfo[0] + // 낙찰된 업체들 조회 (isWinner가 true인 업체들) const awardedCompanies = await db .select({ @@ -1170,11 +1187,22 @@ export async function awardBidding(biddingId: number, selectionReason: string, u eq(biddingCompanies.biddingId, biddingId), eq(biddingCompanies.isWinner, true) )) - + if (awardedCompanies.length === 0) { return { success: false, error: '낙찰된 업체가 없습니다. 먼저 발주비율을 산정해주세요.' } } + // 일반/매각 입찰의 경우 비율 합계 100% 검증 + const contractType = bidding.contractType + if (contractType === 'general' || contractType === 'sale') { + const totalRatio = awardedCompanies.reduce((sum, company) => + sum + (Number(company.awardRatio) || 0), 0) + + if (totalRatio !== 100) { + return { success: false, error: `일반/매각 입찰의 경우 비율 합계가 100%여야 합니다. 현재 합계: ${totalRatio}%` } + } + } + // 최종입찰가 계산 (낙찰된 업체의 견적금액 * 발주비율의 합) let finalBidPrice = 0 for (const company of awardedCompanies) { diff --git a/lib/bidding/detail/table/bidding-invitation-dialog.tsx b/lib/bidding/detail/table/bidding-invitation-dialog.tsx index 48b235f9..cd79850a 100644 --- a/lib/bidding/detail/table/bidding-invitation-dialog.tsx +++ b/lib/bidding/detail/table/bidding-invitation-dialog.tsx @@ -666,7 +666,7 @@ export function BiddingInvitationDialog({ {isGeneratingPdfs ? ( <> - 계약서 생성중... ({Math.round(pdfGenerationProgress)}%) + 입찰 초대중... ({Math.round(pdfGenerationProgress)}%) ) : isPending ? ( <> diff --git a/lib/bidding/service.ts b/lib/bidding/service.ts index 89e4f80f..68efe165 100644 --- a/lib/bidding/service.ts +++ b/lib/bidding/service.ts @@ -420,7 +420,7 @@ export interface UpdateBiddingInput extends UpdateBiddingSchema { } // 자동 입찰번호 생성 -async function generateBiddingNumber( +export async function generateBiddingNumber( userId?: string, tx?: any, maxRetries: number = 5 -- cgit v1.2.3