ERP-node/frontend/lib/api/approval.ts

454 lines
13 KiB
TypeScript
Raw Normal View History

/**
* 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 };
}
}