From dfc898b298e16bf686d2929db0eee50abe87881d Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 14 Oct 2025 05:59:11 +0000 Subject: (최겸) 구매 견적 첨부파일 기능 수정, 제출 후 수정x 추가 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rfq-last/attachment/rfq-attachments-table.tsx | 4 +- lib/rfq-last/attachment/vendor-response-table.tsx | 4 +- lib/rfq-last/service.ts | 97 ++++++++++++++++++++++ .../vendor-response/editor/attachments-upload.tsx | 87 +++++++++++++------ lib/rfq-last/vendor/rfq-vendor-table.tsx | 2 +- lib/rfq-last/vendor/vendor-detail-dialog.tsx | 8 +- 6 files changed, 169 insertions(+), 33 deletions(-) (limited to 'lib') diff --git a/lib/rfq-last/attachment/rfq-attachments-table.tsx b/lib/rfq-last/attachment/rfq-attachments-table.tsx index d97d32fd..92a89417 100644 --- a/lib/rfq-last/attachment/rfq-attachments-table.tsx +++ b/lib/rfq-last/attachment/rfq-attachments-table.tsx @@ -431,9 +431,9 @@ export function RfqAttachmentsTable({ handleAction({ type: "download", row })}> 다운로드 - handleAction({ type: "preview", row })}> + {/* handleAction({ type: "preview", row })}> 미리보기 - + */} handleAction({ type: "history", row })}> 리비전 히스토리 diff --git a/lib/rfq-last/attachment/vendor-response-table.tsx b/lib/rfq-last/attachment/vendor-response-table.tsx index 47a23d18..8488eea1 100644 --- a/lib/rfq-last/attachment/vendor-response-table.tsx +++ b/lib/rfq-last/attachment/vendor-response-table.tsx @@ -460,10 +460,10 @@ export function VendorResponseTable({ 다운로드 - handleAction({ row, type: "preview" })}> + {/* handleAction({ row, type: "preview" })}> 미리보기 - + */} ); diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts index f600d04b..50a5c64c 100644 --- a/lib/rfq-last/service.ts +++ b/lib/rfq-last/service.ts @@ -934,6 +934,103 @@ export async function getRfqVendorAttachments(rfqId: number) { } } +/** + * 특정 RFQ의 특정 벤더가 제출한 모든 응답의 첨부파일 조회, 추후 협력업체가 제출한 첨부파일 히스토리 조회용으로 사용 + */ +export async function getVendorResponseAttachments(rfqId: number, vendorId: number) { + try { + if (!rfqId || rfqId <= 0 || !vendorId || vendorId <= 0) { + return { + success: false, + error: "유효하지 않은 RFQ ID 또는 벤더 ID입니다", + data: [] + } + } + + // 먼저 해당 rfq에 연결된 벤더의 모든응답들을 찾기 + const vendorResponses = await db + .select({ + id: rfqLastVendorResponses.id, + status: rfqLastVendorResponses.status, + responseVersion: rfqLastVendorResponses.responseVersion, + }) + .from(rfqLastVendorResponses) + .where( + and( + eq(rfqLastVendorResponses.rfqsLastId, rfqId), + eq(rfqLastVendorResponses.vendorId, vendorId) + ) + ) + + if (vendorResponses.length === 0) { + return { + success: true, + data: [] + } + } + + const responseIds = vendorResponses.map(r => r.id) + + // 해당 응답들의 모든 첨부파일 조회 + const data = await db + .select({ + // 첨부파일 메인 정보 + id: rfqLastVendorAttachments.id, + vendorResponseId: rfqLastVendorAttachments.vendorResponseId, + attachmentType: rfqLastVendorAttachments.attachmentType, + documentNo: rfqLastVendorAttachments.documentNo, + + // 파일 정보 + fileName: rfqLastVendorAttachments.fileName, + originalFileName: rfqLastVendorAttachments.originalFileName, + filePath: rfqLastVendorAttachments.filePath, + fileSize: rfqLastVendorAttachments.fileSize, + fileType: rfqLastVendorAttachments.fileType, + + // 파일 설명 + description: rfqLastVendorAttachments.description, + + // 유효기간 + validFrom: rfqLastVendorAttachments.validFrom, + validTo: rfqLastVendorAttachments.validTo, + + // 업로드 정보 + uploadedBy: rfqLastVendorAttachments.uploadedBy, + uploadedAt: rfqLastVendorAttachments.uploadedAt, + + // 업로더 정보 + uploadedByName: users.name, + + // 응답 정보 + responseStatus: rfqLastVendorResponses.status, + responseVersion: rfqLastVendorResponses.responseVersion, + }) + .from(rfqLastVendorAttachments) + .leftJoin( + rfqLastVendorResponses, + eq(rfqLastVendorAttachments.vendorResponseId, rfqLastVendorResponses.id) + ) + .leftJoin(users, eq(rfqLastVendorAttachments.uploadedBy, users.id)) + .where(inArray(rfqLastVendorAttachments.vendorResponseId, responseIds)) + .orderBy( + desc(rfqLastVendorAttachments.uploadedAt), + rfqLastVendorAttachments.attachmentType + ) + + return { + data, + success: true + } + } catch (err) { + console.error("getVendorResponseAttachments error:", err) + return { + data: [], + success: false, + error: "첨부파일 조회 중 오류가 발생했습니다" + } + } +} + // 벤더 추가 액션 diff --git a/lib/rfq-last/vendor-response/editor/attachments-upload.tsx b/lib/rfq-last/vendor-response/editor/attachments-upload.tsx index b85407ff..96a88ef7 100644 --- a/lib/rfq-last/vendor-response/editor/attachments-upload.tsx +++ b/lib/rfq-last/vendor-response/editor/attachments-upload.tsx @@ -33,7 +33,8 @@ import { FileCheck, Calculator, Wrench, - X + X, + CheckCircle } from "lucide-react" import { formatBytes } from "@/lib/utils" import { cn } from "@/lib/utils" @@ -52,6 +53,7 @@ interface AttachmentsUploadProps { onExistingAttachmentsChange?: (files: any[]) => void responseId?: number userId?: number + isSubmitted?: boolean } const acceptedFileTypes = { @@ -67,7 +69,8 @@ export default function AttachmentsUpload({ existingAttachments = [], onExistingAttachmentsChange, responseId, - userId + userId, + isSubmitted = false }: AttachmentsUploadProps) { const purchaseInputRef = useRef(null) const designInputRef = useRef(null) @@ -271,6 +274,16 @@ export default function AttachmentsUpload({ return (
+ {/* 제출완료 상태 알림 */} + {isSubmitted && ( + + + + 제출완료: 견적서가 이미 제출되어 추가 파일 업로드가 제한됩니다. + + + )} + {/* 필수 파일 안내 */} @@ -312,13 +325,14 @@ export default function AttachmentsUpload({

@@ -328,11 +342,15 @@ export default function AttachmentsUpload({ type="button" variant="outline" size="sm" - onClick={() => purchaseInputRef.current?.click()} - className="border-blue-500 text-blue-600 hover:bg-blue-50" + onClick={() => !isSubmitted && purchaseInputRef.current?.click()} + className={cn( + "border-blue-500 text-blue-600 hover:bg-blue-50", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + disabled={isSubmitted} > - 구매 문서 선택 + {isSubmitted ? "제출완료" : "구매 문서 선택"}

@@ -382,11 +401,15 @@ export default function AttachmentsUpload({ type="button" variant="outline" size="sm" - onClick={() => designInputRef.current?.click()} - className="border-green-500 text-green-600 hover:bg-green-50" + onClick={() => !isSubmitted && designInputRef.current?.click()} + className={cn( + "border-green-500 text-green-600 hover:bg-green-50", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + disabled={isSubmitted} > - 설계 문서 선택 + {isSubmitted ? "제출완료" : "설계 문서 선택"} handleDeleteClick(file, true, index)} + onClick={() => !isSubmitted && handleDeleteClick(file, true, index)} + className={isSubmitted ? "opacity-50 cursor-not-allowed" : ""} + disabled={isSubmitted} > @@ -523,8 +548,12 @@ export default function AttachmentsUpload({ type="button" variant={file.attachmentType === "구매" ? "default" : "ghost"} size="sm" - className="h-7 px-2 text-xs" - onClick={() => handleTypeChange(index, "구매")} + className={cn( + "h-7 px-2 text-xs", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + onClick={() => !isSubmitted && handleTypeChange(index, "구매")} + disabled={isSubmitted} > 구매 @@ -533,8 +562,12 @@ export default function AttachmentsUpload({ type="button" variant={file.attachmentType === "설계" ? "default" : "ghost"} size="sm" - className="h-7 px-2 text-xs" - onClick={() => handleTypeChange(index, "설계")} + className={cn( + "h-7 px-2 text-xs", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + onClick={() => !isSubmitted && handleTypeChange(index, "설계")} + disabled={isSubmitted} > 설계 @@ -552,7 +585,9 @@ export default function AttachmentsUpload({ type="button" variant="ghost" size="sm" - onClick={() => handleDeleteClick(file, false, index)} + onClick={() => !isSubmitted && handleDeleteClick(file, false, index)} + className={isSubmitted ? "opacity-50 cursor-not-allowed" : ""} + disabled={isSubmitted} > diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx index 55549a6d..dc5564e2 100644 --- a/lib/rfq-last/vendor/rfq-vendor-table.tsx +++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx @@ -1599,7 +1599,7 @@ export function RfqVendorTable({ ) : ( <> - RFQ 발송 ({shortListCount}) + RFQ 발송 ({selectedRows.length}) )} diff --git a/lib/rfq-last/vendor/vendor-detail-dialog.tsx b/lib/rfq-last/vendor/vendor-detail-dialog.tsx index 08288dd6..7946e371 100644 --- a/lib/rfq-last/vendor/vendor-detail-dialog.tsx +++ b/lib/rfq-last/vendor/vendor-detail-dialog.tsx @@ -595,6 +595,7 @@ export function VendorResponseDetailDialog({ 단위 단가 금액 + 통화 납기일 @@ -612,6 +613,9 @@ export function VendorResponseDetailDialog({ {new Intl.NumberFormat("ko-KR").format(item.totalPrice)} + + {response?.pricing?.vendorCurrency || data.currency || item.currency} + {item.vendorDeliveryDate ? format(new Date(item.vendorDeliveryDate), "MM-dd") @@ -662,7 +666,7 @@ export function VendorResponseDetailDialog({

- + */}