From 18ca4ad784aeeab9ab7a13bbc8b3c13b42ca5e49 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 7 Nov 2025 12:01:16 +0900 Subject: (김준회) 결재 미리보기 공통컴포넌트 중복 제거, 기존 코드의 미리보기 호출부 수정, 템플릿 작성 가이드 간략히 추가, 결재 미리보기시 첨부파일 편집 처리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendors-table-toolbar-actions.tsx | 1819 ++++++++++---------- 1 file changed, 903 insertions(+), 916 deletions(-) (limited to 'lib/pq/pq-review-table-new') diff --git a/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx index f93959a6..4584e772 100644 --- a/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx +++ b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx @@ -1,917 +1,904 @@ -"use client" - -import * as React from "react" -import { type Table } from "@tanstack/react-table" -import { Download, ClipboardCheck, X, Send, RefreshCw } from "lucide-react" -import { toast } from "sonner" -import { useSession } from "next-auth/react" - -import { exportTableToExcel } from "@/lib/export" -import { Button } from "@/components/ui/button" -import { PQSubmission } from "./vendors-table-columns" -import { - cancelInvestigationAction, - sendInvestigationResultsAction, - getFactoryLocationAnswer, - getQMManagers -} from "@/lib/pq/service" -import { SiteVisitDialog } from "./site-visit-dialog" -import type { SiteVisitRequestFormValues } from "./site-visit-dialog" -import { RequestInvestigationDialog } from "./request-investigation-dialog" -import { CancelInvestigationDialog, ReRequestInvestigationDialog } from "./cancel-investigation-dialog" -import { SendResultsDialog } from "./send-results-dialog" -import { ApprovalPreviewDialog } from "@/components/approval/ApprovalPreviewDialog" -import { - requestPQInvestigationWithApproval, - reRequestPQInvestigationWithApproval -} from "@/lib/vendor-investigation/approval-actions" -import type { ApprovalLineItem } from "@/components/knox/approval/ApprovalLineSelector" -import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils" - -interface VendorsTableToolbarActionsProps { - table: Table -} - -interface InvestigationInitialData { - investigationMethod?: "PURCHASE_SELF_EVAL" | "DOCUMENT_EVAL" | "PRODUCT_INSPECTION" | "SITE_VISIT_EVAL"; - qmManagerId?: number; - forecastedAt?: Date; - createdAt?: Date; - investigationAddress?: string; - investigationNotes?: string; -} - -export function VendorsTableToolbarActions({ table }: VendorsTableToolbarActionsProps) { - const selectedRows = table.getFilteredSelectedRowModel().rows - const [isLoading, setIsLoading] = React.useState(false) - const { data: session } = useSession() - - // Dialog 상태 관리 - const [isRequestDialogOpen, setIsRequestDialogOpen] = React.useState(false) - const [isCancelDialogOpen, setIsCancelDialogOpen] = React.useState(false) - const [isSendResultsDialogOpen, setIsSendResultsDialogOpen] = React.useState(false) - const [isReRequestDialogOpen, setIsReRequestDialogOpen] = React.useState(false) - const [isReinspectionDialogOpen, setIsReinspectionDialogOpen] = React.useState(false) - const [isApprovalDialogOpen, setIsApprovalDialogOpen] = React.useState(false) - const [isReRequestApprovalDialogOpen, setIsReRequestApprovalDialogOpen] = React.useState(false) - - // 초기 데이터 상태 - const [dialogInitialData, setDialogInitialData] = React.useState(undefined) - - // 실사 의뢰 임시 데이터 (결재 다이얼로그로 전달) - const [investigationFormData, setInvestigationFormData] = React.useState<{ - qmManagerId: number; - qmManagerName: string; - qmManagerEmail?: string; - forecastedAt: Date; - investigationAddress: string; - investigationNotes?: string; - } | null>(null) - - // 실사 재의뢰 임시 데이터 - const [reRequestData, setReRequestData] = React.useState<{ - investigationIds: number[]; - vendorNames: string; - } | null>(null) - - // 결재 템플릿 변수 - const [approvalVariables, setApprovalVariables] = React.useState>({}) - const [reRequestApprovalVariables, setReRequestApprovalVariables] = React.useState>({}) - - // 실사 의뢰 대화상자 열기 핸들러 -// 실사 의뢰 대화상자 열기 핸들러 -const handleOpenRequestDialog = async () => { - setIsLoading(true); - const initialData: InvestigationInitialData = {}; - - try { - // 선택된 행이 정확히 1개인 경우에만 초기값 설정 - if (selectedRows.length === 1) { - const row = selectedRows[0].original; - - // 승인된 PQ이고 아직 실사가 없는 경우 - if (row.status === "APPROVED" && !row.investigation) { - // Factory Location 정보 가져오기 - const locationResponse = await getFactoryLocationAnswer( - row.vendorId, - row.projectId - ); - - // 기본 주소 설정 - Factory Location 응답 또는 fallback - let defaultAddress = ""; - if (locationResponse.success && locationResponse.factoryLocation) { - defaultAddress = locationResponse.factoryLocation; - } else { - // Factory Location을 찾지 못한 경우 fallback - defaultAddress = row.taxId ? - `${row.vendorName} 사업장 (${row.taxId})` : - `${row.vendorName} 사업장`; - } - - // 이미 같은 회사에 대한 다른 실사가 있는지 확인 - const existingInvestigations = table.getFilteredRowModel().rows - .map(r => r.original) - .filter(r => - r.vendorId === row.vendorId && - r.investigation !== null - ); - - // 같은 업체의 이전 실사 기록이 있다면 참고하되, 주소는 Factory Location 사용 - if (existingInvestigations.length > 0) { - // 날짜 기준으로 정렬하여 가장 최근 것을 가져옴 - const latestInvestigation = existingInvestigations.sort((a, b) => { - const dateA = a.investigation?.createdAt || new Date(0); - const dateB = b.investigation?.createdAt || new Date(0); - return (dateB as Date).getTime() - (dateA as Date).getTime(); - })[0].investigation; - - if (latestInvestigation) { - initialData.investigationMethod = latestInvestigation.investigationMethod || undefined; - initialData.qmManagerId = latestInvestigation.qmManagerId || undefined; - initialData.investigationAddress = defaultAddress; // Factory Location 사용 - - // 날짜는 미래로 설정 - const futureDate = new Date(); - futureDate.setDate(futureDate.getDate() + 14); // 기본값으로 2주 후 - initialData.forecastedAt = futureDate; - } - } else { - // 기본값 설정 - initialData.investigationMethod = undefined; - const futureDate = new Date(); - futureDate.setDate(futureDate.getDate() + 14); // 기본값으로 2주 후 - initialData.forecastedAt = futureDate; - initialData.investigationAddress = defaultAddress; // Factory Location 사용 - } - } - // 실사가 이미 있고 수정하는 경우 - // else if (row.investigation) { - // initialData.investigationMethod = row.investigation.investigationMethod || undefined; - // initialData.qmManagerId = row.investigation.qmManagerId !== null ? - // row.investigation.qmManagerId : undefined; - // initialData.forecastedAt = row.investigation.forecastedAt || new Date(); - // initialData.investigationAddress = row.investigation.investigationAddress || ""; - // initialData.investigationNotes = row.investigation.investigationNotes || ""; - // } - } - } catch (error) { - console.error("초기 데이터 로드 중 오류:", error); - toast.error("초기 데이터 로드 중 오류가 발생했습니다."); - } finally { - setIsLoading(false); - - // 초기 데이터 설정 및 대화상자 열기 - setDialogInitialData(Object.keys(initialData).length > 0 ? initialData : undefined); - setIsRequestDialogOpen(true); - } -}; - // 실사 의뢰 요청 처리 - Step 1: RequestInvestigationDialog에서 정보 입력 후 - const handleRequestInvestigation = async (formData: { - qmManagerId: number, - forecastedAt: Date, - investigationAddress: string, - investigationNotes?: string - }) => { - try { - // 승인된 PQ 제출만 필터링 (미실사 PQ 제외) - const approvedPQs = selectedRows.filter(row => - row.original.status === "APPROVED" && - !row.original.investigation && - row.original.type !== "NON_INSPECTION" - ) - - if (approvedPQs.length === 0) { - if (hasNonInspectionPQ) { - toast.error("미실사 PQ는 실사 의뢰할 수 없습니다. 미실사 PQ를 제외하고 선택해주세요.") - } else { - toast.error("실사를 의뢰할 수 있는 업체가 없습니다. 승인된 PQ 제출만 실사 의뢰가 가능합니다.") - } - return - } - - // QM 담당자 이름 및 이메일 조회 - const qmManagersResult = await getQMManagers() - const qmManager = qmManagersResult.success - ? qmManagersResult.data.find(m => m.id === formData.qmManagerId) - : null - const qmManagerName = qmManager?.name || `QM담당자 #${formData.qmManagerId}` - const qmManagerEmail = qmManager?.email || undefined - - // 협력사 이름 목록 생성 - const vendorNames = approvedPQs - .map(row => row.original.vendorName) - .join(', ') - - // 실사 폼 데이터 저장 (이메일 추가) - setInvestigationFormData({ - qmManagerId: formData.qmManagerId, - qmManagerName, - qmManagerEmail, - forecastedAt: formData.forecastedAt, - investigationAddress: formData.investigationAddress, - investigationNotes: formData.investigationNotes, - }) - - // 결재 템플릿 변수 생성 - const requestedAt = new Date() - const { mapPQInvestigationToTemplateVariables } = await import('@/lib/vendor-investigation/handlers') - const variables = await mapPQInvestigationToTemplateVariables({ - vendorNames, - qmManagerName, - qmManagerEmail, - forecastedAt: formData.forecastedAt, - investigationAddress: formData.investigationAddress, - investigationNotes: formData.investigationNotes, - requestedAt, - }) - - setApprovalVariables(variables) - - // RequestInvestigationDialog 닫고 ApprovalPreviewDialog 열기 - setIsRequestDialogOpen(false) - setIsApprovalDialogOpen(true) - } catch (error) { - console.error("결재 준비 중 오류 발생:", error) - toast.error("결재 준비 중 오류가 발생했습니다.") - } - } - - // 실사 의뢰 결재 요청 처리 - Step 2: ApprovalPreviewDialog에서 결재선 선택 후 - const handleApprovalSubmit = async (approvers: ApprovalLineItem[]) => { - debugLog('[InvestigationApproval] 실사 의뢰 결재 요청 시작', { - approversCount: approvers.length, - hasSession: !!session?.user, - hasFormData: !!investigationFormData, - }); - - if (!session?.user || !investigationFormData) { - debugError('[InvestigationApproval] 세션 또는 폼 데이터 없음'); - throw new Error('세션 정보가 없습니다.'); - } - - // 승인된 PQ 제출만 필터링 - const approvedPQs = selectedRows.filter(row => - row.original.status === "APPROVED" && - !row.original.investigation && - row.original.type !== "NON_INSPECTION" - ) - - debugLog('[InvestigationApproval] 승인된 PQ 건수', { - count: approvedPQs.length, - }); - - // 협력사 이름 목록 - const vendorNames = approvedPQs - .map(row => row.original.vendorName) - .join(', ') - - // 결재선에서 EP ID 추출 (상신자 제외) - const approverEpIds = approvers - .filter((line) => line.seq !== "0" && line.epId) - .map((line) => line.epId!) - - debugLog('[InvestigationApproval] 결재선 추출 완료', { - approverEpIds, - }); - - // 결재 워크플로우 시작 - const result = await requestPQInvestigationWithApproval({ - pqSubmissionIds: approvedPQs.map(row => row.original.id), - vendorNames, - qmManagerId: investigationFormData.qmManagerId, - qmManagerName: investigationFormData.qmManagerName, - qmManagerEmail: investigationFormData.qmManagerEmail, - forecastedAt: investigationFormData.forecastedAt, - investigationAddress: investigationFormData.investigationAddress, - investigationNotes: investigationFormData.investigationNotes, - currentUser: { - id: Number(session.user.id), - epId: session.user.epId || null, - email: session.user.email || undefined, - }, - approvers: approverEpIds, - }) - - debugSuccess('[InvestigationApproval] 결재 요청 성공', { - approvalId: result.approvalId, - pendingActionId: result.pendingActionId, - }); - - if (result.status === 'pending_approval') { - // 성공 시에만 상태 초기화 및 페이지 리로드 - setInvestigationFormData(null) - setDialogInitialData(undefined) - window.location.reload() - } - } - - const handleCloseRequestDialog = () => { - setIsRequestDialogOpen(false); - setDialogInitialData(undefined); - }; - - - // 실사 의뢰 취소 처리 - const handleCancelInvestigation = async () => { - setIsLoading(true) - try { - // 실사가 계획됨 상태인 PQ만 필터링 - const plannedInvestigations = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.investigationStatus === "PLANNED" - ) - - if (plannedInvestigations.length === 0) { - toast.error("취소할 수 있는 실사 의뢰가 없습니다. 계획 상태의 실사만 취소할 수 있습니다.") - return - } - - // 서버 액션 호출 - const result = await cancelInvestigationAction( - plannedInvestigations.map(row => row.original.investigation!.id) - ) - - if (result.success) { - toast.success(`${result.count}개 업체에 대한 실사 의뢰가 취소되었습니다.`) - window.location.reload() - } else { - toast.error(result.error || "실사 취소 처리 중 오류가 발생했습니다.") - } - } catch (error) { - console.error("실사 의뢰 취소 중 오류 발생:", error) - toast.error("실사 의뢰 취소 중 오류가 발생했습니다.") - } finally { - setIsLoading(false) - setIsCancelDialogOpen(false) - } - } - - // 실사 재의뢰 처리 - Step 1: 확인 다이얼로그에서 확인 후 - const handleReRequestInvestigation = async (reason?: string) => { - try { - // 취소된 실사만 필터링 - const canceledInvestigations = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.investigationStatus === "CANCELED" - ) - - if (canceledInvestigations.length === 0) { - toast.error("재의뢰할 수 있는 실사가 없습니다. 취소 상태의 실사만 재의뢰할 수 있습니다.") - return - } - - // 협력사 이름 목록 생성 - const vendorNames = canceledInvestigations - .map(row => row.original.vendorName) - .join(', ') - - // 재의뢰 데이터 저장 - const investigationIds = canceledInvestigations.map(row => row.original.investigation!.id) - setReRequestData({ - investigationIds, - vendorNames, - }) - - // 결재 템플릿 변수 생성 - const reRequestedAt = new Date() - const { mapPQReRequestToTemplateVariables } = await import('@/lib/vendor-investigation/handlers') - const variables = await mapPQReRequestToTemplateVariables({ - vendorNames, - investigationCount: investigationIds.length, - reRequestedAt, - reason, - }) - - setReRequestApprovalVariables(variables) - - // ReRequestInvestigationDialog 닫고 ApprovalPreviewDialog 열기 - setIsReRequestDialogOpen(false) - setIsReRequestApprovalDialogOpen(true) - } catch (error) { - console.error("재의뢰 결재 준비 중 오류 발생:", error) - toast.error("재의뢰 결재 준비 중 오류가 발생했습니다.") - } - } - - // 실사 재의뢰 결재 요청 처리 - Step 2: ApprovalPreviewDialog에서 결재선 선택 후 - const handleReRequestApprovalSubmit = async (approvers: ApprovalLineItem[]) => { - debugLog('[ReRequestApproval] 실사 재의뢰 결재 요청 시작', { - approversCount: approvers.length, - hasSession: !!session?.user, - hasReRequestData: !!reRequestData, - }); - - if (!session?.user || !reRequestData) { - debugError('[ReRequestApproval] 세션 또는 재의뢰 데이터 없음'); - throw new Error('세션 정보가 없습니다.'); - } - - debugLog('[ReRequestApproval] 재의뢰 대상', { - investigationIds: reRequestData.investigationIds, - vendorNames: reRequestData.vendorNames, - }); - - // 결재선에서 EP ID 추출 (상신자 제외) - const approverEpIds = approvers - .filter((line) => line.seq !== "0" && line.epId) - .map((line) => line.epId!) - - debugLog('[ReRequestApproval] 결재선 추출 완료', { - approverEpIds, - }); - - // 결재 워크플로우 시작 - const result = await reRequestPQInvestigationWithApproval({ - investigationIds: reRequestData.investigationIds, - vendorNames: reRequestData.vendorNames, - currentUser: { - id: Number(session.user.id), - epId: session.user.epId || null, - email: session.user.email || undefined, - }, - approvers: approverEpIds, - }) - - debugSuccess('[ReRequestApproval] 재의뢰 결재 요청 성공', { - approvalId: result.approvalId, - pendingActionId: result.pendingActionId, - }); - - if (result.status === 'pending_approval') { - // 성공 시에만 상태 초기화 및 페이지 리로드 - setReRequestData(null) - window.location.reload() - } - } - - // 재실사 요청 처리 - const handleRequestReinspection = async ( - data: SiteVisitRequestFormValues, - attachments?: File[] - ) => { - try { - // 보완-재실사 대상 실사만 필터링 - const supplementReinspectInvestigations = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" - ); - - if (supplementReinspectInvestigations.length === 0) { - toast.error("보완-재실사 대상 실사가 없습니다."); - return; - } - - // 첫 번째 대상 실사로 재실사 요청 생성 - const targetRow = supplementReinspectInvestigations[0].original; - const targetInvestigation = targetRow.investigation!; - const { requestSupplementReinspectionAction } = await import('@/lib/vendor-investigation/service'); - - // SiteVisitRequestFormValues를 requestSupplementReinspectionAction 형식으로 변환 - // shiAttendees는 그대로 전달 (새로운 형식: {checked, attendees}) - const result = await requestSupplementReinspectionAction({ - investigationId: targetInvestigation.id, - siteVisitData: { - inspectionDuration: data.inspectionDuration, - requestedStartDate: data.requestedStartDate, - requestedEndDate: data.requestedEndDate, - shiAttendees: data.shiAttendees || {}, - vendorRequests: data.vendorRequests || {}, - additionalRequests: data.additionalRequests || "", - }, - }); - - if (result.success) { - toast.success("재실사 요청이 생성되었습니다."); - setIsReinspectionDialogOpen(false); - window.location.reload(); - } else { - toast.error(result.error || "재실사 요청 생성 중 오류가 발생했습니다."); - } - } catch (error) { - console.error("재실사 요청 오류:", error); - toast.error("재실사 요청 중 오류가 발생했습니다."); - } - }; - - // 실사 결과 발송 처리 - const handleSendInvestigationResults = async (data: { purchaseComment?: string }) => { - try { - setIsLoading(true) - - // 완료된 실사 중 승인된 결과 또는 보완된 결과만 필터링 - const approvedInvestigations = selectedRows.filter(row => { - const investigation = row.original.investigation - return investigation && - (investigation.investigationStatus === "COMPLETED" || - investigation.investigationStatus === "SUPPLEMENT_REQUIRED" || - investigation.evaluationResult === "REJECTED") - - }) - - if (approvedInvestigations.length === 0) { - toast.error("발송할 실사 결과가 없습니다. 완료되고 승인된 실사만 결과를 발송할 수 있습니다.") - return - } - - // 서버 액션 호출 - const result = await sendInvestigationResultsAction({ - investigationIds: approvedInvestigations.map(row => row.original.investigation!.id), - purchaseComment: data.purchaseComment, - }) - - if (result.success) { - toast.success(result.message || `${result.data?.successCount || 0}개 업체에 대한 실사 결과가 발송되었습니다.`) - window.location.reload() - } else { - toast.error(result.error || "실사 결과 발송 처리 중 오류가 발생했습니다.") - } - } catch (error) { - console.error("실사 결과 발송 중 오류 발생:", error) - toast.error("실사 결과 발송 중 오류가 발생했습니다.") - } finally { - setIsLoading(false) - setIsSendResultsDialogOpen(false) - } - } - - // 승인된 업체 수 확인 (미실사 PQ 제외) - const approvedPQsCount = selectedRows.filter(row => - row.original.status === "APPROVED" && - !row.original.investigation && - row.original.type !== "NON_INSPECTION" - ).length - - // 계획 상태 실사 수 확인 - const plannedInvestigationsCount = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.investigationStatus === "PLANNED" - ).length - - // 완료된 실사 수 확인 (승인된 결과만) - const completedInvestigationsCount = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.investigationStatus === "COMPLETED" && - row.original.investigation.evaluationResult === "APPROVED" - ).length - - // 취소된 실사 수 확인 - const canceledInvestigationsCount = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.investigationStatus === "CANCELED" - ).length - - // 재실사 요청 대상 수 확인 (보완-재실사 결과만) - const reinspectInvestigationsCount = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" - ).length - - // 재실사 요청 가능 여부 확인 (방문실사평가 또는 제품검사평가만 가능) - const canRequestReinspection = selectedRows.some(row => { - const investigation = row.original.investigation - if (!investigation) return false - if (investigation.evaluationResult !== "SUPPLEMENT_REINSPECT") return false - const method = investigation.investigationMethod - // 서류평가 또는 구매자체평가는 재방문실사 불가 - return method === "SITE_VISIT_EVAL" || method === "PRODUCT_INSPECTION" - }) - - // 미실사 PQ가 선택되었는지 확인 - const hasNonInspectionPQ = selectedRows.some(row => - row.original.type === "NON_INSPECTION" - ) - - // 실사 방법 라벨 변환 함수 - const getInvestigationMethodLabel = (method: string): string => { - switch (method) { - case "PURCHASE_SELF_EVAL": - return "구매자체평가" - case "DOCUMENT_EVAL": - return "서류평가" - case "PRODUCT_INSPECTION": - return "제품검사평가" - case "SITE_VISIT_EVAL": - return "방문실사평가" - default: - return method - } - } - - // 실사 결과 발송용 데이터 준비 - const auditResults = selectedRows - .filter(row => - row.original.investigation && - (row.original.investigation.investigationStatus === "COMPLETED" || row.original.investigation.investigationStatus === "SUPPLEMENT_REQUIRED") && ( - (row.original.investigation.evaluationResult === "APPROVED" || - row.original.investigation.evaluationResult === "SUPPLEMENT" || - row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" || - row.original.investigation.evaluationResult === "SUPPLEMENT_DOCUMENT")) - ) - .map(row => { - const investigation = row.original.investigation! - const pqSubmission = row.original - - // pqItems를 상세하게 포맷팅 (itemCode-itemName 형태로 모든 항목 표시) - const formatAuditItem = (pqItems: any): string => { - if (!pqItems) return pqSubmission.projectName || "N/A"; - - try { - // 이미 파싱된 객체 배열인 경우 - if (Array.isArray(pqItems)) { - return pqItems.map(item => { - if (typeof item === 'string') return item; - if (typeof item === 'object') { - const code = item.itemCode || item.code || ""; - const name = item.itemName || item.name || ""; - if (code && name) return `${code}-${name}`; - return name || code || String(item); - } - return String(item); - }).join(', '); - } - - // JSON 문자열인 경우 - if (typeof pqItems === 'string') { - try { - const parsed = JSON.parse(pqItems); - if (Array.isArray(parsed)) { - return parsed.map(item => { - if (typeof item === 'string') return item; - if (typeof item === 'object') { - const code = item.itemCode || item.code || ""; - const name = item.itemName || item.name || ""; - if (code && name) return `${code}-${name}`; - return name || code || String(item); - } - return String(item); - }).join(', '); - } - return String(parsed); - } catch { - return String(pqItems); - } - } - - // 기타 경우 - return String(pqItems); - } catch { - return pqSubmission.projectName || "N/A"; - } - }; - - return { - id: investigation.id, - vendorCode: row.original.vendorCode || "N/A", - vendorName: row.original.vendorName || "N/A", - vendorEmail: row.original.email || "N/A", - vendorContactPerson: (row.original as any).representativeName || row.original.vendorName || "N/A", - pqNumber: pqSubmission.pqNumber || "N/A", - auditItem: formatAuditItem(pqSubmission.pqItems), - auditFactoryAddress: investigation.investigationAddress || "N/A", - auditMethod: getInvestigationMethodLabel(investigation.investigationMethod || ""), - auditResult: investigation.evaluationResult === "APPROVED" ? "Pass(승인)" : - investigation.evaluationResult === "SUPPLEMENT" ? "Pass(조건부승인)" : - investigation.evaluationResult === "REJECTED" ? "Fail(미승인)" : "N/A", - additionalNotes: investigation.investigationNotes || undefined, - investigationNotes: investigation.investigationNotes || undefined, - } - }) - - return ( - <> -
- {/* 실사 의뢰 버튼 */} - - - {/* 실사 의뢰 취소 버튼 */} - - - {/* 실사 재의뢰 버튼 */} - - - {/* 재실사 요청 버튼 */} - - - {/* 실사 결과 발송 버튼 */} - - - {/** Export 버튼 */} - -
- - {/* 실사 의뢰 Dialog */} - - - - {/* 실사 취소 Dialog */} - setIsCancelDialogOpen(false)} - onConfirm={handleCancelInvestigation} - selectedCount={plannedInvestigationsCount} - /> - - {/* 실사 재의뢰 Dialog */} - setIsReRequestDialogOpen(false)} - onConfirm={handleReRequestInvestigation} - selectedCount={canceledInvestigationsCount} - /> - - {/* 결과 발송 Dialog */} - setIsSendResultsDialogOpen(false)} - onConfirm={handleSendInvestigationResults} - selectedCount={completedInvestigationsCount} - auditResults={auditResults} - /> - - {/* 재방문실사 요청 Dialog */} - {(() => { - // 보완-재실사 대상 실사 찾기 - const supplementReinspectInvestigations = selectedRows.filter(row => - row.original.investigation && - row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" - ); - - if (supplementReinspectInvestigations.length === 0) { - return null; - } - - const targetRow = supplementReinspectInvestigations[0].original; - const targetInvestigation = targetRow.investigation!; - - return ( - setIsReinspectionDialogOpen(false)} - onSubmit={handleRequestReinspection} - investigation={{ - id: targetInvestigation.id, - investigationMethod: targetInvestigation.investigationMethod || undefined, - investigationAddress: targetInvestigation.investigationAddress || undefined, - investigationNotes: targetInvestigation.investigationNotes || undefined, - vendorName: targetRow.vendorName, - vendorCode: targetRow.vendorCode, - projectName: targetRow.projectName || undefined, - projectCode: targetRow.projectCode || undefined, - pqItems: targetRow.pqItems || null, - }} - isReinspection={true} - /> - ); - })()} - - {/* 결재 미리보기 Dialog - 실사 의뢰 */} - {session?.user && investigationFormData && ( - { - setIsApprovalDialogOpen(open) - if (!open) { - // 다이얼로그가 닫히면 실사 폼 데이터도 초기화 - setInvestigationFormData(null) - } - }} - templateName="Vendor 실사의뢰" - variables={approvalVariables} - title={`Vendor 실사의뢰 - ${selectedRows.filter(row => - row.original.status === "APPROVED" && - !row.original.investigation && - row.original.type !== "NON_INSPECTION" - ).map(row => row.original.vendorName).join(', ')}`} - description={`${approvedPQsCount}개 업체에 대한 실사 의뢰`} - currentUser={{ - id: Number(session.user.id), - epId: session.user.epId || null, - name: session.user.name || null, - email: session.user.email || '', - }} - onSubmit={handleApprovalSubmit} - /> - )} - - {/* 결재 미리보기 Dialog - 실사 재의뢰 */} - {session?.user && reRequestData && ( - { - setIsReRequestApprovalDialogOpen(open) - if (!open) { - // 다이얼로그가 닫히면 재의뢰 데이터도 초기화 - setReRequestData(null) - } - }} - templateName="Vendor 실사 재의뢰" - variables={reRequestApprovalVariables} - title={`Vendor 실사 재의뢰 - ${reRequestData.vendorNames}`} - description={`${reRequestData.investigationIds.length}개 업체에 대한 실사 재의뢰`} - currentUser={{ - id: Number(session.user.id), - epId: session.user.epId || null, - name: session.user.name || null, - email: session.user.email || '', - }} - onSubmit={handleReRequestApprovalSubmit} - /> - )} - - ) +"use client" + +import * as React from "react" +import { type Table } from "@tanstack/react-table" +import { Download, ClipboardCheck, X, Send, RefreshCw } from "lucide-react" +import { toast } from "sonner" +import { useSession } from "next-auth/react" + +import { exportTableToExcel } from "@/lib/export" +import { Button } from "@/components/ui/button" +import { PQSubmission } from "./vendors-table-columns" +import { + cancelInvestigationAction, + sendInvestigationResultsAction, + getFactoryLocationAnswer, + getQMManagers +} from "@/lib/pq/service" +import { SiteVisitDialog } from "./site-visit-dialog" +import type { SiteVisitRequestFormValues } from "./site-visit-dialog" +import { RequestInvestigationDialog } from "./request-investigation-dialog" +import { CancelInvestigationDialog, ReRequestInvestigationDialog } from "./cancel-investigation-dialog" +import { SendResultsDialog } from "./send-results-dialog" +import { ApprovalPreviewDialog } from "@/lib/approval/approval-preview-dialog" +import { + requestPQInvestigationWithApproval, + reRequestPQInvestigationWithApproval +} from "@/lib/vendor-investigation/approval-actions" +import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils" + +interface VendorsTableToolbarActionsProps { + table: Table +} + +interface InvestigationInitialData { + investigationMethod?: "PURCHASE_SELF_EVAL" | "DOCUMENT_EVAL" | "PRODUCT_INSPECTION" | "SITE_VISIT_EVAL"; + qmManagerId?: number; + forecastedAt?: Date; + createdAt?: Date; + investigationAddress?: string; + investigationNotes?: string; +} + +export function VendorsTableToolbarActions({ table }: VendorsTableToolbarActionsProps) { + const selectedRows = table.getFilteredSelectedRowModel().rows + const [isLoading, setIsLoading] = React.useState(false) + const { data: session } = useSession() + + // Dialog 상태 관리 + const [isRequestDialogOpen, setIsRequestDialogOpen] = React.useState(false) + const [isCancelDialogOpen, setIsCancelDialogOpen] = React.useState(false) + const [isSendResultsDialogOpen, setIsSendResultsDialogOpen] = React.useState(false) + const [isReRequestDialogOpen, setIsReRequestDialogOpen] = React.useState(false) + const [isReinspectionDialogOpen, setIsReinspectionDialogOpen] = React.useState(false) + const [isApprovalDialogOpen, setIsApprovalDialogOpen] = React.useState(false) + const [isReRequestApprovalDialogOpen, setIsReRequestApprovalDialogOpen] = React.useState(false) + + // 초기 데이터 상태 + const [dialogInitialData, setDialogInitialData] = React.useState(undefined) + + // 실사 의뢰 임시 데이터 (결재 다이얼로그로 전달) + const [investigationFormData, setInvestigationFormData] = React.useState<{ + qmManagerId: number; + qmManagerName: string; + qmManagerEmail?: string; + forecastedAt: Date; + investigationAddress: string; + investigationNotes?: string; + } | null>(null) + + // 실사 재의뢰 임시 데이터 + const [reRequestData, setReRequestData] = React.useState<{ + investigationIds: number[]; + vendorNames: string; + } | null>(null) + + // 결재 템플릿 변수 + const [approvalVariables, setApprovalVariables] = React.useState>({}) + const [reRequestApprovalVariables, setReRequestApprovalVariables] = React.useState>({}) + + // 실사 의뢰 대화상자 열기 핸들러 +// 실사 의뢰 대화상자 열기 핸들러 +const handleOpenRequestDialog = async () => { + setIsLoading(true); + const initialData: InvestigationInitialData = {}; + + try { + // 선택된 행이 정확히 1개인 경우에만 초기값 설정 + if (selectedRows.length === 1) { + const row = selectedRows[0].original; + + // 승인된 PQ이고 아직 실사가 없는 경우 + if (row.status === "APPROVED" && !row.investigation) { + // Factory Location 정보 가져오기 + const locationResponse = await getFactoryLocationAnswer( + row.vendorId, + row.projectId + ); + + // 기본 주소 설정 - Factory Location 응답 또는 fallback + let defaultAddress = ""; + if (locationResponse.success && locationResponse.factoryLocation) { + defaultAddress = locationResponse.factoryLocation; + } else { + // Factory Location을 찾지 못한 경우 fallback + defaultAddress = row.taxId ? + `${row.vendorName} 사업장 (${row.taxId})` : + `${row.vendorName} 사업장`; + } + + // 이미 같은 회사에 대한 다른 실사가 있는지 확인 + const existingInvestigations = table.getFilteredRowModel().rows + .map(r => r.original) + .filter(r => + r.vendorId === row.vendorId && + r.investigation !== null + ); + + // 같은 업체의 이전 실사 기록이 있다면 참고하되, 주소는 Factory Location 사용 + if (existingInvestigations.length > 0) { + // 날짜 기준으로 정렬하여 가장 최근 것을 가져옴 + const latestInvestigation = existingInvestigations.sort((a, b) => { + const dateA = a.investigation?.createdAt || new Date(0); + const dateB = b.investigation?.createdAt || new Date(0); + return (dateB as Date).getTime() - (dateA as Date).getTime(); + })[0].investigation; + + if (latestInvestigation) { + initialData.investigationMethod = latestInvestigation.investigationMethod || undefined; + initialData.qmManagerId = latestInvestigation.qmManagerId || undefined; + initialData.investigationAddress = defaultAddress; // Factory Location 사용 + + // 날짜는 미래로 설정 + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 14); // 기본값으로 2주 후 + initialData.forecastedAt = futureDate; + } + } else { + // 기본값 설정 + initialData.investigationMethod = undefined; + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 14); // 기본값으로 2주 후 + initialData.forecastedAt = futureDate; + initialData.investigationAddress = defaultAddress; // Factory Location 사용 + } + } + // 실사가 이미 있고 수정하는 경우 + // else if (row.investigation) { + // initialData.investigationMethod = row.investigation.investigationMethod || undefined; + // initialData.qmManagerId = row.investigation.qmManagerId !== null ? + // row.investigation.qmManagerId : undefined; + // initialData.forecastedAt = row.investigation.forecastedAt || new Date(); + // initialData.investigationAddress = row.investigation.investigationAddress || ""; + // initialData.investigationNotes = row.investigation.investigationNotes || ""; + // } + } + } catch (error) { + console.error("초기 데이터 로드 중 오류:", error); + toast.error("초기 데이터 로드 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + + // 초기 데이터 설정 및 대화상자 열기 + setDialogInitialData(Object.keys(initialData).length > 0 ? initialData : undefined); + setIsRequestDialogOpen(true); + } +}; + // 실사 의뢰 요청 처리 - Step 1: RequestInvestigationDialog에서 정보 입력 후 + const handleRequestInvestigation = async (formData: { + qmManagerId: number, + forecastedAt: Date, + investigationAddress: string, + investigationNotes?: string + }) => { + try { + // 승인된 PQ 제출만 필터링 (미실사 PQ 제외) + const approvedPQs = selectedRows.filter(row => + row.original.status === "APPROVED" && + !row.original.investigation && + row.original.type !== "NON_INSPECTION" + ) + + if (approvedPQs.length === 0) { + if (hasNonInspectionPQ) { + toast.error("미실사 PQ는 실사 의뢰할 수 없습니다. 미실사 PQ를 제외하고 선택해주세요.") + } else { + toast.error("실사를 의뢰할 수 있는 업체가 없습니다. 승인된 PQ 제출만 실사 의뢰가 가능합니다.") + } + return + } + + // QM 담당자 이름 및 이메일 조회 + const qmManagersResult = await getQMManagers() + const qmManager = qmManagersResult.success + ? qmManagersResult.data.find(m => m.id === formData.qmManagerId) + : null + const qmManagerName = qmManager?.name || `QM담당자 #${formData.qmManagerId}` + const qmManagerEmail = qmManager?.email || undefined + + // 협력사 이름 목록 생성 + const vendorNames = approvedPQs + .map(row => row.original.vendorName) + .join(', ') + + // 실사 폼 데이터 저장 (이메일 추가) + setInvestigationFormData({ + qmManagerId: formData.qmManagerId, + qmManagerName, + qmManagerEmail, + forecastedAt: formData.forecastedAt, + investigationAddress: formData.investigationAddress, + investigationNotes: formData.investigationNotes, + }) + + // 결재 템플릿 변수 생성 + const requestedAt = new Date() + const { mapPQInvestigationToTemplateVariables } = await import('@/lib/vendor-investigation/handlers') + const variables = await mapPQInvestigationToTemplateVariables({ + vendorNames, + qmManagerName, + qmManagerEmail, + forecastedAt: formData.forecastedAt, + investigationAddress: formData.investigationAddress, + investigationNotes: formData.investigationNotes, + requestedAt, + }) + + setApprovalVariables(variables) + + // RequestInvestigationDialog 닫고 ApprovalPreviewDialog 열기 + setIsRequestDialogOpen(false) + setIsApprovalDialogOpen(true) + } catch (error) { + console.error("결재 준비 중 오류 발생:", error) + toast.error("결재 준비 중 오류가 발생했습니다.") + } + } + + // 실사 의뢰 결재 요청 처리 - Step 2: ApprovalPreviewDialog에서 결재선 선택 후 + const handleApprovalSubmit = async ({ approvers, title, attachments }: { approvers: string[], title: string, attachments?: File[] }) => { + debugLog('[InvestigationApproval] 실사 의뢰 결재 요청 시작', { + approversCount: approvers.length, + hasSession: !!session?.user, + hasFormData: !!investigationFormData, + }); + + if (!session?.user || !investigationFormData) { + debugError('[InvestigationApproval] 세션 또는 폼 데이터 없음'); + throw new Error('세션 정보가 없습니다.'); + } + + // 승인된 PQ 제출만 필터링 + const approvedPQs = selectedRows.filter(row => + row.original.status === "APPROVED" && + !row.original.investigation && + row.original.type !== "NON_INSPECTION" + ) + + debugLog('[InvestigationApproval] 승인된 PQ 건수', { + count: approvedPQs.length, + }); + + // 협력사 이름 목록 + const vendorNames = approvedPQs + .map(row => row.original.vendorName) + .join(', ') + + debugLog('[InvestigationApproval] 결재선 추출 완료', { + approverEpIds: approvers, + }); + + // 결재 워크플로우 시작 (approvers는 이미 EP ID 배열) + const result = await requestPQInvestigationWithApproval({ + pqSubmissionIds: approvedPQs.map(row => row.original.id), + vendorNames, + qmManagerId: investigationFormData.qmManagerId, + qmManagerName: investigationFormData.qmManagerName, + qmManagerEmail: investigationFormData.qmManagerEmail, + forecastedAt: investigationFormData.forecastedAt, + investigationAddress: investigationFormData.investigationAddress, + investigationNotes: investigationFormData.investigationNotes, + currentUser: { + id: Number(session.user.id), + epId: session.user.epId || null, + email: session.user.email || undefined, + }, + approvers: approvers, + }) + + debugSuccess('[InvestigationApproval] 결재 요청 성공', { + approvalId: result.approvalId, + pendingActionId: result.pendingActionId, + }); + + if (result.status === 'pending_approval') { + // 성공 시에만 상태 초기화 및 페이지 리로드 + setInvestigationFormData(null) + setDialogInitialData(undefined) + window.location.reload() + } + } + + const handleCloseRequestDialog = () => { + setIsRequestDialogOpen(false); + setDialogInitialData(undefined); + }; + + + // 실사 의뢰 취소 처리 + const handleCancelInvestigation = async () => { + setIsLoading(true) + try { + // 실사가 계획됨 상태인 PQ만 필터링 + const plannedInvestigations = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "PLANNED" + ) + + if (plannedInvestigations.length === 0) { + toast.error("취소할 수 있는 실사 의뢰가 없습니다. 계획 상태의 실사만 취소할 수 있습니다.") + return + } + + // 서버 액션 호출 + const result = await cancelInvestigationAction( + plannedInvestigations.map(row => row.original.investigation!.id) + ) + + if (result.success) { + toast.success(`${result.count}개 업체에 대한 실사 의뢰가 취소되었습니다.`) + window.location.reload() + } else { + toast.error(result.error || "실사 취소 처리 중 오류가 발생했습니다.") + } + } catch (error) { + console.error("실사 의뢰 취소 중 오류 발생:", error) + toast.error("실사 의뢰 취소 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + setIsCancelDialogOpen(false) + } + } + + // 실사 재의뢰 처리 - Step 1: 확인 다이얼로그에서 확인 후 + const handleReRequestInvestigation = async (reason?: string) => { + try { + // 취소된 실사만 필터링 + const canceledInvestigations = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "CANCELED" + ) + + if (canceledInvestigations.length === 0) { + toast.error("재의뢰할 수 있는 실사가 없습니다. 취소 상태의 실사만 재의뢰할 수 있습니다.") + return + } + + // 협력사 이름 목록 생성 + const vendorNames = canceledInvestigations + .map(row => row.original.vendorName) + .join(', ') + + // 재의뢰 데이터 저장 + const investigationIds = canceledInvestigations.map(row => row.original.investigation!.id) + setReRequestData({ + investigationIds, + vendorNames, + }) + + // 결재 템플릿 변수 생성 + const reRequestedAt = new Date() + const { mapPQReRequestToTemplateVariables } = await import('@/lib/vendor-investigation/handlers') + const variables = await mapPQReRequestToTemplateVariables({ + vendorNames, + investigationCount: investigationIds.length, + reRequestedAt, + reason, + }) + + setReRequestApprovalVariables(variables) + + // ReRequestInvestigationDialog 닫고 ApprovalPreviewDialog 열기 + setIsReRequestDialogOpen(false) + setIsReRequestApprovalDialogOpen(true) + } catch (error) { + console.error("재의뢰 결재 준비 중 오류 발생:", error) + toast.error("재의뢰 결재 준비 중 오류가 발생했습니다.") + } + } + + // 실사 재의뢰 결재 요청 처리 - Step 2: ApprovalPreviewDialog에서 결재선 선택 후 + const handleReRequestApprovalSubmit = async ({ approvers, title, attachments }: { approvers: string[], title: string, attachments?: File[] }) => { + debugLog('[ReRequestApproval] 실사 재의뢰 결재 요청 시작', { + approversCount: approvers.length, + hasSession: !!session?.user, + hasReRequestData: !!reRequestData, + }); + + if (!session?.user || !reRequestData) { + debugError('[ReRequestApproval] 세션 또는 재의뢰 데이터 없음'); + throw new Error('세션 정보가 없습니다.'); + } + + debugLog('[ReRequestApproval] 재의뢰 대상', { + investigationIds: reRequestData.investigationIds, + vendorNames: reRequestData.vendorNames, + }); + + debugLog('[ReRequestApproval] 결재선 추출 완료', { + approverEpIds: approvers, + }); + + // 결재 워크플로우 시작 (approvers는 이미 EP ID 배열) + const result = await reRequestPQInvestigationWithApproval({ + investigationIds: reRequestData.investigationIds, + vendorNames: reRequestData.vendorNames, + currentUser: { + id: Number(session.user.id), + epId: session.user.epId || null, + email: session.user.email || undefined, + }, + approvers: approvers, + }) + + debugSuccess('[ReRequestApproval] 재의뢰 결재 요청 성공', { + approvalId: result.approvalId, + pendingActionId: result.pendingActionId, + }); + + if (result.status === 'pending_approval') { + // 성공 시에만 상태 초기화 및 페이지 리로드 + setReRequestData(null) + window.location.reload() + } + } + + // 재실사 요청 처리 + const handleRequestReinspection = async ( + data: SiteVisitRequestFormValues, + attachments?: File[] + ) => { + try { + // 보완-재실사 대상 실사만 필터링 + const supplementReinspectInvestigations = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" + ); + + if (supplementReinspectInvestigations.length === 0) { + toast.error("보완-재실사 대상 실사가 없습니다."); + return; + } + + // 첫 번째 대상 실사로 재실사 요청 생성 + const targetRow = supplementReinspectInvestigations[0].original; + const targetInvestigation = targetRow.investigation!; + const { requestSupplementReinspectionAction } = await import('@/lib/vendor-investigation/service'); + + // SiteVisitRequestFormValues를 requestSupplementReinspectionAction 형식으로 변환 + // shiAttendees는 그대로 전달 (새로운 형식: {checked, attendees}) + const result = await requestSupplementReinspectionAction({ + investigationId: targetInvestigation.id, + siteVisitData: { + inspectionDuration: data.inspectionDuration, + requestedStartDate: data.requestedStartDate, + requestedEndDate: data.requestedEndDate, + shiAttendees: data.shiAttendees || {}, + vendorRequests: data.vendorRequests || {}, + additionalRequests: data.additionalRequests || "", + }, + }); + + if (result.success) { + toast.success("재실사 요청이 생성되었습니다."); + setIsReinspectionDialogOpen(false); + window.location.reload(); + } else { + toast.error(result.error || "재실사 요청 생성 중 오류가 발생했습니다."); + } + } catch (error) { + console.error("재실사 요청 오류:", error); + toast.error("재실사 요청 중 오류가 발생했습니다."); + } + }; + + // 실사 결과 발송 처리 + const handleSendInvestigationResults = async (data: { purchaseComment?: string }) => { + try { + setIsLoading(true) + + // 완료된 실사 중 승인된 결과 또는 보완된 결과만 필터링 + const approvedInvestigations = selectedRows.filter(row => { + const investigation = row.original.investigation + return investigation && + (investigation.investigationStatus === "COMPLETED" || + investigation.investigationStatus === "SUPPLEMENT_REQUIRED" || + investigation.evaluationResult === "REJECTED") + + }) + + if (approvedInvestigations.length === 0) { + toast.error("발송할 실사 결과가 없습니다. 완료되고 승인된 실사만 결과를 발송할 수 있습니다.") + return + } + + // 서버 액션 호출 + const result = await sendInvestigationResultsAction({ + investigationIds: approvedInvestigations.map(row => row.original.investigation!.id), + purchaseComment: data.purchaseComment, + }) + + if (result.success) { + toast.success(result.message || `${result.data?.successCount || 0}개 업체에 대한 실사 결과가 발송되었습니다.`) + window.location.reload() + } else { + toast.error(result.error || "실사 결과 발송 처리 중 오류가 발생했습니다.") + } + } catch (error) { + console.error("실사 결과 발송 중 오류 발생:", error) + toast.error("실사 결과 발송 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + setIsSendResultsDialogOpen(false) + } + } + + // 승인된 업체 수 확인 (미실사 PQ 제외) + const approvedPQsCount = selectedRows.filter(row => + row.original.status === "APPROVED" && + !row.original.investigation && + row.original.type !== "NON_INSPECTION" + ).length + + // 계획 상태 실사 수 확인 + const plannedInvestigationsCount = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "PLANNED" + ).length + + // 완료된 실사 수 확인 (승인된 결과만) + const completedInvestigationsCount = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "COMPLETED" && + row.original.investigation.evaluationResult === "APPROVED" + ).length + + // 취소된 실사 수 확인 + const canceledInvestigationsCount = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "CANCELED" + ).length + + // 재실사 요청 대상 수 확인 (보완-재실사 결과만) + const reinspectInvestigationsCount = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" + ).length + + // 재실사 요청 가능 여부 확인 (방문실사평가 또는 제품검사평가만 가능) + const canRequestReinspection = selectedRows.some(row => { + const investigation = row.original.investigation + if (!investigation) return false + if (investigation.evaluationResult !== "SUPPLEMENT_REINSPECT") return false + const method = investigation.investigationMethod + // 서류평가 또는 구매자체평가는 재방문실사 불가 + return method === "SITE_VISIT_EVAL" || method === "PRODUCT_INSPECTION" + }) + + // 미실사 PQ가 선택되었는지 확인 + const hasNonInspectionPQ = selectedRows.some(row => + row.original.type === "NON_INSPECTION" + ) + + // 실사 방법 라벨 변환 함수 + const getInvestigationMethodLabel = (method: string): string => { + switch (method) { + case "PURCHASE_SELF_EVAL": + return "구매자체평가" + case "DOCUMENT_EVAL": + return "서류평가" + case "PRODUCT_INSPECTION": + return "제품검사평가" + case "SITE_VISIT_EVAL": + return "방문실사평가" + default: + return method + } + } + + // 실사 결과 발송용 데이터 준비 + const auditResults = selectedRows + .filter(row => + row.original.investigation && + (row.original.investigation.investigationStatus === "COMPLETED" || row.original.investigation.investigationStatus === "SUPPLEMENT_REQUIRED") && ( + (row.original.investigation.evaluationResult === "APPROVED" || + row.original.investigation.evaluationResult === "SUPPLEMENT" || + row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" || + row.original.investigation.evaluationResult === "SUPPLEMENT_DOCUMENT")) + ) + .map(row => { + const investigation = row.original.investigation! + const pqSubmission = row.original + + // pqItems를 상세하게 포맷팅 (itemCode-itemName 형태로 모든 항목 표시) + const formatAuditItem = (pqItems: any): string => { + if (!pqItems) return pqSubmission.projectName || "N/A"; + + try { + // 이미 파싱된 객체 배열인 경우 + if (Array.isArray(pqItems)) { + return pqItems.map(item => { + if (typeof item === 'string') return item; + if (typeof item === 'object') { + const code = item.itemCode || item.code || ""; + const name = item.itemName || item.name || ""; + if (code && name) return `${code}-${name}`; + return name || code || String(item); + } + return String(item); + }).join(', '); + } + + // JSON 문자열인 경우 + if (typeof pqItems === 'string') { + try { + const parsed = JSON.parse(pqItems); + if (Array.isArray(parsed)) { + return parsed.map(item => { + if (typeof item === 'string') return item; + if (typeof item === 'object') { + const code = item.itemCode || item.code || ""; + const name = item.itemName || item.name || ""; + if (code && name) return `${code}-${name}`; + return name || code || String(item); + } + return String(item); + }).join(', '); + } + return String(parsed); + } catch { + return String(pqItems); + } + } + + // 기타 경우 + return String(pqItems); + } catch { + return pqSubmission.projectName || "N/A"; + } + }; + + return { + id: investigation.id, + vendorCode: row.original.vendorCode || "N/A", + vendorName: row.original.vendorName || "N/A", + vendorEmail: row.original.email || "N/A", + vendorContactPerson: (row.original as any).representativeName || row.original.vendorName || "N/A", + pqNumber: pqSubmission.pqNumber || "N/A", + auditItem: formatAuditItem(pqSubmission.pqItems), + auditFactoryAddress: investigation.investigationAddress || "N/A", + auditMethod: getInvestigationMethodLabel(investigation.investigationMethod || ""), + auditResult: investigation.evaluationResult === "APPROVED" ? "Pass(승인)" : + investigation.evaluationResult === "SUPPLEMENT" ? "Pass(조건부승인)" : + investigation.evaluationResult === "REJECTED" ? "Fail(미승인)" : "N/A", + additionalNotes: investigation.investigationNotes || undefined, + investigationNotes: investigation.investigationNotes || undefined, + } + }) + + return ( + <> +
+ {/* 실사 의뢰 버튼 */} + + + {/* 실사 의뢰 취소 버튼 */} + + + {/* 실사 재의뢰 버튼 */} + + + {/* 재실사 요청 버튼 */} + + + {/* 실사 결과 발송 버튼 */} + + + {/** Export 버튼 */} + +
+ + {/* 실사 의뢰 Dialog */} + + + + {/* 실사 취소 Dialog */} + setIsCancelDialogOpen(false)} + onConfirm={handleCancelInvestigation} + selectedCount={plannedInvestigationsCount} + /> + + {/* 실사 재의뢰 Dialog */} + setIsReRequestDialogOpen(false)} + onConfirm={handleReRequestInvestigation} + selectedCount={canceledInvestigationsCount} + /> + + {/* 결과 발송 Dialog */} + setIsSendResultsDialogOpen(false)} + onConfirm={handleSendInvestigationResults} + selectedCount={completedInvestigationsCount} + auditResults={auditResults} + /> + + {/* 재방문실사 요청 Dialog */} + {(() => { + // 보완-재실사 대상 실사 찾기 + const supplementReinspectInvestigations = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT" + ); + + if (supplementReinspectInvestigations.length === 0) { + return null; + } + + const targetRow = supplementReinspectInvestigations[0].original; + const targetInvestigation = targetRow.investigation!; + + return ( + setIsReinspectionDialogOpen(false)} + onSubmit={handleRequestReinspection} + investigation={{ + id: targetInvestigation.id, + investigationMethod: targetInvestigation.investigationMethod || undefined, + investigationAddress: targetInvestigation.investigationAddress || undefined, + investigationNotes: targetInvestigation.investigationNotes || undefined, + vendorName: targetRow.vendorName, + vendorCode: targetRow.vendorCode, + projectName: targetRow.projectName || undefined, + projectCode: targetRow.projectCode || undefined, + pqItems: targetRow.pqItems || null, + }} + isReinspection={true} + /> + ); + })()} + + {/* 결재 미리보기 Dialog - 실사 의뢰 */} + {session?.user && session.user.epId && investigationFormData && ( + { + setIsApprovalDialogOpen(open) + if (!open) { + // 다이얼로그가 닫히면 실사 폼 데이터도 초기화 + setInvestigationFormData(null) + } + }} + templateName="Vendor 실사의뢰" + variables={approvalVariables} + title={`Vendor 실사의뢰 - ${selectedRows.filter(row => + row.original.status === "APPROVED" && + !row.original.investigation && + row.original.type !== "NON_INSPECTION" + ).map(row => row.original.vendorName).join(', ')}`} + currentUser={{ + id: Number(session.user.id), + epId: session.user.epId, + name: session.user.name || undefined, + email: session.user.email || undefined, + }} + onConfirm={handleApprovalSubmit} + /> + )} + + {/* 결재 미리보기 Dialog - 실사 재의뢰 */} + {session?.user && session.user.epId && reRequestData && ( + { + setIsReRequestApprovalDialogOpen(open) + if (!open) { + // 다이얼로그가 닫히면 재의뢰 데이터도 초기화 + setReRequestData(null) + } + }} + templateName="Vendor 실사 재의뢰" + variables={reRequestApprovalVariables} + title={`Vendor 실사 재의뢰 - ${reRequestData.vendorNames}`} + currentUser={{ + id: Number(session.user.id), + epId: session.user.epId, + name: session.user.name || undefined, + email: session.user.email || undefined, + }} + onConfirm={handleReRequestApprovalSubmit} + /> + )} + + ) } \ No newline at end of file -- cgit v1.2.3