/** * 결재 시스템 API 클라이언트 * 엔드포인트: /api/approval/* */ // API URL 동적 설정 const getApiBaseUrl = (): string => { if (process.env.NEXT_PUBLIC_API_URL) { return process.env.NEXT_PUBLIC_API_URL; } if (typeof window !== "undefined") { const currentHost = window.location.hostname; if (currentHost === "v1.vexplor.com") { return "https://api.vexplor.com/api"; } if (currentHost === "localhost" || currentHost === "127.0.0.1") { return "http://localhost:8080/api"; } } return "/api"; }; const API_BASE = getApiBaseUrl(); function getAuthToken(): string | null { if (typeof window === "undefined") return null; return localStorage.getItem("authToken") || sessionStorage.getItem("authToken"); } function getAuthHeaders(): HeadersInit { const token = getAuthToken(); const headers: HeadersInit = { "Content-Type": "application/json" }; if (token) { (headers as Record)["Authorization"] = `Bearer ${token}`; } return headers; } // ============================================================ // 공통 타입 정의 // ============================================================ export interface ApiResponse { success: boolean; data?: T; message?: string; error?: string; total?: number; page?: number; limit?: number; } export interface ApprovalDefinition { definition_id: number; definition_name: string; definition_name_eng?: string; description?: string; default_template_id?: number; max_steps: number; allow_self_approval: boolean; allow_cancel: boolean; is_active: string; company_code: string; created_by?: string; created_at: string; updated_by?: string; updated_at: string; } export interface ApprovalLineTemplate { template_id: number; template_name: string; description?: string; definition_id?: number; definition_name?: string; is_active: string; company_code: string; created_by?: string; created_at: string; updated_by?: string; updated_at: string; steps?: ApprovalLineTemplateStep[]; } export interface ApprovalLineTemplateStep { step_id: number; template_id: number; step_order: number; approver_type: "user" | "position" | "dept"; approver_user_id?: string; approver_position?: string; approver_dept_code?: string; approver_label?: string; company_code: string; } export interface ApprovalRequest { request_id: number; title: string; description?: string; definition_id?: number; definition_name?: string; target_table: string; target_record_id: string; target_record_data?: Record; status: "requested" | "in_progress" | "approved" | "rejected" | "cancelled"; current_step: number; total_steps: number; requester_id: string; requester_name?: string; requester_dept?: string; completed_at?: string; final_approver_id?: string; final_comment?: string; screen_id?: number; button_component_id?: string; company_code: string; created_at: string; updated_at: string; lines?: ApprovalLine[]; } export interface ApprovalLine { line_id: number; request_id: number; step_order: number; approver_id: string; approver_name?: string; approver_position?: string; approver_dept?: string; approver_label?: string; status: "waiting" | "pending" | "approved" | "rejected" | "skipped"; comment?: string; processed_at?: string; company_code: string; created_at: string; // 요청 정보 (my-pending 조회 시 포함) title?: string; target_table?: string; target_record_id?: string; requester_name?: string; requester_dept?: string; request_created_at?: string; } export interface CreateApprovalRequestInput { title: string; description?: string; definition_id?: number; target_table: string; target_record_id: string; target_record_data?: Record; screen_id?: number; button_component_id?: string; approvers: { approver_id: string; approver_name?: string; approver_position?: string; approver_dept?: string; approver_label?: string; }[]; } // ============================================================ // 결재 유형 (Definitions) API // ============================================================ export async function getApprovalDefinitions(params?: { is_active?: string; search?: string; }): Promise> { try { const qs = new URLSearchParams(); if (params?.is_active) qs.append("is_active", params.is_active); if (params?.search) qs.append("search", params.search); const response = await fetch( `${API_BASE}/approval/definitions${qs.toString() ? `?${qs}` : ""}`, { headers: getAuthHeaders(), credentials: "include" } ); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function getApprovalDefinition(id: number): Promise> { try { const response = await fetch(`${API_BASE}/approval/definitions/${id}`, { headers: getAuthHeaders(), credentials: "include", }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function createApprovalDefinition(data: { definition_name: string; definition_name_eng?: string; description?: string; default_template_id?: number; max_steps?: number; allow_self_approval?: boolean; allow_cancel?: boolean; is_active?: string; }): Promise> { try { const response = await fetch(`${API_BASE}/approval/definitions`, { method: "POST", headers: getAuthHeaders(), credentials: "include", body: JSON.stringify(data), }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function updateApprovalDefinition( id: number, data: Partial ): Promise> { try { const response = await fetch(`${API_BASE}/approval/definitions/${id}`, { method: "PUT", headers: getAuthHeaders(), credentials: "include", body: JSON.stringify(data), }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function deleteApprovalDefinition(id: number): Promise> { try { const response = await fetch(`${API_BASE}/approval/definitions/${id}`, { method: "DELETE", headers: getAuthHeaders(), credentials: "include", }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 결재선 템플릿 (Templates) API // ============================================================ export async function getApprovalTemplates(params?: { definition_id?: number; is_active?: string; }): Promise> { try { const qs = new URLSearchParams(); if (params?.definition_id) qs.append("definition_id", String(params.definition_id)); if (params?.is_active) qs.append("is_active", params.is_active); const response = await fetch( `${API_BASE}/approval/templates${qs.toString() ? `?${qs}` : ""}`, { headers: getAuthHeaders(), credentials: "include" } ); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function getApprovalTemplate(id: number): Promise> { try { const response = await fetch(`${API_BASE}/approval/templates/${id}`, { headers: getAuthHeaders(), credentials: "include", }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function createApprovalTemplate(data: { template_name: string; description?: string; definition_id?: number; is_active?: string; steps?: Omit[]; }): Promise> { try { const response = await fetch(`${API_BASE}/approval/templates`, { method: "POST", headers: getAuthHeaders(), credentials: "include", body: JSON.stringify(data), }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function updateApprovalTemplate( id: number, data: { template_name?: string; description?: string; definition_id?: number; is_active?: string; steps?: Omit[]; } ): Promise> { try { const response = await fetch(`${API_BASE}/approval/templates/${id}`, { method: "PUT", headers: getAuthHeaders(), credentials: "include", body: JSON.stringify(data), }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function deleteApprovalTemplate(id: number): Promise> { try { const response = await fetch(`${API_BASE}/approval/templates/${id}`, { method: "DELETE", headers: getAuthHeaders(), credentials: "include", }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 결재 요청 (Requests) API // ============================================================ export async function getApprovalRequests(params?: { status?: string; target_table?: string; requester_id?: string; my_approvals?: boolean; page?: number; limit?: number; }): Promise> { try { const qs = new URLSearchParams(); if (params?.status) qs.append("status", params.status); if (params?.target_table) qs.append("target_table", params.target_table); if (params?.requester_id) qs.append("requester_id", params.requester_id); if (params?.my_approvals !== undefined) qs.append("my_approvals", String(params.my_approvals)); if (params?.page) qs.append("page", String(params.page)); if (params?.limit) qs.append("limit", String(params.limit)); const response = await fetch( `${API_BASE}/approval/requests${qs.toString() ? `?${qs}` : ""}`, { headers: getAuthHeaders(), credentials: "include" } ); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function getApprovalRequest(id: number): Promise> { try { const response = await fetch(`${API_BASE}/approval/requests/${id}`, { headers: getAuthHeaders(), credentials: "include", }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function createApprovalRequest( data: CreateApprovalRequestInput ): Promise> { try { const response = await fetch(`${API_BASE}/approval/requests`, { method: "POST", headers: getAuthHeaders(), credentials: "include", body: JSON.stringify(data), }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function cancelApprovalRequest(id: number): Promise> { try { const response = await fetch(`${API_BASE}/approval/requests/${id}/cancel`, { method: "POST", headers: getAuthHeaders(), credentials: "include", }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 결재 라인 처리 (Lines) API // ============================================================ export async function getMyPendingApprovals(): Promise> { try { const response = await fetch(`${API_BASE}/approval/my-pending`, { headers: getAuthHeaders(), credentials: "include", }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } } export async function processApprovalLine( lineId: number, data: { action: "approved" | "rejected"; comment?: string } ): Promise> { try { const response = await fetch(`${API_BASE}/approval/lines/${lineId}/process`, { method: "POST", headers: getAuthHeaders(), credentials: "include", body: JSON.stringify(data), }); return await response.json(); } catch (error: any) { return { success: false, error: error.message }; } }