'use server' import { ApprovalSubmissionSaga } from '@/lib/approval' import type { ApprovalConfig } from '@/lib/approval/types' import { htmlTableConverter, htmlDescriptionList } from '@/lib/approval' import db from '@/db/db' import { vendors, vendorTypes } from '@/db/schema/vendors' import { inArray, eq } from 'drizzle-orm' /** * 벤더 승인 결재 템플릿 변수 생성 * * @param vendorIds - 벤더 ID 목록 * @param currentUserEmail - 현재 사용자 이메일 * @returns 템플릿 변수 객체 및 벤더 정보 */ export async function prepareVendorApprovalVariables( vendorIds: number[], currentUserEmail?: string ) { // 벤더 정보 조회 const vendorRecords = await db .select({ id: vendors.id, vendorName: vendors.vendorName, vendorCode: vendors.vendorCode, email: vendors.email, status: vendors.status, taxId: vendors.taxId, country: vendors.country, representativeName: vendors.representativeName, phone: vendors.phone, website: vendors.website, representativeEmail: vendors.representativeEmail, postalCode: vendors.postalCode, address: vendors.address, addressDetail: vendors.addressDetail, corporateRegistrationNumber: vendors.corporateRegistrationNumber, businessSize: vendors.businessSize, vendorTypeId: vendors.vendorTypeId, vendorTypeName: vendorTypes.nameKo, }) .from(vendors) .leftJoin(vendorTypes, eq(vendors.vendorTypeId, vendorTypes.id)) .where(inArray(vendors.id, vendorIds)) if (vendorRecords.length === 0) { throw new Error(`벤더를 찾을 수 없습니다: ${vendorIds.join(', ')}`) } // PENDING_REVIEW 상태가 아닌 벤더 확인 const invalidVendors = vendorRecords.filter(v => v.status !== 'PENDING_REVIEW') if (invalidVendors.length > 0) { throw new Error( `가입 신청 중(PENDING_REVIEW) 상태의 벤더만 승인할 수 있습니다. ` + `잘못된 상태: ${invalidVendors.map(v => `${v.vendorName}(${v.status})`).join(', ')}` ) } // 업체규모 매핑 const businessSizeMap: Record = { 'A': '대기업', 'B': '중견기업', 'C': '중소기업', 'D': '소기업', } // 벤더 목록 테이블 생성 const vendorListTable = await htmlTableConverter( vendorRecords.map(v => ({ vendorName: v.vendorName || '-', representativeName: v.representativeName || '-', vendorType: v.vendorTypeName || '-', businessSize: v.businessSize ? businessSizeMap[v.businessSize] || v.businessSize : '-', phone: v.phone || '-', email: v.representativeEmail || '-', })), [ { key: 'vendorName', label: '업체명' }, { key: 'representativeName', label: '대표자명' }, { key: 'vendorType', label: '업체유형' }, { key: 'businessSize', label: '기업규모' }, { key: 'phone', label: '전화번호' }, { key: 'email', label: '이메일' }, ] ) // 벤더별 상세 정보 const vendorDetailsHtml = ( await Promise.all( vendorRecords.map(async (v) => { const details = await htmlDescriptionList([ { label: '업체명', value: v.vendorName || '-' }, { label: '대표자명', value: v.representativeName || '-' }, { label: '업체유형', value: v.vendorTypeName || '-' }, { label: '기업규모', value: v.businessSize ? businessSizeMap[v.businessSize] || v.businessSize : '-' }, { label: '국가', value: v.country || '-' }, { label: '우편번호', value: v.postalCode || '-' }, { label: '주소', value: v.address || '-' }, { label: '상세주소', value: v.addressDetail || '-' }, { label: '전화번호', value: v.phone || '-' }, { label: '이메일', value: v.representativeEmail || '-' }, { label: '홈페이지', value: v.website || '-' }, { label: '사업자등록번호', value: v.taxId || '-' }, { label: '법인등록번호', value: v.corporateRegistrationNumber || '-' }, ]) return `

${v.vendorName}

${details}
` }) ) ).join('') const variables: Record = { '업체수': String(vendorRecords.length), '업체목록': vendorRecords.map(v => v.vendorName).join(', '), '업체목록테이블': vendorListTable, '업체상세정보': vendorDetailsHtml, '요청일': new Date().toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric', }), '요청자': currentUserEmail || '시스템', } return { variables, vendorRecords, vendorNames: vendorRecords.map(v => v.vendorName).join(', '), } } /** * 벤더 가입 승인 결재 상신 * * @param input - 결재 요청 정보 * @returns 결재 상신 결과 */ export async function approveVendorsWithApproval(input: { vendorIds: number[] currentUser: { id: number epId: string | null email?: string } approvers?: string[] }) { console.log(`[Vendor Approval Action] 벤더 승인 결재 상신 시작:`, input.vendorIds) try { // 1. 입력 검증 if (!input.currentUser.epId) { throw new Error('Knox EP ID가 필요합니다. 시스템 관리자에게 문의하세요.') } if (input.vendorIds.length === 0) { throw new Error('승인할 벤더를 선택해주세요.') } // 2. 템플릿 변수 준비 const { variables, vendorRecords, vendorNames } = await prepareVendorApprovalVariables( input.vendorIds, input.currentUser.email ) console.log(`[Vendor Approval Action] ${vendorRecords.length}개 벤더 조회 및 템플릿 변수 준비 완료`) // 3. 결재 상신 (Saga 패턴) const saga = new ApprovalSubmissionSaga( 'vendor_approval', // 핸들러 타입 (handlers-registry에 등록될 키) { vendorIds: input.vendorIds, userId: input.currentUser.id, // 결재 승인 후 실행 시 필요 }, { title: `벤더 가입 승인 요청 - ${vendorRecords.length}개 업체`, description: `${vendorNames} 의 가입을 승인합니다.`, templateName: '벤더 가입 승인 요청', variables, approvers: input.approvers, currentUser: input.currentUser, } as ApprovalConfig ) console.log(`[Vendor Approval Action] 결재 상신 실행 중...`) const result = await saga.execute() console.log(`[Vendor Approval Action] 결재 상신 완료:`, result) return { success: true, message: `${vendorRecords.length}개 벤더의 가입 승인 결재가 상신되었습니다.`, approvalId: result.approvalId, pendingActionId: result.pendingActionId, status: result.status, } } catch (error) { console.error(`[Vendor Approval Action] 결재 상신 실패:`, error) return { success: false, message: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.', error: error instanceof Error ? error.message : 'Unknown error', } } }