Files
sam-react-prod/src/components/material/ReceivingManagement/actions.ts
유병철 1f640622e0 feat(WEB): 자재/영업/품질 모듈 기능 개선 및 문서 컴포넌트 추가
- 입고관리: 상세/목록 UI 개선, actions 로직 강화
- 재고현황: 상세/목록 개선, StockAuditModal 신규 추가
- 영업주문관리: 페이지 구조 개선, OrderSalesDetailEdit 기능 강화
- 주문: OrderRegistration 개선, SalesOrderDocument 신규 추가
- 견적: QuoteTransactionModal 기능 개선
- 품질: InspectionModalV2, ImportInspectionDocument 대폭 개선
- UniversalListPage: 템플릿 기능 확장

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 21:15:25 +09:00

970 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 입고 관리 서버 액션
*
* API Endpoints:
* - GET /api/v1/receivings - 목록 조회
* - GET /api/v1/receivings/stats - 통계 조회
* - GET /api/v1/receivings/{id} - 상세 조회
* - POST /api/v1/receivings - 등록
* - PUT /api/v1/receivings/{id} - 수정
* - DELETE /api/v1/receivings/{id} - 삭제
* - POST /api/v1/receivings/{id}/process - 입고처리
*/
'use server';
// ===== 목데이터 모드 플래그 =====
const USE_MOCK_DATA = true;
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type {
ReceivingItem,
ReceivingDetail,
ReceivingStats,
ReceivingStatus,
ReceivingProcessFormData,
} from './types';
// ===== 목데이터 =====
const MOCK_RECEIVING_LIST: ReceivingItem[] = [
{
id: '1',
lotNo: 'LOT-2026-001',
inspectionStatus: '적',
inspectionDate: '2026-01-25',
supplier: '(주)대한철강',
itemCode: 'STEEL-001',
itemName: 'SUS304 스테인리스 판재',
specification: '1000x2000x3T',
unit: 'EA',
receivingQty: 100,
receivingDate: '2026-01-26',
createdBy: '김철수',
status: 'completed',
},
{
id: '2',
lotNo: 'LOT-2026-002',
inspectionStatus: '적',
inspectionDate: '2026-01-26',
supplier: '삼성전자부품',
itemCode: 'ELEC-002',
itemName: 'MCU 컨트롤러 IC',
specification: 'STM32F103C8T6',
unit: 'EA',
receivingQty: 500,
receivingDate: '2026-01-27',
createdBy: '이영희',
status: 'completed',
},
{
id: '3',
lotNo: 'LOT-2026-003',
inspectionStatus: '-',
inspectionDate: undefined,
supplier: '한국플라스틱',
itemCode: 'PLAS-003',
itemName: 'ABS 사출 케이스',
specification: '150x100x50',
unit: 'SET',
receivingQty: undefined,
receivingDate: undefined,
createdBy: '박민수',
status: 'receiving_pending',
},
{
id: '4',
lotNo: 'LOT-2026-004',
inspectionStatus: '부적',
inspectionDate: '2026-01-27',
supplier: '(주)대한철강',
itemCode: 'STEEL-002',
itemName: '알루미늄 프로파일',
specification: '40x40x2000L',
unit: 'EA',
receivingQty: 50,
receivingDate: '2026-01-28',
createdBy: '김철수',
status: 'inspection_pending',
},
{
id: '5',
lotNo: 'LOT-2026-005',
inspectionStatus: '-',
inspectionDate: undefined,
supplier: '글로벌전자',
itemCode: 'ELEC-005',
itemName: 'DC 모터 24V',
specification: '24V 100RPM',
unit: 'EA',
receivingQty: undefined,
receivingDate: undefined,
createdBy: '최지훈',
status: 'receiving_pending',
},
{
id: '6',
lotNo: 'LOT-2026-006',
inspectionStatus: '적',
inspectionDate: '2026-01-24',
supplier: '동양화학',
itemCode: 'CHEM-001',
itemName: '에폭시 접착제',
specification: '500ml',
unit: 'EA',
receivingQty: 200,
receivingDate: '2026-01-25',
createdBy: '이영희',
status: 'completed',
},
{
id: '7',
lotNo: 'LOT-2026-007',
inspectionStatus: '적',
inspectionDate: '2026-01-28',
supplier: '삼성전자부품',
itemCode: 'ELEC-007',
itemName: '커패시터 100uF',
specification: '100uF 50V',
unit: 'EA',
receivingQty: 1000,
receivingDate: '2026-01-28',
createdBy: '박민수',
status: 'completed',
},
{
id: '8',
lotNo: 'LOT-2026-008',
inspectionStatus: '-',
inspectionDate: undefined,
supplier: '한국볼트',
itemCode: 'BOLT-001',
itemName: 'SUS 볼트 M8x30',
specification: 'M8x30 SUS304',
unit: 'EA',
receivingQty: undefined,
receivingDate: undefined,
createdBy: '김철수',
status: 'receiving_pending',
},
];
const MOCK_RECEIVING_STATS: ReceivingStats = {
receivingPendingCount: 3,
receivingCompletedCount: 4,
inspectionPendingCount: 1,
inspectionCompletedCount: 5,
};
// 기획서 2026-01-28 기준 상세 목데이터
const MOCK_RECEIVING_DETAIL: Record<string, ReceivingDetail> = {
'1': {
id: '1',
// 기본 정보
lotNo: 'LOT-2026-001',
itemCode: 'STEEL-001',
itemName: 'SUS304 스테인리스 판재',
specification: '1000x2000x3T',
unit: 'EA',
supplier: '(주)대한철강',
receivingQty: 100,
receivingDate: '2026-01-26',
createdBy: '김철수',
status: 'completed',
remark: '',
// 수입검사 정보
inspectionDate: '2026-01-25',
inspectionResult: '합격',
certificateFile: undefined,
// 하위 호환
orderNo: 'PO-2026-001',
orderUnit: 'EA',
},
'2': {
id: '2',
lotNo: 'LOT-2026-002',
itemCode: 'ELEC-002',
itemName: 'MCU 컨트롤러 IC',
specification: 'STM32F103C8T6',
unit: 'EA',
supplier: '삼성전자부품',
receivingQty: 500,
receivingDate: '2026-01-27',
createdBy: '이영희',
status: 'completed',
remark: '긴급 입고',
inspectionDate: '2026-01-26',
inspectionResult: '합격',
orderNo: 'PO-2026-002',
orderUnit: 'EA',
},
'3': {
id: '3',
lotNo: 'LOT-2026-003',
itemCode: 'PLAS-003',
itemName: 'ABS 사출 케이스',
specification: '150x100x50',
unit: 'SET',
supplier: '한국플라스틱',
receivingQty: undefined,
receivingDate: undefined,
createdBy: '박민수',
status: 'receiving_pending',
remark: '',
inspectionDate: undefined,
inspectionResult: undefined,
orderNo: 'PO-2026-003',
orderUnit: 'SET',
},
'4': {
id: '4',
lotNo: 'LOT-2026-004',
itemCode: 'STEEL-002',
itemName: '알루미늄 프로파일',
specification: '40x40x2000L',
unit: 'EA',
supplier: '(주)대한철강',
receivingQty: 50,
receivingDate: '2026-01-28',
createdBy: '김철수',
status: 'inspection_pending',
remark: '검사 진행 중',
inspectionDate: '2026-01-27',
inspectionResult: '불합격',
orderNo: 'PO-2026-004',
orderUnit: 'EA',
},
'5': {
id: '5',
lotNo: 'LOT-2026-005',
itemCode: 'ELEC-005',
itemName: 'DC 모터 24V',
specification: '24V 100RPM',
unit: 'EA',
supplier: '글로벌전자',
receivingQty: undefined,
receivingDate: undefined,
createdBy: '최지훈',
status: 'receiving_pending',
remark: '',
inspectionDate: undefined,
inspectionResult: undefined,
orderNo: 'PO-2026-005',
orderUnit: 'EA',
},
};
// ===== API 데이터 타입 =====
interface ReceivingApiData {
id: number;
receiving_number: string;
order_no?: string;
order_date?: string;
item_id?: number;
item_code: string;
item_name: string;
specification?: string;
supplier: string;
order_qty: string | number;
order_unit: string;
due_date?: string;
receiving_qty?: string | number;
receiving_date?: string;
lot_no?: string;
supplier_lot?: string;
receiving_location?: string;
receiving_manager?: string;
status: ReceivingStatus;
remark?: string;
creator?: { id: number; name: string };
created_at?: string;
updated_at?: string;
}
interface ReceivingApiPaginatedResponse {
data: ReceivingApiData[];
current_page: number;
last_page: number;
per_page: number;
total: number;
}
interface ReceivingApiStatsResponse {
receiving_pending_count: number;
shipping_count: number;
inspection_pending_count: number;
today_receiving_count: number;
}
// ===== API → Frontend 변환 (목록용) =====
function transformApiToListItem(data: ReceivingApiData): ReceivingItem {
return {
id: String(data.id),
orderNo: data.order_no || data.receiving_number,
itemCode: data.item_code,
itemName: data.item_name,
supplier: data.supplier,
orderQty: parseFloat(String(data.order_qty)) || 0,
orderUnit: data.order_unit || 'EA',
receivingQty: data.receiving_qty ? parseFloat(String(data.receiving_qty)) : undefined,
lotNo: data.lot_no,
status: data.status,
};
}
// ===== API → Frontend 변환 (상세용) =====
function transformApiToDetail(data: ReceivingApiData): ReceivingDetail {
return {
id: String(data.id),
orderNo: data.order_no || data.receiving_number,
orderDate: data.order_date,
supplier: data.supplier,
itemCode: data.item_code,
itemName: data.item_name,
specification: data.specification,
orderQty: parseFloat(String(data.order_qty)) || 0,
orderUnit: data.order_unit || 'EA',
dueDate: data.due_date,
status: data.status,
receivingDate: data.receiving_date,
receivingQty: data.receiving_qty ? parseFloat(String(data.receiving_qty)) : undefined,
receivingLot: data.lot_no,
supplierLot: data.supplier_lot,
receivingLocation: data.receiving_location,
receivingManager: data.receiving_manager,
};
}
// ===== API → Frontend 변환 (통계용) =====
function transformApiToStats(data: ReceivingApiStatsResponse): ReceivingStats {
return {
receivingPendingCount: data.receiving_pending_count,
shippingCount: data.shipping_count,
inspectionPendingCount: data.inspection_pending_count,
todayReceivingCount: data.today_receiving_count,
};
}
// ===== Frontend → API 변환 (등록/수정용) =====
function transformFrontendToApi(
data: Partial<ReceivingDetail>
): Record<string, unknown> {
const result: Record<string, unknown> = {};
if (data.orderNo !== undefined) result.order_no = data.orderNo;
if (data.orderDate !== undefined) result.order_date = data.orderDate;
if (data.itemCode !== undefined) result.item_code = data.itemCode;
if (data.itemName !== undefined) result.item_name = data.itemName;
if (data.specification !== undefined) result.specification = data.specification;
if (data.supplier !== undefined) result.supplier = data.supplier;
if (data.orderQty !== undefined) result.order_qty = data.orderQty;
if (data.orderUnit !== undefined) result.order_unit = data.orderUnit;
if (data.dueDate !== undefined) result.due_date = data.dueDate;
if (data.status !== undefined) result.status = data.status;
return result;
}
// ===== Frontend → API 변환 (입고처리용) =====
function transformProcessDataToApi(
data: ReceivingProcessFormData
): Record<string, unknown> {
return {
receiving_qty: data.receivingQty,
lot_no: data.receivingLot,
supplier_lot: data.supplierLot,
receiving_location: data.receivingLocation,
remark: data.remark,
};
}
// ===== 페이지네이션 타입 =====
interface PaginationMeta {
currentPage: number;
lastPage: number;
perPage: number;
total: number;
}
// ===== 입고 목록 조회 =====
export async function getReceivings(params?: {
page?: number;
perPage?: number;
startDate?: string;
endDate?: string;
status?: string;
search?: string;
}): Promise<{
success: boolean;
data: ReceivingItem[];
pagination: PaginationMeta;
error?: string;
__authError?: boolean;
}> {
// ===== 목데이터 모드 =====
if (USE_MOCK_DATA) {
let filteredData = [...MOCK_RECEIVING_LIST];
// 상태 필터
if (params?.status && params.status !== 'all') {
filteredData = filteredData.filter(item => item.status === params.status);
}
// 검색 필터
if (params?.search) {
const search = params.search.toLowerCase();
filteredData = filteredData.filter(
item =>
item.lotNo?.toLowerCase().includes(search) ||
item.itemCode.toLowerCase().includes(search) ||
item.itemName.toLowerCase().includes(search) ||
item.supplier.toLowerCase().includes(search)
);
}
const page = params?.page || 1;
const perPage = params?.perPage || 20;
const total = filteredData.length;
const lastPage = Math.ceil(total / perPage);
const startIndex = (page - 1) * perPage;
const paginatedData = filteredData.slice(startIndex, startIndex + perPage);
return {
success: true,
data: paginatedData,
pagination: {
currentPage: page,
lastPage,
perPage,
total,
},
};
}
try {
const searchParams = new URLSearchParams();
if (params?.page) searchParams.set('page', String(params.page));
if (params?.perPage) searchParams.set('per_page', String(params.perPage));
if (params?.startDate) searchParams.set('start_date', params.startDate);
if (params?.endDate) searchParams.set('end_date', params.endDate);
if (params?.status && params.status !== 'all') {
searchParams.set('status', params.status);
}
if (params?.search) searchParams.set('search', params.search);
const queryString = searchParams.toString();
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings${queryString ? `?${queryString}` : ''}`;
const { response, error } = await serverFetch(url, {
method: 'GET',
cache: 'no-store',
});
if (error) {
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: error.message,
__authError: error.code === 'UNAUTHORIZED',
};
}
if (!response) {
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: '입고 목록 조회에 실패했습니다.',
};
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: result.message || '입고 목록 조회에 실패했습니다.',
};
}
const paginatedData: ReceivingApiPaginatedResponse = result.data || {
data: [],
current_page: 1,
last_page: 1,
per_page: 20,
total: 0,
};
const receivings = (paginatedData.data || []).map(transformApiToListItem);
return {
success: true,
data: receivings,
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('[ReceivingActions] getReceivings error:', error);
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: '서버 오류가 발생했습니다.',
};
}
}
// ===== 입고 통계 조회 =====
export async function getReceivingStats(): Promise<{
success: boolean;
data?: ReceivingStats;
error?: string;
__authError?: boolean;
}> {
// ===== 목데이터 모드 =====
if (USE_MOCK_DATA) {
return { success: true, data: MOCK_RECEIVING_STATS };
}
try {
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/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 = await response.json();
if (!response.ok || !result.success || !result.data) {
return { success: false, error: result.message || '입고 통계 조회에 실패했습니다.' };
}
return { success: true, data: transformApiToStats(result.data) };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[ReceivingActions] getReceivingStats error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 입고 상세 조회 =====
export async function getReceivingById(id: string): Promise<{
success: boolean;
data?: ReceivingDetail;
error?: string;
__authError?: boolean;
}> {
// ===== 목데이터 모드 =====
if (USE_MOCK_DATA) {
const detail = MOCK_RECEIVING_DETAIL[id];
if (detail) {
return { success: true, data: detail };
}
return { success: false, error: '입고 정보를 찾을 수 없습니다.' };
}
try {
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${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 = await response.json();
if (!response.ok || !result.success || !result.data) {
return { success: false, error: result.message || '입고 조회에 실패했습니다.' };
}
return { success: true, data: transformApiToDetail(result.data) };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[ReceivingActions] getReceivingById error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 입고 등록 =====
export async function createReceiving(
data: Partial<ReceivingDetail>
): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> {
try {
const apiData = transformFrontendToApi(data);
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings`,
{ 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 = await response.json();
if (!response.ok || !result.success) {
return { success: false, error: result.message || '입고 등록에 실패했습니다.' };
}
return { success: true, data: transformApiToDetail(result.data) };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[ReceivingActions] createReceiving error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 입고 수정 =====
export async function updateReceiving(
id: string,
data: Partial<ReceivingDetail>
): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> {
try {
const apiData = transformFrontendToApi(data);
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${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 = await response.json();
if (!response.ok || !result.success) {
return { success: false, error: result.message || '입고 수정에 실패했습니다.' };
}
return { success: true, data: transformApiToDetail(result.data) };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[ReceivingActions] updateReceiving error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 입고 삭제 =====
export async function deleteReceiving(
id: string
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
try {
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${id}`,
{ method: 'DELETE' }
);
if (error) {
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
}
if (!response) {
return { success: false, error: '입고 삭제에 실패했습니다.' };
}
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('[ReceivingActions] deleteReceiving error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 입고처리 =====
export async function processReceiving(
id: string,
data: ReceivingProcessFormData
): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> {
try {
const apiData = transformProcessDataToApi(data);
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${id}/process`,
{ 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 = await response.json();
if (!response.ok || !result.success) {
return { success: false, error: result.message || '입고처리에 실패했습니다.' };
}
return { success: true, data: transformApiToDetail(result.data) };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[ReceivingActions] processReceiving error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 수입검사 템플릿 타입 (ImportInspectionDocument와 동일) =====
export interface InspectionTemplateResponse {
templateId: string;
templateName: string;
headerInfo: {
productName: string;
specification: string;
materialNo: string;
lotSize: number;
supplier: string;
lotNo: string;
inspectionDate: string;
inspector: string;
reportDate: string;
approvers: {
writer?: string;
reviewer?: string;
approver?: string;
};
};
inspectionItems: Array<{
id: string;
no: number;
name: string;
subName?: string;
parentId?: string;
standard: {
description?: string;
value?: string | number;
options?: Array<{
id: string;
label: string;
tolerance: string;
isSelected: boolean;
}>;
};
inspectionMethod: string;
inspectionCycle: string;
measurementType: 'okng' | 'numeric' | 'both';
measurementCount: number;
rowSpan?: number;
isSubRow?: boolean;
}>;
notes?: string[];
}
// ===== 수입검사 템플릿 조회 (품목명/규격 기반) =====
export async function getInspectionTemplate(params: {
itemName: string;
specification: string;
lotNo?: string;
supplier?: string;
}): Promise<{
success: boolean;
data?: InspectionTemplateResponse;
error?: string;
__authError?: boolean;
}> {
// ===== 목데이터 모드 - EGI 강판 템플릿 반환 =====
if (USE_MOCK_DATA) {
// 품목명/규격에 따라 다른 템플릿 반환 (추후 24종 확장)
const mockTemplate: InspectionTemplateResponse = {
templateId: 'EGI-001',
templateName: '전기 아연도금 강판',
headerInfo: {
productName: params.itemName || '전기 아연도금 강판 (KS D 3528, SECC) "EGI 평국판"',
specification: params.specification || '1.55 * 1218 × 480',
materialNo: 'PE02RB',
lotSize: 200,
supplier: params.supplier || '지오TNS (KG스틸)',
lotNo: params.lotNo || '250715-02',
inspectionDate: new Date().toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit' }).replace('. ', '/').replace('.', ''),
inspector: '노원호',
reportDate: new Date().toISOString().split('T')[0],
approvers: {
writer: '노원호',
reviewer: '',
approver: '',
},
},
inspectionItems: [
{
id: 'appearance',
no: 1,
name: '겉모양',
standard: { description: '사용상 해로운 결함이 없을 것' },
inspectionMethod: '육안검사',
inspectionCycle: '',
measurementType: 'okng',
measurementCount: 3,
},
{
id: 'thickness',
no: 2,
name: '치수',
subName: '두께',
standard: {
value: 1.55,
options: [
{ id: 't1', label: '0.8 이상 ~ 1.0 미만', tolerance: '± 0.07', isSelected: false },
{ id: 't2', label: '1.0 이상 ~ 1.25 미만', tolerance: '± 0.08', isSelected: false },
{ id: 't3', label: '1.25 이상 ~ 1.6 미만', tolerance: '± 0.10', isSelected: true },
{ id: 't4', label: '1.6 이상 ~ 2.0 미만', tolerance: '± 0.12', isSelected: false },
],
},
inspectionMethod: 'n = 3\nc = 0',
inspectionCycle: '체크검사',
measurementType: 'numeric',
measurementCount: 3,
rowSpan: 3,
},
{
id: 'width',
no: 2,
name: '치수',
subName: '너비',
parentId: 'thickness',
standard: {
value: 1219,
options: [{ id: 'w1', label: '1250 미만', tolerance: '+ 7\n- 0', isSelected: true }],
},
inspectionMethod: '',
inspectionCycle: '',
measurementType: 'numeric',
measurementCount: 3,
isSubRow: true,
},
{
id: 'length',
no: 2,
name: '치수',
subName: '길이',
parentId: 'thickness',
standard: {
value: 480,
options: [{ id: 'l1', label: '2000 이상 ~ 4000 미만', tolerance: '+ 15\n- 0', isSelected: true }],
},
inspectionMethod: '',
inspectionCycle: '',
measurementType: 'numeric',
measurementCount: 3,
isSubRow: true,
},
{
id: 'tensileStrength',
no: 3,
name: '인장강도 (N/㎟)',
standard: { description: '270 이상' },
inspectionMethod: '',
inspectionCycle: '',
measurementType: 'numeric',
measurementCount: 1,
},
{
id: 'elongation',
no: 4,
name: '연신율 %',
standard: {
options: [
{ id: 'e1', label: '두께 0.6 이상 ~ 1.0 미만', tolerance: '36 이상', isSelected: false },
{ id: 'e2', label: '두께 1.0 이상 ~ 1.6 미만', tolerance: '37 이상', isSelected: true },
{ id: 'e3', label: '두께 1.6 이상 ~ 2.3 미만', tolerance: '38 이상', isSelected: false },
],
},
inspectionMethod: '공급업체\n밀시트',
inspectionCycle: '입고시',
measurementType: 'numeric',
measurementCount: 1,
},
{
id: 'zincCoating',
no: 5,
name: '아연의 최소 부착량 (g/㎡)',
standard: { description: '편면 17 이상' },
inspectionMethod: '',
inspectionCycle: '',
measurementType: 'numeric',
measurementCount: 2,
},
],
notes: [
'※ 1.55mm의 경우 KS F 4510에 따른 MIN 1.5의 기준에 따름',
'※ 두께의 경우 너비 1000 이상 ~ 1250 미만 기준에 따름',
],
};
return { success: true, data: mockTemplate };
}
// ===== 실제 API 호출 =====
try {
const searchParams = new URLSearchParams();
searchParams.set('item_name', params.itemName);
searchParams.set('specification', params.specification);
if (params.lotNo) searchParams.set('lot_no', params.lotNo);
if (params.supplier) searchParams.set('supplier', params.supplier);
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/inspection-templates?${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 = await response.json();
if (!response.ok || !result.success || !result.data) {
return { success: false, error: result.message || '검사 템플릿 조회에 실패했습니다.' };
}
return { success: true, data: result.data };
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[ReceivingActions] getInspectionTemplate error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}