454 lines
13 KiB
TypeScript
454 lines
13 KiB
TypeScript
/**
|
|
* 결재 시스템 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<string, string>)["Authorization"] = `Bearer ${token}`;
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
// ============================================================
|
|
// 공통 타입 정의
|
|
// ============================================================
|
|
|
|
export interface ApiResponse<T = any> {
|
|
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<string, any>;
|
|
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<string, any>;
|
|
screen_id?: number;
|
|
button_component_id?: string;
|
|
approval_mode?: "sequential" | "parallel";
|
|
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<ApiResponse<ApprovalDefinition[]>> {
|
|
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<ApiResponse<ApprovalDefinition>> {
|
|
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<ApiResponse<ApprovalDefinition>> {
|
|
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<ApprovalDefinition>
|
|
): Promise<ApiResponse<ApprovalDefinition>> {
|
|
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<ApiResponse<void>> {
|
|
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<ApiResponse<ApprovalLineTemplate[]>> {
|
|
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<ApiResponse<ApprovalLineTemplate>> {
|
|
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<ApprovalLineTemplateStep, "step_id" | "template_id" | "company_code">[];
|
|
}): Promise<ApiResponse<ApprovalLineTemplate>> {
|
|
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<ApprovalLineTemplateStep, "step_id" | "template_id" | "company_code">[];
|
|
}
|
|
): Promise<ApiResponse<ApprovalLineTemplate>> {
|
|
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<ApiResponse<void>> {
|
|
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;
|
|
target_record_id?: string;
|
|
requester_id?: string;
|
|
my_approvals?: boolean;
|
|
page?: number;
|
|
limit?: number;
|
|
}): Promise<ApiResponse<ApprovalRequest[]>> {
|
|
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?.target_record_id) qs.append("target_record_id", params.target_record_id);
|
|
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<ApiResponse<ApprovalRequest>> {
|
|
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<ApiResponse<ApprovalRequest>> {
|
|
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<ApiResponse<void>> {
|
|
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<ApiResponse<ApprovalLine[]>> {
|
|
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<ApiResponse<void>> {
|
|
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 };
|
|
}
|
|
}
|