diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-25 05:00:10 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-25 05:00:10 +0000 |
| commit | ca6545ad76c548a3202e0deee1e2b1dde51bc413 (patch) | |
| tree | e18ed085c205b602d228b52a289487bea716f442 /lib/techsales-rfq | |
| parent | 6824e097d768f724cf439b410ccfb1ab9685ac98 (diff) | |
(최겸) 기술영업 rfq 수정(리비전 오류 및 첨부파일 오류 수정)+스키마 일부 변경
Diffstat (limited to 'lib/techsales-rfq')
4 files changed, 63 insertions, 51 deletions
diff --git a/lib/techsales-rfq/service.ts b/lib/techsales-rfq/service.ts index 25e1f379..ffa29acd 100644 --- a/lib/techsales-rfq/service.ts +++ b/lib/techsales-rfq/service.ts @@ -955,27 +955,32 @@ export async function submitTechSalesVendorQuotation(data: { // }); // } - // 항상 revision 저장 (변경사항 여부와 관계없이) - await tx.insert(techSalesVendorQuotationRevisions).values({ - quotationId: data.id, - version: currentQuotation.quotationVersion || 1, - snapshot: { - currency: currentQuotation.currency, - totalPrice: currentQuotation.totalPrice, - validUntil: currentQuotation.validUntil, - remark: currentQuotation.remark, - status: currentQuotation.status, - quotationVersion: currentQuotation.quotationVersion, - submittedAt: currentQuotation.submittedAt, - acceptedAt: currentQuotation.acceptedAt, - updatedAt: currentQuotation.updatedAt, - }, - changeReason: "견적서 제출", - revisedBy: data.updatedBy, - }); + // 첫 제출인지 확인 (quotationVersion이 null인 경우) + const isFirstSubmission = currentQuotation.quotationVersion === null; + + // 첫 제출이 아닌 경우에만 revision 저장 (변경사항 이력 관리) + if (!isFirstSubmission) { + await tx.insert(techSalesVendorQuotationRevisions).values({ + quotationId: data.id, + version: currentQuotation.quotationVersion || 1, + snapshot: { + currency: currentQuotation.currency, + totalPrice: currentQuotation.totalPrice, + validUntil: currentQuotation.validUntil, + remark: currentQuotation.remark, + status: currentQuotation.status, + quotationVersion: currentQuotation.quotationVersion, + submittedAt: currentQuotation.submittedAt, + acceptedAt: currentQuotation.acceptedAt, + updatedAt: currentQuotation.updatedAt, + }, + changeReason: "견적서 제출", + revisedBy: data.updatedBy, + }); + } - // 새로운 버전 번호 계산 (항상 1 증가) - const newRevisionId = (currentQuotation.quotationVersion || 1) + 1; + // 새로운 버전 번호 계산 (첫 제출은 1, 재제출은 1 증가) + const newRevisionId = isFirstSubmission ? 1 : (currentQuotation.quotationVersion || 1) + 1; // 새로운 버전으로 업데이트 const result = await tx @@ -1177,7 +1182,7 @@ export async function getVendorQuotations(input: { }); } - // 조인을 포함한 데이터 조회 + // 조인을 포함한 데이터 조회 (중복 제거를 위해 techSalesAttachments JOIN 제거) const data = await db .select({ id: techSalesVendorQuotations.id, @@ -1211,16 +1216,16 @@ export async function getVendorQuotations(input: { FROM tech_sales_rfq_items WHERE tech_sales_rfq_items.rfq_id = ${techSalesRfqs.id} )`, - // RFQ 첨부파일 개수 + // RFQ 첨부파일 개수 (RFQ_COMMON 타입만 카운트) attachmentCount: sql<number>`( SELECT COUNT(*) FROM tech_sales_attachments WHERE tech_sales_attachments.tech_sales_rfq_id = ${techSalesRfqs.id} + AND tech_sales_attachments.attachment_type = 'RFQ_COMMON' )`, }) .from(techSalesVendorQuotations) .leftJoin(techSalesRfqs, eq(techSalesVendorQuotations.rfqId, techSalesRfqs.id)) - .leftJoin(techSalesAttachments, eq(techSalesRfqs.id, techSalesAttachments.techSalesRfqId)) .leftJoin(biddingProjects, eq(techSalesRfqs.biddingProjectId, biddingProjects.id)) .where(finalWhere) .orderBy(...orderBy) @@ -2727,12 +2732,14 @@ export async function addTechVendorsToTechSalesRfq(input: { } // 🔥 중요: 벤더 추가 시에는 견적서를 생성하지 않고, "Assigned" 상태로만 생성 + // quotation_version은 null로 설정하여 벤더가 실제 견적 제출 시에만 리비전 생성 const [quotation] = await tx .insert(techSalesVendorQuotations) .values({ rfqId: input.rfqId, vendorId: vendorId, status: "Assigned", // Draft가 아닌 Assigned 상태로 생성 + quotationVersion: null, // 리비전은 견적 제출 시에만 생성 createdBy: input.createdBy, updatedBy: input.createdBy, }) diff --git a/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx b/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx index 7832fa2b..0195b10c 100644 --- a/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx +++ b/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx @@ -119,12 +119,12 @@ function QuotationCard({ {statusInfo.label}
</Badge>
</div>
- {changeReason && (
+ {/* {changeReason && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<FileText className="size-4" />
<span>{changeReason}</span>
</div>
- )}
+ )} */}
</CardHeader>
<CardContent className="space-y-3">
<div className="grid grid-cols-2 gap-4">
@@ -250,7 +250,7 @@ export function QuotationHistoryDialog({ return (
<Dialog open={open} onOpenChange={handleOpenChange}>
- <DialogContent className=" max-h-[80vh] overflow-y-auto">
+ <DialogContent className="w-[80vw] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>견적서 수정 히스토리</DialogTitle>
<DialogDescription>
@@ -258,7 +258,7 @@ export function QuotationHistoryDialog({ </DialogDescription>
</DialogHeader>
- <div className="space-y-4">
+ <div className="space-y-4 overflow-x-auto">
{isLoading ? (
<div className="space-y-4">
{[1, 2, 3].map((i) => (
diff --git a/lib/techsales-rfq/table/tech-sales-rfq-attachments-sheet.tsx b/lib/techsales-rfq/table/tech-sales-rfq-attachments-sheet.tsx index a7b487e1..3b0fd38d 100644 --- a/lib/techsales-rfq/table/tech-sales-rfq-attachments-sheet.tsx +++ b/lib/techsales-rfq/table/tech-sales-rfq-attachments-sheet.tsx @@ -49,7 +49,7 @@ import { import prettyBytes from "pretty-bytes" import { formatDate } from "@/lib/utils" -import { processTechSalesRfqAttachments, getTechSalesRfqAttachments } from "@/lib/techsales-rfq/service" +import { processTechSalesRfqAttachments } from "@/lib/techsales-rfq/service" const MAX_FILE_SIZE = 6e8 // 600MB @@ -113,6 +113,8 @@ interface TechSalesRfqAttachmentsSheetProps rfq: TechSalesRfq | null /** 첨부파일 타입 */ attachmentType?: "RFQ_COMMON" | "TBE_RESULT" | "CBE_RESULT" + /** 읽기 전용 모드 (벤더용) */ + readOnly?: boolean /** 업로드/삭제 후 상위 테이블에 attachmentCount 등을 업데이트하기 위한 콜백 */ // onAttachmentsUpdated?: (rfqId: number, newAttachmentCount: number) => void @@ -123,6 +125,7 @@ export function TechSalesRfqAttachmentsSheet({ // onAttachmentsUpdated, rfq, attachmentType = "RFQ_COMMON", + readOnly = false, ...props }: TechSalesRfqAttachmentsSheetProps) { const [isPending, setIsPending] = React.useState(false) @@ -135,24 +138,24 @@ export function TechSalesRfqAttachmentsSheet({ title: "TBE 결과 첨부파일", description: "기술 평가(TBE) 결과 파일을 관리합니다.", fileTypeLabel: "TBE 결과", - canEdit: true + canEdit: !readOnly } case "CBE_RESULT": return { title: "CBE 결과 첨부파일", description: "상업성 평가(CBE) 결과 파일을 관리합니다.", fileTypeLabel: "CBE 결과", - canEdit: true + canEdit: !readOnly } default: // RFQ_COMMON return { title: "RFQ 첨부파일", - description: "RFQ 공통 첨부파일을 관리합니다.", + description: readOnly ? "RFQ 공통 첨부파일을 조회합니다." : "RFQ 공통 첨부파일을 관리합니다.", fileTypeLabel: "공통", - canEdit: true + canEdit: !readOnly } } - }, [attachmentType, rfq?.status]) + }, [attachmentType, readOnly]) // // RFQ 상태에 따른 편집 가능 여부 결정 (readOnly prop이 true면 항상 false) // const isEditable = React.useMemo(() => { diff --git a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx index 4c5cdf8e..e79d7c4d 100644 --- a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx +++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx @@ -218,7 +218,7 @@ export function VendorQuotationsTable({ vendorId, rfqType }: VendorQuotationsTab return data; }, [data.length, data.map(item => `${item.id}-${item.status}-${item.updatedAt}`).join(',')]); - // 첨부파일 시트 열기 함수 + // 첨부파일 시트 열기 함수 (벤더는 RFQ_COMMON 타입만 조회) const openAttachmentsSheet = React.useCallback(async (rfqId: number) => { try { // RFQ 정보 조회 (data에서 rfqId에 해당하는 데이터 찾기) @@ -236,20 +236,22 @@ export function VendorQuotationsTable({ vendorId, rfqType }: VendorQuotationsTab return } - // API 응답을 ExistingTechSalesAttachment 형식으로 변환 - const attachments: ExistingTechSalesAttachment[] = result.data.map(att => ({ - id: att.id, - techSalesRfqId: att.techSalesRfqId || rfqId, - fileName: att.fileName, - originalFileName: att.originalFileName, - filePath: att.filePath, - fileSize: att.fileSize || undefined, - fileType: att.fileType || undefined, - attachmentType: att.attachmentType as "RFQ_COMMON" | "VENDOR_SPECIFIC", - description: att.description || undefined, - createdBy: att.createdBy, - createdAt: att.createdAt, - })) + // API 응답을 ExistingTechSalesAttachment 형식으로 변환하고 RFQ_COMMON 타입만 필터링 + const attachments: ExistingTechSalesAttachment[] = result.data + .filter(att => att.attachmentType === "RFQ_COMMON") // 벤더는 RFQ_COMMON 타입만 조회 + .map(att => ({ + id: att.id, + techSalesRfqId: att.techSalesRfqId || rfqId, + fileName: att.fileName, + originalFileName: att.originalFileName, + filePath: att.filePath, + fileSize: att.fileSize || undefined, + fileType: att.fileType || undefined, + attachmentType: att.attachmentType as "RFQ_COMMON" | "TBE_RESULT" | "CBE_RESULT", + description: att.description || undefined, + createdBy: att.createdBy, + createdAt: att.createdAt, + })) setAttachmentsDefault(attachments) setSelectedRfqForAttachments({ @@ -383,7 +385,7 @@ export function VendorQuotationsTable({ vendorId, rfqType }: VendorQuotationsTab // useDataTable 훅 사용 const { table } = useDataTable({ data: stableData, - columns: columns as any, + columns: columns as any, // 타입 오류 임시 해결 pageCount, rowCount: total, filterFields, @@ -488,8 +490,8 @@ export function VendorQuotationsTable({ vendorId, rfqType }: VendorQuotationsTab onOpenChange={setAttachmentsOpen} defaultAttachments={attachmentsDefault} rfq={selectedRfqForAttachments} - onAttachmentsUpdated={() => {}} // 읽기 전용이므로 빈 함수 - readOnly={true} // 벤더 쪽에서는 항상 읽기 전용 + attachmentType="RFQ_COMMON" // 벤더는 RFQ_COMMON 타입만 조회 + readOnly={true} // 벤더는 항상 읽기 전용 /> {/* 아이템 보기 다이얼로그 */} |
