summaryrefslogtreecommitdiff
path: root/lib/approval/example-usage.ts
blob: 357f3ef10d3eb64f807d07c2d4b9cc046a0d8374 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/**
 * 결재 워크플로우 사용 예시 (템플릿 기반)
 * 
 * 이 파일은 실제 사용 방법을 보여주는 예시입니다.
 * 실제 구현 시 각 서버 액션에 적용하면 됩니다.
 * 
 * 업데이트: 템플릿 및 변수 치환 지원
 */

import { registerActionHandler, withApproval } from './approval-workflow';
import {
  htmlTableConverter,
  htmlDescriptionList,
} from './template-utils';

// ============================================================================
// 1단계: 액션 핸들러 등록
// ============================================================================

/**
 * 실사 요청의 실제 비즈니스 로직
 * 결재 승인 후 실행될 함수
 */
async function createInvestigationInternal(payload: {
  vendorId: number;
  vendorName: string;
  reason: string;
  scheduledDate: Date;
}) {
  // 실제 DB 작업 등 비즈니스 로직
  console.log('Creating investigation:', payload);
  
  // const result = await db.insert(vendorInvestigations).values({
  //   vendorId: payload.vendorId,
  //   reason: payload.reason,
  //   scheduledDate: payload.scheduledDate,
  //   status: 'scheduled',
  // }).returning();
  
  return { success: true, investigationId: 123 };
}

// 핸들러 등록 (앱 시작 시 한 번만 실행되면 됨)
registerActionHandler('vendor_investigation_request', createInvestigationInternal);

// ============================================================================
// 2단계: 결재가 필요한 서버 액션 작성
// ============================================================================

/**
 * 사용자가 호출하는 서버 액션
 * 결재를 거쳐서 실사 요청을 생성 (템플릿 기반)
 */
export async function createInvestigationWithApproval(data: {
  vendorId: number;
  vendorName: string;
  reason: string;
  scheduledDate: Date;
  currentUser: { id: number; epId: string | null };
  approvers?: string[]; // Knox EP ID 배열
}) {
  'use server'; // Next.js 15 server action

  // 1. 템플릿 변수 생성 (HTML 변환 포함)
  const summaryTable: string = await htmlTableConverter(
    [
      { 항목: '협력업체명', 내용: data.vendorName },
      { 항목: '실사 사유', 내용: data.reason },
      { 항목: '예정일', 내용: data.scheduledDate.toLocaleDateString('ko-KR') },
    ],
    [
      { key: '항목', label: '항목' },
      { key: '내용', label: '내용' },
    ]
  );

  const basicInfo: string = await htmlDescriptionList([
    { label: '협력업체명', value: data.vendorName },
    { label: '실사 사유', value: data.reason },
  ]);

  const variables: Record<string, string> = {
    수신자이름: '결재자',
    협력업체명: data.vendorName,
    '실사의뢰 요약 테이블 1': summaryTable,
    '실사의뢰 기본정보': basicInfo,
    '실사의뢰 요약 문구': `${data.vendorName}에 대한 실사를 요청합니다.`,
  };

  // 2. 결재 워크플로우 시작 (템플릿 사용)
  const result = await withApproval(
    'vendor_investigation_request',
    {
      vendorId: data.vendorId,
      vendorName: data.vendorName,
      reason: data.reason,
      scheduledDate: data.scheduledDate,
    },
    {
      title: `실사 요청 - ${data.vendorName}`,
      description: `협력업체 ${data.vendorName}에 대한 실사 요청`,
      templateName: '협력업체 실사 요청', // 한국어 템플릿명
      variables, // 치환할 변수들
      approvers: data.approvers,
      currentUser: data.currentUser,
    }
  );

  return result;
}

// ============================================================================
// 3단계: UI에서 호출
// ============================================================================

/**
 * 클라이언트 컴포넌트에서 사용 예시
 * 
 * ```tsx
 * const handleSubmit = async (formData) => {
 *   try {
 *     const result = await createInvestigationWithApproval({
 *       vendorId: formData.vendorId,
 *       vendorName: formData.vendorName,
 *       reason: formData.reason,
 *       scheduledDate: new Date(formData.scheduledDate),
 *       currentUser: session.user,
 *       approvers: selectedApprovers, // 결재선
 *     });
 * 
 *     // 결재 상신 완료
 *     alert(`결재가 상신되었습니다. 결재 ID: ${result.approvalId}`);
 *     
 *     // 결재 현황 페이지로 이동하거나 대기 상태 표시
 *     router.push(`/approval/status/${result.approvalId}`);
 *   } catch (error) {
 *     console.error('Failed to submit approval:', error);
 *   }
 * };
 * ```
 */

// ============================================================================
// 다른 액션 타입 예시
// ============================================================================

/**
 * 발주 요청 핸들러
 */
async function createPurchaseOrderInternal(payload: {
  vendorId: number;
  items: Array<{ itemId: number; quantity: number }>;
  totalAmount: number;
}) {
  console.log('Creating purchase order:', payload);
  return { success: true, orderId: 456 };
}

registerActionHandler('purchase_order_request', createPurchaseOrderInternal);

/**
 * 계약 승인 핸들러
 */
async function approveContractInternal(payload: {
  contractId: number;
  approverComments: string;
}) {
  console.log('Approving contract:', payload);
  return { success: true, contractId: payload.contractId };
}

registerActionHandler('contract_approval', approveContractInternal);

// ============================================================================
// 추가 유틸리티
// ============================================================================

/**
 * 결재 상태를 수동으로 확인 (UI에서 "새로고침" 버튼용)
 */
export async function refreshApprovalStatus(apInfId: string) {
  'use server';
  
  const { checkSingleApprovalStatus } = await import('./approval-polling-service');
  return await checkSingleApprovalStatus(apInfId);
}