summaryrefslogtreecommitdiff
path: root/lib/bidding/actions.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-27 03:08:50 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-27 03:08:50 +0000
commit79cfa7ea8f21ae227dbb2843ae536fe876ba7c55 (patch)
treef12efae72c62286c1a2e9a3f31d695ca22d83b6e /lib/bidding/actions.ts
parente1da84ac863989b9f63b089c09aaa2bbcdc3d6cd (diff)
(최겸) 구매 입찰 수정
Diffstat (limited to 'lib/bidding/actions.ts')
-rw-r--r--lib/bidding/actions.ts139
1 files changed, 30 insertions, 109 deletions
diff --git a/lib/bidding/actions.ts b/lib/bidding/actions.ts
index 78f07219..0bf2af57 100644
--- a/lib/bidding/actions.ts
+++ b/lib/bidding/actions.ts
@@ -655,111 +655,7 @@ async function getUserNameById(userId: string): Promise<string> {
}
}
-// 조기개찰 액션
-export async function earlyOpenBiddingAction(biddingId: number) {
- try {
- const session = await getServerSession(authOptions)
- if (!session?.user?.name) {
- return { success: false, message: '인증이 필요합니다.' }
- }
-
- const userName = session.user.name
-
- return await db.transaction(async (tx) => {
- // 1. 입찰 정보 확인
- const [bidding] = await tx
- .select({
- id: biddings.id,
- status: biddings.status,
- submissionEndDate: biddings.submissionEndDate,
- title: biddings.title
- })
- .from(biddings)
- .where(eq(biddings.id, biddingId))
- .limit(1)
-
- if (!bidding) {
- return { success: false, message: '입찰 정보를 찾을 수 없습니다.' }
- }
-
- // 2. 입찰서 제출기간 내인지 확인
- const now = new Date()
- if (bidding.submissionEndDate && now > bidding.submissionEndDate) {
- return { success: false, message: '입찰서 제출기간이 종료되었습니다.' }
- }
-
- // 3. 참여 현황 확인
- const [participationStats] = await tx
- .select({
- participantExpected: db.$count(biddingCompanies),
- participantParticipated: db.$count(biddingCompanies, eq(biddingCompanies.invitationStatus, 'bidding_submitted')),
- participantDeclined: db.$count(biddingCompanies, and(
- eq(biddingCompanies.invitationStatus, 'bidding_declined'),
- eq(biddingCompanies.biddingId, biddingId)
- )),
- participantPending: db.$count(biddingCompanies, and(
- eq(biddingCompanies.invitationStatus, 'pending'),
- eq(biddingCompanies.biddingId, biddingId)
- )),
- })
- .from(biddingCompanies)
- .where(eq(biddingCompanies.biddingId, biddingId))
-
- // 실제 SQL 쿼리로 변경
- const [stats] = await tx
- .select({
- participantExpected: sql<number>`COUNT(*)`.as('participant_expected'),
- participantParticipated: sql<number>`COUNT(CASE WHEN invitation_status = 'bidding_submitted' THEN 1 END)`.as('participant_participated'),
- participantDeclined: sql<number>`COUNT(CASE WHEN invitation_status IN ('bidding_declined', 'bidding_cancelled') THEN 1 END)`.as('participant_declined'),
- participantPending: sql<number>`COUNT(CASE WHEN invitation_status IN ('pending', 'bidding_sent', 'bidding_accepted') THEN 1 END)`.as('participant_pending'),
- })
- .from(biddingCompanies)
- .where(eq(biddingCompanies.biddingId, biddingId))
-
- const participantExpected = Number(stats.participantExpected) || 0
- const participantParticipated = Number(stats.participantParticipated) || 0
- const participantDeclined = Number(stats.participantDeclined) || 0
- const participantPending = Number(stats.participantPending) || 0
-
- // 4. 조기개찰 조건 검증
- // - 미제출 협력사 = 0
- if (participantPending > 0) {
- return { success: false, message: `미제출 협력사가 ${participantPending}명 있어 조기개찰이 불가능합니다.` }
- }
-
- // - 참여협력사 + 포기협력사 = 참여예정협력사
- if (participantParticipated + participantDeclined !== participantExpected) {
- return { success: false, message: '모든 협력사가 참여 또는 포기하지 않아 조기개찰이 불가능합니다.' }
- }
-
- // 5. 참여협력사 중 최종응찰 버튼을 클릭한 업체들만 있는지 검증
- // bidding_submitted 상태인 업체들이 있는지 확인 (이미 위에서 검증됨)
-
- // 6. 입찰평가중 상태로 변경
- await tx
- .update(biddings)
- .set({
- status: 'evaluation_of_bidding',
- openedAt: new Date(),
- openedBy: userName,
- updatedAt: new Date(),
- updatedBy: userName,
- })
- .where(eq(biddings.id, biddingId))
-
- return { success: true, message: '조기개찰이 완료되었습니다.' }
- })
-
- } catch (error) {
- console.error('조기개찰 실패:', error)
- return {
- success: false,
- message: error instanceof Error ? error.message : '조기개찰 중 오류가 발생했습니다.'
- }
- }
-}
-
-// 개찰 액션
+// 개찰 액션 (조기개찰 포함)
export async function openBiddingAction(biddingId: number) {
try {
const session = await getServerSession(authOptions)
@@ -786,10 +682,35 @@ export async function openBiddingAction(biddingId: number) {
return { success: false, message: '입찰 정보를 찾을 수 없습니다.' }
}
- // 2. 입찰서 제출기간이 종료되었는지 확인
const now = new Date()
- if (bidding.submissionEndDate && now <= bidding.submissionEndDate) {
- return { success: false, message: '입찰서 제출기간이 아직 종료되지 않았습니다.' }
+ const isDeadlinePassed = bidding.submissionEndDate && now > bidding.submissionEndDate
+
+ // 2. 개찰 가능 여부 확인
+ if (!isDeadlinePassed) {
+ // 마감일이 지나지 않았으면 조기개찰 조건 확인
+ // 조기개찰 조건: 모든 대상 업체가 응찰(최종제출)했거나 포기했는지 확인 (미제출 0)
+
+ const [stats] = await tx
+ .select({
+ participantExpected: sql<number>`COUNT(*)`.as('participant_expected'),
+ participantFinalSubmitted: sql<number>`COUNT(CASE WHEN invitation_status = 'bidding_submitted' THEN 1 END)`.as('participant_final_submitted'),
+ participantDeclined: sql<number>`COUNT(CASE WHEN invitation_status IN ('bidding_declined', 'bidding_cancelled') THEN 1 END)`.as('participant_declined'),
+ })
+ .from(biddingCompanies)
+ .where(eq(biddingCompanies.biddingId, biddingId))
+
+ const participantExpected = Number(stats.participantExpected) || 0
+ const participantFinalSubmitted = Number(stats.participantFinalSubmitted) || 0
+ const participantDeclined = Number(stats.participantDeclined) || 0
+
+ // 조건: 전체 대상 = 최종제출 + 포기
+ if (participantExpected !== participantFinalSubmitted + participantDeclined) {
+ const pending = participantExpected - (participantFinalSubmitted + participantDeclined);
+ return {
+ success: false,
+ message: `입찰서 제출기간이 종료되지 않았으며, 최종제출하지 않은 업체가 ${pending}곳 있어 조기개찰할 수 없습니다.`
+ }
+ }
}
// 3. 입찰평가중 상태로 변경
@@ -804,7 +725,7 @@ export async function openBiddingAction(biddingId: number) {
})
.where(eq(biddings.id, biddingId))
- return { success: true, message: '개찰이 완료되었습니다.' }
+ return { success: true, message: isDeadlinePassed ? '개찰이 완료되었습니다.' : '조기개찰이 완료되었습니다.' }
})
} catch (error) {