"use server" import { getKnoxConfig, createJsonHeaders, createFormHeaders } from '../common'; // Knox API Approval 서버 액션들 // 가이드: lib/knox-api/approval/guide.html // ========== 타입 정의 ========== // 공통 응답 타입 export interface BaseResponse { result: string; } // 결재 경로 타입 export interface ApprovalLine { epId?: string; userId?: string; emailAddress?: string; seq: string; role: string; // 기안(0), 결재(1), 합의(2), 후결(3), 병렬합의(4), 병렬결재(7), 통보(9) aplnStatsCode: string; // 미결(0), 결재(1), 반려(2), 전결(3), 자동결재(5) arbPmtYn: string; // 전결권한여부 contentsMdfyPmtYn: string; // 본문수정권한여부 aplnMdfyPmtYn: string; // 경로변경권한여부 opinion?: string; // 상신의견 (상신자만) } // 결재 상신 요청 타입 export interface SubmitApprovalRequest { contents: string; // 결재본문 contentsType: string; // 본문종류 (TEXT, HTML, MIME) docSecuType: string; // 보안문서타입 (PERSONAL, CONFIDENTIAL, CONFIDENTIAL_STRICT) notifyOption: string; // 통보옵션 (0-3) urgYn: string; // 긴급여부 (Y/N) sbmDt: string; // 상신일시 (YYYYMMDDHHMMSS) timeZone: string; // 타임존 (GMT, GMT+9 등) docMngSaveCode: string; // 문서관리저장코드 (0: 안함, 1: 저장) subject: string; // 결재제목 sbmLang: string; // 상신언어 (ko, ja, zh, en) apInfId: string; // 연계ID (32자리 고유값) importantYn?: string; // 중요여부 (Y/N) aplns: ApprovalLine[]; // 결재경로 attachments?: File[]; // 첨부파일 } // 결재 상신 응답 타입 export interface SubmitApprovalResponse extends BaseResponse { data: { apInfId: string; }; } // 결재 상세 조회 응답 타입 export interface ApprovalDetailResponse extends BaseResponse { data: { contentsType: string; sbmDt: string; sbmLang: string; apInfId: string; systemId: string; notifyOption: string; urgYn: string; docSecuType: string; status: string; // 암호화실패(-3), 암호화중(-2), 예약상신(-1), 보류(0), 진행중(1), 완결(2), 반려(3), 상신취소(4), 전결(5), 후완결(6) timeZone: string; subject: string; aplns: ApprovalLine[]; attachments?: File[]; }; } // 결재 본문 조회 응답 타입 export interface ApprovalContentResponse extends BaseResponse { data: { contents: string; contentsType: string; apInfId: string; }; } // 결재 상황 조회 요청 타입 export interface ApprovalStatusRequest { apinfids: { apinfid: string }[]; } // 결재 상황 조회 응답 타입 export interface ApprovalStatusResponse extends BaseResponse { data: { apInfId: string; docChgNum: string; status: string; }[]; } // 상신 취소 응답 타입 export interface CancelApprovalResponse extends BaseResponse { data: { apInfId: string; }; } // 개인 결재경로 목록 조회 응답 타입 export interface OwnApprovalLineListResponse extends BaseResponse { data: ApprovalLine[]; } // 개인 결재경로 상세 조회 응답 타입 export interface OwnApprovalLineDetailResponse extends BaseResponse { data: ApprovalLine; } // 상신함 리스트 조회 응답 타입 export interface SubmissionListResponse extends BaseResponse { data: SubmitApprovalRequest[]; } // 연계 이력 조회 응답 타입 export interface ApprovalHistoryResponse extends BaseResponse { data: SubmitApprovalRequest[]; } // 연계 ID 조회 응답 타입 export interface ApprovalIdsResponse extends BaseResponse { data: string[]; } // ========== 서버 액션 함수들 ========== /** * 결재 상신 * POST /approval/api/v2.0/approvals/submit */ export async function submitApproval( request: SubmitApprovalRequest ): Promise { try { const config = await getKnoxConfig(); const formData = new FormData(); // JSON 데이터 생성 const approvalData = { contents: request.contents, contentsType: request.contentsType, docSecuType: request.docSecuType, notifyOption: request.notifyOption, urgYn: request.urgYn, sbmDt: request.sbmDt, timeZone: request.timeZone, docMngSaveCode: request.docMngSaveCode, subject: request.subject, sbmLang: request.sbmLang, apInfId: request.apInfId, importantYn: request.importantYn, aplns: request.aplns }; formData.append('approval', JSON.stringify(approvalData)); // 첨부파일 처리 if (request.attachments) { request.attachments.forEach((file) => { formData.append('attachments', file); }); } const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/submit`, { method: 'POST', headers: await createFormHeaders(), body: formData, }); if (!response.ok) { throw new Error(`결재 상신 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('결재 상신 오류:', error); throw error; } } /** * 보안 결재 상신 * POST /approval/api/v2.0/approvals/secu-submit */ export async function submitSecurityApproval( request: SubmitApprovalRequest ): Promise { try { const config = await getKnoxConfig(); const formData = new FormData(); // JSON 데이터 생성 const approvalData = { contents: request.contents, contentsType: request.contentsType, docSecuType: request.docSecuType, // CONFIDENTIAL 또는 CONFIDENTIAL_STRICT notifyOption: request.notifyOption, urgYn: request.urgYn, sbmDt: request.sbmDt, timeZone: request.timeZone, docMngSaveCode: request.docMngSaveCode, subject: request.subject, sbmLang: request.sbmLang, apInfId: request.apInfId, importantYn: request.importantYn, aplns: request.aplns }; formData.append('approval', JSON.stringify(approvalData)); // 첨부파일 처리 if (request.attachments) { request.attachments.forEach((file) => { formData.append('attachments', file); }); } const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/secu-submit`, { method: 'POST', headers: await createFormHeaders(), body: formData, }); if (!response.ok) { throw new Error(`보안 결재 상신 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('보안 결재 상신 오류:', error); throw error; } } /** * 결재 상세 상황 조회 * GET /approval/api/v2.0/approvals/{apInfId}/detail */ export async function getApprovalDetail( apInfId: string ): Promise { try { const config = await getKnoxConfig(); const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/${apInfId}/detail`, { method: 'GET', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`결재 상세 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('결재 상세 조회 오류:', error); throw error; } } /** * 결재 본문 조회 * GET /approval/api/v2.0/approvals/{apInfId}/content */ export async function getApprovalContent( apInfId: string ): Promise { try { const config = await getKnoxConfig(); const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/${apInfId}/content`, { method: 'GET', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`결재 본문 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('결재 본문 조회 오류:', error); throw error; } } /** * 결재 상황 조회 * POST /approval/api/v2.0/approvals/status */ export async function getApprovalStatus( request: ApprovalStatusRequest ): Promise { try { const config = await getKnoxConfig(); const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/status`, { method: 'POST', headers: await createJsonHeaders(), body: JSON.stringify(request.apinfids), }); if (!response.ok) { throw new Error(`결재 상황 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('결재 상황 조회 오류:', error); throw error; } } /** * 결재 연계 ID 조회 * GET /approval/api/v2.0/approvals/apinfids */ export async function getApprovalIds( apIds?: string[] ): Promise { try { const config = await getKnoxConfig(); let url = `${config.baseUrl}/approval/api/v2.0/approvals/apinfids`; if (apIds && apIds.length > 0) { const params = new URLSearchParams(); apIds.forEach(id => params.append('apId', id)); url += `?${params.toString()}`; } const response = await fetch(url, { method: 'GET', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`결재 연계 ID 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('결재 연계 ID 조회 오류:', error); throw error; } } /** * 상신함 리스트 조회 * GET /approval/api/v2.0/approvals/submission */ export async function getSubmissionList( params?: Record ): Promise { try { const config = await getKnoxConfig(); let url = `${config.baseUrl}/approval/api/v2.0/approvals/submission`; if (params) { const searchParams = new URLSearchParams(params); url += `?${searchParams.toString()}`; } const response = await fetch(url, { method: 'GET', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`상신함 리스트 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('상신함 리스트 조회 오류:', error); throw error; } } /** * 연계 이력 조회 * GET /approval/api/v2.0/approvals/apinfidinfos */ export async function getApprovalHistory( params?: Record ): Promise { try { const config = await getKnoxConfig(); let url = `${config.baseUrl}/approval/api/v2.0/approvals/apinfidinfos`; if (params) { const searchParams = new URLSearchParams(params); url += `?${searchParams.toString()}`; } const response = await fetch(url, { method: 'GET', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`연계 이력 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('연계 이력 조회 오류:', error); throw error; } } /** * 상신 취소 * POST /approval/api/v2.0/approvals/{apInfId}/cancel */ export async function cancelApproval( apInfId: string ): Promise { try { const config = await getKnoxConfig(); const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/${apInfId}/cancel`, { method: 'POST', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`상신 취소 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('상신 취소 오류:', error); throw error; } } /** * 저장된 결재경로 목록 조회 * GET /approval/api/v2.0/approvals/ownaplnlist */ export async function getOwnApprovalLineList( params?: Record ): Promise { try { const config = await getKnoxConfig(); let url = `${config.baseUrl}/approval/api/v2.0/approvals/ownaplnlist`; if (params) { const searchParams = new URLSearchParams(params); url += `?${searchParams.toString()}`; } const response = await fetch(url, { method: 'GET', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`저장된 결재경로 목록 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('저장된 결재경로 목록 조회 오류:', error); throw error; } } /** * 저장된 결재경로 상세 조회 * GET /approval/api/v2.0/approvals/{pslAplnId}/ownaplndetail */ export async function getOwnApprovalLineDetail( pslAplnId: string ): Promise { try { const config = await getKnoxConfig(); const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/${pslAplnId}/ownaplndetail`, { method: 'GET', headers: await createJsonHeaders(), }); if (!response.ok) { throw new Error(`저장된 결재경로 상세 조회 실패: ${response.status}`); } return await response.json(); } catch (error) { console.error('저장된 결재경로 상세 조회 오류:', error); throw error; } } // ========== 유틸리티 함수들 ========== /** * 결재 상신 요청 데이터 생성 도우미 */ export async function createSubmitApprovalRequest( contents: string, subject: string, approvalLines: ApprovalLine[], options: Partial = {} ): Promise { const config = await getKnoxConfig(); const now = new Date(); const sbmDt = now.toISOString().replace(/[-:T]/g, '').slice(0, 14); const apInfId = `${config.systemId}${sbmDt}${Math.random().toString(36).substr(2, 9)}`.padEnd(32, '0'); return { contents, subject, aplns: approvalLines, contentsType: 'TEXT', docSecuType: 'PERSONAL', notifyOption: '0', urgYn: 'N', sbmDt, timeZone: 'GMT+9', docMngSaveCode: '0', sbmLang: 'ko', apInfId, importantYn: 'N', ...options }; } /** * 결재 라인 생성 도우미 */ export async function createApprovalLine( userInfo: { epId?: string; userId?: string; emailAddress?: string }, role: string, seq: string, options: Partial = {} ): Promise { return { ...userInfo, seq, role, aplnStatsCode: '0', arbPmtYn: 'Y', contentsMdfyPmtYn: 'Y', aplnMdfyPmtYn: 'Y', ...options }; } /** * 결재 상태 문자열 변환 */ export async function getApprovalStatusText(status: string): Promise { const statusMap: Record = { '-3': '암호화실패', '-2': '암호화중', '-1': '예약상신', '0': '보류', '1': '진행중', '2': '완결', '3': '반려', '4': '상신취소', '5': '전결', '6': '후완결' }; return statusMap[status] || '알 수 없음'; } /** * 결재 역할 문자열 변환 */ export async function getApprovalRoleText(role: string): Promise { const roleMap: Record = { '0': '기안', '1': '결재', '2': '합의', '3': '후결', '4': '병렬합의', '7': '병렬결재', '9': '통보' }; return roleMap[role] || '알 수 없음'; }