From 835df8ddc115ffa74414db2a4fab7efc0d0056a9 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 25 Nov 2025 11:51:27 +0000 Subject: (최겸) 구매 입찰 수정v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bidding/approval-actions.ts | 67 +- lib/bidding/detail/service.ts | 8 +- .../detail/table/bidding-detail-vendor-columns.tsx | 8 +- lib/bidding/failure/biddings-closure-dialog.tsx | 53 +- lib/bidding/failure/biddings-failure-table.tsx | 131 +- lib/bidding/handlers.ts | 130 +- .../list/biddings-table-toolbar-actions.tsx | 1 - lib/bidding/list/create-bidding-dialog.tsx | 2148 -------------------- lib/bidding/receive/biddings-receive-table.tsx | 4 +- .../vendor/components/pr-items-pricing-table.tsx | 8 +- lib/bidding/vendor/partners-bidding-detail.tsx | 62 +- 11 files changed, 249 insertions(+), 2371 deletions(-) delete mode 100644 lib/bidding/list/create-bidding-dialog.tsx (limited to 'lib') diff --git a/lib/bidding/approval-actions.ts b/lib/bidding/approval-actions.ts index 9a37d83b..06f5c206 100644 --- a/lib/bidding/approval-actions.ts +++ b/lib/bidding/approval-actions.ts @@ -188,7 +188,7 @@ export async function requestBiddingInvitationWithApproval(data: { .update(biddings) .set({ status: 'approval_pending', // 결재 진행중 상태 - updatedBy: data.currentUser.epId, + updatedBy: Number(data.currentUser.epId), updatedAt: new Date() }) .where(eq(biddings.id, data.biddingId)); @@ -269,13 +269,20 @@ export async function prepareBiddingClosureApprovalData(data: { // 1. 입찰 정보 조회 (템플릿 변수용) debugLog('[BiddingClosureApproval] 입찰 정보 조회 시작'); const { default: db } = await import('@/db/db'); - const { biddings } = await import('@/db/schema'); + const { biddings, prItemsForBidding, biddingCompanies, vendors } = await import('@/db/schema'); const { eq } = await import('drizzle-orm'); const biddingInfo = await db .select({ id: biddings.id, title: biddings.title, + biddingNumber: biddings.biddingNumber, + projectName: biddings.projectName, + itemName: biddings.itemName, + biddingType: biddings.biddingType, + bidPicName: biddings.bidPicName, + supplyPicName: biddings.supplyPicName, + targetPrice: biddings.targetPrice, }) .from(biddings) .where(eq(biddings.id, data.biddingId)) @@ -286,9 +293,41 @@ export async function prepareBiddingClosureApprovalData(data: { throw new Error('입찰 정보를 찾을 수 없습니다'); } + const bidding = biddingInfo[0]; + + // 입찰 대상 자재 정보 조회 + const biddingItemsInfo = await db + .select({ + id: prItemsForBidding.id, + materialCode: prItemsForBidding.materialNumber, + materialCodeName: prItemsForBidding.materialInfo, + quantity: prItemsForBidding.quantity, + quantityUnit: prItemsForBidding.quantityUnit, + targetUnitPrice: prItemsForBidding.targetUnitPrice, + currency: prItemsForBidding.targetCurrency, + }) + .from(prItemsForBidding) + .where(eq(prItemsForBidding.biddingId, data.biddingId)); + + // 입찰 참여 업체 정보 조회 + const vendorSubmissions = await db + .select({ + vendorId: vendors.id, + vendorName: vendors.vendorName, + vendorCode: vendors.vendorCode, + finalQuoteAmount: biddingCompanies.finalQuoteAmount, + submitted: biddingCompanies.isBiddingParticipated, + targetPrice: biddingCompanies.preQuoteAmount, // 사전견적 금액을 내정가로 사용 + }) + .from(biddingCompanies) + .leftJoin(vendors, eq(biddingCompanies.companyId, vendors.id)) + .where(eq(biddingCompanies.biddingId, data.biddingId)); + debugLog('[BiddingClosureApproval] 입찰 정보 조회 완료', { biddingId: data.biddingId, - title: biddingInfo[0].title, + title: bidding.title, + itemCount: biddingItemsInfo.length, + vendorCount: vendorSubmissions.length, }); // 2. 템플릿 변수 매핑 @@ -296,7 +335,9 @@ export async function prepareBiddingClosureApprovalData(data: { const requestedAt = new Date(); const { mapBiddingClosureToTemplateVariables } = await import('./handlers'); const variables = await mapBiddingClosureToTemplateVariables({ - biddingId: data.biddingId, + bidding, + biddingItems: biddingItemsInfo, + vendorSubmissions, description: data.description, requestedAt, }); @@ -305,7 +346,8 @@ export async function prepareBiddingClosureApprovalData(data: { }); return { - bidding: biddingInfo[0], + bidding, + biddingItems: biddingItemsInfo, variables, }; } @@ -341,12 +383,19 @@ export async function requestBiddingClosureWithApproval(data: { const { eq } = await import('drizzle-orm'); // 유찰상태인지 확인 - const { bidding } = await db + const biddingResult = await db .select() .from(biddings) .where(eq(biddings.id, data.biddingId)) .limit(1); + if (biddingResult.length === 0) { + debugError('[BiddingClosureApproval] 입찰 정보를 찾을 수 없음'); + throw new Error('입찰 정보를 찾을 수 없습니다'); + } + + const bidding = biddingResult[0]; + if (bidding.status !== 'bidding_disposal') { debugError('[BiddingClosureApproval] 유찰 상태가 아닙니다.'); throw new Error('유찰 상태인 입찰만 폐찰할 수 있습니다.'); @@ -359,7 +408,7 @@ export async function requestBiddingClosureWithApproval(data: { .update(biddings) .set({ status: 'approval_pending', // 폐찰 결재 진행중 상태 - updatedBy: data.currentUser.epId, + updatedBy: Number(data.currentUser.id), updatedAt: new Date() }) .where(eq(biddings.id, data.biddingId)); @@ -370,7 +419,7 @@ export async function requestBiddingClosureWithApproval(data: { }); // 3. 결재 데이터 준비 - const { bidding: approvalBidding, variables } = await prepareBiddingClosureApprovalData({ + const { bidding: approvalBidding, biddingItems, variables } = await prepareBiddingClosureApprovalData({ biddingId: data.biddingId, description: data.description, }); @@ -514,7 +563,7 @@ export async function requestBiddingAwardWithApproval(data: { .update(biddings) .set({ status: 'approval_pending', // 낙찰 결재 진행중 상태 - updatedBy: data.currentUser.epId, + updatedBy: Number(data.currentUser.id), updatedAt: new Date() }) .where(eq(biddings.id, data.biddingId)); diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts index 297c6f98..e49c0628 100644 --- a/lib/bidding/detail/service.ts +++ b/lib/bidding/detail/service.ts @@ -1360,10 +1360,10 @@ export async function updateBiddingParticipation( } // ================================================= -// 품목별 견적 관련 함수들 (본입찰용) +// 품목별 입찰 관련 함수들 (본입찰용) // ================================================= -// 품목별 견적 임시 저장 (본입찰용) +// 품목별 입찰 임시 저장 (본입찰용) export async function saveBiddingDraft( biddingCompanyId: number, prItemQuotations: Array<{ @@ -1380,7 +1380,7 @@ export async function saveBiddingDraft( let totalAmount = 0 await db.transaction(async (tx) => { - // 품목별 견적 Upsert 방식으로 저장 + // 품목별 입찰 Upsert 방식으로 저장 for (const item of prItemQuotations) { // 기존 데이터 확인 const existingItem = await tx @@ -1437,7 +1437,7 @@ export async function saveBiddingDraft( return { success: true, - message: '품목별 견적이 임시 저장되었습니다.', + message: '품목별 입찰이 임시 저장되었습니다.', totalAmount } } catch (error) { diff --git a/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx index 80e50119..a0b69020 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx @@ -72,7 +72,7 @@ export function getBiddingDetailVendorColumns({ }, { accessorKey: 'quotationAmount', - header: '견적금액', + header: '입찰금액', cell: ({ row }) => { const hasAmount = row.original.quotationAmount && Number(row.original.quotationAmount) > 0 return ( @@ -82,7 +82,7 @@ export function getBiddingDetailVendorColumns({