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 --- lib/vendor-regular-registrations/handlers.ts | 151 +++-- ...regular-registrations-table-toolbar-actions.tsx | 663 ++++++++++----------- 2 files changed, 419 insertions(+), 395 deletions(-) (limited to 'lib/vendor-regular-registrations') diff --git a/lib/vendor-regular-registrations/handlers.ts b/lib/vendor-regular-registrations/handlers.ts index 95acde23..7490d81a 100644 --- a/lib/vendor-regular-registrations/handlers.ts +++ b/lib/vendor-regular-registrations/handlers.ts @@ -107,7 +107,12 @@ export async function mapRegistrationToTemplateVariables(payload: { const { requestData, requestedAt, vendorId } = payload; // vendors 테이블에서 추가 정보 가져오기 - let vendorInfo: any = {}; + let vendorInfo: { + postalCode?: string | null; + businessSize?: string | null; + addressDetail?: string | null; + } = {}; + if (vendorId) { try { const vendorResult = await db @@ -115,7 +120,6 @@ export async function mapRegistrationToTemplateVariables(payload: { postalCode: vendors.postalCode, businessSize: vendors.businessSize, addressDetail: vendors.addressDetail, - // FAX, 사업유형, 산업유형은 vendors 테이블에 없으므로 빈 값으로 처리 }) .from(vendors) .where(eq(vendors.id, vendorId)) @@ -126,6 +130,7 @@ export async function mapRegistrationToTemplateVariables(payload: { console.warn('[Template Variables] Failed to fetch vendor info:', error); } } + // 추가정보 조회 let additionalInfo = { businessType: '', @@ -137,77 +142,101 @@ export async function mapRegistrationToTemplateVariables(payload: { }; if (vendorId) { - const additionalInfoResult = await db - .select({ - businessType: vendorAdditionalInfo.businessType, - industryType: vendorAdditionalInfo.industryType, - companySize: vendorAdditionalInfo.companySize, - revenue: vendorAdditionalInfo.revenue, - factoryEstablishedDate: vendorAdditionalInfo.factoryEstablishedDate, - preferredContractTerms: vendorAdditionalInfo.preferredContractTerms, - }) - .from(vendorAdditionalInfo) - .where(eq(vendorAdditionalInfo.vendorId, vendorId)) - .limit(1); + try { + const additionalInfoResult = await db + .select({ + businessType: vendorAdditionalInfo.businessType, + industryType: vendorAdditionalInfo.industryType, + companySize: vendorAdditionalInfo.companySize, + revenue: vendorAdditionalInfo.revenue, + factoryEstablishedDate: vendorAdditionalInfo.factoryEstablishedDate, + preferredContractTerms: vendorAdditionalInfo.preferredContractTerms, + }) + .from(vendorAdditionalInfo) + .where(eq(vendorAdditionalInfo.vendorId, vendorId)) + .limit(1); - additionalInfo = additionalInfoResult[0] || additionalInfo; + const info = additionalInfoResult[0]; + if (info) { + additionalInfo = { + businessType: info.businessType ?? '', + industryType: info.industryType ?? '', + companySize: info.companySize ?? '', + revenue: info.revenue ?? '', + factoryEstablishedDate: info.factoryEstablishedDate ?? '', + preferredContractTerms: info.preferredContractTerms ?? '', + }; + } + } catch (error) { + console.warn('[Template Variables] Failed to fetch additional info:', error); + } } console.log('[Template Variables] Additional info:', additionalInfo); + + // 변수명은 공백 없이 깔끔하게 정의 (template-utils.ts에서 자동으로 trim 처리됨) const variables = { - // 협력업체 기본정보 (템플릿의 정확한 변수명 사용) - ' 협력업체 기본정보-사업자번호 ': requestData.businessNumber || '', - ' 협력업체 기본정보-업체명 ': requestData.companyNameKor || '', - ' 협력업체 기본정보-대표자명 ': requestData.representativeNameKor || '', - ' 협력업체 기본정보 대표전화 ': requestData.headOfficePhone || '', - ' 협력업체 기본정보 -FAX ': '', // FAX 정보는 vendors 테이블에 없으므로 빈 문자열 - ' 협력업체 기본정보 -E-mail ': requestData.representativeEmail || '', - ' 협력업체 기본정보-우편번호 ': vendorInfo.postalCode || '', // vendors 테이블에서 우편번호 가져오기 - ' 협력업체 기본정보-회사주소': requestData.headOfficeAddress || '', - ' 협력업체 기본정보-상세주소': vendorInfo.addressDetail || '', // 상세주소는 벤더 상세주소로 - ' 협력업체 기본정보-사업유형': additionalInfo.businessType || '', // 주요품목을 사업유형으로 사용 - ' 협력업체 기본정보-산업유형': additionalInfo.industryType || '', // 주요품목을 산업유형으로도 사용 - ' 협력업체 기본정보-회사규모': additionalInfo.companySize || '', // 기업규모 + // 협력업체 기본정보 + '협력업체기본정보-사업자번호': requestData.businessNumber || '', + '협력업체기본정보-업체명': requestData.companyNameKor || '', + '협력업체기본정보-대표자명': requestData.representativeNameKor || '', + '협력업체기본정보-대표전화': requestData.headOfficePhone || '', + '협력업체기본정보-FAX': '', // FAX 정보는 vendors 테이블에 없으므로 빈 문자열 + '협력업체기본정보-Email': requestData.representativeEmail || '', + '협력업체기본정보-우편번호': vendorInfo.postalCode || '', + '협력업체기본정보-회사주소': requestData.headOfficeAddress || '', + '협력업체기본정보-상세주소': vendorInfo.addressDetail || '', + '협력업체기본정보-사업유형': additionalInfo.businessType || '', + '협력업체기본정보-산업유형': additionalInfo.industryType || '', + '협력업체기본정보-회사규모': additionalInfo.companySize || '', - // 담당자 연락처 (각 담당자별로 동일한 정보 반복 - 템플릿에서 여러 번 사용됨) - ' 협력업체 관리-상세보기-영업담당자-담당자명 ': requestData.businessContacts.sales.name || '', - ' 협력업체 관리-상세보기-영업담당자-직급 ': requestData.businessContacts.sales.position || '', - ' 협력업체 관리-상세보기-영업담당자-부서 ': requestData.businessContacts.sales.department || '', - ' 협력업체 관리-상세보기-영업담당자-담당업무 ': requestData.businessContacts.sales.responsibility || '', - ' 협력업체 관리-상세보기-영업담당자-이메일 ': requestData.businessContacts.sales.email || '', - ' 협력업체 관리-상세보기-설계담당자-담당자명 ': requestData.businessContacts.design.name || '', - ' 협력업체 관리-상세보기-설계담당자-직급 ': requestData.businessContacts.design.position || '', - ' 협력업체 관리-상세보기-설계담당자-부서 ': requestData.businessContacts.design.department || '', - ' 협력업체 관리-상세보기-설계담당자-담당업무 ': requestData.businessContacts.design.responsibility || '', - ' 협력업체 관리-상세보기-설계담당자-이메일 ': requestData.businessContacts.design.email || '', - ' 협력업체 관리-상세보기-납기담당자-담당자명 ': requestData.businessContacts.delivery.name || '', - ' 협력업체 관리-상세보기-납기담당자-직급 ': requestData.businessContacts.delivery.position || '', - ' 협력업체 관리-상세보기-납기담당자-부서 ': requestData.businessContacts.delivery.department || '', - ' 협력업체 관리-상세보기-납기담당자-담당업무 ': requestData.businessContacts.delivery.responsibility || '', - ' 협력업체 관리-상세보기-납기담당자-이메일 ': requestData.businessContacts.delivery.email || '', - ' 협력업체 관리-상세보기-품질담당자-담당자명 ': requestData.businessContacts.quality.name || '', - ' 협력업체 관리-상세보기-품질담당자-직급 ': requestData.businessContacts.quality.position || '', - ' 협력업체 관리-상세보기-품질담당자-부서 ': requestData.businessContacts.quality.department || '', - ' 협력업체 관리-상세보기-품질담당자-담당업무 ': requestData.businessContacts.quality.responsibility || '', - ' 협력업체 관리-상세보기-품질담당자-이메일 ': requestData.businessContacts.quality.email || '', - ' 협력업체 관리-상세보기-세금계산서담당자-담당자명 ': requestData.businessContacts.taxInvoice.name || '', - ' 협력업체 관리-상세보기-세금계산서담당자-직급 ': requestData.businessContacts.taxInvoice.position || '', - ' 협력업체 관리-상세보기-세금계산서담당자-부서 ': requestData.businessContacts.taxInvoice.department || '', - ' 협력업체 관리-상세보기-세금계산서담당자-담당업무 ': requestData.businessContacts.taxInvoice.responsibility || '', - ' 협력업체 관리-상세보기-세금계산서담당자-이메일 ': requestData.businessContacts.taxInvoice.email || '', + // 담당자 연락처 - 영업담당자 + '영업담당자-담당자명': requestData.businessContacts.sales.name || '', + '영업담당자-직급': requestData.businessContacts.sales.position || '', + '영업담당자-부서': requestData.businessContacts.sales.department || '', + '영업담당자-담당업무': requestData.businessContacts.sales.responsibility || '', + '영업담당자-이메일': requestData.businessContacts.sales.email || '', + + // 담당자 연락처 - 설계담당자 + '설계담당자-담당자명': requestData.businessContacts.design.name || '', + '설계담당자-직급': requestData.businessContacts.design.position || '', + '설계담당자-부서': requestData.businessContacts.design.department || '', + '설계담당자-담당업무': requestData.businessContacts.design.responsibility || '', + '설계담당자-이메일': requestData.businessContacts.design.email || '', + + // 담당자 연락처 - 납기담당자 + '납기담당자-담당자명': requestData.businessContacts.delivery.name || '', + '납기담당자-직급': requestData.businessContacts.delivery.position || '', + '납기담당자-부서': requestData.businessContacts.delivery.department || '', + '납기담당자-담당업무': requestData.businessContacts.delivery.responsibility || '', + '납기담당자-이메일': requestData.businessContacts.delivery.email || '', + + // 담당자 연락처 - 품질담당자 + '품질담당자-담당자명': requestData.businessContacts.quality.name || '', + '품질담당자-직급': requestData.businessContacts.quality.position || '', + '품질담당자-부서': requestData.businessContacts.quality.department || '', + '품질담당자-담당업무': requestData.businessContacts.quality.responsibility || '', + '품질담당자-이메일': requestData.businessContacts.quality.email || '', + + // 담당자 연락처 - 세금계산서담당자 + '세금계산서담당자-담당자명': requestData.businessContacts.taxInvoice.name || '', + '세금계산서담당자-직급': requestData.businessContacts.taxInvoice.position || '', + '세금계산서담당자-부서': requestData.businessContacts.taxInvoice.department || '', + '세금계산서담당자-담당업무': requestData.businessContacts.taxInvoice.responsibility || '', + '세금계산서담당자-이메일': requestData.businessContacts.taxInvoice.email || '', - // 기본계약서 현황 (정규업체 등록 시점에는 아직 계약서가 없으므로 빈 값) - '정규업체등록관리-문서현황-계약동의현황-계약유형 ': '정규업체 등록 요청', - '정규업체등록관리-문서현황-계약동의현황-상태 ': '등록 대기', - '정규업체등록관리-문서현황-계약동의현황-서약일자 ': new Date(requestedAt).toLocaleDateString('ko-KR'), + // 기본계약서 현황 + '계약유형': '정규업체 등록 요청', + '계약상태': '등록 대기', + '서약일자': new Date(requestedAt).toLocaleDateString('ko-KR'), }; // 디버깅을 위한 로그 출력 console.log('[Template Variables] Generated variables:', Object.keys(variables)); console.log('[Template Variables] Sample values:', { - companyName: variables[' 협력업체 기본정보-업체명 '], - businessNumber: variables[' 협력업체 기본정보-사업자번호 '], - representative: variables[' 협력업체 기본정보-대표자명 '], + companyName: variables['협력업체기본정보-업체명'], + businessNumber: variables['협력업체기본정보-사업자번호'], + representative: variables['협력업체기본정보-대표자명'], }); return variables; diff --git a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx index f40a41f7..f879f065 100644 --- a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx +++ b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx @@ -1,334 +1,329 @@ -"use client" - -import { type Table } from "@tanstack/react-table" -import { toast } from "sonner" - -import { Button } from "@/components/ui/button" -import { Mail, FileWarning, Scale, FileText } from "lucide-react" -import type { VendorRegularRegistration } from "@/config/vendorRegularRegistrationsColumnsConfig" -import { - sendMissingContractRequestEmails, - sendAdditionalInfoRequestEmails, - skipLegalReview -} from "../service" -import { useState } from "react" -import { SkipReasonDialog } from "@/components/vendor-regular-registrations/skip-reason-dialog" -import { RegistrationRequestDialog } from "@/components/vendor-regular-registrations/registration-request-dialog" -import { ApprovalPreviewDialog } from "@/components/approval/ApprovalPreviewDialog" -import { useRouter } from "next/navigation" -import { useSession } from "next-auth/react" -import { registerVendorWithApproval } from "../approval-actions" -import { mapRegistrationToTemplateVariables } from "../handlers" -import type { RegistrationRequestData } from "@/components/vendor-regular-registrations/registration-request-dialog" - -interface VendorRegularRegistrationsTableToolbarActionsProps { - table: Table -} - -export function VendorRegularRegistrationsTableToolbarActions({ - table, -}: VendorRegularRegistrationsTableToolbarActionsProps) { - const router = useRouter() - const { data: session } = useSession() - - const [syncLoading, setSyncLoading] = useState<{ - missingContract: boolean; - additionalInfo: boolean; - legalSkip: boolean; - registrationRequest: boolean; - }>({ - missingContract: false, - additionalInfo: false, - legalSkip: false, - registrationRequest: false, - }) - - const [skipDialogs, setSkipDialogs] = useState<{ - legalReview: boolean; - }>({ - legalReview: false, - }) - - // 2-step 결재 프로세스를 위한 상태 - const [registrationRequestDialog, setRegistrationRequestDialog] = useState<{ - open: boolean; - registration: VendorRegularRegistration | null; - }>({ - open: false, - registration: null, - }) - - const [approvalDialog, setApprovalDialog] = useState<{ - open: boolean; - registration: VendorRegularRegistration | null; - }>({ - open: false, - registration: null, - }) - - // 결재를 위한 중간 상태 저장 - const [registrationFormData, setRegistrationFormData] = useState(null) - const [approvalVariables, setApprovalVariables] = useState>({}) - - const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original) - - - - const handleSendMissingContractRequest = async () => { - if (selectedRows.length === 0) { - toast.error("이메일을 발송할 업체를 선택해주세요.") - return - } - - setSyncLoading(prev => ({ ...prev, missingContract: true })) - try { - const vendorIds = selectedRows.map(row => row.vendorId) - const result = await sendMissingContractRequestEmails(vendorIds) - - if (result.success) { - toast.success(result.message) - } else { - toast.error(result.error) - } - } catch (error) { - console.error("Error sending missing contract request:", error) - toast.error("누락계약요청 이메일 발송 중 오류가 발생했습니다.") - } finally { - setSyncLoading(prev => ({ ...prev, missingContract: false })) - } - } - - const handleSendAdditionalInfoRequest = async () => { - if (selectedRows.length === 0) { - toast.error("이메일을 발송할 업체를 선택해주세요.") - return - } - - setSyncLoading(prev => ({ ...prev, additionalInfo: true })) - try { - const vendorIds = selectedRows.map(row => row.vendorId) - const result = await sendAdditionalInfoRequestEmails(vendorIds) - - if (result.success) { - toast.success(result.message) - } else { - toast.error(result.error) - } - } catch (error) { - console.error("Error sending additional info request:", error) - toast.error("추가정보요청 이메일 발송 중 오류가 발생했습니다.") - } finally { - setSyncLoading(prev => ({ ...prev, additionalInfo: false })) - } - } - - const handleLegalReviewSkip = async (reason: string) => { - const cpReviewRows = selectedRows.filter(row => row.status === "cp_review"); - if (cpReviewRows.length === 0) { - toast.error("CP검토 상태인 업체를 선택해주세요."); - return; - } - - setSyncLoading(prev => ({ ...prev, legalSkip: true })); - try { - const vendorIds = cpReviewRows.map(row => row.vendorId); - const result = await skipLegalReview(vendorIds, reason); - - if (result.success) { - toast.success(result.message); - router.refresh(); - } else { - toast.error(result.error); - } - } catch (error) { - console.error("Error skipping legal review:", error); - toast.error("법무검토 Skip 처리 중 오류가 발생했습니다."); - } finally { - setSyncLoading(prev => ({ ...prev, legalSkip: false })); - } - }; - - // 등록요청 핸들러 - Step 1: 정보 입력 - const handleRegistrationRequest = () => { - const approvalReadyRows = selectedRows.filter(row => row.status === "approval_ready"); - - if (approvalReadyRows.length === 0) { - toast.error("조건충족 상태의 벤더를 선택해주세요."); - return; - } - - if (approvalReadyRows.length > 1) { - toast.error("정규업체 등록 요청은 한 번에 하나씩만 가능합니다."); - return; - } - - setRegistrationRequestDialog({ - open: true, - registration: approvalReadyRows[0], - }); - }; - - // 등록요청 정보 입력 완료 - Step 1에서 Step 2로 전환 - const handleRegistrationRequestSubmit = async (requestData: RegistrationRequestData) => { - if (!registrationRequestDialog.registration || !session?.user) return; - - try { - // 폼 데이터 저장 - setRegistrationFormData(requestData); - - // 결재 템플릿 변수 생성 - const requestedAt = new Date(); - const variables = await mapRegistrationToTemplateVariables({ - requestData, - requestedAt, - }); - - setApprovalVariables(variables); - - // RegistrationRequestDialog 닫고 ApprovalPreviewDialog 열기 - setRegistrationRequestDialog({ open: false, registration: null }); - setApprovalDialog({ - open: true, - registration: registrationRequestDialog.registration, - }); - } catch (error) { - console.error("결재 준비 중 오류 발생:", error); - toast.error("결재 준비 중 오류가 발생했습니다."); - } - }; - - // 결재 상신 - Step 2: 결재선 선택 후 최종 상신 - const handleApprovalSubmit = async (approvers: any[]) => { - if (!approvalDialog.registration || !registrationFormData || !session?.user) { - toast.error("세션 정보가 없습니다."); - return; - } - - setSyncLoading(prev => ({ ...prev, registrationRequest: true })); - try { - // 결재선에서 EP ID 추출 (상신자 제외) - const approverEpIds = approvers - .filter((line) => line.seq !== "0" && line.epId) - .map((line) => line.epId!); - - // 결재 워크플로우 시작 - const result = await registerVendorWithApproval({ - registrationId: approvalDialog.registration.id, - requestData: registrationFormData, - vendorId: approvalDialog.registration.vendorId, // vendors 테이블에서 정보를 가져오기 위한 vendorId - currentUser: { - id: Number(session.user.id), - epId: session.user.epId || null, - email: session.user.email || undefined, - }, - approvers: approverEpIds, - }); - - if (result.status === 'pending_approval') { - // 성공 시에만 상태 초기화 및 페이지 리로드 - setRegistrationFormData(null); - setApprovalVariables({}); - setApprovalDialog({ open: false, registration: null }); - toast.success("정규업체 등록 결재가 상신되었습니다."); - router.refresh(); - } - } catch (error) { - console.error("결재 상신 중 오류:", error); - toast.error("결재 상신 중 오류가 발생했습니다."); - } finally { - setSyncLoading(prev => ({ ...prev, registrationRequest: false })); - } - }; - - // CP검토 상태인 선택된 행들 개수 - const cpReviewCount = selectedRows.filter(row => row.status === "cp_review").length; - - // 조건충족 상태인 선택된 행들 개수 - const approvalReadyCount = selectedRows.filter(row => row.status === "approval_ready").length; - - return ( -
- - - - - - - - - setSkipDialogs(prev => ({ ...prev, legalReview: open }))} - title="GTC Skip" - description={`선택된 ${cpReviewCount}개 업체의 GTC를 Skip하고 CP완료 상태로 변경합니다. Skip 사유를 입력해주세요.`} - onConfirm={handleLegalReviewSkip} - loading={syncLoading.legalSkip} - /> - - setRegistrationRequestDialog(prev => ({ ...prev, open }))} - registration={registrationRequestDialog.registration} - onSubmit={handleRegistrationRequestSubmit} - /> - - {/* 결재 미리보기 Dialog - 정규업체 등록 */} - {session?.user && approvalDialog.registration && ( - { - setApprovalDialog(prev => ({ ...prev, open })); - if (!open) { - // 다이얼로그가 닫히면 폼 데이터도 초기화 - setRegistrationFormData(null); - setApprovalVariables({}); - } - }} - templateName="정규업체 등록" - variables={approvalVariables} - title={`정규업체 등록 - ${approvalDialog.registration.companyName}`} - description={`${approvalDialog.registration.companyName} 정규업체 등록 요청`} - currentUser={{ - id: Number(session.user.id), - epId: session.user.epId || null, - name: session.user.name || null, - email: session.user.email || '', - }} - onSubmit={handleApprovalSubmit} - /> - )} -
- ) -} +"use client" + +import { type Table } from "@tanstack/react-table" +import { toast } from "sonner" + +import { Button } from "@/components/ui/button" +import { Mail, FileWarning, Scale, FileText } from "lucide-react" +import type { VendorRegularRegistration } from "@/config/vendorRegularRegistrationsColumnsConfig" +import { + sendMissingContractRequestEmails, + sendAdditionalInfoRequestEmails, + skipLegalReview +} from "../service" +import { useState } from "react" +import { SkipReasonDialog } from "@/components/vendor-regular-registrations/skip-reason-dialog" +import { RegistrationRequestDialog } from "@/components/vendor-regular-registrations/registration-request-dialog" +import { ApprovalPreviewDialog } from "@/lib/approval/approval-preview-dialog" +import { useRouter } from "next/navigation" +import { useSession } from "next-auth/react" +import { registerVendorWithApproval } from "../approval-actions" +import { mapRegistrationToTemplateVariables } from "../handlers" +import type { RegistrationRequestData } from "@/components/vendor-regular-registrations/registration-request-dialog" + +interface VendorRegularRegistrationsTableToolbarActionsProps { + table: Table +} + +export function VendorRegularRegistrationsTableToolbarActions({ + table, +}: VendorRegularRegistrationsTableToolbarActionsProps) { + const router = useRouter() + const { data: session } = useSession() + + const [syncLoading, setSyncLoading] = useState<{ + missingContract: boolean; + additionalInfo: boolean; + legalSkip: boolean; + registrationRequest: boolean; + }>({ + missingContract: false, + additionalInfo: false, + legalSkip: false, + registrationRequest: false, + }) + + const [skipDialogs, setSkipDialogs] = useState<{ + legalReview: boolean; + }>({ + legalReview: false, + }) + + // 2-step 결재 프로세스를 위한 상태 + const [registrationRequestDialog, setRegistrationRequestDialog] = useState<{ + open: boolean; + registration: VendorRegularRegistration | null; + }>({ + open: false, + registration: null, + }) + + const [approvalDialog, setApprovalDialog] = useState<{ + open: boolean; + registration: VendorRegularRegistration | null; + }>({ + open: false, + registration: null, + }) + + // 결재를 위한 중간 상태 저장 + const [registrationFormData, setRegistrationFormData] = useState(null) + const [approvalVariables, setApprovalVariables] = useState>({}) + + const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original) + + + + const handleSendMissingContractRequest = async () => { + if (selectedRows.length === 0) { + toast.error("이메일을 발송할 업체를 선택해주세요.") + return + } + + setSyncLoading(prev => ({ ...prev, missingContract: true })) + try { + const vendorIds = selectedRows.map(row => row.vendorId) + const result = await sendMissingContractRequestEmails(vendorIds) + + if (result.success) { + toast.success(result.message) + } else { + toast.error(result.error) + } + } catch (error) { + console.error("Error sending missing contract request:", error) + toast.error("누락계약요청 이메일 발송 중 오류가 발생했습니다.") + } finally { + setSyncLoading(prev => ({ ...prev, missingContract: false })) + } + } + + const handleSendAdditionalInfoRequest = async () => { + if (selectedRows.length === 0) { + toast.error("이메일을 발송할 업체를 선택해주세요.") + return + } + + setSyncLoading(prev => ({ ...prev, additionalInfo: true })) + try { + const vendorIds = selectedRows.map(row => row.vendorId) + const result = await sendAdditionalInfoRequestEmails(vendorIds) + + if (result.success) { + toast.success(result.message) + } else { + toast.error(result.error) + } + } catch (error) { + console.error("Error sending additional info request:", error) + toast.error("추가정보요청 이메일 발송 중 오류가 발생했습니다.") + } finally { + setSyncLoading(prev => ({ ...prev, additionalInfo: false })) + } + } + + const handleLegalReviewSkip = async (reason: string) => { + const cpReviewRows = selectedRows.filter(row => row.status === "cp_review"); + if (cpReviewRows.length === 0) { + toast.error("CP검토 상태인 업체를 선택해주세요."); + return; + } + + setSyncLoading(prev => ({ ...prev, legalSkip: true })); + try { + const vendorIds = cpReviewRows.map(row => row.vendorId); + const result = await skipLegalReview(vendorIds, reason); + + if (result.success) { + toast.success(result.message); + router.refresh(); + } else { + toast.error(result.error); + } + } catch (error) { + console.error("Error skipping legal review:", error); + toast.error("법무검토 Skip 처리 중 오류가 발생했습니다."); + } finally { + setSyncLoading(prev => ({ ...prev, legalSkip: false })); + } + }; + + // 등록요청 핸들러 - Step 1: 정보 입력 + const handleRegistrationRequest = () => { + const approvalReadyRows = selectedRows.filter(row => row.status === "approval_ready"); + + if (approvalReadyRows.length === 0) { + toast.error("조건충족 상태의 벤더를 선택해주세요."); + return; + } + + if (approvalReadyRows.length > 1) { + toast.error("정규업체 등록 요청은 한 번에 하나씩만 가능합니다."); + return; + } + + setRegistrationRequestDialog({ + open: true, + registration: approvalReadyRows[0], + }); + }; + + // 등록요청 정보 입력 완료 - Step 1에서 Step 2로 전환 + const handleRegistrationRequestSubmit = async (requestData: RegistrationRequestData) => { + if (!registrationRequestDialog.registration || !session?.user) return; + + try { + // 폼 데이터 저장 + setRegistrationFormData(requestData); + + // 결재 템플릿 변수 생성 (vendorId 포함) + const requestedAt = new Date(); + const variables = await mapRegistrationToTemplateVariables({ + requestData, + requestedAt, + vendorId: registrationRequestDialog.registration.vendorId, // vendors 테이블에서 추가 정보 가져오기 + }); + + setApprovalVariables(variables); + + // RegistrationRequestDialog 닫고 ApprovalPreviewDialog 열기 + setRegistrationRequestDialog({ open: false, registration: null }); + setApprovalDialog({ + open: true, + registration: registrationRequestDialog.registration, + }); + } catch (error) { + console.error("결재 준비 중 오류 발생:", error); + toast.error("결재 준비 중 오류가 발생했습니다."); + } + }; + + // 결재 상신 - Step 2: 결재선 선택 후 최종 상신 + const handleApprovalSubmit = async ({ approvers, title, attachments }: { approvers: string[], title: string, attachments?: File[] }) => { + if (!approvalDialog.registration || !registrationFormData || !session?.user) { + toast.error("세션 정보가 없습니다."); + return; + } + + setSyncLoading(prev => ({ ...prev, registrationRequest: true })); + try { + // 결재 워크플로우 시작 (approvers는 이미 EP ID 배열) + const result = await registerVendorWithApproval({ + registrationId: approvalDialog.registration.id, + requestData: registrationFormData, + vendorId: approvalDialog.registration.vendorId, // vendors 테이블에서 정보를 가져오기 위한 vendorId + currentUser: { + id: Number(session.user.id), + epId: session.user.epId || null, + email: session.user.email || undefined, + }, + approvers: approvers, + }); + + if (result.status === 'pending_approval') { + // 성공 시에만 상태 초기화 및 페이지 리로드 + setRegistrationFormData(null); + setApprovalVariables({}); + setApprovalDialog({ open: false, registration: null }); + toast.success("정규업체 등록 결재가 상신되었습니다."); + router.refresh(); + } + } catch (error) { + console.error("결재 상신 중 오류:", error); + toast.error("결재 상신 중 오류가 발생했습니다."); + } finally { + setSyncLoading(prev => ({ ...prev, registrationRequest: false })); + } + }; + + // CP검토 상태인 선택된 행들 개수 + const cpReviewCount = selectedRows.filter(row => row.status === "cp_review").length; + + // 조건충족 상태인 선택된 행들 개수 + const approvalReadyCount = selectedRows.filter(row => row.status === "approval_ready").length; + + return ( +
+ + + + + + + + + setSkipDialogs(prev => ({ ...prev, legalReview: open }))} + title="GTC Skip" + description={`선택된 ${cpReviewCount}개 업체의 GTC를 Skip하고 CP완료 상태로 변경합니다. Skip 사유를 입력해주세요.`} + onConfirm={handleLegalReviewSkip} + loading={syncLoading.legalSkip} + /> + + setRegistrationRequestDialog(prev => ({ ...prev, open }))} + registration={registrationRequestDialog.registration} + onSubmit={handleRegistrationRequestSubmit} + /> + + {/* 결재 미리보기 Dialog - 정규업체 등록 */} + {session?.user && session.user.epId && approvalDialog.registration && ( + { + setApprovalDialog(prev => ({ ...prev, open })); + if (!open) { + // 다이얼로그가 닫히면 폼 데이터도 초기화 + setRegistrationFormData(null); + setApprovalVariables({}); + } + }} + templateName="정규업체 등록" + variables={approvalVariables} + title={`정규업체 등록 - ${approvalDialog.registration.companyName}`} + currentUser={{ + id: Number(session.user.id), + epId: session.user.epId, + name: session.user.name || undefined, + email: session.user.email || undefined, + }} + onConfirm={handleApprovalSubmit} + /> + )} +
+ ) +} -- cgit v1.2.3