From 088a161f8852dd7566619baca93257c0ccd901b7 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 20 Nov 2025 02:38:04 +0000 Subject: (최겸) 구매 입찰 제출기간 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bidding/manage/bidding-schedule-editor.tsx | 95 ++++++++++++++++------ db/schema/bidding.ts | 4 +- .../bidding-detail-vendor-toolbar-actions.tsx | 9 +- .../vendor/partners-bidding-list-columns.tsx | 14 ++-- 4 files changed, 83 insertions(+), 39 deletions(-) diff --git a/components/bidding/manage/bidding-schedule-editor.tsx b/components/bidding/manage/bidding-schedule-editor.tsx index f2978f95..f3260f04 100644 --- a/components/bidding/manage/bidding-schedule-editor.tsx +++ b/components/bidding/manage/bidding-schedule-editor.tsx @@ -19,7 +19,7 @@ import { BiddingInvitationDialog } from '@/lib/bidding/detail/table/bidding-invi import { sendBiddingBasicContracts, getSelectedVendorsForBidding } from '@/lib/bidding/pre-quote/service' import { registerBidding } from '@/lib/bidding/detail/service' import { useToast } from '@/hooks/use-toast' - +import { format } from 'date-fns' interface BiddingSchedule { submissionStartDate?: string submissionEndDate?: string @@ -112,7 +112,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc }) const [isLoading, setIsLoading] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false) - const [biddingInfo, setBiddingInfo] = React.useState<{ title: string; projectName?: string; status: string } | null>(null) + const [biddingInfo, setBiddingInfo] = React.useState<{ title: string; projectName?: string; status: string; biddingNumber?: string } | null>(null) const [isBiddingInvitationDialogOpen, setIsBiddingInvitationDialogOpen] = React.useState(false) const [isApprovalDialogOpen, setIsApprovalDialogOpen] = React.useState(false) const [selectedVendors, setSelectedVendors] = React.useState([]) @@ -120,6 +120,19 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc const [approvalTitle, setApprovalTitle] = React.useState('') const [invitationData, setInvitationData] = React.useState(null) + // 차수 추출 헬퍼 함수 + const getRoundNumber = (biddingNumber: string): number => { + const match = biddingNumber.match(/-(\d+)$/) + return match ? parseInt(match[1]) : 1 + } + + // 차수 증가된 입찰인지 확인 (01이 아닌 경우) + const isRoundIncreased = (biddingNumber?: string): boolean => { + if (!biddingNumber) return false + const round = getRoundNumber(biddingNumber) + return round > 1 + } + // 데이터 로딩 React.useEffect(() => { const loadSchedule = async () => { @@ -132,25 +145,12 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc title: bidding.title || '', projectName: bidding.projectName || undefined, status: bidding.status || '', + biddingNumber: bidding.biddingNumber || undefined, }) - // 날짜를 문자열로 변환하는 헬퍼 - const formatDateTime = (date: unknown): string => { - if (!date) return '' - if (typeof date === 'string') { - // 이미 datetime-local 형식인 경우 - if (date.includes('T')) { - return date.slice(0, 16) - } - return date - } - if (date instanceof Date) return date.toISOString().slice(0, 16) - return '' - } - setSchedule({ - submissionStartDate: formatDateTime(bidding.submissionStartDate), - submissionEndDate: formatDateTime(bidding.submissionEndDate), + submissionStartDate: bidding.submissionStartDate ? new Date(bidding.submissionStartDate).toISOString().slice(0, 16) : '', + submissionEndDate: bidding.submissionEndDate ? new Date(bidding.submissionEndDate).toISOString().slice(0, 16) : '', remarks: bidding.remarks || '', isUrgent: bidding.isUrgent || false, hasSpecificationMeeting: bidding.hasSpecificationMeeting || false, @@ -286,6 +286,48 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc } } + // 차수 증가된 입찰의 직접 입찰공고 함수 (결재 생략) + const handleDirectBiddingInvitation = async (data: BiddingInvitationData, vendors: VendorContractRequirement[]) => { + try { + if (!session?.user?.id) { + toast({ + title: '오류', + description: '사용자 정보가 없습니다.', + variant: 'destructive', + }) + return + } + + // 입찰 등록 (결재 생략) + const result = await registerBidding(biddingId, session.user.id.toString()) + + if (result.success) { + toast({ + title: '입찰공고 완료', + description: '차수 증가된 입찰이 성공적으로 공고되었습니다.', + }) + + // 다이얼로그 닫기 및 페이지 새로고침 + setIsBiddingInvitationDialogOpen(false) + setInvitationData(null) + router.refresh() + } else { + toast({ + title: '오류', + description: result.error || '입찰공고 중 오류가 발생했습니다.', + variant: 'destructive', + }) + } + } catch (error) { + console.error('직접 입찰공고 중 오류 발생:', error) + toast({ + title: '오류', + description: '입찰공고 중 오류가 발생했습니다.', + variant: 'destructive', + }) + } + } + // 입찰 초대 발송 핸들러 - 결재 준비 및 결재 다이얼로그 열기 const handleBiddingInvitationSend = async (data: BiddingInvitationData) => { try { @@ -309,7 +351,14 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc return } - // 결재 데이터 준비 (템플릿 변수, 제목 등) + // 차수 증가된 입찰(02, 03 등)인지 확인 + if (isRoundIncreased(biddingInfo?.biddingNumber)) { + // 차수 증가된 입찰은 결재 생략하고 바로 입찰 등록 + await handleDirectBiddingInvitation(data, vendors) + return + } + + // 일반 입찰의 경우 결재 데이터 준비 (템플릿 변수, 제목 등) const approvalData = await prepareBiddingApprovalData({ biddingId, vendors, @@ -435,7 +484,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc handleScheduleChange('submissionStartDate', e.target.value)} /> @@ -444,7 +493,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc handleScheduleChange('submissionEndDate', e.target.value)} /> @@ -613,7 +662,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc 입찰서 제출 기간: {schedule.submissionStartDate && schedule.submissionEndDate - ? `${new Date(schedule.submissionStartDate).toLocaleString('ko-KR')} ~ ${new Date(schedule.submissionEndDate).toLocaleString('ko-KR')}` + ? `${format(new Date(schedule.submissionStartDate), "yyyy-MM-dd HH:mm")} ~ ${format(new Date(schedule.submissionEndDate), "yyyy-MM-dd HH:mm")}` : '미설정' } @@ -634,7 +683,7 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
사양설명회 일시: - {new Date(specMeetingInfo.meetingDate).toLocaleString('ko-KR')} + {format(new Date(specMeetingInfo.meetingDate), "yyyy-MM-dd HH:mm")}
)} diff --git a/db/schema/bidding.ts b/db/schema/bidding.ts index ab9b3373..d657086f 100644 --- a/db/schema/bidding.ts +++ b/db/schema/bidding.ts @@ -698,10 +698,10 @@ export const biddingStatusLabels = { vendor_selected: '업체선정', bid_opening: '개찰', early_bid_opening: '조기개찰', - rebidding: '재입찰', + rebidding: '입찰종료-재입찰', disposal_cancelled: '유찰취소', bid_closure: '폐찰', - round_increase: '차수증가' + round_increase: '입찰종료-차수증가' } as const export const contractTypeLabels = { diff --git a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx index c1d59677..34ee690f 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx @@ -215,12 +215,7 @@ export function BiddingDetailVendorToolbarActions({ title: "성공", description: successResult.message, }) - // 새로 생성된 입찰의 상세 페이지로 이동 - if (successResult.biddingId) { - router.push(`/evcp/bid/${successResult.biddingId}/info`) - } else { - router.push(`/evcp/bid/${biddingId}/info`) - } + router.push(`/evcp/bid`) onSuccess() } else { const errorResult = result as { success: false; error: string } @@ -326,7 +321,7 @@ export function BiddingDetailVendorToolbarActions({ 차수증가 확인 - 입찰을 차수증가 처리하시겠습니까? 차수증가 후 새로운 입찰 화면으로 이동합니다. + 입찰을 차수증가 처리하시겠습니까? 차수증가 후 입찰 관리 페이지로 이동합니다. diff --git a/lib/bidding/vendor/partners-bidding-list-columns.tsx b/lib/bidding/vendor/partners-bidding-list-columns.tsx index 6e8591c2..d9058e97 100644 --- a/lib/bidding/vendor/partners-bidding-list-columns.tsx +++ b/lib/bidding/vendor/partners-bidding-list-columns.tsx @@ -20,7 +20,7 @@ import { Paperclip, AlertTriangle } from 'lucide-react' -import { formatDate } from '@/lib/utils' +import { format } from 'date-fns' import { biddingStatusLabels, contractTypeLabels } from '@/db/schema' import { PartnersBiddingListItem } from '../detail/service' import { Checkbox } from '@/components/ui/checkbox' @@ -291,9 +291,9 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL } return (
-
{formatDate(startDate, 'KR')}
+
{format(new Date(startDate), "yyyy-MM-dd")}
~
-
{formatDate(endDate, 'KR')}
+
{format(new Date(endDate), "yyyy-MM-dd")}
) }, @@ -315,7 +315,7 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL return (
- {formatDate(deadline, 'KR')} + {format(new Date(deadline), "yyyy-MM-dd HH:mm")} {isExpired && ( 마감 @@ -339,9 +339,9 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL return (
-
{formatDate(startDate, 'KR')}
+
{format(new Date(startDate), "yyyy-MM-dd")}
~
-
{formatDate(endDate, 'KR')}
+
{format(new Date(endDate), "yyyy-MM-dd")}
) }, @@ -387,7 +387,7 @@ export function getPartnersBiddingListColumns({ setRowAction }: PartnersBiddingL columnHelper.accessor('updatedAt', { header: '최종수정일', cell: ({ row }) => ( -
{formatDate(row.original.updatedAt, 'KR')}
+
{format(new Date(row.original.updatedAt), "yyyy-MM-dd HH:mm")}
), }), -- cgit v1.2.3