Files
sam-react-prod/src/components/production/WorkOrders/actions.ts
권혁성 f5fbe1efc8 feat(WEB): 절곡품 선생산→재고적재 Phase 2 - 수동 작업지시 및 재고 필터
- WorkOrderCreate: 수동 모드 품목 검색/추가/수량 관리 UI 구현
- WorkOrders/actions: items 파라미터 추가, searchItemsForWorkOrder 함수 추가
- StockStatusList: 품목분류(BENDING/SCREEN/STEEL/ALUMINUM) 필터 추가
- StockStatus/actions: itemCategory 파라미터 지원
2026-02-22 04:19:41 +09:00

1137 lines
32 KiB
TypeScript

/**
* 작업지시 관리 서버 액션
*
* API Endpoints:
* - GET /api/v1/work-orders - 목록 조회
* - GET /api/v1/work-orders/stats - 통계 조회
* - GET /api/v1/work-orders/{id} - 상세 조회
* - POST /api/v1/work-orders - 등록
* - PUT /api/v1/work-orders/{id} - 수정
* - DELETE /api/v1/work-orders/{id} - 삭제
* - PATCH /api/v1/work-orders/{id}/status - 상태 변경
* - PATCH /api/v1/work-orders/{id}/assign - 담당자 배정
* - PATCH /api/v1/work-orders/{id}/bending/toggle - 벤딩 필드 토글
* - POST /api/v1/work-orders/{id}/issues - 이슈 등록
* - PATCH /api/v1/work-orders/{id}/issues/{issueId}/resolve - 이슈 해결
* - PATCH /api/v1/work-orders/{id}/items/{itemId}/status - 품목 상태 변경
*/
'use server';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import { buildApiUrl } from '@/lib/api/query-params';
import type {
WorkOrder,
WorkOrderStats,
WorkOrderStatus,
WorkOrderApiPaginatedResponse,
WorkOrderStatsApi,
} from './types';
import {
transformApiToFrontend,
transformFrontendToApi,
transformStatsApiToFrontend,
} from './types';
// ===== 페이지네이션 타입 =====
interface PaginationMeta {
currentPage: number;
lastPage: number;
perPage: number;
total: number;
}
// ===== 작업지시 목록 조회 =====
export async function getWorkOrders(params?: {
page?: number;
perPage?: number;
status?: WorkOrderStatus | 'all';
processId?: number | 'all' | 'none'; // 공정 ID (FK → processes.id), 'none' = 미지정
processType?: 'screen' | 'slat' | 'bending'; // 공정 타입 필터
priority?: string; // 우선순위 필터 (urgent/priority/normal)
search?: string;
startDate?: string;
endDate?: string;
}): Promise<{
success: boolean;
data: WorkOrder[];
pagination: PaginationMeta;
error?: string;
}> {
const emptyResponse = {
success: false,
data: [] as WorkOrder[],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
};
try {
const url = buildApiUrl('/api/v1/work-orders', {
page: params?.page,
per_page: params?.perPage,
status: params?.status && params.status !== 'all' ? params.status : undefined,
// 'none': 공정 미지정 필터 (process_id IS NULL), 'all': 제외
process_id: params?.processId && params.processId !== 'all' ? String(params.processId) : undefined,
process_type: params?.processType,
priority: params?.priority && params.priority !== 'all' ? params.priority : undefined,
search: params?.search,
start_date: params?.startDate,
end_date: params?.endDate,
});
const { response, error } = await serverFetch(url, { method: 'GET' });
if (error || !response) {
return { ...emptyResponse, error: error?.message || 'API 요청 실패' };
}
if (!response.ok) {
console.warn('[WorkOrderActions] GET work-orders error:', response.status);
return { ...emptyResponse, error: `API 오류: ${response.status}` };
}
const result = await response.json();
if (!result.success) {
return {
...emptyResponse,
error: result.message || '작업지시 목록 조회에 실패했습니다.',
};
}
const paginatedData: WorkOrderApiPaginatedResponse = result.data || {
data: [],
current_page: 1,
last_page: 1,
per_page: 20,
total: 0,
};
const workOrders = (paginatedData.data || []).map(transformApiToFrontend);
return {
success: true,
data: workOrders,
pagination: {
currentPage: paginatedData.current_page,
lastPage: paginatedData.last_page,
perPage: paginatedData.per_page,
total: paginatedData.total,
},
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getWorkOrders error:', error);
return { ...emptyResponse, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 작업지시 통계 조회 =====
export async function getWorkOrderStats(): Promise<{
success: boolean;
data?: WorkOrderStats;
error?: string;
}> {
try {
const url = buildApiUrl('/api/v1/work-orders/stats');
const { response, error } = await serverFetch(url, { method: 'GET' });
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
if (!response.ok) {
console.warn('[WorkOrderActions] GET stats error:', response.status);
return { success: false, error: `API 오류: ${response.status}` };
}
const result = await response.json();
if (!result.success) {
return {
success: false,
error: result.message || '통계 조회에 실패했습니다.',
};
}
const statsApi: WorkOrderStatsApi = result.data;
return {
success: true,
data: transformStatsApiToFrontend(statsApi),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getWorkOrderStats error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 작업지시 상세 조회 =====
export async function getWorkOrderById(id: string): Promise<{
success: boolean;
data?: WorkOrder;
error?: string;
}> {
try {
const url = buildApiUrl(`/api/v1/work-orders/${id}`);
const { response, error } = await serverFetch(url, { method: 'GET' });
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
if (!response.ok) {
console.error('[WorkOrderActions] GET work-order error:', response.status);
return { success: false, error: `API 오류: ${response.status}` };
}
const result = await response.json();
if (!result.success || !result.data) {
return {
success: false,
error: result.message || '작업지시 조회에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getWorkOrderById error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 작업지시 등록 =====
export async function createWorkOrder(
data: Partial<WorkOrder> & {
salesOrderId?: number;
assigneeId?: number; // 단일 담당자 (하위 호환)
assigneeIds?: number[]; // 다중 담당자
teamId?: number;
items?: Array<{ // 수동 등록 시 품목 목록
item_id?: number;
item_name: string;
specification?: string;
quantity?: number;
unit?: string;
}>;
}
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
try {
// 다중 담당자 우선, 없으면 단일 담당자 배열로 변환
const assigneeIds = data.assigneeIds && data.assigneeIds.length > 0
? data.assigneeIds
: data.assigneeId
? [data.assigneeId]
: undefined;
const apiData = {
...transformFrontendToApi(data),
sales_order_id: data.salesOrderId,
assignee_ids: assigneeIds, // 배열로 전송
team_id: data.teamId,
...(data.items && data.items.length > 0 ? { items: data.items } : {}),
};
const { response, error } = await serverFetch(
buildApiUrl('/api/v1/work-orders'),
{
method: 'POST',
body: JSON.stringify(apiData),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '작업지시 등록에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] createWorkOrder error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 품목 검색 (수동 등록용) =====
export interface ManualItemOption {
id: number;
code: string;
name: string;
specification: string;
unit: string;
itemCategory: string;
}
export async function searchItemsForWorkOrder(
query?: string,
itemCategory?: string
): Promise<{ success: boolean; data: ManualItemOption[] }> {
try {
const { response, error } = await serverFetch(
buildApiUrl('/api/v1/items', {
search: query,
item_category: itemCategory,
per_page: 50,
}),
{ method: 'GET' }
);
if (error || !response) {
return { success: false, data: [] };
}
const result = await response.json();
if (!response.ok || !result.success) {
return { success: false, data: [] };
}
const items: ManualItemOption[] = (result.data?.data || []).map((item: Record<string, unknown>) => ({
id: item.id as number,
code: (item.item_code || item.code) as string,
name: (item.item_name || item.name) as string,
specification: (item.specification || '') as string,
unit: (item.unit || 'EA') as string,
itemCategory: (item.item_category || '') as string,
}));
return { success: true, data: items };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] searchItemsForWorkOrder error:', error);
return { success: false, data: [] };
}
}
// ===== 작업지시 수정 =====
export async function updateWorkOrder(
id: string,
data: Partial<WorkOrder> & { assigneeIds?: number[] }
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
try {
const apiData = transformFrontendToApi(data);
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${id}`),
{
method: 'PUT',
body: JSON.stringify(apiData),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '작업지시 수정에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] updateWorkOrder error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 작업지시 삭제 =====
export async function deleteWorkOrder(id: string): Promise<{ success: boolean; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${id}`),
{ method: 'DELETE' }
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '작업지시 삭제에 실패했습니다.',
};
}
return { success: true };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] deleteWorkOrder error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 작업지시 상태 변경 =====
export async function updateWorkOrderStatus(
id: string,
status: WorkOrderStatus
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${id}/status`),
{
method: 'PATCH',
body: JSON.stringify({ status }),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '상태 변경에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] updateWorkOrderStatus error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 담당자 배정 =====
export async function assignWorkOrder(
id: string,
assigneeIds: number | number[], // 단일 또는 다중 담당자
teamId?: number
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
try {
// 배열로 통일
const ids = Array.isArray(assigneeIds) ? assigneeIds : [assigneeIds];
const body: { assignee_ids: number[]; team_id?: number } = { assignee_ids: ids };
if (teamId) body.team_id = teamId;
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${id}/assign`),
{
method: 'PATCH',
body: JSON.stringify(body),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '담당자 배정에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] assignWorkOrder error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 벤딩 필드 토글 =====
export async function toggleBendingField(
id: string,
field: string
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${id}/bending/toggle`),
{
method: 'PATCH',
body: JSON.stringify({ field }),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '벤딩 필드 토글에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] toggleBendingField error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 이슈 등록 =====
export async function addWorkOrderIssue(
id: string,
data: {
title: string;
description?: string;
priority?: 'low' | 'medium' | 'high';
}
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${id}/issues`),
{
method: 'POST',
body: JSON.stringify(data),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '이슈 등록에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] addWorkOrderIssue error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 이슈 해결 =====
export async function resolveWorkOrderIssue(
workOrderId: string,
issueId: string
): Promise<{ success: boolean; data?: WorkOrder; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/issues/${issueId}/resolve`),
{ method: 'PATCH' }
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '이슈 해결 처리에 실패했습니다.',
};
}
return {
success: true,
data: transformApiToFrontend(result.data),
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] resolveWorkOrderIssue error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 품목 상태 변경 =====
export type WorkOrderItemStatus = 'waiting' | 'in_progress' | 'completed';
export async function updateWorkOrderItemStatus(
workOrderId: string,
itemId: number,
status: WorkOrderItemStatus
): Promise<{
success: boolean;
itemId: number;
status: WorkOrderItemStatus;
workOrderStatus?: string;
workOrderStatusChanged?: boolean;
error?: string;
}> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/items/${itemId}/status`),
{
method: 'PATCH',
body: JSON.stringify({ status }),
}
);
if (error || !response) {
return { success: false, itemId, status, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
itemId,
status,
error: result.message || '품목 상태 변경에 실패했습니다.',
};
}
return {
success: true,
itemId,
status: result.data?.item?.status || status,
workOrderStatus: result.data?.work_order_status,
workOrderStatusChanged: result.data?.work_order_status_changed || false,
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] updateWorkOrderItemStatus error:', error);
return { success: false, itemId, status, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 중간검사 성적서 데이터 조회 =====
export interface InspectionReportItem {
id: number;
item_name: string;
specification: string;
quantity: number;
sort_order: number;
status: string;
options: Record<string, unknown> | null;
inspection_data: Record<string, unknown> | null;
}
export interface InspectionReportNodeGroup {
node_id: number | null;
node_name: string;
floor: string;
code: string;
width: number;
height: number;
total_quantity: number;
options: Record<string, unknown>;
items: InspectionReportItem[];
}
export interface InspectionReportData {
work_order: {
id: number;
order_no: string;
status: string;
planned_date: string | null;
due_date: string | null;
};
order: {
id: number;
order_no: string;
client_name: string | null;
site_name: string | null;
order_date: string | null;
} | null;
node_groups?: InspectionReportNodeGroup[];
items: InspectionReportItem[];
summary: {
total_items: number;
inspected_items: number;
passed_items: number;
failed_items: number;
};
}
export async function getInspectionReport(
workOrderId: string
): Promise<{ success: boolean; data?: InspectionReportData; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-report`),
{ method: 'GET' }
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '검사 성적서 데이터를 불러올 수 없습니다.',
};
}
return { success: true, data: result.data };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getInspectionReport error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 중간검사 데이터 저장 (deprecated: WorkerScreen/actions.ts의 saveItemInspection 사용) =====
/** @deprecated WorkerScreen/actions.ts의 saveItemInspection을 사용하세요 */
export async function saveInspectionData(
workOrderId: string,
processType: string,
data: unknown
): Promise<{ success: boolean; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection`),
{
method: 'POST',
body: JSON.stringify({
process_type: processType,
inspection_data: data,
}),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '검사 데이터 저장에 실패했습니다.',
};
}
return { success: true };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] saveInspectionData error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 검사 문서 템플릿 조회 (document_template 기반) =====
import type { InspectionTemplateData } from '@/components/production/WorkerScreen/types';
export async function getInspectionTemplate(
workOrderId: string
): Promise<{ success: boolean; data?: InspectionTemplateData; error?: string }> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-template`),
{ method: 'GET' }
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return { success: false, error: result.message || '검사 템플릿을 불러올 수 없습니다.' };
}
return { success: true, data: result.data };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getInspectionTemplate error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 검사 문서 저장 (Document + DocumentData) =====
export async function saveInspectionDocument(
workOrderId: string,
data: {
step_id?: number;
title?: string;
data: Record<string, unknown>[];
approvers?: { role_name: string; user_id?: number }[];
}
): Promise<{
success: boolean;
data?: { document_id: number; document_no: string; status: string };
error?: string;
}> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-document`),
{
method: 'POST',
body: JSON.stringify(data),
}
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return { success: false, error: result.message || '검사 문서 저장에 실패했습니다.' };
}
return { success: true, data: result.data };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] saveInspectionDocument error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 검사 문서 resolve (기존 문서/템플릿 조회) =====
export async function resolveInspectionDocument(
workOrderId: string,
params?: { step_id?: number }
): Promise<{
success: boolean;
data?: { mode: 'existing' | 'new'; document?: Record<string, unknown>; template?: Record<string, unknown> };
error?: string;
}> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-resolve`, { step_id: params?.step_id }),
{ method: 'GET' }
);
if (error || !response) {
return { success: false, error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return { success: false, error: result.message || '검사 문서 조회에 실패했습니다.' };
}
return { success: true, data: result.data };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] resolveInspectionDocument error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 수주 목록 조회 (작업지시 생성용) =====
export interface SalesOrderForWorkOrder {
id: number;
orderNo: string;
client: string;
projectName: string;
dueDate: string;
status: string;
itemCount: number;
splitCount: number;
}
export async function getSalesOrdersForWorkOrder(params?: {
q?: string;
status?: string;
}): Promise<{
success: boolean;
data: SalesOrderForWorkOrder[];
error?: string;
}> {
try {
// 작업지시 생성 가능한 상태만 조회 (예: 회계확인 완료)
const url = buildApiUrl('/api/v1/orders', {
for_work_order: '1',
q: params?.q,
status: params?.status,
});
const { response, error } = await serverFetch(url, { method: 'GET' });
if (error || !response) {
return { success: false, data: [], error: error?.message || 'API 요청 실패' };
}
if (!response.ok) {
console.warn('[WorkOrderActions] GET orders error:', response.status);
return { success: false, data: [], error: `API 오류: ${response.status}` };
}
const result = await response.json();
if (!result.success) {
return {
success: false,
data: [],
error: result.message || '수주 목록 조회에 실패했습니다.',
};
}
// API 응답 변환
const salesOrders: SalesOrderForWorkOrder[] = (result.data?.data || result.data || []).map(
(item: {
id: number;
order_no: string;
client?: { name: string };
project_name?: string;
due_date?: string;
status: string;
items_count?: number;
split_count?: number;
}) => ({
id: item.id,
orderNo: item.order_no,
client: item.client?.name || '-',
projectName: item.project_name || '-',
dueDate: item.due_date || '-',
status: item.status,
itemCount: item.items_count || 0,
splitCount: item.split_count || 0,
})
);
return {
success: true,
data: salesOrders,
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getSalesOrdersForWorkOrder error:', error);
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
}
}
// ===== 부서 + 사용자 조회 (담당자 선택용) =====
export interface DepartmentUser {
id: number;
name: string;
email: string;
}
export interface DepartmentWithUsers {
id: number;
name: string;
code: string | null;
users: DepartmentUser[];
children: DepartmentWithUsers[];
}
export async function getDepartmentsWithUsers(): Promise<{
success: boolean;
data: DepartmentWithUsers[];
error?: string;
}> {
try {
const url = buildApiUrl('/api/v1/departments/tree', { with_users: 1 });
const { response, error } = await serverFetch(url, { method: 'GET' });
if (error || !response) {
return { success: false, data: [], error: error?.message || 'API 요청 실패' };
}
if (!response.ok) {
console.warn('[WorkOrderActions] GET departments error:', response.status);
return { success: false, data: [], error: `API 오류: ${response.status}` };
}
const result = await response.json();
if (!result.success) {
return {
success: false,
data: [],
error: result.message || '부서 목록 조회에 실패했습니다.',
};
}
// API 응답을 프론트엔드 형식으로 변환
const transformDepartment = (dept: {
id: number;
name: string;
code: string | null;
users?: { id: number; name: string; email: string }[];
children?: unknown[];
}): DepartmentWithUsers => ({
id: dept.id,
name: dept.name,
code: dept.code,
users: (dept.users || []).map((u) => ({
id: u.id,
name: u.name,
email: u.email,
})),
children: (dept.children || []).map((child) =>
transformDepartment(child as typeof dept)
),
});
const departments = (result.data || []).map(transformDepartment);
return {
success: true,
data: departments,
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getDepartmentsWithUsers error:', error);
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
}
}
// ===== 공정 목록 조회 (작업지시 생성용) =====
export interface ProcessOption {
id: number;
processCode: string;
processName: string;
}
export async function getProcessOptions(): Promise<{
success: boolean;
data: ProcessOption[];
error?: string;
}> {
try {
const url = buildApiUrl('/api/v1/processes/options');
const { response, error } = await serverFetch(url, { method: 'GET' });
if (error || !response) {
return { success: false, data: [], error: error?.message || 'API 요청 실패' };
}
if (!response.ok) {
console.warn('[WorkOrderActions] GET process options error:', response.status);
return { success: false, data: [], error: `API 오류: ${response.status}` };
}
const result = await response.json();
if (!result.success) {
return {
success: false,
data: [],
error: result.message || '공정 목록 조회에 실패했습니다.',
};
}
// API 응답 변환
const processes: ProcessOption[] = (result.data || []).map(
(item: {
id: number;
process_code: string;
process_name: string;
}) => ({
id: item.id,
processCode: item.process_code,
processName: item.process_name,
})
);
return {
success: true,
data: processes,
};
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getProcessOptions error:', error);
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
}
}
// ===== 자재 투입 LOT 번호 조회 =====
export interface MaterialInputLot {
lot_no: string;
item_code: string;
item_name: string;
total_qty: number;
input_count: number;
first_input_at: string;
}
export async function getMaterialInputLots(workOrderId: string): Promise<{
success: boolean;
data: MaterialInputLot[];
error?: string;
}> {
try {
const { response, error } = await serverFetch(
buildApiUrl(`/api/v1/work-orders/${workOrderId}/material-input-lots`),
{ method: 'GET' }
);
if (error || !response) {
return { success: false, data: [], error: error?.message || 'API 요청 실패' };
}
const result = await response.json();
if (!response.ok || !result.success) {
return { success: false, data: [], error: result.message || '투입 LOT 조회에 실패했습니다.' };
}
return { success: true, data: result.data || [] };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[WorkOrderActions] getMaterialInputLots error:', error);
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
}
}