summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-10-14 15:13:43 +0900
committerjoonhoekim <26rote@gmail.com>2025-10-14 15:13:43 +0900
commitb61931f50ba63f9a91ac8eb01fd91aa6e2fb6119 (patch)
tree35aaa7b9d3c509236d94490deb24271133ddbbaa
parent598a40808b45413b373334092c3b60cab97d09b6 (diff)
parentdfc898b298e16bf686d2929db0eee50abe87881d (diff)
(김준회) AVL, Vendorpool 관련 요구사항 처리
-rw-r--r--lib/rfq-last/attachment/rfq-attachments-table.tsx4
-rw-r--r--lib/rfq-last/attachment/vendor-response-table.tsx4
-rw-r--r--lib/rfq-last/service.ts97
-rw-r--r--lib/rfq-last/vendor-response/editor/attachments-upload.tsx87
-rw-r--r--lib/rfq-last/vendor/rfq-vendor-table.tsx2
-rw-r--r--lib/rfq-last/vendor/vendor-detail-dialog.tsx8
6 files changed, 169 insertions, 33 deletions
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({
<DropdownMenuItem onClick={() => handleAction({ type: "download", row })}>
다운로드
</DropdownMenuItem>
- <DropdownMenuItem onClick={() => handleAction({ type: "preview", row })}>
+ {/* <DropdownMenuItem onClick={() => handleAction({ type: "preview", row })}>
미리보기
- </DropdownMenuItem>
+ </DropdownMenuItem> */}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => 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({
<Download className="mr-2 h-4 w-4" />
다운로드
</DropdownMenuItem>
- <DropdownMenuItem onClick={() => handleAction({ row, type: "preview" })}>
+ {/* <DropdownMenuItem onClick={() => handleAction({ row, type: "preview" })}>
<Eye className="mr-2 h-4 w-4" />
미리보기
- </DropdownMenuItem>
+ </DropdownMenuItem> */}
</DropdownMenuContent>
</DropdownMenu>
);
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<HTMLInputElement>(null)
const designInputRef = useRef<HTMLInputElement>(null)
@@ -271,6 +274,16 @@ export default function AttachmentsUpload({
return (
<div className="space-y-4">
+ {/* 제출완료 상태 알림 */}
+ {isSubmitted && (
+ <Alert>
+ <CheckCircle className="h-4 w-4" />
+ <AlertDescription>
+ <strong>제출완료:</strong> 견적서가 이미 제출되어 추가 파일 업로드가 제한됩니다.
+ </AlertDescription>
+ </Alert>
+ )}
+
{/* 필수 파일 안내 */}
<Alert>
<AlertCircle className="h-4 w-4" />
@@ -312,13 +325,14 @@ export default function AttachmentsUpload({
<div
className={cn(
"border-2 border-dashed rounded-lg p-6 text-center transition-colors",
- purchaseDragActive ? "border-blue-500 bg-blue-50" : "border-gray-300",
- "hover:border-blue-400 hover:bg-blue-50/50"
+ purchaseDragActive && !isSubmitted ? "border-blue-500 bg-blue-50" : "border-gray-300",
+ !isSubmitted && "hover:border-blue-400 hover:bg-blue-50/50",
+ isSubmitted && "opacity-50 cursor-not-allowed bg-gray-50"
)}
- onDragEnter={handlePurchaseDrag}
- onDragLeave={handlePurchaseDrag}
- onDragOver={handlePurchaseDrag}
- onDrop={handlePurchaseDrop}
+ onDragEnter={!isSubmitted ? handlePurchaseDrag : undefined}
+ onDragLeave={!isSubmitted ? handlePurchaseDrag : undefined}
+ onDragOver={!isSubmitted ? handlePurchaseDrag : undefined}
+ onDrop={!isSubmitted ? handlePurchaseDrop : undefined}
>
<Calculator className="mx-auto h-10 w-10 text-blue-500 mb-3" />
<p className="text-sm text-gray-600 mb-2">
@@ -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}
>
<Paperclip className="h-4 w-4 mr-2" />
- 구매 문서 선택
+ {isSubmitted ? "제출완료" : "구매 문서 선택"}
</Button>
<input
ref={purchaseInputRef}
@@ -366,13 +384,14 @@ export default function AttachmentsUpload({
<div
className={cn(
"border-2 border-dashed rounded-lg p-6 text-center transition-colors",
- designDragActive ? "border-green-500 bg-green-50" : "border-gray-300",
- "hover:border-green-400 hover:bg-green-50/50"
+ designDragActive && !isSubmitted ? "border-green-500 bg-green-50" : "border-gray-300",
+ !isSubmitted && "hover:border-green-400 hover:bg-green-50/50",
+ isSubmitted && "opacity-50 cursor-not-allowed bg-gray-50"
)}
- onDragEnter={handleDesignDrag}
- onDragLeave={handleDesignDrag}
- onDragOver={handleDesignDrag}
- onDrop={handleDesignDrop}
+ onDragEnter={!isSubmitted ? handleDesignDrag : undefined}
+ onDragLeave={!isSubmitted ? handleDesignDrag : undefined}
+ onDragOver={!isSubmitted ? handleDesignDrag : undefined}
+ onDrop={!isSubmitted ? handleDesignDrop : undefined}
>
<Wrench className="mx-auto h-10 w-10 text-green-500 mb-3" />
<p className="text-sm text-gray-600 mb-2">
@@ -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}
>
<Paperclip className="h-4 w-4 mr-2" />
- 설계 문서 선택
+ {isSubmitted ? "제출완료" : "설계 문서 선택"}
</Button>
<input
ref={designInputRef}
@@ -494,7 +517,9 @@ export default function AttachmentsUpload({
type="button"
variant="ghost"
size="sm"
- onClick={() => handleDeleteClick(file, true, index)}
+ onClick={() => !isSubmitted && handleDeleteClick(file, true, index)}
+ className={isSubmitted ? "opacity-50 cursor-not-allowed" : ""}
+ disabled={isSubmitted}
>
<Trash2 className="h-4 w-4 text-red-500" />
</Button>
@@ -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}
>
<Calculator className="h-3 w-3 mr-1" />
구매
@@ -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}
>
<Wrench className="h-3 w-3 mr-1" />
설계
@@ -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}
>
<Trash2 className="h-4 w-4 text-red-500" />
</Button>
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({
) : (
<>
<Send className="h-4 w-4 mr-2" />
- RFQ 발송 ({shortListCount})
+ RFQ 발송 ({selectedRows.length})
</>
)}
</Button>
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({
<TableHead>단위</TableHead>
<TableHead className="text-right">단가</TableHead>
<TableHead className="text-right">금액</TableHead>
+ <TableHead>통화</TableHead>
<TableHead>납기일</TableHead>
</TableRow>
</TableHeader>
@@ -613,6 +614,9 @@ export function VendorResponseDetailDialog({
{new Intl.NumberFormat("ko-KR").format(item.totalPrice)}
</TableCell>
<TableCell>
+ {response?.pricing?.vendorCurrency || data.currency || item.currency}
+ </TableCell>
+ <TableCell>
{item.vendorDeliveryDate
? format(new Date(item.vendorDeliveryDate), "MM-dd")
: "-"}
@@ -662,7 +666,7 @@ export function VendorResponseDetailDialog({
</div>
</div>
<div className="flex items-center gap-2">
- <Button
+ {/* <Button
variant="ghost"
size="sm"
onClick={() => {
@@ -671,7 +675,7 @@ export function VendorResponseDetailDialog({
}}
>
<Eye className="h-4 w-4" />
- </Button>
+ </Button> */}
<Button
variant="ghost"
size="sm"