summaryrefslogtreecommitdiff
path: root/lib/bidding/detail
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/detail')
-rw-r--r--lib/bidding/detail/bidding-actions.ts136
-rw-r--r--lib/bidding/detail/service.ts87
2 files changed, 88 insertions, 135 deletions
diff --git a/lib/bidding/detail/bidding-actions.ts b/lib/bidding/detail/bidding-actions.ts
index fb659039..4140ec72 100644
--- a/lib/bidding/detail/bidding-actions.ts
+++ b/lib/bidding/detail/bidding-actions.ts
@@ -22,7 +22,7 @@ async function getUserNameById(userId: string): Promise<string> {
}
}
-// 응찰 취소 서버 액션 (최종제출이 아닌 경우만 가능)
+// 응찰 포기 서버 액션 (최종제출이 아닌 경우만 가능)
export async function cancelBiddingResponse(
biddingCompanyId: number,
userId: string
@@ -63,7 +63,7 @@ export async function cancelBiddingResponse(
finalQuoteAmount: null,
finalQuoteSubmittedAt: null,
isFinalSubmission: false,
- invitationStatus: 'bidding_cancelled', // 응찰 취소 상태
+ invitationStatus: 'bidding_cancelled', // 응찰 포기 상태
updatedAt: new Date()
})
.where(eq(biddingCompanies.id, biddingCompanyId))
@@ -86,142 +86,14 @@ export async function cancelBiddingResponse(
return {
success: true,
- message: '응찰이 취소되었습니다.'
+ message: '응찰이 포기되었습니다.'
}
})
} catch (error) {
console.error('Failed to cancel bidding response:', error)
return {
success: false,
- error: error instanceof Error ? error.message : '응찰 취소에 실패했습니다.'
+ error: error instanceof Error ? error.message : '응찰 포기에 실패했습니다.'
}
}
}
-
-// 모든 벤더가 최종제출했는지 확인
-export async function checkAllVendorsFinalSubmitted(biddingId: number) {
- try {
- const companies = await db
- .select({
- id: biddingCompanies.id,
- isFinalSubmission: biddingCompanies.isFinalSubmission,
- invitationStatus: biddingCompanies.invitationStatus,
- })
- .from(biddingCompanies)
- .where(
- and(
- eq(biddingCompanies.biddingId, biddingId),
- eq(biddingCompanies.isBiddingInvited, true) // 본입찰 초대된 업체만
- )
- )
-
- // 초대된 업체가 없으면 false
- if (companies.length === 0) {
- return {
- allSubmitted: false,
- totalCompanies: 0,
- submittedCompanies: 0
- }
- }
-
- // 모든 업체가 최종제출했는지 확인
- const submittedCompanies = companies.filter(c => c.isFinalSubmission).length
- const allSubmitted = companies.every(c => c.isFinalSubmission)
-
- return {
- allSubmitted,
- totalCompanies: companies.length,
- submittedCompanies
- }
- } catch (error) {
- console.error('Failed to check all vendors final submitted:', error)
- return {
- allSubmitted: false,
- totalCompanies: 0,
- submittedCompanies: 0
- }
- }
-}
-
-// // 개찰 서버 액션 (조기개찰/개찰 구분)
-// export async function performBidOpening(
-// biddingId: number,
-// userId: string,
-// isEarly: boolean = false // 조기개찰 여부
-// ) {
-// try {
-// const userName = await getUserNameById(userId)
-
-// return await db.transaction(async (tx) => {
-// // 1. 입찰 정보 조회
-// const [bidding] = await tx
-// .select({
-// id: biddings.id,
-// status: biddings.status,
-// submissionEndDate: biddings.submissionEndDate,
-// })
-// .from(biddings)
-// .where(eq(biddings.id, biddingId))
-// .limit(1)
-
-// if (!bidding) {
-// return {
-// success: false,
-// error: '입찰 정보를 찾을 수 없습니다.'
-// }
-// }
-
-// // 2. 개찰 가능 여부 확인 (evaluation_of_bidding 상태에서만)
-// if (bidding.status !== 'evaluation_of_bidding') {
-// return {
-// success: false,
-// error: '입찰평가중 상태에서만 개찰할 수 있습니다.'
-// }
-// }
-
-// // 3. 모든 벤더가 최종제출했는지 확인
-// const checkResult = await checkAllVendorsFinalSubmitted(biddingId)
-// if (!checkResult.allSubmitted) {
-// return {
-// success: false,
-// error: `모든 벤더가 최종 제출해야 개찰할 수 있습니다. (${checkResult.submittedCompanies}/${checkResult.totalCompanies})`
-// }
-// }
-
-// // 4. 조기개찰 여부 결정
-// const now = new Date()
-// const submissionEndDate = bidding.submissionEndDate ? new Date(bidding.submissionEndDate) : null
-// const isBeforeDeadline = submissionEndDate && now < submissionEndDate
-
-// // 마감일 전이면 조기개찰, 마감일 후면 일반 개찰
-// const newStatus = (isEarly || isBeforeDeadline) ? 'early_bid_opening' : 'bid_opening'
-
-// // 5. 입찰 상태 변경
-// await tx
-// .update(biddings)
-// .set({
-// status: newStatus,
-// updatedAt: new Date()
-// })
-// .where(eq(biddings.id, biddingId))
-
-// // 캐시 무효화
-// revalidateTag(`bidding-${biddingId}`)
-// revalidateTag('bidding-detail')
-// revalidatePath(`/evcp/bid/${biddingId}`)
-
-// return {
-// success: true,
-// message: `${newStatus === 'early_bid_opening' ? '조기개찰' : '개찰'}이 완료되었습니다.`,
-// status: newStatus
-// }
-// })
-// } catch (error) {
-// console.error('Failed to perform bid opening:', error)
-// return {
-// success: false,
-// error: error instanceof Error ? error.message : '개찰에 실패했습니다.'
-// }
-// }
-// }
-
diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts
index 8f9bf018..c9aaa66c 100644
--- a/lib/bidding/detail/service.ts
+++ b/lib/bidding/detail/service.ts
@@ -854,6 +854,7 @@ export async function registerBidding(biddingId: number, userId: string) {
// 3. 선정된 업체들에게 본입찰 초대 메일 발송
debugLog('registerBidding: Sending emails...')
for (const company of selectedCompanies) {
+ // 벤더 메인 이메일로 발송
if (company.contactEmail) {
try {
await sendEmail({
@@ -879,6 +880,51 @@ export async function registerBidding(biddingId: number, userId: string) {
debugError(`Failed to send bidding invitation email to ${company.contactEmail}:`, emailError)
}
}
+
+ // 추가 담당자들에게도 이메일 발송
+ try {
+ const contactInfos = await db
+ .select({
+ contactName: biddingCompaniesContacts.contactName,
+ contactEmail: biddingCompaniesContacts.contactEmail
+ })
+ .from(biddingCompaniesContacts)
+ .where(and(
+ eq(biddingCompaniesContacts.biddingId, biddingId),
+ eq(biddingCompaniesContacts.vendorId, company.companyId)
+ ));
+
+ for (const contact of contactInfos) {
+ // 벤더 메인 이메일과 중복되지 않는 경우에만 발송
+ if (contact.contactEmail && contact.contactEmail !== company.contactEmail) {
+ try {
+ await sendEmail({
+ to: contact.contactEmail,
+ template: 'bidding-invitation',
+ context: {
+ companyName: company.companyName,
+ biddingNumber: bidding.biddingNumber,
+ title: bidding.title,
+ projectName: bidding.projectName,
+ itemName: bidding.itemName,
+ biddingType: bidding.biddingType,
+ submissionStartDate: bidding.submissionStartDate,
+ submissionEndDate: bidding.submissionEndDate,
+ biddingUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/partners/bid/${biddingId}`,
+ bidPicName: bidding.bidPicName,
+ supplyPicName: bidding.supplyPicName,
+ language: 'ko'
+ }
+ })
+ debugLog(`registerBidding: Email sent to contact ${contact.contactEmail}`)
+ } catch (emailError) {
+ debugError(`Failed to send bidding invitation email to contact ${contact.contactEmail}:`, emailError)
+ }
+ }
+ }
+ } catch (contactError) {
+ debugError('Failed to fetch contact emails:', contactError)
+ }
}
// 4. 입찰 공고 SMS 알림 전송
debugLog('registerBidding: Sending SMS...')
@@ -1467,6 +1513,41 @@ export async function saveBiddingDraft(
}
}
+// 본입찰용 품목별 견적 조회 (협력업체용)
+export async function getPartnerBiddingItemQuotations(biddingCompanyId: number) {
+ try {
+ const savedQuotations = await db
+ .select({
+ prItemId: companyPrItemBids.prItemId,
+ bidUnitPrice: companyPrItemBids.bidUnitPrice,
+ bidAmount: companyPrItemBids.bidAmount,
+ proposedDeliveryDate: companyPrItemBids.proposedDeliveryDate,
+ technicalSpecification: companyPrItemBids.technicalSpecification,
+ currency: companyPrItemBids.currency
+ })
+ .from(companyPrItemBids)
+ .where(
+ and(
+ eq(companyPrItemBids.biddingCompanyId, biddingCompanyId),
+ eq(companyPrItemBids.isPreQuote, false) // 본입찰 데이터
+ )
+ )
+
+ // Decimal 타입을 number로 변환
+ return savedQuotations.map(item => ({
+ prItemId: item.prItemId,
+ bidUnitPrice: parseFloat(item.bidUnitPrice || '0'),
+ bidAmount: parseFloat(item.bidAmount || '0'),
+ proposedDeliveryDate: item.proposedDeliveryDate,
+ technicalSpecification: item.technicalSpecification,
+ currency: item.currency
+ }))
+ } catch (error) {
+ console.error('Failed to get partner bidding item quotations:', error)
+ return []
+ }
+}
+
// =================================================
// 협력업체 페이지용 함수들 (Partners)
// =================================================
@@ -1839,14 +1920,14 @@ export async function submitPartnerResponse(
// adjustmentDate: response.priceAdjustmentForm.adjustmentDate || null,
// nonApplicableReason: response.priceAdjustmentForm.nonApplicableReason,
// }
-
+ //
// // 기존 연동제 정보가 있는지 확인
// const existingPriceAdjustment = await tx
// .select()
// .from(priceAdjustmentForms)
// .where(eq(priceAdjustmentForms.companyConditionResponsesId, companyConditionResponseId))
// .limit(1)
-
+ //
// if (existingPriceAdjustment.length > 0) {
// // 업데이트
// await tx
@@ -2573,4 +2654,4 @@ export async function setSpecificationMeetingParticipation(biddingCompanyId: num
console.error('Failed to update specification meeting participation:', error)
return { success: false, error: '사양설명회 참여상태 업데이트에 실패했습니다.' }
}
-} \ No newline at end of file
+}