diff options
| -rw-r--r-- | lib/bidding/detail/service.ts | 109 | ||||
| -rw-r--r-- | lib/bidding/list/biddings-table-columns.tsx | 4 | ||||
| -rw-r--r-- | lib/bidding/service.ts | 2 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-detail.tsx | 10 | ||||
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-list-columns.tsx | 40 |
5 files changed, 31 insertions, 134 deletions
diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts index 6ab9270e..39bf0c46 100644 --- a/lib/bidding/detail/service.ts +++ b/lib/bidding/detail/service.ts @@ -918,114 +918,6 @@ export async function registerBidding(biddingId: number, userId: string) { } } -// 재입찰 생성 (기존 입찰의 revision 업데이트 + 메일 발송) -export async function createRebidding(biddingId: number, userId: string) { - try { - // 기존 입찰 정보 조회 - const bidding = await db - .select() - .from(biddings) - .where(eq(biddings.id, biddingId)) - .limit(1) - - if (bidding.length === 0) { - return { success: false, error: '입찰을 찾을 수 없습니다.' } - } - - const originalBidding = bidding[0] - - // 기존 입찰 참여 업체들 조회 - const participantCompanies = await db - .select({ - companyId: biddingCompanies.companyId, - companyName: vendors.vendorName, - contactEmail: vendors.email - }) - .from(biddingCompanies) - .leftJoin(vendors, eq(biddingCompanies.companyId, vendors.id)) - .where(and( - eq(biddingCompanies.biddingId, biddingId), - eq(biddingCompanies.isBiddingParticipated, true) - )) - const userName = await getUserNameById(userId) - // 기존 입찰의 revision 증가 및 상태 변경 - const updatedBidding = await db - .update(biddings) - .set({ - revision: (originalBidding.revision || 0) + 1, - status: 'bidding_opened', // 재입찰 시 다시 오픈 상태로 - updatedBy: userName, - updatedAt: new Date() - }) - .where(eq(biddings.id, biddingId)) - .returning({ - id: biddings.id, - biddingNumber: biddings.biddingNumber, - revision: biddings.revision - }) - - if (updatedBidding.length === 0) { - return { success: false, error: '재입찰 업데이트에 실패했습니다.' } - } - - // // 참여 업체들의 상태를 대기로 변경 - // await db - // .update(biddingCompanies) - // .set({ - // isBiddingParticipated: null, // 대기 상태로 변경 - // invitationStatus: 'sent', - // updatedAt: new Date() - // }) - // .where(and( - // eq(biddingCompanies.biddingId, biddingId), - // eq(biddingCompanies.isBiddingParticipated, true) - // )) - - // 재입찰 안내 메일 발송 - for (const company of participantCompanies) { - if (company.contactEmail) { - try { - await sendEmail({ - to: company.contactEmail, - template: 'rebidding-invitation', - context: { - companyName: company.companyName, - biddingNumber: updatedBidding[0].biddingNumber, - title: originalBidding.title, - projectName: originalBidding.projectName, - itemName: originalBidding.itemName, - biddingType: originalBidding.biddingType, - revision: updatedBidding[0].revision || 1, - submissionStartDate: originalBidding.submissionStartDate, - submissionEndDate: originalBidding.submissionEndDate, - biddingUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/partners/bid/${biddingId}`, - bidPicName: originalBidding.bidPicName, - supplyPicName: originalBidding.supplyPicName, - language: 'ko' - } - }) - } catch (emailError) { - console.error(`Failed to send rebidding email to ${company.contactEmail}:`, emailError) - } - } - } - - // 캐시 무효화 - revalidateTag(`bidding-${biddingId}`) - revalidateTag('quotation-vendors') - revalidateTag('quotation-details') - revalidatePath('/evcp/bid') - revalidatePath(`/evcp/bid/${biddingId}`) - - return { - success: true, - message: `재입찰이 성공적으로 처리되었습니다. ${participantCompanies.length}개 업체에 안내 메일을 발송했습니다.` - } - } catch (error) { - console.error('Failed to create rebidding:', error) - return { success: false, error: '재입찰 처리에 실패했습니다.' } - } -} // 업체 선정 사유 업데이트 export async function updateVendorSelectionReason(biddingId: number, selectedCompanyId: number, selectionReason: string, userId: string) { @@ -1559,6 +1451,7 @@ export interface PartnersBiddingListItem { respondedAt: string | null finalQuoteAmount: number | null finalQuoteSubmittedAt: string | null + isFinalSubmission: boolean | null isWinner: boolean | null isAttendingMeeting: boolean | null isPreQuoteSelected: boolean | null diff --git a/lib/bidding/list/biddings-table-columns.tsx b/lib/bidding/list/biddings-table-columns.tsx index 48c32302..40c7f271 100644 --- a/lib/bidding/list/biddings-table-columns.tsx +++ b/lib/bidding/list/biddings-table-columns.tsx @@ -246,7 +246,7 @@ export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef size: 100, meta: { excelHeader: "입찰등록일" }, }, - + // ░░░ 입찰서제출기간 ░░░ { id: "submissionPeriod", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰서제출기간" />, @@ -263,7 +263,7 @@ export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef return ( <div className="text-xs"> <div className={`${isActive ? 'text-green-600 font-medium' : isPast ? 'text-red-600' : 'text-gray-600'}`}> - {formatDate(startDate, "KR")} ~ {formatDate(endDate, "KR")} + {new Date(startDate).toISOString().slice(0, 16).replace('T', ' ')} ~ {new Date(endDate).toISOString().slice(0, 16).replace('T', ' ')} </div> {isActive && ( <Badge variant="default" className="text-xs mt-1">진행중</Badge> diff --git a/lib/bidding/service.ts b/lib/bidding/service.ts index 68ae016e..14bed105 100644 --- a/lib/bidding/service.ts +++ b/lib/bidding/service.ts @@ -1166,7 +1166,7 @@ export async function createBidding(input: CreateBiddingInput, userId: string) { .insert(biddings) .values({ biddingNumber, - originalBiddingNumber: null, // 원입찰번호는 단순 정보이므로 null + originalBiddingNumber: null, // 원입찰번호는 초기 생성이므로 아직 없음 revision: input.revision || 0, // 프로젝트 정보 (PR 아이템에서 설정됨) diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx index fe254dad..66c90eaf 100644 --- a/lib/bidding/vendor/partners-bidding-detail.tsx +++ b/lib/bidding/vendor/partners-bidding-detail.tsx @@ -25,7 +25,7 @@ import { Calendar, ChevronDown } from 'lucide-react' - +import { format } from 'date-fns' import { formatDate } from '@/lib/utils' import { getBiddingDetailsForPartners, @@ -806,9 +806,9 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <Label className="text-sm font-medium text-muted-foreground mb-2 block">계약기간</Label> <div className="p-3 bg-muted/50 rounded-lg"> <div className="flex items-center gap-2 text-sm"> - <span className="font-medium">{formatDate(biddingDetail.contractStartDate, 'KR')}</span> + <span className="font-medium">{format(new Date(biddingDetail.contractStartDate), "yyyy-MM-dd")}</span> <span className="text-muted-foreground">~</span> - <span className="font-medium">{formatDate(biddingDetail.contractEndDate, 'KR')}</span> + <span className="font-medium">{format(new Date(biddingDetail.contractEndDate), "yyyy-MM-dd")}</span> </div> </div> </div> @@ -874,12 +874,12 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD <div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm"> {biddingDetail.submissionStartDate && biddingDetail.submissionEndDate && ( <div> - <span className="font-medium">응찰기간:</span> {formatDate(biddingDetail.submissionStartDate, 'KR')} ~ {formatDate(biddingDetail.submissionEndDate, 'KR')} + <span className="font-medium">입찰서 제출기간:</span> {new Date(biddingDetail.submissionStartDate).toISOString().slice(0, 16).replace('T', ' ')} ~ {new Date(biddingDetail.submissionEndDate).toISOString().slice(0, 16).replace('T', ' ')} </div> )} {biddingDetail.evaluationDate && ( <div> - <span className="font-medium">평가일:</span> {formatDate(biddingDetail.evaluationDate, 'KR')} + <span className="font-medium">평가일:</span> {format(new Date(biddingDetail.evaluationDate), "yyyy-MM-dd HH:mm")} </div> )} </div> diff --git a/lib/bidding/vendor/partners-bidding-list-columns.tsx b/lib/bidding/vendor/partners-bidding-list-columns.tsx index d9058e97..ba8efae6 100644 --- a/lib/bidding/vendor/partners-bidding-list-columns.tsx +++ b/lib/bidding/vendor/partners-bidding-list-columns.tsx @@ -240,22 +240,6 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL }, }), - // 사전견적 참여의사 - columnHelper.accessor('isPreQuoteParticipated', { - header: '사전견적 참여의사', - cell: ({ row }) => { - const participated = row.original.isPreQuoteParticipated - if (participated === null) { - return <Badge variant="outline">미결정</Badge> - } - return ( - <Badge variant={participated ? 'default' : 'destructive'}> - {participated ? '참여' : '미참여'} - </Badge> - ) - }, - }), - // 입찰 참여의사 columnHelper.accessor('isBiddingParticipated', { header: '입찰 참여의사', @@ -272,6 +256,26 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL }, }), + // 입찰 제출여부 + columnHelper.display({ + id: 'biddingSubmissionStatus', + header: '입찰 제출여부', + cell: ({ row }) => { + const finalQuoteAmount = row.original.finalQuoteAmount + const isFinalSubmission = row.original.isFinalSubmission + + if (!finalQuoteAmount) { + return <Badge variant="outline">미제출</Badge> + } + + if (isFinalSubmission) { + return <Badge variant="default">최종제출</Badge> + } + + return <Badge variant="secondary">제출</Badge> + }, + }), + // 계약구분 columnHelper.accessor('contractType', { header: '계약구분', @@ -291,9 +295,9 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL } return ( <div className="text-sm"> - <div>{format(new Date(startDate), "yyyy-MM-dd")}</div> + <div>{new Date(startDate).toISOString().slice(0, 16).replace('T', ' ')}</div> <div className="text-muted-foreground">~</div> - <div>{format(new Date(endDate), "yyyy-MM-dd")}</div> + <div>{new Date(endDate).toISOString().slice(0, 16).replace('T', ' ')}</div> </div> ) }, |
