"use server" import { revalidateTag, unstable_cache } from "next/cache"; import { getVendorRegularRegistrations, createVendorRegularRegistration, updateVendorRegularRegistration, getVendorRegularRegistrationById, } from "./repository"; import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { headers } from "next/headers"; import { sendEmail } from "@/lib/mail/sendEmail"; import type { RegistrationRequestData } from "@/components/vendor-regular-registrations/registration-request-dialog"; import { vendors, vendorRegularRegistrations, vendorAttachments, vendorInvestigations, vendorInvestigationAttachments, basicContract, vendorPQSubmissions, vendorBusinessContacts, vendorAdditionalInfo, basicContractTemplates, users } from "@/db/schema"; import db from "@/db/db"; import { inArray, eq, desc, and, lt } from "drizzle-orm"; import { sendTestVendorDataToMDG } from "@/lib/soap/mdg/send/vendor-master/action"; // 3개월 이상 정규등록검토 상태인 등록을 장기미등록으로 변경 async function updatePendingApprovals() { try { const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); // 3개월 이상 조건충족 상태인 등록들을 조회 const outdatedRegistrations = await db .select() .from(vendorRegularRegistrations) .where( and( eq(vendorRegularRegistrations.status, "approval_ready"), lt(vendorRegularRegistrations.updatedAt, threeMonthsAgo) ) ); // 장기미등록으로 상태 변경 if (outdatedRegistrations.length > 0) { await db .update(vendorRegularRegistrations) .set({ status: "registration_failed", updatedAt: new Date(), remarks: "3개월 이상 조건충족 상태로 장기미등록으로 변경", }) .where( and( eq(vendorRegularRegistrations.status, "approval_ready"), lt(vendorRegularRegistrations.updatedAt, threeMonthsAgo) ) ); console.log(`${outdatedRegistrations.length}개의 등록이 장기미등록으로 변경되었습니다.`); } } catch (error) { console.error("장기미등록 상태 업데이트 오류:", error); } } // 캐싱과 에러 핸들링이 포함된 조회 함수 export async function fetchVendorRegularRegistrations(input?: { search?: string; status?: string[]; page?: number; perPage?: number; }) { return unstable_cache( async () => { try { // 장기미등록 상태 업데이트 실행 await updatePendingApprovals(); const registrations = await getVendorRegularRegistrations(); let filteredData = registrations; // 검색 필터링 if (input?.search) { const searchLower = input.search.toLowerCase(); filteredData = filteredData.filter( (reg) => reg.companyName.toLowerCase().includes(searchLower) || reg.businessNumber.toLowerCase().includes(searchLower) || reg.potentialCode?.toLowerCase().includes(searchLower) || reg.representative?.toLowerCase().includes(searchLower) ); } // 상태 필터링 if (input?.status && input.status.length > 0) { filteredData = filteredData.filter((reg) => input.status!.includes(reg.status) ); } // 페이지네이션 const page = input?.page || 1; const perPage = input?.perPage || 50; const offset = (page - 1) * perPage; const paginatedData = filteredData.slice(offset, offset + perPage); const pageCount = Math.ceil(filteredData.length / perPage); return { success: true, data: paginatedData, pageCount, total: filteredData.length, }; } catch (error) { console.error("Error in fetchVendorRegularRegistrations:", error); return { success: false, error: error instanceof Error ? error.message : "정규업체 등록 목록을 가져오는 중 오류가 발생했습니다.", }; } }, [JSON.stringify(input || {})], { revalidate: 60, // 1분 캐시로 단축 tags: ["vendor-regular-registrations"], } )(); } export async function getCurrentUserInfo() { const session = await getServerSession(authOptions); return { userId: session?.user?.id ? String(session.user.id) : null, userName: session?.user?.name || null, }; } // 누락계약요청 이메일 발송 export async function sendMissingContractRequestEmails(vendorIds: number[]) { try { const session = await getServerSession(authOptions); if (!session?.user) { return { success: false, error: "로그인이 필요합니다." }; } // 벤더 정보 조회 const vendorList = await db .select({ id: vendors.id, vendorName: vendors.vendorName, email: vendors.email, }) .from(vendors) .where(inArray(vendors.id, vendorIds)); if (vendorList.length === 0) { return { success: false, error: "선택된 업체를 찾을 수 없습니다." }; } const headersList = await headers(); const host = headersList.get('host') || 'localhost:3000'; // const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http'; const protocol ='http';// 운영 시점에서는 https로 변경 const baseUrl = `${protocol}://${host}`; const contractManagementUrl = `${baseUrl}/ko/partners/basic-contract`; // 실제 기본계약 관리 페이지 URL로 수정 필요 let successCount = 0; let errorCount = 0; // 각 벤더에게 이메일 발송 await Promise.all( vendorList.map(async (vendor) => { if (!vendor.email) { errorCount++; return; } try { await sendEmail({ to: vendor.email, subject: "[SHI] 정규업체 등록을 위한 기본계약/서약 진행 요청", template: "vendor-missing-contract-request", context: { vendorName: vendor.vendorName, contractManagementUrl, senderName: session.user.name || "구매담당자", senderEmail: session.user.email || "", currentYear: new Date().getFullYear(), }, }); successCount++; } catch (error) { console.error(`Failed to send email to ${vendor.vendorName}:`, error); errorCount++; } }) ); if (errorCount > 0) { return { success: false, error: `${successCount}개 업체에 발송 성공, ${errorCount}개 업체 발송 실패`, }; } return { success: true, message: `${successCount}개 업체에 누락계약요청 이메일을 발송했습니다.`, }; } catch (error) { console.error("Error sending missing contract request emails:", error); return { success: false, error: error instanceof Error ? error.message : "이메일 발송 중 오류가 발생했습니다.", }; } } // 추가정보요청 이메일 발송 export async function sendAdditionalInfoRequestEmails(vendorIds: number[]) { try { const session = await getServerSession(authOptions); if (!session?.user) { return { success: false, error: "로그인이 필요합니다." }; } // 벤더 정보 조회 const vendorList = await db .select({ id: vendors.id, vendorName: vendors.vendorName, email: vendors.email, }) .from(vendors) .where(inArray(vendors.id, vendorIds)); if (vendorList.length === 0) { return { success: false, error: "선택된 업체를 찾을 수 없습니다." }; } const headersList = await headers(); const host = headersList.get('host') || 'localhost:3000'; // const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http'; const protocol ='http';// 운영 시점에서는 https로 변경 const baseUrl = `${protocol}://${host}`; const vendorInfoUrl = `${baseUrl}/ko/partners/info`; // 실제 업체정보 관리 페이지 URL로 수정 필요 let successCount = 0; let errorCount = 0; // 각 벤더에게 이메일 발송 await Promise.all( vendorList.map(async (vendor) => { if (!vendor.email) { errorCount++; return; } try { await sendEmail({ to: vendor.email, subject: "[SHI] 정규업체 등록을 위한 추가정보 입력 요청", template: "vendor-regular-registration-request", context: { vendorName: vendor.vendorName, vendorInfoUrl, senderName: session.user.name || "구매담당자", senderEmail: session.user.email || "", currentYear: new Date().getFullYear(), }, }); successCount++; } catch (error) { console.error(`Failed to send email to ${vendor.vendorName}:`, error); errorCount++; } }) ); if (errorCount > 0) { return { success: false, error: `${successCount}개 업체에 발송 성공, ${errorCount}개 업체 발송 실패`, }; } return { success: true, message: `${successCount}개 업체에 추가정보요청 이메일을 발송했습니다.`, }; } catch (error) { console.error("Error sending additional info request emails:", error); return { success: false, error: error instanceof Error ? error.message : "이메일 발송 중 오류가 발생했습니다.", }; } } // 법무검토 Skip 기능 export async function skipLegalReview(vendorIds: number[], skipReason: string) { try { const session = await getServerSession(authOptions); if (!session?.user) { return { success: false, error: "로그인이 필요합니다." }; } let successCount = 0; let errorCount = 0; for (const vendorId of vendorIds) { try { // 해당 벤더의 registration 찾기 또는 생성 const vendorList = await db .select({ id: vendors.id }) .from(vendors) .where(eq(vendors.id, vendorId)); if (vendorList.length === 0) { errorCount++; continue; } // registration 조회 const existingRegistrations = await db .select() .from(vendorRegularRegistrations) .where(eq(vendorRegularRegistrations.vendorId, vendorId)); let registrationId; if (existingRegistrations.length === 0) { // 새로 생성 const newRegistration = await createVendorRegularRegistration({ vendorId: vendorId, status: "under_review", // 검토중으로 변경 remarks: `GTC Skip: ${skipReason}`, }); registrationId = newRegistration.id; } else { // 기존 registration 업데이트 registrationId = existingRegistrations[0].id; const currentRemarks = existingRegistrations[0].remarks || ""; const newRemarks = currentRemarks ? `${currentRemarks}\nGTC Skip: ${skipReason}` : `GTC Skip: ${skipReason}`; await updateVendorRegularRegistration(registrationId, { gtcSkipped: true, // GTC Skip 여부 설정 remarks: newRemarks, }); } successCount++; } catch (error) { console.error(`Failed to skip legal review for vendor ${vendorId}:`, error); errorCount++; } } if (errorCount > 0) { return { success: false, error: `${successCount}개 업체 처리 성공, ${errorCount}개 업체 처리 실패`, }; } return { success: true, message: `${successCount}개 업체의 GTC를 Skip 처리했습니다.`, }; } catch (error) { console.error("Error skipping legal review:", error); return { success: false, error: error instanceof Error ? error.message : "GTC Skip 처리 중 오류가 발생했습니다.", }; } } // 주요품목 업데이트 export async function updateMajorItems( registrationId: number, majorItems: string ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return { success: false, error: "로그인이 필요합니다." }; } const result = await updateVendorRegularRegistration(registrationId, { majorItems: majorItems, }); if (!result) { return { success: false, error: "등록 정보를 찾을 수 없습니다." }; } // 캐시 무효화 revalidateTag("vendor-regular-registrations"); return { success: true, message: "주요품목이 업데이트되었습니다.", }; } catch (error) { console.error("Error updating major items:", error); return { success: false, error: error instanceof Error ? error.message : "주요품목 업데이트 중 오류가 발생했습니다.", }; } } // 벤더용 현황 조회 함수들 export async function fetchVendorRegistrationStatus(vendorId: number) { return unstable_cache( async () => { try { // 벤더 기본 정보 const vendor = await db .select({ id: vendors.id, vendorName: vendors.vendorName, taxId: vendors.taxId, representativeName: vendors.representativeName, country: vendors.country, createdAt: vendors.createdAt, updatedAt: vendors.updatedAt, }) .from(vendors) .where(eq(vendors.id, vendorId)) .limit(1) if (!vendor[0]) { return { success: false, error: "벤더 정보를 찾을 수 없습니다.", } } // 정규업체 등록 정보 (없을 수도 있음 - 기존 정규업체이거나 아직 등록 진행 안함) const registration = await db .select({ id: vendorRegularRegistrations.id, vendorId: vendorRegularRegistrations.vendorId, potentialCode: vendorRegularRegistrations.potentialCode, status: vendorRegularRegistrations.status, majorItems: vendorRegularRegistrations.majorItems, registrationRequestDate: vendorRegularRegistrations.registrationRequestDate, assignedDepartment: vendorRegularRegistrations.assignedDepartment, assignedDepartmentCode: vendorRegularRegistrations.assignedDepartmentCode, assignedUser: vendorRegularRegistrations.assignedUser, assignedUserCode: vendorRegularRegistrations.assignedUserCode, remarks: vendorRegularRegistrations.remarks, safetyQualificationContent: vendorRegularRegistrations.safetyQualificationContent, gtcSkipped: vendorRegularRegistrations.gtcSkipped, createdAt: vendorRegularRegistrations.createdAt, updatedAt: vendorRegularRegistrations.updatedAt, }) .from(vendorRegularRegistrations) .where(eq(vendorRegularRegistrations.vendorId, vendorId)) .limit(1) // 정규업체 등록 정보가 없는 경우 (정상적인 상황) if (!registration[0]) { return { success: false, error: "정규업체 등록 진행 정보가 없습니다.", // 에러가 아닌 정보성 메시지 noRegistration: true // 등록 정보가 없음을 명시적으로 표시 } } // 벤더 첨부파일 조회 const vendorFiles = await db .select() .from(vendorAttachments) .where(eq(vendorAttachments.vendorId, vendorId)) // 실사 결과 조회 (vendor_investigation_attachments) const investigationFiles = await db .select({ attachmentId: vendorInvestigationAttachments.id, fileName: vendorInvestigationAttachments.fileName, filePath: vendorInvestigationAttachments.filePath, createdAt: vendorInvestigationAttachments.createdAt, }) .from(vendorInvestigationAttachments) .innerJoin(vendorInvestigations, eq(vendorInvestigationAttachments.investigationId, vendorInvestigations.id)) .where(eq(vendorInvestigations.vendorId, vendorId)) // PQ 제출 정보 const pqSubmission = await db .select() .from(vendorPQSubmissions) .where(eq(vendorPQSubmissions.vendorId, vendorId)) .orderBy(desc(vendorPQSubmissions.createdAt)) .limit(1) // 기본계약 정보 - 템플릿 정보와 함께 조회 const allVendorContracts = await db .select({ templateId: basicContract.templateId, templateName: basicContractTemplates.templateName, status: basicContract.status, createdAt: basicContract.createdAt, filePath: basicContract.filePath, fileName: basicContract.fileName, }) .from(basicContract) .leftJoin(basicContractTemplates, eq(basicContract.templateId, basicContractTemplates.id)) .where(eq(basicContract.vendorId, vendorId)) .orderBy(desc(basicContract.createdAt)) // 계약 필터링 (기술자료, 비밀유지 제외) const filteredContracts = allVendorContracts.filter(contract => contract.templateName && !contract.templateName.includes("기술자료") && !contract.templateName.includes("비밀유지") ) // 템플릿 이름별로 가장 최신 계약만 유지 const vendorContracts = filteredContracts.reduce((acc: typeof filteredContracts, contract) => { const existing = acc.find((c: typeof contract) => c.templateName === contract.templateName) if (!existing || (contract.createdAt && existing.createdAt && contract.createdAt > existing.createdAt)) { return acc.filter((c: typeof contract) => c.templateName !== contract.templateName).concat(contract) } return acc }, [] as typeof filteredContracts) console.log(`🏢 Partners 벤더 ID ${vendorId} 기본계약 정보:`, { allContractsCount: allVendorContracts.length, filteredContractsCount: filteredContracts.length, finalContractsCount: vendorContracts.length, vendorContracts: vendorContracts.map((c: any) => ({ templateName: c.templateName, status: c.status, createdAt: c.createdAt })) }) // 업무담당자 정보 const businessContacts = await db .select() .from(vendorBusinessContacts) .where(eq(vendorBusinessContacts.vendorId, vendorId)) // 추가정보 const additionalInfo = await db .select() .from(vendorAdditionalInfo) .where(eq(vendorAdditionalInfo.vendorId, vendorId)) .limit(1) // 문서 제출 현황 계산 const documentStatus = { businessRegistration: vendorFiles.some(f => f.attachmentType === "BUSINESS_REGISTRATION"), creditEvaluation: vendorFiles.some(f => f.attachmentType === "CREDIT_REPORT"), // CREDIT_EVALUATION -> CREDIT_REPORT bankCopy: vendorFiles.some(f => f.attachmentType === "BANK_ACCOUNT_COPY"), // BANK_COPY -> BANK_ACCOUNT_COPY auditResult: investigationFiles.length > 0, // DocumentStatusDialog에서 사용하는 키 cpDocument: vendorContracts.some(c => c.status === "COMPLETED"), gtc: vendorContracts.some(c => c.templateName?.includes("GTC") && c.status === "COMPLETED"), standardSubcontract: vendorContracts.some(c => c.templateName?.includes("표준하도급") && c.status === "COMPLETED"), safetyHealth: vendorContracts.some(c => c.templateName?.includes("안전보건") && c.status === "COMPLETED"), ethics: vendorContracts.some(c => c.templateName?.includes("윤리") && c.status === "COMPLETED"), domesticCredit: vendorContracts.some(c => c.templateName?.includes("신용") && c.status === "COMPLETED"), safetyQualification: investigationFiles.length > 0, } // 문서별 파일 정보 (다운로드용) const documentFiles = { businessRegistration: vendorFiles.filter(f => f.attachmentType === "BUSINESS_REGISTRATION"), creditEvaluation: vendorFiles.filter(f => f.attachmentType === "CREDIT_REPORT"), bankCopy: vendorFiles.filter(f => f.attachmentType === "BANK_ACCOUNT_COPY"), auditResult: investigationFiles, } // 미완성 항목 계산 const missingDocuments = Object.entries(documentStatus) .filter(([, value]) => !value) .map(([key]) => key) const requiredContactTypes = ["sales", "design", "delivery", "quality", "tax_invoice"] const existingContactTypes = businessContacts.map(contact => contact.contactType) const missingContactTypes = requiredContactTypes.filter(type => !existingContactTypes.includes(type)) // 추가정보 완료 여부 (업무담당자 + 추가정보 테이블 모두 필요) const contactsCompleted = missingContactTypes.length === 0 const additionalInfoTableCompleted = !!additionalInfo[0] const additionalInfoCompleted = contactsCompleted && additionalInfoTableCompleted console.log(`🔍 Partners 벤더 ID ${vendorId} 전체 데이터:`, { vendor: vendor[0], registration: registration[0], safetyQualificationContent: registration[0]?.safetyQualificationContent, gtcSkipped: registration[0]?.gtcSkipped, requiredContactTypes, existingContactTypes, missingContactTypes, contactsCompleted, additionalInfoTableCompleted, additionalInfoData: additionalInfo[0], finalAdditionalInfoCompleted: additionalInfoCompleted, basicContractsCount: vendorContracts.length }) return { success: true, data: { vendor: vendor[0], registration: registration[0] || null, documentStatus, documentFiles, // 문서별 파일 정보 추가 missingDocuments, businessContacts, missingContactTypes, additionalInfo: additionalInfo[0] || null, // 실제 추가정보 데이터 반환 additionalInfoCompleted, // 완료 여부는 별도 필드로 추가 pqSubmission: pqSubmission[0] || null, auditPassed: investigationFiles.length > 0, basicContracts: vendorContracts, // 기본계약 정보 추가 incompleteItemsCount: { documents: missingDocuments.length, contacts: missingContactTypes.length, additionalInfo: !additionalInfo[0] ? 1 : 0, } } } } catch (error) { console.error("Error in fetchVendorRegistrationStatus:", error) return { success: false, error: error instanceof Error ? error.message : "현황 조회 중 오류가 발생했습니다.", } } }, [`vendor-registration-status-${vendorId}`], { revalidate: 300, // 5분 캐시 tags: ["vendor-registration-status", `vendor-${vendorId}`], } )() } // 서명/직인 업로드 (임시 - 실제로는 파일 업로드 로직 필요) export async function uploadVendorSignature(vendorId: number, signatureData: { type: "signature" | "seal" signerName?: string imageFile: string // base64 or file path }) { try { // TODO: 실제 파일 업로드 및 저장 로직 구현 console.log("Signature upload for vendor:", vendorId, signatureData) // 캐시 무효화 revalidateTag(`vendor-registration-status`) revalidateTag(`vendor-${vendorId}`) return { success: true, message: "서명/직인이 등록되었습니다.", } } catch (error) { console.error("Error uploading signature:", error) return { success: false, error: error instanceof Error ? error.message : "서명/직인 등록 중 오류가 발생했습니다.", } } } // 업무담당자 정보 저장 export async function saveVendorBusinessContacts( vendorId: number, contacts: Array<{ contactType: "sales" | "design" | "delivery" | "quality" | "tax_invoice" contactName: string position: string department: string responsibility: string email: string }> ) { try { // 기존 데이터 삭제 await db .delete(vendorBusinessContacts) .where(eq(vendorBusinessContacts.vendorId, vendorId)) // 새 데이터 삽입 if (contacts.length > 0) { await db .insert(vendorBusinessContacts) .values(contacts.map(contact => ({ ...contact, vendorId, }))) } // 캐시 무효화 revalidateTag("vendor-registration-status") revalidateTag(`vendor-${vendorId}`) return { success: true, message: "업무담당자 정보가 저장되었습니다.", } } catch (error) { console.error("Error saving business contacts:", error) return { success: false, error: error instanceof Error ? error.message : "업무담당자 정보 저장 중 오류가 발생했습니다.", } } } // 추가정보 저장 export async function saveVendorAdditionalInfo( vendorId: number, info: { businessType?: string industryType?: string companySize?: string revenue?: string factoryEstablishedDate?: string preferredContractTerms?: string } ) { try { const existing = await db .select() .from(vendorAdditionalInfo) .where(eq(vendorAdditionalInfo.vendorId, vendorId)) .limit(1) if (existing[0]) { // 업데이트 await db .update(vendorAdditionalInfo) .set({ ...info, factoryEstablishedDate: info.factoryEstablishedDate || null, revenue: info.revenue || null, updatedAt: new Date(), }) .where(eq(vendorAdditionalInfo.vendorId, vendorId)) } else { // 신규 삽입 await db .insert(vendorAdditionalInfo) .values({ ...info, vendorId, factoryEstablishedDate: info.factoryEstablishedDate || null, revenue: info.revenue || null, }) } // 캐시 무효화 revalidateTag("vendor-registration-status") revalidateTag(`vendor-${vendorId}`) return { success: true, message: "추가정보가 저장되었습니다.", } } catch (error) { console.error("Error saving additional info:", error) return { success: false, error: error instanceof Error ? error.message : "추가정보 저장 중 오류가 발생했습니다.", } } } // 안전적격성 평가 업데이트 export async function updateSafetyQualification( registrationId: number, safetyQualificationContent: string ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return { success: false, error: "로그인이 필요합니다." }; } const result = await updateVendorRegularRegistration(registrationId, { safetyQualificationContent: safetyQualificationContent.trim(), }); if (!result) { return { success: false, error: "등록 정보를 찾을 수 없습니다." }; } // 캐시 무효화 revalidateTag("vendor-regular-registrations"); return { success: true, message: "안전적격성 평가가 등록되었습니다.", }; } catch (error) { console.error("Error updating safety qualification:", error); return { success: false, error: error instanceof Error ? error.message : "안전적격성 평가 등록 중 오류가 발생했습니다.", }; } } // 정규업체 등록 요청을 위한 상세 데이터 조회 export async function fetchRegistrationRequestData(registrationId: number) { try { // 등록 정보 조회 const registration = await db .select() .from(vendorRegularRegistrations) .where(eq(vendorRegularRegistrations.id, registrationId)) .limit(1); if (!registration[0]) { return { success: false, error: "등록 정보를 찾을 수 없습니다." }; } // 벤더 정보 조회 const vendor = await db .select({ id: vendors.id, vendorName: vendors.vendorName, taxId: vendors.taxId, representativeName: vendors.representativeName, representativeBirth: vendors.representativeBirth, representativeEmail: vendors.representativeEmail, representativePhone: vendors.representativePhone, representativeWorkExpirence: vendors.representativeWorkExpirence, country: vendors.country, corporateRegistrationNumber: vendors.corporateRegistrationNumber, address: vendors.address, phone: vendors.phone, email: vendors.email, createdAt: vendors.createdAt, updatedAt: vendors.updatedAt, }) .from(vendors) .where(eq(vendors.id, registration[0].vendorId)) .limit(1); if (!vendor[0]) { return { success: false, error: "벤더 정보를 찾을 수 없습니다." }; } // 업무담당자 정보 조회 const businessContacts = await db .select() .from(vendorBusinessContacts) .where(eq(vendorBusinessContacts.vendorId, vendor[0].id)); // 추가정보 조회 const additionalInfo = await db .select() .from(vendorAdditionalInfo) .where(eq(vendorAdditionalInfo.vendorId, vendor[0].id)) .limit(1); return { success: true, data: { registration: registration[0], vendor: vendor[0], businessContacts, additionalInfo: additionalInfo[0] || null, } }; } catch (error) { console.error("정규업체 등록 요청 데이터 조회 오류:", error); return { success: false, error: error instanceof Error ? error.message : "데이터 조회 중 오류가 발생했습니다." }; } } // 정규업체 등록 요청 서버 액션 // export async function submitRegistrationRequest( // registrationId: number, // requestData: RegistrationRequestData // ) { // try { // const session = await getServerSession(authOptions); // if (!session?.user) { // return { success: false, error: "인증이 필요합니다." }; // } // // 현재 등록 정보 조회 // const registration = await db // .select() // .from(vendorRegularRegistrations) // .where(eq(vendorRegularRegistrations.id, registrationId)) // .limit(1); // if (!registration[0]) { // return { success: false, error: "등록 정보를 찾을 수 없습니다." }; // } // // 조건충족 상태인지 확인 // console.log("📋 업데이트 전 현재 데이터:", { // registrationId, // currentStatus: registration[0].status, // currentRemarks: registration[0].remarks, // currentUpdatedAt: registration[0].updatedAt // }); // if (registration[0].status !== "approval_ready") { // return { success: false, error: "조건충족 상태가 아닙니다." }; // } // // 정규업체 등록 요청 데이터를 JSON으로 저장 // const registrationRequestData = { // requestDate: new Date(), // requestedBy: session.user.id, // requestedByName: session.user.name, // requestData: requestData, // status: "requested" // 요청됨 // }; // // 트랜잭션으로 상태 변경 // const updateResult = await db.transaction(async (tx) => { // return await tx // .update(vendorRegularRegistrations) // .set({ // status: "registration_requested", // remarks: `정규업체 등록 요청됨 - ${new Date().toISOString()}\n요청자: ${session.user.name}`, // updatedAt: new Date(), // }) // .where(eq(vendorRegularRegistrations.id, registrationId)); // }); // console.log("🔄 업데이트 결과:", { // registrationId, // updateResult, // statusToSet: "registration_requested" // }); // // MDG 인터페이스 연동 // const mdgResult = await sendRegistrationRequestToMDG(registrationId, requestData); // if (!mdgResult.success) { // console.error('❌ MDG 송신 실패:', mdgResult.error); // // MDG 송신 실패해도 등록 요청은 성공으로 처리 (재시도 가능하도록) // } else { // console.log('✅ MDG 송신 성공:', mdgResult.message); // } // // Knox 결재 연동은 별도의 결재 워크플로우에서 처리됩니다. // // UI에서 registerVendorWithApproval()을 호출하여 결재 프로세스를 시작합니다. // console.log("✅ 정규업체 등록 요청 데이터:", { // registrationId, // companyName: requestData.companyNameKor, // businessNumber: requestData.businessNumber, // representative: requestData.representativeNameKor, // requestedBy: session.user.name, // requestDate: new Date().toISOString() // }); // // 캐시 무효화 - 더 강력한 무효화 // revalidateTag("vendor-regular-registrations"); // revalidateTag(`vendor-regular-registration-${registrationId}`); // revalidateTag("vendor-registration-status"); // return { // success: true, // message: `정규업체 등록 요청이 성공적으로 제출되었습니다.\n${mdgResult.success ? 'MDG 인터페이스 연동이 완료되었습니다.' : 'MDG 인터페이스 연동에 실패했습니다. (재시도 가능)'}\n결재 승인 후 정규업체 등록이 완료됩니다.` // }; // } catch (error) { // console.error("정규업체 등록 요청 오류:", error); // return { // success: false, // error: error instanceof Error ? error.message : "정규업체 등록 요청 중 오류가 발생했습니다." // }; // } // } // MDG로 정규업체 등록 요청 데이터를 보내는 함수 export async function sendRegistrationRequestToMDG( registrationId: number, requestData: RegistrationRequestData ) { try { console.log('🚀 MDG로 정규업체 등록 요청 데이터 송신 시작'); // 세션 사용자 정보 가져오기 const session = await getServerSession(authOptions); const userId = session?.user?.id || 'EVCP_USER'; //users table에서 userid로 nonsapuserid 찾기 const userResult = await db.query.users.findFirst({ where: eq(users.id, Number(userId)), columns: { nonsapUserId: true } }); const nonsapUserId = userResult?.nonsapUserId || 'EVCP_USER'; // 등록 정보 조회 const registration = await db .select() .from(vendorRegularRegistrations) .where(eq(vendorRegularRegistrations.id, registrationId)) .limit(1); if (!registration[0]) { return { success: false, error: "등록 정보를 찾을 수 없습니다." }; } // registration[0].vendorId를 이용해 벤더 정보 조회 const vendor = await db .select() .from(vendors) .where(eq(vendors.id, registration[0].vendorId)) .limit(1); if (!vendor[0]) { return { success: false, error: "벤더 정보를 찾을 수 없습니다." }; } // MDG 필수 필드 매핑 (이메일 내용 기반) const mdgData: Record = { // 1. BP_HEADER: 벤더코드가 있으면 벤더코드를, 없으면 eVCP에서 관리번호를 보내드리겠습니다. (필수) BP_HEADER: vendor[0].vendorCode || vendor[0].id.toString(), // 2. ZZSRMCD: eVCP에서 내부관리번호를 보내드리겠습니다. (필수) ZZSRMCD: vendor[0].id.toString(), // 3. SORT1: 벤더명 보내드립니다. (필수) SORT1: requestData.companyNameKor, // 4. NAME1: 벤더명 보내드립니다. (필수) NAME1: requestData.companyNameKor, // 5. NAME2: 벤더 영문명 (있는 경우) (선택) NAME2: requestData.companyNameEng || '', // 6. KTOKK: 셈플로 받은자료에는 "LIEF"로 되어 있습니다. 고정값 (필수) KTOKK: 'LIEF', // 7. J_1KFREPRE: 대표자명 (필수) J_1KFREPRE: requestData.representativeNameKor, // 8. MASTERFLAG: 지시자 같은데, 어떤값을 보내면 되나요? -> V (필수) MASTERFLAG: 'V', // 9. IBND_TYPE: 입력가능한 값을 알려주시기 바랍니다. -> 생성 : I, 변경: U (필수) IBND_TYPE: 'I', // 10. ZZREQID: SAP의 USER ID를 보내드리겠습니다. (필수) ZZREQID: nonsapUserId, // 11. ADDRNO: I/F정의서에는 필수입력으로 되어 있습니다. -> 빈값으로 처리 (필수) ADDRNO: '', // 12. COUNTRY: ISO 3166-1의 규약에 따른 국가코드를 보내드릴 예정입니다. (필수) COUNTRY: vendor[0].country || 'KR', // 13. POST_CODE1: 우편번호를 송부하겠습니다. (필수) POST_CODE1: vendor[0].postalCode || '', // 14. CITY1: 상세 주소를 송부하겠습니다. (필수) - 샘플에서는 상세주소가 들어감 CITY1: vendor[0].addressDetail || '', // 15. STREET: 주소를 송부하겠습니다. (필수) - 샘플에서는 기본주소가 들어감 STREET: vendor[0].address || '', // 16. TEL_NUMBER: 전화번호 (필수) TEL_NUMBER: vendor[0].phone || '', // 17. R3_USER: 전화/휴대폰 구분자로 해석됩니다. 0이면 전화, 1이면 휴대폰 (필수) R3_USER: '0', // 일반 전화번호로 가정 // 18. TAXTYPE: 국가 코드에 맞게 하면 됩니다. 국가 KR -> TAXTYPE KR2 (필수) TAXTYPE: (vendor[0].country || 'KR') + '2', // 19. TAXNUM: 사업자번호 (필수) TAXNUM: vendor[0].taxId || '', // 20. BP_TX_TYP: 대표자 주민번호 YYMMDD + 0000000 (YYMMDD0000000) - 대표자 생년월일 기준으로 생성 BP_TX_TYP: requestData.representativeBirthDate ? requestData.representativeBirthDate.replace(/-/g, '') + '0000000' : '', // 21. STCD3: 법인등록번호 (선택) STCD3: requestData.corporateNumber || '', // 22. CONSNUMBER: 순번 (샘플에서는 1, 2로 설정됨) CONSNUMBER: '1', // 23. ZZIND03: 기업규모 (A,B,C,D 값을 넣는 것으로 알고 있습니다.) (선택) ZZIND03: 'B', // 기본값으로 B 설정 // 24. J_1KFTBUS: 사업유형 (샘플에서는 "건설업외") J_1KFTBUS: '', // 25. J_1KFTIND: 산업유형 (샘플에서는 "제조업") J_1KFTIND: '', // 26. SMTP_ADDR: 대표 이메일 주소 (필수) SMTP_ADDR: requestData.representativeEmail || vendor[0].email || '', // 27. URI_ADDR: 웹사이트 주소 (선택) URI_ADDR: vendor[0].website || '', // 28. ZZCNAME1: 해당 벤더의 첫번째 유저의 이름 (영문) (선택) ZZCNAME1: requestData.representativeNameEng || '', // 29. ZZCNAME2: 해당 벤더의 첫번째 유저의 이름 (한글) (선택) ZZCNAME2: requestData.representativeNameKor || '', // 30. ZZTELF1_C: 해당 벤더의 첫번째 유저의 전화번호 (선택) ZZTELF1_C: requestData.representativeContact || '', }; // MDG로 데이터 전송 console.log('📤 MDG 송신 데이터:', mdgData); const result = await sendTestVendorDataToMDG(mdgData); console.log('📤 MDG 송신 결과:', result); if (!result.success) { // 필수 필드 누락 에러인 경우 더 자세한 메시지 제공 if (result.message.includes('필수 필드가 누락되었습니다')) { return { success: false, error: `MDG 송신 실패: ${result.message}\n\n누락된 필수 필드들을 확인하고 다시 시도해주세요.` }; } } return { success: result.success, message: result.success ? 'MDG로 정규업체 등록 요청이 성공적으로 전송되었습니다.' : `MDG 송신 실패: ${result.message}`, responseData: result.responseData, generatedXML: result.generatedXML }; } catch (error) { console.error('❌ MDG 송신 실패:', error); return { success: false, error: error instanceof Error ? error.message : 'MDG 송신 중 오류가 발생했습니다.' }; } }