452 lines
14 KiB
TypeScript
452 lines
14 KiB
TypeScript
'use server';
|
|
|
|
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
|
import type { Process, ProcessFormData, ClassificationRule } from '@/types/process';
|
|
|
|
// ============================================================================
|
|
// API 타입 정의
|
|
// ============================================================================
|
|
|
|
interface ApiProcess {
|
|
id: number;
|
|
tenant_id: number;
|
|
process_code: string;
|
|
process_name: string;
|
|
description: string | null;
|
|
process_type: string;
|
|
department: string | null;
|
|
work_log_template: string | null;
|
|
required_workers: number;
|
|
equipment_info: string | null;
|
|
work_steps: string[] | null;
|
|
note: string | null;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
classification_rules?: ApiClassificationRule[];
|
|
}
|
|
|
|
interface ApiClassificationRule {
|
|
id: number;
|
|
process_id: number;
|
|
registration_type: string;
|
|
rule_type: string;
|
|
matching_type: string;
|
|
condition_value: string;
|
|
priority: number;
|
|
description: string | null;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
interface ApiResponse<T> {
|
|
success: boolean;
|
|
message: string;
|
|
data: T;
|
|
}
|
|
|
|
interface PaginatedResponse<T> {
|
|
current_page: number;
|
|
data: T[];
|
|
last_page: number;
|
|
per_page: number;
|
|
total: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 데이터 변환 함수
|
|
// ============================================================================
|
|
|
|
function transformApiToFrontend(apiData: ApiProcess): Process {
|
|
return {
|
|
id: String(apiData.id),
|
|
processCode: apiData.process_code,
|
|
processName: apiData.process_name,
|
|
description: apiData.description ?? undefined,
|
|
processType: apiData.process_type as Process['processType'],
|
|
department: apiData.department ?? '',
|
|
workLogTemplate: apiData.work_log_template ?? undefined,
|
|
classificationRules: (apiData.classification_rules ?? []).map(transformRuleApiToFrontend),
|
|
requiredWorkers: apiData.required_workers,
|
|
equipmentInfo: apiData.equipment_info ?? undefined,
|
|
workSteps: apiData.work_steps ?? [],
|
|
note: apiData.note ?? undefined,
|
|
status: apiData.is_active ? '사용중' : '미사용',
|
|
createdAt: apiData.created_at,
|
|
updatedAt: apiData.updated_at,
|
|
};
|
|
}
|
|
|
|
function transformRuleApiToFrontend(apiRule: ApiClassificationRule): ClassificationRule {
|
|
return {
|
|
id: String(apiRule.id),
|
|
registrationType: apiRule.registration_type as ClassificationRule['registrationType'],
|
|
ruleType: apiRule.rule_type as ClassificationRule['ruleType'],
|
|
matchingType: apiRule.matching_type as ClassificationRule['matchingType'],
|
|
conditionValue: apiRule.condition_value,
|
|
priority: apiRule.priority,
|
|
description: apiRule.description ?? undefined,
|
|
isActive: apiRule.is_active,
|
|
createdAt: apiRule.created_at,
|
|
};
|
|
}
|
|
|
|
function transformFrontendToApi(data: ProcessFormData): Record<string, unknown> {
|
|
return {
|
|
process_name: data.processName,
|
|
process_type: data.processType,
|
|
department: data.department || null,
|
|
work_log_template: data.workLogTemplate || null,
|
|
required_workers: data.requiredWorkers,
|
|
equipment_info: data.equipmentInfo || null,
|
|
work_steps: data.workSteps ? data.workSteps.split(',').map((s) => s.trim()).filter(Boolean) : [],
|
|
note: data.note || null,
|
|
is_active: data.isActive,
|
|
classification_rules: data.classificationRules.map((rule) => ({
|
|
registration_type: rule.registrationType,
|
|
rule_type: rule.ruleType,
|
|
matching_type: rule.matchingType,
|
|
condition_value: rule.conditionValue,
|
|
priority: rule.priority,
|
|
description: rule.description || null,
|
|
is_active: rule.isActive,
|
|
})),
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// API 함수
|
|
// ============================================================================
|
|
|
|
/**
|
|
* 공정 목록 조회
|
|
*/
|
|
export async function getProcessList(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
q?: string;
|
|
status?: string;
|
|
process_type?: string;
|
|
}): Promise<{ success: boolean; data?: { items: Process[]; total: number; page: number; totalPages: number }; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (params?.page) searchParams.set('page', String(params.page));
|
|
if (params?.size) searchParams.set('size', String(params.size));
|
|
if (params?.q) searchParams.set('q', params.q);
|
|
if (params?.status) searchParams.set('status', params.status);
|
|
if (params?.process_type) searchParams.set('process_type', params.process_type);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/v1/processes?${searchParams.toString()}`,
|
|
{ method: 'GET', cache: 'no-store' }
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '목록 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<PaginatedResponse<ApiProcess>> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '목록 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
items: result.data.data.map(transformApiToFrontend),
|
|
total: result.data.total,
|
|
page: result.data.current_page,
|
|
totalPages: result.data.last_page,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[getProcessList] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 상세 조회
|
|
*/
|
|
export async function getProcessById(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes/${id}`, {
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<ApiProcess> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '조회에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[getProcessById] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 생성
|
|
*/
|
|
export async function createProcess(data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const apiData = transformFrontendToApi(data);
|
|
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(apiData),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '등록에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<ApiProcess> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '등록에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[createProcess] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 수정
|
|
*/
|
|
export async function updateProcess(id: string, data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const apiData = transformFrontendToApi(data);
|
|
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(apiData),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '수정에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<ApiProcess> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '수정에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[updateProcess] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 삭제
|
|
*/
|
|
export async function deleteProcess(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '삭제에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<null> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '삭제에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('[deleteProcess] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 일괄 삭제
|
|
*/
|
|
export async function deleteProcesses(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes`, {
|
|
method: 'DELETE',
|
|
body: JSON.stringify({ ids: ids.map((id) => parseInt(id, 10)) }),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '일괄 삭제에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<{ deleted_count: number }> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '일괄 삭제에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, deletedCount: result.data.deleted_count };
|
|
} catch (error) {
|
|
console.error('[deleteProcesses] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 상태 토글
|
|
*/
|
|
export async function toggleProcessActive(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes/${id}/toggle`, {
|
|
method: 'PATCH',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '상태 변경에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<ApiProcess> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '상태 변경에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToFrontend(result.data) };
|
|
} catch (error) {
|
|
console.error('[toggleProcessActive] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 옵션 목록 (드롭다운용)
|
|
*/
|
|
export async function getProcessOptions(): Promise<{
|
|
success: boolean;
|
|
data?: Array<{ id: string; processCode: string; processName: string; processType: string; department: string }>;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes/options`, {
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '옵션 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<Array<{ id: number; process_code: string; process_name: string; process_type: string; department: string }>> =
|
|
await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '옵션 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: result.data.map((item) => ({
|
|
id: String(item.id),
|
|
processCode: item.process_code,
|
|
processName: item.process_name,
|
|
processType: item.process_type,
|
|
department: item.department ?? '',
|
|
})),
|
|
};
|
|
} catch (error) {
|
|
console.error('[getProcessOptions] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 통계
|
|
*/
|
|
export async function getProcessStats(): Promise<{
|
|
success: boolean;
|
|
data?: { total: number; active: number; inactive: number; byType: Record<string, number> };
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/processes/stats`, {
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '통계 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result: ApiResponse<{ total: number; active: number; inactive: number; by_type: Record<string, number> }> = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '통계 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
total: result.data.total,
|
|
active: result.data.active,
|
|
inactive: result.data.inactive,
|
|
byType: result.data.by_type,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[getProcessStats] Error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|