From 4863ba5d336297dddcc8d6d4b414beceb5559742 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 8 Dec 2025 03:03:26 +0000 Subject: (최겸) 기술영업 rfq eml첨부기능 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/detail-table/rfq-detail-table.tsx | 47 +++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) (limited to 'lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx') diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx index 72f03dc3..aee15594 100644 --- a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx +++ b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx @@ -19,6 +19,7 @@ import { VendorCommunicationDrawer } from "./vendor-communication-drawer" import { DeleteVendorDialog } from "./delete-vendors-dialog" import { QuotationHistoryDialog } from "@/lib/techsales-rfq/table/detail-table/quotation-history-dialog" import { TechSalesQuotationAttachmentsSheet, type QuotationAttachment } from "../tech-sales-quotation-attachments-sheet" +import { TechSalesVendorEmlAttachmentsSheet, type VendorEmlAttachment } from "../tech-sales-vendor-eml-attachments-sheet" import type { QuotationInfo } from "./rfq-detail-column" import { VendorContactSelectionDialog } from "./vendor-contact-selection-dialog" import { QuotationContactsViewDialog } from "./quotation-contacts-view-dialog" @@ -89,6 +90,12 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps const [quotationAttachments, setQuotationAttachments] = useState([]) const [isLoadingAttachments, setIsLoadingAttachments] = useState(false) + // eml 첨부파일 sheet 상태 관리 + const [emlAttachmentsSheetOpen, setEmlAttachmentsSheetOpen] = useState(false) + const [selectedQuotationForEml, setSelectedQuotationForEml] = useState(null) + const [emlAttachments, setEmlAttachments] = useState([]) + const [isLoadingEmlAttachments, setIsLoadingEmlAttachments] = useState(false) + // 벤더 contact 선택 다이얼로그 상태 관리 const [contactSelectionDialogOpen, setContactSelectionDialogOpen] = useState(false) @@ -463,6 +470,31 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps } }, []) + // eml 첨부파일 sheet 열기 핸들러 + const handleOpenEmlAttachmentsSheet = useCallback(async (quotationId: number, quotationInfo: QuotationInfo) => { + try { + setIsLoadingEmlAttachments(true) + setSelectedQuotationForEml(quotationInfo) + setEmlAttachmentsSheetOpen(true) + + const { getTechSalesVendorQuotationEmlAttachments } = await import("@/lib/techsales-rfq/service") + const result = await getTechSalesVendorQuotationEmlAttachments(quotationId) + + if (result.error) { + toast.error(result.error) + setEmlAttachments([]) + } else { + setEmlAttachments(result.data || []) + } + } catch (error) { + console.error("eml 첨부파일 조회 오류:", error) + toast.error("eml 첨부파일을 불러오는 중 오류가 발생했습니다.") + setEmlAttachments([]) + } finally { + setIsLoadingEmlAttachments(false) + } + }, []) + // 담당자 조회 다이얼로그 열기 함수 const handleOpenContactsDialog = useCallback((quotationId: number, vendorName?: string) => { setSelectedQuotationForContacts({ id: quotationId, vendorName }) @@ -554,8 +586,9 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps unreadMessages, onQuotationClick: handleOpenHistoryDialog, openQuotationAttachmentsSheet: handleOpenQuotationAttachmentsSheet, - openContactsDialog: handleOpenContactsDialog - }), [unreadMessages, handleOpenHistoryDialog, handleOpenQuotationAttachmentsSheet, handleOpenContactsDialog]) + openContactsDialog: handleOpenContactsDialog, + openEmlAttachmentsSheet: handleOpenEmlAttachmentsSheet + }), [unreadMessages, handleOpenHistoryDialog, handleOpenQuotationAttachmentsSheet, handleOpenContactsDialog, handleOpenEmlAttachmentsSheet]) // 필터 필드 정의 (메모이제이션) const advancedFilterFields = useMemo( @@ -928,6 +961,16 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps isLoading={isLoadingAttachments} /> + {/* eml 첨부파일 Sheet */} + + {/* 벤더 contact 선택 다이얼로그 */} Date: Mon, 8 Dec 2025 03:24:15 +0000 Subject: (최겸) 기술영업 rfq 프로젝트 숨기기 기능 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/soap/ecc/mapper/bidding-and-pr-mapper.ts | 1 + lib/techsales-rfq/repository.ts | 1 + lib/techsales-rfq/service.ts | 28 ++++++++++++++++++++-- .../table/detail-table/rfq-detail-table.tsx | 3 ++- .../vendor-contact-selection-dialog.tsx | 22 +++++++++++++---- .../vendor-response/detail/project-info-tab.tsx | 13 +++++++--- .../table/vendor-quotations-table-columns.tsx | 7 ++++-- 7 files changed, 63 insertions(+), 12 deletions(-) (limited to 'lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx') diff --git a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts index 9bf61452..4cdaf90d 100644 --- a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts +++ b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts @@ -296,6 +296,7 @@ export async function mapECCBiddingHeaderToBidding( // PR 정보 prNumber, // 첫번째 PR의 ZREQ_FN 값 hasPrDocument: false, // PR문서는 POS를 말하는 것으로 보임. + plant: eccHeader.WERKS || null, // 플랜트 코드(WERKS) // 상태 및 설정 status: 'bidding_generated', // 입찰생성 상태 diff --git a/lib/techsales-rfq/repository.ts b/lib/techsales-rfq/repository.ts index e6138651..61072d3f 100644 --- a/lib/techsales-rfq/repository.ts +++ b/lib/techsales-rfq/repository.ts @@ -94,6 +94,7 @@ export async function selectTechSalesRfqsWithJoin( // 담당자 및 비고 picCode: techSalesRfqs.picCode, + hideProjectInfoForVendors: techSalesRfqs.hideProjectInfoForVendors, remark: techSalesRfqs.remark, cancelReason: techSalesRfqs.cancelReason, description: techSalesRfqs.description, diff --git a/lib/techsales-rfq/service.ts b/lib/techsales-rfq/service.ts index 13d0bbce..8ce41cba 100644 --- a/lib/techsales-rfq/service.ts +++ b/lib/techsales-rfq/service.ts @@ -557,6 +557,7 @@ export async function sendTechSalesRfqToVendors(input: { email?: string | null; epId?: string | null; }; + hideProjectInfoForVendors?: boolean; }) { unstable_noStore(); try { @@ -573,6 +574,7 @@ export async function sendTechSalesRfqToVendors(input: { materialCode: true, description: true, rfqType: true, + hideProjectInfoForVendors: true, }, with: { biddingProject: true, @@ -604,6 +606,23 @@ export async function sendTechSalesRfqToVendors(input: { } const isResend = rfq.status === TECH_SALES_RFQ_STATUSES.RFQ_SENT; + const effectiveHideProjectInfo = + typeof input.hideProjectInfoForVendors === "boolean" + ? input.hideProjectInfoForVendors + : rfq.hideProjectInfoForVendors ?? false; + + if ( + typeof input.hideProjectInfoForVendors === "boolean" && + input.hideProjectInfoForVendors !== rfq.hideProjectInfoForVendors + ) { + await db + .update(techSalesRfqs) + .set({ + hideProjectInfoForVendors: input.hideProjectInfoForVendors, + updatedAt: new Date(), + }) + .where(eq(techSalesRfqs.id, input.rfqId)); + } // 현재 사용자 정보 조회 const sender = await db.query.users.findFirst({ @@ -728,6 +747,9 @@ export async function sendTechSalesRfqToVendors(input: { const rfqItemsResult = await getTechSalesRfqItems(rfq.id); const rfqItems = rfqItemsResult.data || []; + const projectNameForVendor = effectiveHideProjectInfo ? "" : rfq.biddingProject?.projNm || ""; + const projectCodeForVendor = effectiveHideProjectInfo ? "" : rfq.biddingProject?.pspid || ""; + // 이메일 컨텍스트 구성 const emailContext = { language: language, @@ -735,8 +757,8 @@ export async function sendTechSalesRfqToVendors(input: { id: rfq.id, code: rfq.rfqCode, title: rfqItems.length > 0 ? rfqItems.map(item => item.itemList).join(', ') : '', - projectCode: rfq.biddingProject?.pspid || '', - projectName: rfq.biddingProject?.projNm || '', + projectCode: projectCodeForVendor, + projectName: projectNameForVendor, description: rfq.remark || '', dueDate: rfq.dueDate ? formatDate(rfq.dueDate, "KR") : 'N/A', materialCode: rfq.materialCode || '', @@ -990,6 +1012,7 @@ export async function getTechSalesVendorQuotation(quotationId: number) { projMsrm: quotation.projMsrm, ptypeNm: quotation.ptypeNm, } : null, + hideProjectInfoForVendors: quotation.hideProjectInfoForVendors ?? false, }, // 벤더 정보 @@ -1414,6 +1437,7 @@ export async function getVendorQuotations(input: { dueDate: techSalesRfqs.dueDate, rfqStatus: techSalesRfqs.status, description: techSalesRfqs.description, + hideProjectInfoForVendors: techSalesRfqs.hideProjectInfoForVendors, // 프로젝트 정보 (직접 조인) projNm: biddingProjects.projNm, // 아이템 개수 diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx index aee15594..d8ced6f8 100644 --- a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx +++ b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx @@ -257,7 +257,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps contactId: number; contactEmail: string; contactName: string; - }>) => { + }>, options?: { hideProjectInfoForVendors?: boolean }) => { if (!selectedRfqId) { toast.error("선택된 RFQ가 없습니다."); return; @@ -301,6 +301,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps name: session.data.user.name || undefined, email: session.data.user.email || undefined, }, + hideProjectInfoForVendors: options?.hideProjectInfoForVendors, }); if (result.success) { diff --git a/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx b/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx index d83394bb..8daa9be7 100644 --- a/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx +++ b/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx @@ -49,7 +49,10 @@ interface VendorContactSelectionDialogProps { onOpenChange: (open: boolean) => void vendorIds: number[] rfqId?: number // RFQ ID 추가 - onSendRfq: (selectedContacts: SelectedContact[]) => Promise + onSendRfq: ( + selectedContacts: SelectedContact[], + options: { hideProjectInfoForVendors: boolean } + ) => Promise } export function VendorContactSelectionDialog({ @@ -63,6 +66,7 @@ export function VendorContactSelectionDialog({ const [selectedContacts, setSelectedContacts] = useState([]) const [isLoading, setIsLoading] = useState(false) const [isSending, setIsSending] = useState(false) + const [hideProjectInfoForVendors, setHideProjectInfoForVendors] = useState(false) // 벤더 contact 정보 조회 useEffect(() => { @@ -77,6 +81,7 @@ export function VendorContactSelectionDialog({ setVendorsWithContacts({}) setSelectedContacts([]) setIsLoading(false) + setHideProjectInfoForVendors(false) } }, [open]) @@ -177,7 +182,7 @@ export function VendorContactSelectionDialog({ try { setIsSending(true) - await onSendRfq(selectedContacts) + await onSendRfq(selectedContacts, { hideProjectInfoForVendors }) onOpenChange(false) } catch (error) { console.error("RFQ 발송 오류:", error) @@ -328,8 +333,17 @@ export function VendorContactSelectionDialog({
-
- 총 {selectedContacts.length}명의 연락처가 선택됨 +
+
+ 총 {selectedContacts.length}명의 연락처가 선택됨 +
+