From ba43cd261d10c6b0c5218a9da3f946993b21de6e Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Thu, 6 Nov 2025 15:50:45 +0900 Subject: (김준회) 결재: 협력업체관리: 가입승인: 가입승인시 결재절차 구현 (템플릿 관련 부분은 미구현) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/vendors/approval-actions.ts | 119 ++++++++++++++++++++++++++++ lib/vendors/approval-handlers.ts | 119 ++++++++++++++++++++++++++++ lib/vendors/table/approve-vendor-dialog.tsx | 34 +++++--- 3 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 lib/vendors/approval-actions.ts create mode 100644 lib/vendors/approval-handlers.ts (limited to 'lib/vendors') diff --git a/lib/vendors/approval-actions.ts b/lib/vendors/approval-actions.ts new file mode 100644 index 00000000..69d09caa --- /dev/null +++ b/lib/vendors/approval-actions.ts @@ -0,0 +1,119 @@ +'use server' + +import { ApprovalSubmissionSaga } from '@/lib/approval' +import type { ApprovalConfig } from '@/lib/approval/types' +import db from '@/db/db' +import { vendors } from '@/db/schema/vendors' +import { inArray } from 'drizzle-orm' + +/** + * 벤더 가입 승인 결재 상신 + * + * @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 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, + }) + .from(vendors) + .where(inArray(vendors.id, input.vendorIds)) + + if (vendorRecords.length === 0) { + throw new Error(`벤더를 찾을 수 없습니다: ${input.vendorIds.join(', ')}`) + } + + // 3. 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(', ')}` + ) + } + + console.log(`[Vendor Approval Action] ${vendorRecords.length}개 벤더 조회 완료`) + + // 4. 템플릿 변수 준비 (TODO: 실제 템플릿에 맞게 수정 필요) + const variables: Record = { + // TODO: 다음 대화에서 제공될 템플릿에 맞게 변수 매핑 + '업체수': String(vendorRecords.length), + '업체목록': vendorRecords.map(v => + `${v.vendorName} (${v.vendorCode || '코드 미할당'})` + ).join('\n'), + '요청일': new Date().toLocaleDateString('ko-KR'), + '요청자': input.currentUser.email || 'Unknown', + } + + console.log(`[Vendor Approval Action] 템플릿 변수 준비 완료`) + + // 5. 결재 상신 (Saga 패턴) + const saga = new ApprovalSubmissionSaga( + 'vendor_approval', // 핸들러 타입 (handlers-registry에 등록될 키) + { + vendorIds: input.vendorIds, + userId: input.currentUser.id, // 결재 승인 후 실행 시 필요 + }, + { + title: `벤더 가입 승인 요청 - ${vendorRecords.length}개 업체`, + description: `${vendorRecords.map(v => v.vendorName).join(', ')} 의 가입을 승인합니다.`, + 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', + } + } +} + diff --git a/lib/vendors/approval-handlers.ts b/lib/vendors/approval-handlers.ts new file mode 100644 index 00000000..bc4d24a6 --- /dev/null +++ b/lib/vendors/approval-handlers.ts @@ -0,0 +1,119 @@ +'use server' + +import db from "@/db/db" +import { vendors } from "@/db/schema/vendors" +import { inArray } from "drizzle-orm" +import { sendSingleVendorToMDG } from "@/lib/soap/mdg/send/vendor-master/send-single-vendor" +import { approveVendors } from "./service" + +/** + * 벤더 가입 승인 핸들러 (결재 승인 후 자동 실행) + * + * 이 함수는 결재 승인 후 approval-polling-service에 의해 자동으로 호출됩니다. + * 직접 호출하지 마세요. 결재 상신은 approveVendorsWithApproval()을 사용하세요. + * + * @param payload - { vendorIds: number[], userId: number } + * @returns 승인 결과 + */ +export async function approveVendorWithMDGInternal(payload: { + vendorIds: number[] + userId: number +}) { + console.log(`[Vendor Approval Handler] 벤더 승인 시작:`, payload.vendorIds) + + try { + // 1. 벤더 정보 조회 + const vendorRecords = await db + .select({ + id: vendors.id, + vendorName: vendors.vendorName, + status: vendors.status, + }) + .from(vendors) + .where(inArray(vendors.id, payload.vendorIds)) + + if (vendorRecords.length === 0) { + throw new Error(`벤더를 찾을 수 없습니다: ${payload.vendorIds.join(', ')}`) + } + + console.log(`[Vendor Approval Handler] ${vendorRecords.length}개 벤더 조회 완료`) + + // 2. 각 벤더를 개별적으로 MDG에 전송 + const mdgResults: Array<{ + vendorId: number + vendorName: string + success: boolean + error?: string + }> = [] + + for (const vendor of vendorRecords) { + console.log(`[Vendor Approval Handler] MDG 전송 시작: ${vendor.vendorName} (ID: ${vendor.id})`) + + try { + const result = await sendSingleVendorToMDG({ + vendorId: vendor.id, + mode: 'NEW_VENDOR', // 신규 벤더 가입 승인 + }) + + if (result.success) { + console.log(`[Vendor Approval Handler] ✅ MDG 전송 성공: ${vendor.vendorName}`) + mdgResults.push({ + vendorId: vendor.id, + vendorName: vendor.vendorName, + success: true, + }) + } else { + console.error(`[Vendor Approval Handler] ❌ MDG 전송 실패: ${vendor.vendorName} - ${result.message}`) + mdgResults.push({ + vendorId: vendor.id, + vendorName: vendor.vendorName, + success: false, + error: result.message, + }) + // MDG 전송 실패 시 전체 프로세스 중단 + throw new Error(`MDG 전송 실패 (${vendor.vendorName}): ${result.message}`) + } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : 'Unknown error' + console.error(`[Vendor Approval Handler] ❌ MDG 전송 예외: ${vendor.vendorName} - ${errorMsg}`) + mdgResults.push({ + vendorId: vendor.id, + vendorName: vendor.vendorName, + success: false, + error: errorMsg, + }) + throw error + } + } + + console.log(`[Vendor Approval Handler] 모든 벤더 MDG 전송 완료`) + + // 3. 기존 approveVendors 함수 실행 + // (상태 변경, 유저 활성화, 역할 할당, 이메일 발송, 로그 기록 등) + console.log(`[Vendor Approval Handler] 기존 approveVendors 함수 실행`) + + const approveResult = await approveVendors({ + ids: payload.vendorIds, + userId: payload.userId, + }) + + if (approveResult.error) { + console.error(`[Vendor Approval Handler] approveVendors 실패:`, approveResult.error) + throw new Error(`벤더 승인 처리 실패: ${approveResult.error}`) + } + + console.log(`[Vendor Approval Handler] 벤더 승인 완료: ${vendorRecords.length}개`) + + return { + success: true, + message: `${vendorRecords.length}개 벤더가 승인되었습니다.`, + approvedVendors: vendorRecords, + mdgResults, + } + + } catch (error) { + console.error(`[Vendor Approval Handler] 벤더 승인 실패:`, error) + throw error + } +} + diff --git a/lib/vendors/table/approve-vendor-dialog.tsx b/lib/vendors/table/approve-vendor-dialog.tsx index 980953aa..786399a4 100644 --- a/lib/vendors/table/approve-vendor-dialog.tsx +++ b/lib/vendors/table/approve-vendor-dialog.tsx @@ -28,7 +28,8 @@ import { DrawerTrigger, } from "@/components/ui/drawer" import { Vendor } from "@/db/schema/vendors" -import { approveVendors, rejectVendors } from "../service" +import { rejectVendors } from "../service" +import { approveVendorsWithApproval } from "../approval-actions" import { useSession } from "next-auth/react" interface VendorDecisionDialogProps @@ -55,25 +56,36 @@ export function VendorDecisionDialog({ return } + if (!session?.user?.epId) { + toast.error("Knox EP ID가 없습니다. 시스템 관리자에게 문의하세요.") + return + } + startApproveTransition(async () => { try { - console.log("🔍 [DEBUG] 승인 요청 시작 - vendors:", vendors.map(v => ({ id: v.id, vendorName: v.vendorName, email: v.email }))); - console.log("🔍 [DEBUG] 세션 정보:", { userId: session.user.id, userType: typeof session.user.id }); + console.log("🔍 [DEBUG] 결재 상신 시작 - vendors:", vendors.map(v => ({ id: v.id, vendorName: v.vendorName, email: v.email }))); + console.log("🔍 [DEBUG] 세션 정보:", { userId: session.user.id, epId: session.user.epId }); - const { error } = await approveVendors({ - ids: vendors.map((vendor) => vendor.id), - userId: Number(session.user.id) + const result = await approveVendorsWithApproval({ + vendorIds: vendors.map((vendor) => vendor.id), + currentUser: { + id: Number(session.user.id), + epId: session.user.epId as string, // 위에서 검증했으므로 타입 단언 + email: session.user.email || undefined, + }, + // TODO: 필요시 approvers 배열 추가 + // approvers: ['EP001', 'EP002'], }) - if (error) { - console.error("🚨 [DEBUG] 승인 처리 에러:", error); - toast.error(error) + if (!result.success) { + console.error("🚨 [DEBUG] 결재 상신 에러:", result.message); + toast.error(result.message || "결재 상신에 실패했습니다.") return } - console.log("✅ [DEBUG] 승인 처리 성공"); + console.log("✅ [DEBUG] 결재 상신 성공:", result); props.onOpenChange?.(false) - toast.success("협력업체 등록이 승인되었습니다.") + toast.success(`결재가 상신되었습니다. (결재ID: ${result.approvalId})`) onSuccess?.() } catch (error) { console.error("🚨 [DEBUG] 예상치 못한 에러:", error); -- cgit v1.2.3