diff options
Diffstat (limited to 'lib/bidding/selection/actions.ts')
| -rw-r--r-- | lib/bidding/selection/actions.ts | 156 |
1 files changed, 88 insertions, 68 deletions
diff --git a/lib/bidding/selection/actions.ts b/lib/bidding/selection/actions.ts index 16b2c083..0d2a8a75 100644 --- a/lib/bidding/selection/actions.ts +++ b/lib/bidding/selection/actions.ts @@ -115,20 +115,17 @@ export async function saveSelectionResult(data: SaveSelectionResultData) { // 견적 히스토리 조회 export async function getQuotationHistory(biddingId: number, vendorId: number) { try { - // biddingCompanies에서 해당 벤더의 스냅샷 데이터 조회 - const companyData = await db + // 현재 bidding의 biddingNumber와 originalBiddingNumber 조회 + const currentBiddingInfo = await db .select({ - quotationSnapshots: biddingCompanies.quotationSnapshots + biddingNumber: biddings.biddingNumber, + originalBiddingNumber: biddings.originalBiddingNumber }) - .from(biddingCompanies) - .where(and( - eq(biddingCompanies.biddingId, biddingId), - eq(biddingCompanies.companyId, vendorId) - )) + .from(biddings) + .where(eq(biddings.id, biddingId)) .limit(1) - // 데이터 존재 여부 및 유효성 체크 - if (!companyData.length || !companyData[0]?.quotationSnapshots) { + if (!currentBiddingInfo.length) { return { success: true, data: { @@ -137,40 +134,62 @@ export async function getQuotationHistory(biddingId: number, vendorId: number) { } } - let snapshots = companyData[0].quotationSnapshots + const baseNumber = currentBiddingInfo[0].originalBiddingNumber || currentBiddingInfo[0].biddingNumber.split('-')[0] - // quotationSnapshots가 JSONB 타입이므로 파싱이 필요할 수 있음 - if (typeof snapshots === 'string') { - try { - snapshots = JSON.parse(snapshots) - } catch (parseError) { - console.error('Failed to parse quotationSnapshots:', parseError) - return { - success: true, - data: { - history: [] - } - } + // 동일한 originalBiddingNumber를 가진 모든 bidding 조회 + const relatedBiddings = await db + .select({ + id: biddings.id, + biddingNumber: biddings.biddingNumber, + targetPrice: biddings.targetPrice, + currency: biddings.currency, + createdAt: biddings.createdAt + }) + .from(biddings) + .where(eq(biddings.originalBiddingNumber, baseNumber)) + .orderBy(biddings.createdAt) + + // 각 bidding에 대한 벤더의 견적 정보 조회 + const historyPromises = relatedBiddings.map(async (bidding) => { + const biddingCompanyData = await db + .select({ + finalQuoteAmount: biddingCompanies.finalQuoteAmount, + responseSubmittedAt: biddingCompanies.responseSubmittedAt, + isFinalSubmission: biddingCompanies.isFinalSubmission + }) + .from(biddingCompanies) + .where(and( + eq(biddingCompanies.biddingId, bidding.id), + eq(biddingCompanies.companyId, vendorId) + )) + .limit(1) + + if (!biddingCompanyData.length || !biddingCompanyData[0].finalQuoteAmount || !biddingCompanyData[0].responseSubmittedAt) { + return null } - } - // snapshots가 배열인지 확인 - if (!Array.isArray(snapshots)) { - console.error('quotationSnapshots is not an array:', typeof snapshots) return { - success: true, - data: { - history: [] - } + biddingId: bidding.id, + biddingNumber: bidding.biddingNumber, + finalQuoteAmount: biddingCompanyData[0].finalQuoteAmount, + responseSubmittedAt: biddingCompanyData[0].responseSubmittedAt, + isFinalSubmission: biddingCompanyData[0].isFinalSubmission, + targetPrice: bidding.targetPrice, + currency: bidding.currency } - } + }) - // PR 항목 정보 조회 (스냅샷의 prItemId로 매핑하기 위해) - const prItemIds = snapshots.flatMap(snapshot => - snapshot.items?.map((item: any) => item.prItemId) || [] - ).filter((id: number, index: number, arr: number[]) => arr.indexOf(id) === index) + const historyData = (await Promise.all(historyPromises)).filter(Boolean) + + // biddingNumber의 suffix를 기준으로 정렬 (-01, -02, -03 등) + const sortedHistory = historyData.sort((a, b) => { + const aSuffix = a!.biddingNumber.split('-')[1] ? parseInt(a!.biddingNumber.split('-')[1]) : 0 + const bSuffix = b!.biddingNumber.split('-')[1] ? parseInt(b!.biddingNumber.split('-')[1]) : 0 + return aSuffix - bSuffix + }) - const prItems = prItemIds.length > 0 ? await db + // PR 항목 정보 조회 (현재 bidding 기준) + const prItems = await db .select({ id: prItemsForBidding.id, itemNumber: prItemsForBidding.itemNumber, @@ -180,53 +199,54 @@ export async function getQuotationHistory(biddingId: number, vendorId: number) { requestedDeliveryDate: prItemsForBidding.requestedDeliveryDate }) .from(prItemsForBidding) - .where(sql`${prItemsForBidding.id} IN ${prItemIds}`) : [] + .where(eq(prItemsForBidding.biddingId, biddingId)) - // PR 항목을 Map으로 변환하여 빠른 조회를 위해 - const prItemMap = new Map(prItems.map(item => [item.id, item])) - - // bidding 정보 조회 (targetPrice, currency) - const biddingInfo = await db - .select({ - targetPrice: biddings.targetPrice, - currency: biddings.currency - }) - .from(biddings) - .where(eq(biddings.id, biddingId)) - .limit(1) + // 각 히스토리 항목에 대한 PR 아이템 견적 조회 + const history = await Promise.all(sortedHistory.map(async (item, index) => { + // 각 bidding에 대한 PR 아이템 견적 조회 + const prItemBids = await db + .select({ + prItemId: companyPrItemBids.prItemId, + bidUnitPrice: companyPrItemBids.bidUnitPrice, + bidAmount: companyPrItemBids.bidAmount, + proposedDeliveryDate: companyPrItemBids.proposedDeliveryDate + }) + .from(companyPrItemBids) + .where(and( + eq(companyPrItemBids.biddingId, item!.biddingId), + eq(companyPrItemBids.companyId, vendorId) + )) - const targetPrice = biddingInfo[0]?.targetPrice ? parseFloat(biddingInfo[0].targetPrice.toString()) : null - const currency = biddingInfo[0]?.currency || 'KRW' + const targetPrice = item!.targetPrice ? parseFloat(item!.targetPrice.toString()) : null + const totalAmount = parseFloat(item!.finalQuoteAmount.toString()) - // 스냅샷 데이터를 변환 - const history = snapshots.map((snapshot: any) => { const vsTargetPrice = targetPrice && targetPrice > 0 - ? ((snapshot.totalAmount - targetPrice) / targetPrice) * 100 + ? ((totalAmount - targetPrice) / targetPrice) * 100 : 0 - const items = snapshot.items?.map((item: any) => { - const prItem = prItemMap.get(item.prItemId) + const items = prItemBids.map(bid => { + const prItem = prItems.find(p => p.id === bid.prItemId) return { - itemCode: prItem?.itemNumber || `ITEM${item.prItemId}`, + itemCode: prItem?.itemNumber || `ITEM${bid.prItemId}`, itemName: prItem?.itemInfo || '품목 정보 없음', quantity: prItem?.quantity || 0, unit: prItem?.quantityUnit || 'EA', - unitPrice: item.bidUnitPrice, - totalPrice: item.bidAmount, - deliveryDate: item.proposedDeliveryDate ? new Date(item.proposedDeliveryDate) : prItem?.requestedDeliveryDate ? new Date(prItem.requestedDeliveryDate) : new Date() + unitPrice: parseFloat(bid.bidUnitPrice.toString()), + totalPrice: parseFloat(bid.bidAmount.toString()), + deliveryDate: bid.proposedDeliveryDate ? new Date(bid.proposedDeliveryDate) : prItem?.requestedDeliveryDate ? new Date(prItem.requestedDeliveryDate) : new Date() } - }) || [] + }) return { - id: snapshot.id, - round: snapshot.round, - submittedAt: new Date(snapshot.submittedAt), - totalAmount: snapshot.totalAmount, - currency: snapshot.currency || currency, + id: item!.biddingId, + round: index + 1, // 1차, 2차, 3차... + submittedAt: new Date(item!.responseSubmittedAt), + totalAmount, + currency: item!.currency || 'KRW', vsTargetPrice: parseFloat(vsTargetPrice.toFixed(2)), items } - }) + })) return { success: true, |
