769 lines
27 KiB
TypeScript
769 lines
27 KiB
TypeScript
'use server';
|
|
|
|
|
|
import { executeServerAction } from '@/lib/api/execute-server-action';
|
|
import { buildApiUrl } from '@/lib/api/query-params';
|
|
import type { PaginatedApiResponse } from '@/lib/api/types';
|
|
import type { Process, ProcessFormData, ClassificationRule, IndividualItem, ProcessStep } 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;
|
|
manager: string | null;
|
|
process_category: string | null;
|
|
use_production_date: boolean;
|
|
work_log_template: string | null;
|
|
document_template_id: number | null;
|
|
document_template?: { id: number; name: string; category: string } | null;
|
|
work_log_template_id: number | null;
|
|
work_log_template_relation?: { id: number; name: string; category: string } | null;
|
|
options?: {
|
|
needs_inspection?: boolean;
|
|
needs_work_log?: boolean;
|
|
} | 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[];
|
|
process_items?: ApiProcessItem[];
|
|
steps?: ApiProcessStep[];
|
|
}
|
|
|
|
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 ApiProcessItem {
|
|
id: number;
|
|
process_id: number;
|
|
item_id: number;
|
|
priority: number;
|
|
is_active: boolean;
|
|
item?: {
|
|
id: number;
|
|
code: string;
|
|
name: string;
|
|
};
|
|
}
|
|
|
|
interface ApiResponse<T> {
|
|
success: boolean;
|
|
message: string;
|
|
data: T;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 데이터 변환 함수
|
|
// ============================================================================
|
|
|
|
function transformApiToFrontend(apiData: ApiProcess): Process {
|
|
// Pattern 규칙 변환
|
|
const patternRules = (apiData.classification_rules ?? []).map(transformRuleApiToFrontend);
|
|
|
|
// 개별 품목 → individual 분류 규칙으로 변환
|
|
const individualRules = transformProcessItemsToRules(apiData.process_items ?? []);
|
|
|
|
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 ?? '',
|
|
manager: apiData.manager ?? undefined,
|
|
processCategory: apiData.process_category ?? undefined,
|
|
useProductionDate: apiData.use_production_date ?? false,
|
|
workLogTemplate: apiData.work_log_template ?? undefined,
|
|
documentTemplateId: apiData.document_template_id ?? undefined,
|
|
documentTemplateName: apiData.document_template?.name ?? undefined,
|
|
workLogTemplateId: apiData.work_log_template_id ?? undefined,
|
|
workLogTemplateName: apiData.work_log_template_relation?.name ?? undefined,
|
|
needsInspection: apiData.options?.needs_inspection ?? false,
|
|
needsWorkLog: apiData.options?.needs_work_log ?? false,
|
|
classificationRules: [...patternRules, ...individualRules],
|
|
requiredWorkers: apiData.required_workers,
|
|
equipmentInfo: apiData.equipment_info ?? undefined,
|
|
workSteps: apiData.work_steps ?? [],
|
|
steps: (apiData.steps ?? []).map(transformStepApiToFrontend),
|
|
note: apiData.note ?? undefined,
|
|
status: apiData.is_active ? '사용중' : '미사용',
|
|
createdAt: apiData.created_at,
|
|
updatedAt: apiData.updated_at,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* process_items 배열을 individual 분류 규칙으로 변환
|
|
* 모든 개별 품목을 하나의 규칙으로 통합
|
|
*/
|
|
function transformProcessItemsToRules(processItems: ApiProcessItem[]): ClassificationRule[] {
|
|
if (processItems.length === 0) return [];
|
|
|
|
const activeItems = processItems.filter(pi => pi.is_active);
|
|
if (activeItems.length === 0) return [];
|
|
|
|
// 모든 품목 ID를 쉼표로 구분하여 하나의 규칙으로 통합
|
|
const itemIds = activeItems
|
|
.map(pi => String(pi.item_id))
|
|
.join(',');
|
|
|
|
// 품목 상세 정보 추출 (code, name 포함)
|
|
const items: IndividualItem[] = activeItems
|
|
.filter(pi => pi.item) // item 정보가 있는 것만
|
|
.map(pi => ({
|
|
id: String(pi.item!.id),
|
|
code: pi.item!.code,
|
|
name: pi.item!.name,
|
|
}));
|
|
|
|
return [{
|
|
id: `individual-${Date.now()}`,
|
|
registrationType: 'individual',
|
|
ruleType: '품목코드',
|
|
matchingType: 'equals',
|
|
conditionValue: itemIds,
|
|
priority: 0,
|
|
description: `개별 품목 ${activeItems.length}개`,
|
|
isActive: true,
|
|
createdAt: new Date().toISOString(),
|
|
items, // 품목 상세 정보 추가
|
|
}];
|
|
}
|
|
|
|
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> {
|
|
// 패턴 규칙만 분리 (individual 제외)
|
|
const patternRules = data.classificationRules.filter(
|
|
(rule) => rule.registrationType === 'pattern'
|
|
);
|
|
|
|
// 개별 품목 규칙에서 item_ids 추출
|
|
const individualRules = data.classificationRules.filter(
|
|
(rule) => rule.registrationType === 'individual'
|
|
);
|
|
|
|
// 개별 품목의 conditionValue에서 ID 배열 추출 (쉼표 구분)
|
|
const itemIds: number[] = individualRules.flatMap((rule) =>
|
|
rule.conditionValue
|
|
.split(',')
|
|
.map((id) => parseInt(id.trim(), 10))
|
|
.filter((n) => !isNaN(n) && n > 0)
|
|
);
|
|
|
|
return {
|
|
process_name: data.processName,
|
|
process_type: data.processType,
|
|
department: data.department || null,
|
|
manager: data.manager || null,
|
|
process_category: data.processCategory || null,
|
|
use_production_date: data.useProductionDate ?? false,
|
|
work_log_template: data.workLogTemplate || null,
|
|
document_template_id: data.documentTemplateId || null,
|
|
work_log_template_id: data.workLogTemplateId || null,
|
|
options: {
|
|
needs_inspection: data.needsInspection ?? false,
|
|
needs_work_log: data.needsWorkLog ?? false,
|
|
},
|
|
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,
|
|
// 패턴 규칙만 전송 (registration_type 제외)
|
|
classification_rules: patternRules.map((rule) => ({
|
|
rule_type: rule.ruleType,
|
|
matching_type: rule.matchingType,
|
|
condition_value: rule.conditionValue,
|
|
priority: rule.priority,
|
|
description: rule.description || null,
|
|
is_active: rule.isActive,
|
|
})),
|
|
// 개별 품목 ID 배열 전송
|
|
item_ids: itemIds,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 }> {
|
|
const result = await executeServerAction<PaginatedApiResponse<ApiProcess>>({
|
|
url: buildApiUrl('/api/v1/processes', {
|
|
page: params?.page,
|
|
size: params?.size,
|
|
q: params?.q,
|
|
status: params?.status,
|
|
process_type: params?.process_type,
|
|
}),
|
|
errorMessage: '공정 목록 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return {
|
|
success: true,
|
|
data: {
|
|
items: result.data.data.map(transformApiToFrontend),
|
|
total: result.data.total,
|
|
page: result.data.current_page,
|
|
totalPages: result.data.last_page,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 공정 상세 조회
|
|
*/
|
|
export async function getProcessById(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${id}`),
|
|
transform: (data: ApiProcess) => transformApiToFrontend(data),
|
|
errorMessage: '공정 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 생성
|
|
*/
|
|
export async function createProcess(data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl('/api/v1/processes'),
|
|
method: 'POST',
|
|
body: transformFrontendToApi(data),
|
|
transform: (d: ApiProcess) => transformApiToFrontend(d),
|
|
errorMessage: '공정 등록에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 수정
|
|
*/
|
|
export async function updateProcess(id: string, data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${id}`),
|
|
method: 'PUT',
|
|
body: transformFrontendToApi(data),
|
|
transform: (d: ApiProcess) => transformApiToFrontend(d),
|
|
errorMessage: '공정 수정에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 품목 제거 (item_ids만 업데이트)
|
|
*/
|
|
export async function removeProcessItem(processId: string, remainingItemIds: number[]): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${processId}`),
|
|
method: 'PUT',
|
|
body: { item_ids: remainingItemIds },
|
|
transform: (d: ApiProcess) => transformApiToFrontend(d),
|
|
errorMessage: '품목 제거에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 삭제
|
|
*/
|
|
export async function deleteProcess(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${id}`),
|
|
method: 'DELETE',
|
|
errorMessage: '공정 삭제에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 일괄 삭제
|
|
*/
|
|
export async function deleteProcesses(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction<{ deleted_count: number }>({
|
|
url: buildApiUrl('/api/v1/processes'),
|
|
method: 'DELETE',
|
|
body: { ids: ids.map((id) => parseInt(id, 10)) },
|
|
errorMessage: '공정 일괄 삭제에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return { success: true, deletedCount: result.data.deleted_count };
|
|
}
|
|
|
|
/**
|
|
* 공정 상태 토글
|
|
*/
|
|
export async function toggleProcessActive(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${id}/toggle`),
|
|
method: 'PATCH',
|
|
transform: (d: ApiProcess) => transformApiToFrontend(d),
|
|
errorMessage: '공정 상태 변경에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 순서 변경
|
|
*/
|
|
export async function reorderProcesses(
|
|
processes: { id: string; order: number }[]
|
|
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl('/api/v1/processes/reorder'),
|
|
method: 'PATCH',
|
|
body: {
|
|
items: processes.map((p) => ({
|
|
id: parseInt(p.id, 10),
|
|
sort_order: p.order,
|
|
})),
|
|
},
|
|
errorMessage: '공정 순서 변경에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 옵션 목록 (드롭다운용)
|
|
*/
|
|
export async function getProcessOptions(): Promise<{
|
|
success: boolean;
|
|
data?: Array<{ id: string; processCode: string; processName: string; processType: string; department: string }>;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
interface ApiOptionItem { id: number; process_code: string; process_name: string; process_type: string; department: string }
|
|
const result = await executeServerAction<ApiOptionItem[]>({
|
|
url: buildApiUrl('/api/v1/processes/options'),
|
|
errorMessage: '공정 옵션 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
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 ?? '',
|
|
})),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 공정 통계
|
|
*/
|
|
export async function getProcessStats(): Promise<{
|
|
success: boolean;
|
|
data?: { total: number; active: number; inactive: number; byType: Record<string, number> };
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
interface ApiStats { total: number; active: number; inactive: number; by_type: Record<string, number> }
|
|
const result = await executeServerAction<ApiStats>({
|
|
url: buildApiUrl('/api/v1/processes/stats'),
|
|
errorMessage: '공정 통계 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return {
|
|
success: true,
|
|
data: {
|
|
total: result.data.total,
|
|
active: result.data.active,
|
|
inactive: result.data.inactive,
|
|
byType: result.data.by_type,
|
|
},
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// 부서 옵션 타입 및 함수
|
|
// ============================================================================
|
|
|
|
export interface DepartmentOption {
|
|
id: string;
|
|
value: string;
|
|
label: string;
|
|
}
|
|
|
|
/**
|
|
* 부서 목록 조회
|
|
*/
|
|
export async function getDepartmentOptions(): Promise<DepartmentOption[]> {
|
|
const defaultOptions: DepartmentOption[] = [
|
|
{ id: 'default-1', value: '생산부', label: '생산부' },
|
|
{ id: 'default-2', value: '품질관리부', label: '품질관리부' },
|
|
{ id: 'default-3', value: '물류부', label: '물류부' },
|
|
{ id: 'default-4', value: '영업부', label: '영업부' },
|
|
];
|
|
interface DeptResponse { data: Array<{ id: number; name: string }> }
|
|
const result = await executeServerAction<DeptResponse>({
|
|
url: buildApiUrl('/api/v1/departments'),
|
|
errorMessage: '부서 목록 조회에 실패했습니다.',
|
|
});
|
|
if (!result.success || !result.data?.data) return defaultOptions;
|
|
const seenNames = new Set<string>();
|
|
return result.data.data
|
|
.filter((dept) => {
|
|
if (seenNames.has(dept.name)) return false;
|
|
seenNames.add(dept.name);
|
|
return true;
|
|
})
|
|
.map((dept) => ({
|
|
id: String(dept.id),
|
|
value: dept.name,
|
|
label: dept.name,
|
|
}));
|
|
}
|
|
|
|
// ============================================================================
|
|
// 품목 옵션 타입 및 함수
|
|
// ============================================================================
|
|
|
|
export interface ItemOption {
|
|
value: string;
|
|
label: string;
|
|
code: string;
|
|
id: string;
|
|
fullName: string;
|
|
type: string;
|
|
// TODO: API 응답에 process_name, process_category 필드 추가 후 활성화
|
|
processName?: string;
|
|
processCategory?: string;
|
|
}
|
|
|
|
interface GetItemListParams {
|
|
q?: string;
|
|
itemType?: string;
|
|
size?: number;
|
|
/** 해당 공정 외 다른 공정에 이미 배정된 품목 제외 (공정 ID) */
|
|
excludeProcessId?: string;
|
|
}
|
|
|
|
/**
|
|
* 품목 목록 조회 (분류 규칙용)
|
|
* - excludeProcessId: 다른 공정에 이미 배정된 품목 제외 (중복 방지)
|
|
*
|
|
* TODO: 백엔드 API 수정 요청
|
|
* - 응답에 process_name, process_category 필드 추가 필요 (공정 품목 선택 팝업에서 공정/구분 컬럼 표시용)
|
|
* - 파라미터에 processName, processCategory 필터 추가 필요 (공정/구분 필터링용)
|
|
*/
|
|
export async function getItemList(params?: GetItemListParams): Promise<ItemOption[]> {
|
|
interface ItemListResponse { data: Array<{ id: number; name: string; item_code?: string; item_type?: string; item_type_name?: string }> }
|
|
const result = await executeServerAction<ItemListResponse>({
|
|
url: buildApiUrl('/api/v1/items', {
|
|
size: params?.size || 1000,
|
|
q: params?.q,
|
|
item_type: params?.itemType,
|
|
exclude_process_id: params?.excludeProcessId,
|
|
}),
|
|
errorMessage: '품목 목록 조회에 실패했습니다.',
|
|
});
|
|
if (!result.success || !result.data?.data) return [];
|
|
return result.data.data.map((item) => ({
|
|
value: String(item.id),
|
|
label: item.name,
|
|
code: item.item_code || '',
|
|
id: String(item.id),
|
|
fullName: item.name,
|
|
type: item.item_type_name || item.item_type || '',
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* 품목 유형 옵션 조회 (common_codes에서 동적 조회)
|
|
*/
|
|
export async function getItemTypeOptions(): Promise<Array<{ value: string; label: string }>> {
|
|
const result = await executeServerAction<Array<{ code: string; name: string }>>({
|
|
url: buildApiUrl('/api/v1/settings/common/item_type'),
|
|
errorMessage: '품목 유형 옵션 조회에 실패했습니다.',
|
|
});
|
|
if (!result.success || !result.data) return [];
|
|
return result.data.map((item) => ({ value: item.code, label: item.name }));
|
|
}
|
|
|
|
// ============================================================================
|
|
// 문서 양식 (Document Template) API
|
|
// ============================================================================
|
|
|
|
export interface DocumentTemplateOption {
|
|
id: number;
|
|
name: string;
|
|
category: string;
|
|
}
|
|
|
|
/**
|
|
* 문서 양식 목록 조회 (드롭다운용)
|
|
*/
|
|
export async function getDocumentTemplates(): Promise<{
|
|
success: boolean;
|
|
data?: DocumentTemplateOption[];
|
|
error?: string;
|
|
}> {
|
|
interface ApiTemplateItem { id: number; name: string; category: string }
|
|
const result = await executeServerAction<{ data: ApiTemplateItem[] }>({
|
|
url: buildApiUrl('/api/v1/document-templates', { is_active: 1, per_page: 100 }),
|
|
errorMessage: '문서 양식 목록 조회에 실패했습니다.',
|
|
});
|
|
if (!result.success || !result.data?.data) return { success: false, error: result.error };
|
|
return {
|
|
success: true,
|
|
data: result.data.data.map((item) => ({
|
|
id: item.id,
|
|
name: item.name,
|
|
category: item.category,
|
|
})),
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// 공정 단계 (Process Step) API
|
|
// ============================================================================
|
|
|
|
interface ApiProcessStepOptions {
|
|
inspection_setting?: Record<string, unknown>;
|
|
inspection_scope?: {
|
|
type: string;
|
|
sample_size?: number;
|
|
sample_base?: string;
|
|
};
|
|
}
|
|
|
|
interface ApiProcessStep {
|
|
id: number;
|
|
process_id: number;
|
|
step_code: string;
|
|
step_name: string;
|
|
is_required: boolean;
|
|
needs_approval: boolean;
|
|
needs_inspection: boolean;
|
|
is_active: boolean;
|
|
sort_order: number;
|
|
connection_type: string | null;
|
|
connection_target: string | null;
|
|
completion_type: string | null;
|
|
options: ApiProcessStepOptions | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
function transformStepApiToFrontend(apiStep: ApiProcessStep): ProcessStep {
|
|
const opts = apiStep.options;
|
|
return {
|
|
id: String(apiStep.id),
|
|
stepCode: apiStep.step_code,
|
|
stepName: apiStep.step_name,
|
|
isRequired: apiStep.is_required,
|
|
needsApproval: apiStep.needs_approval,
|
|
needsInspection: apiStep.needs_inspection,
|
|
isActive: apiStep.is_active,
|
|
order: apiStep.sort_order,
|
|
connectionType: (apiStep.connection_type as ProcessStep['connectionType']) || '없음',
|
|
connectionTarget: apiStep.connection_target ?? undefined,
|
|
completionType: (apiStep.completion_type as ProcessStep['completionType']) || 'click_complete',
|
|
inspectionSetting: opts?.inspection_setting as ProcessStep['inspectionSetting'],
|
|
inspectionScope: opts?.inspection_scope ? {
|
|
type: opts.inspection_scope.type as 'all' | 'sampling' | 'group',
|
|
sampleSize: opts.inspection_scope.sample_size,
|
|
sampleBase: opts.inspection_scope.sample_base as 'order' | 'lot' | undefined,
|
|
} : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 공정 단계 목록 조회
|
|
*/
|
|
export async function getProcessSteps(processId: string): Promise<{
|
|
success: boolean;
|
|
data?: ProcessStep[];
|
|
error?: string;
|
|
}> {
|
|
const result = await executeServerAction<ApiProcessStep[]>({
|
|
url: buildApiUrl(`/api/v1/processes/${processId}/steps`),
|
|
errorMessage: '공정 단계 목록 조회에 실패했습니다.',
|
|
});
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return { success: true, data: result.data.map(transformStepApiToFrontend) };
|
|
}
|
|
|
|
/**
|
|
* 공정 단계 상세 조회
|
|
*/
|
|
export async function getProcessStepById(processId: string, stepId: string): Promise<{
|
|
success: boolean;
|
|
data?: ProcessStep;
|
|
error?: string;
|
|
}> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
|
|
transform: (d: ApiProcessStep) => transformStepApiToFrontend(d),
|
|
errorMessage: '공정 단계 조회에 실패했습니다.',
|
|
});
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 단계 생성
|
|
*/
|
|
export async function createProcessStep(
|
|
processId: string,
|
|
data: Omit<ProcessStep, 'id'>
|
|
): Promise<{ success: boolean; data?: ProcessStep; error?: string }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${processId}/steps`),
|
|
method: 'POST',
|
|
body: {
|
|
step_name: data.stepName,
|
|
is_required: data.isRequired,
|
|
needs_approval: data.needsApproval,
|
|
needs_inspection: data.needsInspection,
|
|
is_active: data.isActive,
|
|
connection_type: data.connectionType || null,
|
|
connection_target: data.connectionTarget || null,
|
|
completion_type: data.completionType || null,
|
|
options: (data.inspectionSetting || data.inspectionScope) ? {
|
|
inspection_setting: data.inspectionSetting || null,
|
|
inspection_scope: data.inspectionScope ? {
|
|
type: data.inspectionScope.type,
|
|
sample_size: data.inspectionScope.sampleSize,
|
|
sample_base: data.inspectionScope.sampleBase,
|
|
} : null,
|
|
} : null,
|
|
},
|
|
transform: (d: ApiProcessStep) => transformStepApiToFrontend(d),
|
|
errorMessage: '공정 단계 등록에 실패했습니다.',
|
|
});
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 단계 수정
|
|
*/
|
|
export async function updateProcessStep(
|
|
processId: string,
|
|
stepId: string,
|
|
data: Partial<ProcessStep>
|
|
): Promise<{ success: boolean; data?: ProcessStep; error?: string }> {
|
|
const apiData: Record<string, unknown> = {};
|
|
if (data.stepName !== undefined) apiData.step_name = data.stepName;
|
|
if (data.isRequired !== undefined) apiData.is_required = data.isRequired;
|
|
if (data.needsApproval !== undefined) apiData.needs_approval = data.needsApproval;
|
|
if (data.needsInspection !== undefined) apiData.needs_inspection = data.needsInspection;
|
|
if (data.isActive !== undefined) apiData.is_active = data.isActive;
|
|
if (data.connectionType !== undefined) apiData.connection_type = data.connectionType || null;
|
|
if (data.connectionTarget !== undefined) apiData.connection_target = data.connectionTarget || null;
|
|
if (data.completionType !== undefined) apiData.completion_type = data.completionType || null;
|
|
if (data.inspectionSetting !== undefined || data.inspectionScope !== undefined) {
|
|
apiData.options = {
|
|
inspection_setting: data.inspectionSetting || null,
|
|
inspection_scope: data.inspectionScope ? {
|
|
type: data.inspectionScope.type,
|
|
sample_size: data.inspectionScope.sampleSize,
|
|
sample_base: data.inspectionScope.sampleBase,
|
|
} : null,
|
|
};
|
|
}
|
|
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
|
|
method: 'PUT',
|
|
body: apiData,
|
|
transform: (d: ApiProcessStep) => transformStepApiToFrontend(d),
|
|
errorMessage: '공정 단계 수정에 실패했습니다.',
|
|
});
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 단계 삭제
|
|
*/
|
|
export async function deleteProcessStep(
|
|
processId: string,
|
|
stepId: string
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
|
|
method: 'DELETE',
|
|
errorMessage: '공정 단계 삭제에 실패했습니다.',
|
|
});
|
|
return { success: result.success, error: result.error };
|
|
}
|
|
|
|
/**
|
|
* 공정 단계 순서 변경
|
|
*/
|
|
export async function reorderProcessSteps(
|
|
processId: string,
|
|
steps: { id: string; order: number }[]
|
|
): Promise<{ success: boolean; data?: ProcessStep[]; error?: string }> {
|
|
const result = await executeServerAction<ApiProcessStep[]>({
|
|
url: buildApiUrl(`/api/v1/processes/${processId}/steps/reorder`),
|
|
method: 'PATCH',
|
|
body: {
|
|
items: steps.map((s) => ({
|
|
id: parseInt(s.id, 10),
|
|
sort_order: s.order,
|
|
})),
|
|
},
|
|
errorMessage: '공정 단계 순서 변경에 실패했습니다.',
|
|
});
|
|
if (!result.success || !result.data) return { success: false, error: result.error };
|
|
return { success: true, data: result.data.map(transformStepApiToFrontend) };
|
|
}
|