summaryrefslogtreecommitdiff
path: root/lib/vendors
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors')
-rw-r--r--lib/vendors/approval-actions.ts119
-rw-r--r--lib/vendors/approval-handlers.ts119
-rw-r--r--lib/vendors/table/approve-vendor-dialog.tsx34
3 files changed, 261 insertions, 11 deletions
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<string, string> = {
+ // 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);