From c4f5472b961afb237dc819f9dd3f42a7b8f71075 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 18 Nov 2025 10:30:31 +0000 Subject: (최겸) 구매 입찰 수정, 입찰초대 결재 등록, 재입찰, 차수증가, 폐찰, 유찰취소 로직 수정, readonly 추가 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(procurement)/bid/[id]/companies/page.tsx | 4 +- .../(evcp)/(procurement)/bid/[id]/info/page.tsx | 4 +- .../(evcp)/(procurement)/bid/[id]/items/page.tsx | 4 +- .../(procurement)/bid/[id]/schedule/page.tsx | 4 +- components/bidding/bidding-round-actions.tsx | 221 ------ .../bidding/create/bidding-create-dialog.tsx | 121 ++-- .../bidding/manage/bidding-basic-info-editor.tsx | 136 ++-- .../bidding/manage/bidding-companies-editor.tsx | 48 +- .../manage/bidding-detail-vendor-create-dialog.tsx | 14 - components/bidding/manage/bidding-items-editor.tsx | 49 +- .../bidding/manage/bidding-schedule-editor.tsx | 225 +++--- lib/approval/handlers-registry.ts | 15 + ...\210\353\214\200 \352\262\260\354\236\254.html" | 805 +++++++++++++++++++++ lib/bidding/approval-actions.ts | 243 +++++++ .../detail/table/bidding-detail-vendor-columns.tsx | 22 +- .../bidding-detail-vendor-toolbar-actions.tsx | 59 +- lib/bidding/failure/biddings-failure-table.tsx | 63 +- lib/bidding/handlers.ts | 283 ++++++++ lib/bidding/list/biddings-table-columns.tsx | 11 +- lib/bidding/service.ts | 74 +- 20 files changed, 1862 insertions(+), 543 deletions(-) delete mode 100644 components/bidding/bidding-round-actions.tsx create mode 100644 "lib/approval/templates/\354\236\205\354\260\260\354\264\210\353\214\200 \352\262\260\354\236\254.html" create mode 100644 lib/bidding/approval-actions.ts create mode 100644 lib/bidding/handlers.ts diff --git a/app/[lng]/evcp/(evcp)/(procurement)/bid/[id]/companies/page.tsx b/app/[lng]/evcp/(evcp)/(procurement)/bid/[id]/companies/page.tsx index f1699665..0ef26754 100644 --- a/app/[lng]/evcp/(evcp)/(procurement)/bid/[id]/companies/page.tsx +++ b/app/[lng]/evcp/(evcp)/(procurement)/bid/[id]/companies/page.tsx @@ -55,7 +55,7 @@ export default async function BiddingCompaniesPage({ params }: PageProps) {

- + - - -

- 유찰 상태에서 차수증가 또는 재입찰을 진행할 수 있습니다. -

- - - - {/* 차수증가 확인 다이얼로그 */} - - - - 차수증가 - - 현재 입찰의 정보를 복제하여 새로운 차수의 입찰을 생성합니다. -
- 기존 입찰 조건, 아이템, 벤더 정보가 복제되며, 벤더 제출 정보는 초기화됩니다. -
-
- 계속하시겠습니까? -
-
- - 취소 - - {isPending ? "처리중..." : "확인"} - - -
-
- - {/* 재입찰 확인 다이얼로그 */} - - - - 재입찰 - - 현재 입찰의 정보를 복제하여 재입찰을 생성합니다. -
- 기존 입찰 조건, 아이템, 벤더 정보가 복제되며, 벤더 제출 정보는 초기화됩니다. -
-
- 계속하시겠습니까? -
-
- - 취소 - - {isPending ? "처리중..." : "확인"} - - -
-
- - ) -} - - diff --git a/components/bidding/create/bidding-create-dialog.tsx b/components/bidding/create/bidding-create-dialog.tsx index bdb00a01..c7d79435 100644 --- a/components/bidding/create/bidding-create-dialog.tsx +++ b/components/bidding/create/bidding-create-dialog.tsx @@ -38,12 +38,23 @@ import { import { TAX_CONDITIONS } from '@/lib/tax-conditions/types' import { getBiddingNoticeTemplate } from '@/lib/bidding/service' import TiptapEditor from '@/components/qna/tiptap-editor' + +// Dropzone components +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone" import { PurchaseGroupCodeSelector } from '@/components/common/selectors/purchase-group-code' import { ProcurementManagerSelector } from '@/components/common/selectors/procurement-manager' import type { PurchaseGroupCodeWithUser } from '@/components/common/selectors/purchase-group-code/purchase-group-code-service' import type { ProcurementManagerWithUser } from '@/components/common/selectors/procurement-manager/procurement-manager-service' import { createBidding } from '@/lib/bidding/service' import { useSession } from 'next-auth/react' +import { useRouter } from 'next/navigation' interface BiddingCreateDialogProps { form: UseFormReturn @@ -52,6 +63,7 @@ interface BiddingCreateDialogProps { export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProps) { const { data: session } = useSession() + const router = useRouter() const userId = session?.user?.id ? Number(session.user.id) : null; const [isOpen, setIsOpen] = React.useState(false) @@ -145,6 +157,13 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp React.useEffect(() => { if (isOpen) { + if (userId && session?.user?.name) { + // 현재 사용자의 정보를 임시로 입찰담당자로 설정 + form.setValue('bidPicName', session.user.name) + form.setValue('bidPicId', userId) + // userCode는 현재 세션에 없으므로 이름으로 설정 (실제로는 API에서 가져와야 함) + // form.setValue('bidPicCode', session.user.name) + } loadPaymentTerms() loadIncoterms() loadShippingPlaces() @@ -280,7 +299,7 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp })) // sparePartOptions가 undefined인 경우 빈 문자열로 설정 - const biddingData: CreateBiddingInput = { + const biddingData = { ...data, attachments, vendorAttachments, @@ -298,6 +317,12 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp if (result.success) { toast.success("입찰이 성공적으로 생성되었습니다.") + + // 생성된 입찰의 상세 페이지로 이동 + if ('data' in result && result.data?.id) { + router.push(`/evcp/bid/${result.data.id}`) + } + setIsOpen(false) form.reset() setShiAttachmentFiles([]) @@ -1130,30 +1155,35 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp SHI용 첨부파일

- SHI에서 제공하는 문서나 파일을 업로드하세요 + 내부 보관를 위해 필요한 문서나 파일을 업로드 하세요.

-
-
- -
-

- 파일을 드래그 앤 드롭하거나{' '} - -

-
-
-
+ { + const newFiles = Array.from(files) + setShiAttachmentFiles(prev => [...prev, ...newFiles]) + }} + onDropRejected={() => { + toast({ + title: "File upload rejected", + description: "Please check file size and type.", + variant: "destructive", + }) + }} + > + {() => ( + +
+ +
+ 파일을 드래그하여 업로드 +
+
+
+ )} +
{shiAttachmentFiles.length > 0 && (
@@ -1197,30 +1227,35 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp 협력업체용 첨부파일

- 협력업체에서 제공하는 문서나 파일을 업로드하세요 + 협력사로 제공하는 문서나 파일을 업로드 하세요.

-
-
- -
-

- 파일을 드래그 앤 드롭하거나{' '} - -

-
-
-
+ { + const newFiles = Array.from(files) + setVendorAttachmentFiles(prev => [...prev, ...newFiles]) + }} + onDropRejected={() => { + toast({ + title: "File upload rejected", + description: "Please check file size and type.", + variant: "destructive", + }) + }} + > + {() => ( + +
+ +
+ 파일을 드래그하여 업로드 +
+
+
+ )} +
{vendorAttachmentFiles.length > 0 && (
diff --git a/components/bidding/manage/bidding-basic-info-editor.tsx b/components/bidding/manage/bidding-basic-info-editor.tsx index e92c39a5..f0d56689 100644 --- a/components/bidding/manage/bidding-basic-info-editor.tsx +++ b/components/bidding/manage/bidding-basic-info-editor.tsx @@ -45,6 +45,16 @@ import type { ProcurementManagerWithUser } from '@/components/common/selectors/p import { getBiddingDocuments, uploadBiddingDocument, deleteBiddingDocument } from '@/lib/bidding/detail/service' import { downloadFile } from '@/lib/file-download' +// Dropzone components +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone" + // 입찰 기본 정보 에디터 컴포넌트 interface BiddingBasicInfo { title?: string @@ -78,6 +88,7 @@ interface BiddingBasicInfo { interface BiddingBasicInfoEditorProps { biddingId: number + readonly?: boolean } interface UploadedDocument { @@ -95,7 +106,7 @@ interface UploadedDocument { uploadedBy: string } -export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProps) { +export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingBasicInfoEditorProps) { const [isLoading, setIsLoading] = React.useState(true) const [isSubmitting, setIsSubmitting] = React.useState(false) const [isLoadingTemplate, setIsLoadingTemplate] = React.useState(false) @@ -535,7 +546,7 @@ export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProp 입찰명 * - + @@ -883,7 +894,7 @@ export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProp 입찰개요 -