From 1540eac291761ffd8fc1947ed626e4e4a4407922 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 17 Oct 2025 08:08:33 +0000 Subject: (최겸) 견적입찰 비교관련 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rfq-last/compare-action.ts | 236 +++++++---- lib/rfq-last/quotation-compare-view.tsx | 466 ++++++++++++++------- .../editor/commercial-terms-form.tsx | 7 +- lib/rfq-last/vendor/rfq-vendor-table.tsx | 6 +- 4 files changed, 471 insertions(+), 244 deletions(-) (limited to 'lib') diff --git a/lib/rfq-last/compare-action.ts b/lib/rfq-last/compare-action.ts index 1a50a373..57f8f00f 100644 --- a/lib/rfq-last/compare-action.ts +++ b/lib/rfq-last/compare-action.ts @@ -1,17 +1,17 @@ "use server"; import db from "@/db/db"; -import { eq, and, inArray, ne, asc, desc } from "drizzle-orm"; +import { eq, and, inArray, ne, asc, isNotNull } from "drizzle-orm"; import { rfqsLast, rfqLastDetails, rfqPrItems, rfqLastVendorResponses, rfqLastVendorQuotationItems, + rfqLastVendorAttachments, vendors, paymentTerms, incoterms, - vendorSelections, users } from "@/db/schema"; import { revalidatePath } from "next/cache"; @@ -62,22 +62,22 @@ export interface VendorResponseVersion { // 벤더 제안 조건 vendorConditions: { - currency?: string; - paymentTermsCode?: string; - paymentTermsDesc?: string; - incotermsCode?: string; - incotermsDesc?: string; + currency?: string | null; + paymentTermsCode?: string | null; + paymentTermsDesc?: string | null; + incotermsCode?: string | null; + incotermsDesc?: string | null; deliveryDate?: Date | null; - contractDuration?: string; - taxCode?: string; - placeOfShipping?: string; - placeOfDestination?: string; - firstAcceptance?: "수용" | "부분수용" | "거부"; - firstDescription?: string; - sparepartAcceptance?: "수용" | "부분수용" | "거부"; - sparepartDescription?: string; - materialPriceRelatedYn?: boolean; - materialPriceRelatedReason?: string; + contractDuration?: string | null; + taxCode?: string | null; + placeOfShipping?: string | null; + placeOfDestination?: string | null; + firstAcceptance?: "수용" | "부분수용" | "거부" | null; + firstDescription?: string | null; + sparepartAcceptance?: "수용" | "부분수용" | "거부" | null; + sparepartDescription?: string | null; + materialPriceRelatedYn?: boolean | null; + materialPriceRelatedReason?: string | null; }; // 조건 차이 분석 @@ -88,8 +88,8 @@ export interface VendorResponseVersion { }; // 비고 - generalRemark?: string; - technicalProposal?: string; + generalRemark?: string | null; + technicalProposal?: string | null; // 품목별 견적 아이템 정보 추가 quotationItems?: { @@ -98,11 +98,14 @@ export interface VendorResponseVersion { totalPrice: number; currency: string; quantity: number; - deliveryDate?: Date | null; - leadTime?: number; - manufacturer?: string; - modelNo?: string; + deliveryDate: Date | null | undefined; + leadTime: number | null | undefined; + manufacturer: string | null | undefined; + modelNo: string | null | undefined; }[]; + + // 첨부파일 정보 + attachments?: VendorAttachment[]; } export interface VendorComparison { @@ -119,14 +122,14 @@ export interface VendorComparison { incotermsCode: string; incotermsDesc?: string; deliveryDate: Date | null; - contractDuration?: string; - taxCode?: string; - placeOfShipping?: string; - placeOfDestination?: string; + contractDuration?: string | null; + taxCode?: string | null; + placeOfShipping?: string | null; + placeOfDestination?: string | null; firstYn: boolean; - firstDescription?: string; + firstDescription?: string | null; sparepartYn: boolean; - sparepartDescription?: string; + sparepartDescription?: string | null; materialPriceRelatedYn: boolean; }; @@ -150,22 +153,22 @@ export interface VendorComparison { // 레거시 호환: 최신 응답의 조건 정보 vendorConditions: { - currency?: string; - paymentTermsCode?: string; - paymentTermsDesc?: string; - incotermsCode?: string; - incotermsDesc?: string; + currency?: string | null; + paymentTermsCode?: string | null; + paymentTermsDesc?: string | null; + incotermsCode?: string | null; + incotermsDesc?: string | null; deliveryDate?: Date | null; - contractDuration?: string; - taxCode?: string; - placeOfShipping?: string; - placeOfDestination?: string; - firstAcceptance?: "수용" | "부분수용" | "거부"; - firstDescription?: string; - sparepartAcceptance?: "수용" | "부분수용" | "거부"; - sparepartDescription?: string; - materialPriceRelatedYn?: boolean; - materialPriceRelatedReason?: string; + contractDuration?: string | null; + taxCode?: string | null; + placeOfShipping?: string | null; + placeOfDestination?: string | null; + firstAcceptance?: "수용" | "부분수용" | "거부" | null; + firstDescription?: string | null; + sparepartAcceptance?: "수용" | "부분수용" | "거부" | null; + sparepartDescription?: string | null; + materialPriceRelatedYn?: boolean | null; + materialPriceRelatedReason?: string | null; }; // 레거시 호환: 최신 응답의 조건 차이 분석 @@ -176,23 +179,23 @@ export interface VendorComparison { }; // 레거시 호환: 최신 응답의 비고 - generalRemark?: string; - technicalProposal?: string; + generalRemark?: string | null; + technicalProposal?: string | null; // 선정 관련 정보 isSelected?: boolean; selectionDate?: Date | null; - selectionReason?: string; - selectedBy?: number; + selectionReason?: string | null; + selectedBy?: number | null; selectedByName?: string; selectionApprovalStatus?: "대기" | "승인" | "반려" | null; - selectionApprovedBy?: number; + selectionApprovedBy?: number | null; selectionApprovedAt?: Date | null; - selectionApprovalComment?: string; + selectionApprovalComment?: string | null; // 계약 관련 정보 추가 - contractStatus?: string; - contractNo?: string; + contractStatus?: string | null; + contractNo?: string | null; contractCreatedAt?: Date | null; } @@ -202,9 +205,14 @@ export interface PrItemComparison { prItem: string; materialCode: string; materialDescription: string; + materialCategory?: string; requestedQuantity: number; uom: string; + size?: string; + grossWeight?: number; + gwUom?: string; requestedDeliveryDate: Date | null; + remark?: string; vendorQuotes: { vendorId: number; @@ -231,6 +239,23 @@ export interface PrItemComparison { }; } +export interface VendorAttachment { + id: number; + attachmentType: string; + documentNo?: string; + fileName: string; + originalFileName: string; + filePath: string; + fileSize?: number; + fileType?: string; + description?: string; + validFrom?: Date | null; + validTo?: Date | null; + uploadedBy: number; + uploadedAt: Date; + uploaderName?: string; +} + // ===== 메인 조회 함수 ===== export async function getComparisonData( @@ -307,7 +332,7 @@ export async function getComparisonData( ) .where(inArray(vendors.id, vendorIds)); - // 2b. 모든 차수의 벤더 응답 조회 (isLatest 조건 제거) + // 2b. 벤더가 실제 제출한 응답만 조회 (submittedAt이 null이 아닌 것만) const allVendorResponses = await db .select({ responseId: rfqLastVendorResponses.id, @@ -347,7 +372,8 @@ export async function getComparisonData( .where( and( eq(rfqLastVendorResponses.rfqsLastId, rfqId), - inArray(rfqLastVendorResponses.vendorId, vendorIds) + inArray(rfqLastVendorResponses.vendorId, vendorIds), + isNotNull(rfqLastVendorResponses.submittedAt) // 벤더가 실제 제출한 것만 ) ) .orderBy(asc(rfqLastVendorResponses.vendorId), asc(rfqLastVendorResponses.responseVersion)); @@ -386,7 +412,7 @@ export async function getComparisonData( incotermsData.map(ic => [ic.code, ic.description]) ); - // 5. PR Items 조회 + // 5. PR Items 조회 (추가 필드 포함) const prItems = await db .select({ id: rfqPrItems.id, @@ -394,9 +420,14 @@ export async function getComparisonData( prItem: rfqPrItems.prItem, materialCode: rfqPrItems.materialCode, materialDescription: rfqPrItems.materialDescription, + materialCategory: rfqPrItems.materialCategory, quantity: rfqPrItems.quantity, uom: rfqPrItems.uom, + size: rfqPrItems.size, + grossWeight: rfqPrItems.grossWeight, + gwUom: rfqPrItems.gwUom, deliveryDate: rfqPrItems.deliveryDate, + remark: rfqPrItems.remark, }) .from(rfqPrItems) .where(eq(rfqPrItems.rfqsLastId, rfqId)); @@ -424,6 +455,31 @@ export async function getComparisonData( .where(inArray(rfqLastVendorQuotationItems.vendorResponseId, allResponseIds)) : []; + // 6b. 벤더 첨부파일 조회 (모든 응답 버전 포함) + const vendorAttachments = allResponseIds.length > 0 + ? 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, + uploaderName: users.name, + }) + .from(rfqLastVendorAttachments) + .leftJoin(users, eq(rfqLastVendorAttachments.uploadedBy, users.id)) + .where(inArray(rfqLastVendorAttachments.vendorResponseId, allResponseIds)) + : []; + // 7. 데이터 가공 및 분석 - 각 벤더별 최신 차수 기준으로 평균 계산 // 각 벤더별로 가장 높은 responseVersion을 가진 응답 찾기 const latestResponsesByVendor = new Map(); @@ -454,7 +510,7 @@ export async function getComparisonData( : 0; // 8. 벤더별 비교 데이터 구성 (차수별 응답 포함) - const vendorComparisons: VendorComparison[] = vendorData.map((v) => { + const vendorComparisons = vendorData.map((v) => { // 이 벤더의 모든 응답 가져오기 const vendorResponses = allVendorResponses.filter(r => r.vendorId === v.vendorId); @@ -501,10 +557,30 @@ export async function getComparisonData( totalPrice: q.totalPrice || 0, currency: q.currency || "USD", quantity: q.quantity || 0, - deliveryDate: q.deliveryDate, - leadTime: q.leadTime, - manufacturer: q.manufacturer, - modelNo: q.modelNo, + deliveryDate: q.deliveryDate || undefined, + leadTime: q.leadTime || undefined, + manufacturer: q.manufacturer || undefined, + modelNo: q.modelNo || undefined, + })); + + // 이 응답의 첨부파일 가져오기 + const responseAttachments = vendorAttachments + .filter(a => a.vendorResponseId === resp.responseId) + .map(a => ({ + id: a.id, + attachmentType: a.attachmentType, + documentNo: a.documentNo || undefined, + fileName: a.fileName, + originalFileName: a.originalFileName, + filePath: a.filePath, + fileSize: a.fileSize || undefined, + fileType: a.fileType || undefined, + description: a.description || undefined, + validFrom: a.validFrom || undefined, + validTo: a.validTo || undefined, + uploadedBy: a.uploadedBy, + uploadedAt: a.uploadedAt, + uploaderName: a.uploaderName || undefined, })); return { @@ -544,9 +620,10 @@ export async function getComparisonData( criticalDifferences, }, - generalRemark: resp.generalRemark, - technicalProposal: resp.technicalProposal, + generalRemark: resp.generalRemark || undefined, + technicalProposal: resp.technicalProposal || undefined, quotationItems: responseQuotationItems, + attachments: responseAttachments, }; }); @@ -567,14 +644,14 @@ export async function getComparisonData( incotermsCode: v.buyerIncotermsCode || "", incotermsDesc: incotermsMap.get(v.buyerIncotermsCode || ""), deliveryDate: v.buyerDeliveryDate, - contractDuration: v.buyerContractDuration, - taxCode: v.buyerTaxCode, - placeOfShipping: v.buyerPlaceOfShipping, - placeOfDestination: v.buyerPlaceOfDestination, + contractDuration: v.buyerContractDuration || undefined, + taxCode: v.buyerTaxCode || undefined, + placeOfShipping: v.buyerPlaceOfShipping || undefined, + placeOfDestination: v.buyerPlaceOfDestination || undefined, firstYn: v.buyerFirstYn || false, - firstDescription: v.buyerFirstDescription, + firstDescription: v.buyerFirstDescription || undefined, sparepartYn: v.buyerSparepartYn || false, - sparepartDescription: v.buyerSparepartDescription, + sparepartDescription: v.buyerSparepartDescription || undefined, materialPriceRelatedYn: v.buyerMaterialPriceRelatedYn || false, }, @@ -618,23 +695,23 @@ export async function getComparisonData( criticalDifferences: [], }, - generalRemark: latestResp?.generalRemark, - technicalProposal: latestResp?.technicalProposal, + generalRemark: latestResp?.generalRemark || undefined, + technicalProposal: latestResp?.technicalProposal || undefined, // 선정 관련 정보 isSelected: v.isSelected || false, selectionDate: v.selectionDate, - selectionReason: v.selectionReason, - selectedBy: v.selectedBy, + selectionReason: v.selectionReason || undefined, + selectedBy: v.selectedBy || undefined, selectedByName: v.isSelected ? selectedByName : undefined, - selectionApprovalStatus: v.selectionApprovalStatus, - selectionApprovedBy: v.selectionApprovedBy, + selectionApprovalStatus: v.selectionApprovalStatus || undefined, + selectionApprovedBy: v.selectionApprovedBy || undefined, selectionApprovedAt: v.selectionApprovedAt, - selectionApprovalComment: v.selectionApprovalComment, + selectionApprovalComment: v.selectionApprovalComment || undefined, // 계약 관련 정보 - contractStatus: v.contractStatus, - contractNo: v.contractNo, + contractStatus: v.contractStatus || undefined, + contractNo: v.contractNo || undefined, contractCreatedAt: v.contractCreatedAt, }; }); @@ -690,9 +767,14 @@ export async function getComparisonData( prItem: item.prItem || "", materialCode: item.materialCode || "", materialDescription: item.materialDescription || "", + materialCategory: item.materialCategory || undefined, requestedQuantity: item.quantity || 0, uom: item.uom || "", + size: item.size || undefined, + grossWeight: item.grossWeight || undefined, + gwUom: item.gwUom || undefined, requestedDeliveryDate: item.deliveryDate, + remark: item.remark || undefined, vendorQuotes: itemQuotes, priceAnalysis: { lowestPrice: Math.min(...unitPrices) || 0, diff --git a/lib/rfq-last/quotation-compare-view.tsx b/lib/rfq-last/quotation-compare-view.tsx index 723d1044..527fc4d8 100644 --- a/lib/rfq-last/quotation-compare-view.tsx +++ b/lib/rfq-last/quotation-compare-view.tsx @@ -28,6 +28,8 @@ import { X, RefreshCw, Clock, + Download, + Paperclip, } from "lucide-react"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; @@ -193,13 +195,14 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { switch (selectedContractType) { case "PO": + const poSelectionReason: string | undefined = selectedVendor.selectionReason || undefined; result = await createPO({ rfqId: data.rfqInfo.id, vendorId: selectedVendor.vendorId, vendorName: selectedVendor.vendorName, totalAmount: selectedVendor.totalAmount, currency: selectedVendor.currency, - selectionReason: selectedVendor.selectionReason, + selectionReason: poSelectionReason, }); break; @@ -712,18 +715,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { - - + + + {data.vendors.map((vendor) => ( - + + + ))} @@ -731,21 +745,28 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 통화 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; // 최신 응답 (이미 정렬되어 있음) return ( - + + + + ); })} @@ -753,42 +774,47 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 지급조건 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( - + + + ); })} @@ -796,42 +822,47 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 인코텀즈 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( - + + + ); })} @@ -839,22 +870,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 선적지 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( - + + + + ); })} @@ -862,22 +900,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 하역지 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( - + + + + ); })} @@ -885,30 +930,35 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 납기일 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( - + + + + ); })} @@ -916,22 +966,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 세금조건 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( - + + + + ); })} @@ -939,22 +996,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 계약기간 */} - {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( - + + + + ); })} @@ -980,11 +1044,13 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { + + + - - + @@ -994,15 +1060,30 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { return ( - - + + + + - ); @@ -1054,6 +1130,76 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { )} + + {/* 첨부파일 */} + {selectedResponse?.attachments && selectedResponse.attachments.length > 0 && ( +
+

+ + 제출 문서 ({selectedResponse.attachments.length}건) +

+
+ {selectedResponse.attachments.map((attachment) => ( +
+
+ +
+

+ {attachment.originalFileName} +

+
+ + {attachment.attachmentType} + + {attachment.documentNo && ( + 문서번호: {attachment.documentNo} + )} + {attachment.fileSize && ( + {(attachment.fileSize / 1024).toFixed(1)} KB + )} + {attachment.uploadedAt && ( + + 업로드: {format(new Date(attachment.uploadedAt), "yyyy-MM-dd HH:mm")} + + )} +
+ {attachment.description && ( +

+ {attachment.description} +

+ )} + {(attachment.validFrom || attachment.validTo) && ( +

+ 유효기간: {attachment.validFrom ? format(new Date(attachment.validFrom), "yyyy-MM-dd") : "-"} ~ {attachment.validTo ? format(new Date(attachment.validTo), "yyyy-MM-dd") : "-"} +

+ )} +
+
+ +
+ ))} +
+
+ )} diff --git a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx index d896ee34..b8b3a830 100644 --- a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx +++ b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx @@ -85,11 +85,14 @@ export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsF const isDifferentPaymentTerms = vendorPaymentTermsCode !== rfqDetail.paymentTermsCode const isDifferentIncoterms = vendorIncotermsCode !== rfqDetail.incotermsCode - // 날짜만 비교 (년월일만 체크) + // 날짜만 비교 (년월일만 체크) - 로컬 시간대 기준 const formatDateOnly = (date: Date | string | null) => { if (!date) return null const d = new Date(date) - return d.toISOString().split('T')[0] // YYYY-MM-DD 형식으로 변환 + const year = d.getFullYear() + const month = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` } const isDifferentDeliveryDate = !isFrameContract && formatDateOnly(vendorDeliveryDate) !== formatDateOnly(rfqDetail.deliveryDate) diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx index 428160d5..c0f80aca 100644 --- a/lib/rfq-last/vendor/rfq-vendor-table.tsx +++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx @@ -344,10 +344,6 @@ export function RfqVendorTable({ row.response?.submission?.submittedAt ); - if (vendorsWithQuotation.length < 2) { - toast.warning("비교를 위해 최소 2개 이상의 견적서가 필요합니다."); - return; - } // 견적 비교 페이지로 이동 또는 모달 열기 const vendorIds = vendorsWithQuotation @@ -1656,7 +1652,7 @@ export function RfqVendorTable({ size="sm" onClick={handleQuotationCompare} disabled={quotationCount < 1} - className={quotationCount >= 2 ? "border-blue-500 text-blue-600 hover:bg-blue-50" : ""} + className={quotationCount >= 1 ? "border-blue-500 text-blue-600 hover:bg-blue-50" : ""} > 견적 비교 -- cgit v1.2.3
항목구매자 제시항목
- {vendor.vendorName} + + + SHI 제시 + +
+ {vendor.vendorName} +
{vendor.isSelected && ( 선정 )} -
통화{data.vendors[0]?.buyerConditions.currency} -
- {latestResponse?.vendorConditions?.currency || vendor.buyerConditions.currency} - {latestResponse?.vendorConditions?.currency && latestResponse.vendorConditions.currency !== vendor.buyerConditions.currency && ( - 변경 - )} -
-
+ {vendor.buyerConditions.currency} + +
+ {latestResponse?.vendorConditions?.currency || vendor.buyerConditions.currency} + {latestResponse?.vendorConditions?.currency && latestResponse.vendorConditions.currency !== vendor.buyerConditions.currency && ( + 변경 + )} +
+
지급조건 - - - - {data.vendors[0]?.buyerConditions.paymentTermsCode} - - - {data.vendors[0]?.buyerConditions.paymentTermsDesc} - - - - -
+ +
- {latestResponse?.vendorConditions?.paymentTermsCode || vendor.buyerConditions.paymentTermsCode} + {vendor.buyerConditions.paymentTermsCode} - {latestResponse?.vendorConditions?.paymentTermsDesc || vendor.buyerConditions.paymentTermsDesc} + {vendor.buyerConditions.paymentTermsDesc} - {latestResponse?.vendorConditions?.paymentTermsCode && - latestResponse.vendorConditions.paymentTermsCode !== vendor.buyerConditions.paymentTermsCode && ( - 변경 - )} - - +
+ + + + {latestResponse?.vendorConditions?.paymentTermsCode || vendor.buyerConditions.paymentTermsCode} + + + {latestResponse?.vendorConditions?.paymentTermsDesc || vendor.buyerConditions.paymentTermsDesc} + + + + {latestResponse?.vendorConditions?.paymentTermsCode && + latestResponse.vendorConditions.paymentTermsCode !== vendor.buyerConditions.paymentTermsCode && ( + 변경 + )} +
+
인코텀즈 - - - - {data.vendors[0]?.buyerConditions.incotermsCode} - - - {data.vendors[0]?.buyerConditions.incotermsDesc} - - - - -
+ +
- {latestResponse?.vendorConditions?.incotermsCode || vendor.buyerConditions.incotermsCode} + {vendor.buyerConditions.incotermsCode} - {latestResponse?.vendorConditions?.incotermsDesc || vendor.buyerConditions.incotermsDesc} + {vendor.buyerConditions.incotermsDesc} - {latestResponse?.vendorConditions?.incotermsCode && - latestResponse.vendorConditions.incotermsCode !== vendor.buyerConditions.incotermsCode && ( - 변경 - )} - - +
+ + + + {latestResponse?.vendorConditions?.incotermsCode || vendor.buyerConditions.incotermsCode} + + + {latestResponse?.vendorConditions?.incotermsDesc || vendor.buyerConditions.incotermsDesc} + + + + {latestResponse?.vendorConditions?.incotermsCode && + latestResponse.vendorConditions.incotermsCode !== vendor.buyerConditions.incotermsCode && ( + 변경 + )} +
+
선적지{data.vendors[0]?.buyerConditions.placeOfShipping || "-"} -
- {latestResponse?.vendorConditions?.placeOfShipping || vendor.buyerConditions.placeOfShipping || "-"} - {latestResponse?.vendorConditions?.placeOfShipping && - latestResponse.vendorConditions.placeOfShipping !== vendor.buyerConditions.placeOfShipping && ( - 변경 - )} -
-
+ {vendor.buyerConditions.placeOfShipping || "-"} + +
+ {latestResponse?.vendorConditions?.placeOfShipping || vendor.buyerConditions.placeOfShipping || "-"} + {latestResponse?.vendorConditions?.placeOfShipping && + latestResponse.vendorConditions.placeOfShipping !== vendor.buyerConditions.placeOfShipping && ( + 변경 + )} +
+
하역지{data.vendors[0]?.buyerConditions.placeOfDestination || "-"} -
- {latestResponse?.vendorConditions?.placeOfDestination || vendor.buyerConditions.placeOfDestination || "-"} - {latestResponse?.vendorConditions?.placeOfDestination && - latestResponse.vendorConditions.placeOfDestination !== vendor.buyerConditions.placeOfDestination && ( - 변경 - )} -
-
+ {vendor.buyerConditions.placeOfDestination || "-"} + +
+ {latestResponse?.vendorConditions?.placeOfDestination || vendor.buyerConditions.placeOfDestination || "-"} + {latestResponse?.vendorConditions?.placeOfDestination && + latestResponse.vendorConditions.placeOfDestination !== vendor.buyerConditions.placeOfDestination && ( + 변경 + )} +
+
납기일 - {data.vendors[0]?.buyerConditions.deliveryDate - ? format(new Date(data.vendors[0].buyerConditions.deliveryDate), "yyyy-MM-dd") - : "-"} - -
- {latestResponse?.vendorConditions?.deliveryDate - ? format(new Date(latestResponse.vendorConditions.deliveryDate), "yyyy-MM-dd") - : vendor.buyerConditions.deliveryDate - ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd") - : "-"} - {latestResponse?.vendorConditions?.deliveryDate && vendor.buyerConditions.deliveryDate && - new Date(latestResponse.vendorConditions.deliveryDate).getTime() !== new Date(vendor.buyerConditions.deliveryDate).getTime() && ( - 변경 - )} -
-
+ {vendor.buyerConditions.deliveryDate + ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd") + : "-"} + +
+ {latestResponse?.vendorConditions?.deliveryDate + ? format(new Date(latestResponse.vendorConditions.deliveryDate), "yyyy-MM-dd") + : vendor.buyerConditions.deliveryDate + ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd") + : "-"} + {/* {latestResponse?.vendorConditions?.deliveryDate && vendor.buyerConditions.deliveryDate && + new Date(latestResponse.vendorConditions.deliveryDate).getTime() !== new Date(vendor.buyerConditions.deliveryDate).getTime() && ( + 변경 + )} */} +
+
세금조건{data.vendors[0]?.buyerConditions.taxCode || "-"} -
- {latestResponse?.vendorConditions?.taxCode || vendor.buyerConditions.taxCode || "-"} - {latestResponse?.vendorConditions?.taxCode && - latestResponse.vendorConditions.taxCode !== vendor.buyerConditions.taxCode && ( - 변경 - )} -
-
+ {vendor.buyerConditions.taxCode || "-"} + +
+ {latestResponse?.vendorConditions?.taxCode || vendor.buyerConditions.taxCode || "-"} + {latestResponse?.vendorConditions?.taxCode && + latestResponse.vendorConditions.taxCode !== vendor.buyerConditions.taxCode && ( + 변경 + )} +
+
계약기간{data.vendors[0]?.buyerConditions.contractDuration || "-"} -
- {latestResponse?.vendorConditions?.contractDuration || vendor.buyerConditions.contractDuration || "-"} - {latestResponse?.vendorConditions?.contractDuration && - latestResponse.vendorConditions.contractDuration !== vendor.buyerConditions.contractDuration && ( - 변경 - )} -
-
+ {vendor.buyerConditions.contractDuration || "-"} + +
+ {latestResponse?.vendorConditions?.contractDuration || vendor.buyerConditions.contractDuration || "-"} + {latestResponse?.vendorConditions?.contractDuration && + latestResponse.vendorConditions.contractDuration !== vendor.buyerConditions.contractDuration && ( + 변경 + )} +
+
품목코드 품목명자재분류 수량규격중량 단가 총액납기제조사납기일자
{prItem.materialCode} -

{prItem.materialDescription}

+
+

{prItem.materialCode}

{prItem.prNo} • {prItem.prItem}

+

{prItem.materialDescription}

+ {prItem.remark && ( +

{prItem.remark}

+ )} +
+ {prItem.materialCategory || "-"} + - {quoteItem.quantity} {prItem.uom} +

{quoteItem.quantity} {prItem.uom}

+

요청: {prItem.requestedQuantity}

+
+ {prItem.size || "-"} + + {prItem.grossWeight ? `${prItem.grossWeight} ${prItem.gwUom || ""}` : "-"} {formatAmount(quoteItem.unitPrice, quoteItem.currency)} @@ -1016,16 +1097,11 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { : quoteItem.leadTime ? `${quoteItem.leadTime}일` : "-"} - - {quoteItem.manufacturer ? ( -
-

{quoteItem.manufacturer}

- {quoteItem.modelNo && ( -

{quoteItem.modelNo}

- )} -
- ) : "-"} + {prItem.requestedDeliveryDate && ( +

+ 요청: {format(new Date(prItem.requestedDeliveryDate), "yyyy-MM-dd")} +

+ )}