Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -17,8 +17,12 @@
|
||||
const USE_MOCK_DATA = false;
|
||||
|
||||
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { executeServerAction } from '@/lib/api/execute-server-action';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
import type {
|
||||
ReceivingItem,
|
||||
ReceivingDetail,
|
||||
@@ -577,86 +581,29 @@ export async function getReceivings(params?: {
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
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);
|
||||
|
||||
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 emptyPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
const result = await executeServerAction<ReceivingApiPaginatedResponse>({
|
||||
url: `${API_URL}/api/v1/receivings${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '입고 목록 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, data: [], pagination: emptyPagination, __authError: true };
|
||||
if (!result.success || !result.data) return { success: false, data: [], pagination: emptyPagination, error: result.error };
|
||||
|
||||
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: '서버 오류가 발생했습니다.',
|
||||
};
|
||||
}
|
||||
const pd = result.data;
|
||||
return {
|
||||
success: true,
|
||||
data: (pd.data || []).map(transformApiToListItem),
|
||||
pagination: { currentPage: pd.current_page, lastPage: pd.last_page, perPage: pd.per_page, total: pd.total },
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 입고 통계 조회 =====
|
||||
@@ -666,37 +613,15 @@ export async function getReceivingStats(): Promise<{
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
// ===== 목데이터 모드 =====
|
||||
if (USE_MOCK_DATA) {
|
||||
return { success: true, data: MOCK_RECEIVING_STATS };
|
||||
}
|
||||
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: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/stats`,
|
||||
transform: (data: ReceivingApiStatsResponse) => transformApiToStats(data),
|
||||
errorMessage: '입고 통계 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 입고 상세 조회 =====
|
||||
@@ -706,75 +631,34 @@ export async function getReceivingById(id: string): Promise<{
|
||||
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: '입고 정보를 찾을 수 없습니다.' };
|
||||
return detail ? { success: true, data: detail } : { 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: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}`,
|
||||
transform: (data: ReceivingApiData) => transformApiToDetail(data),
|
||||
errorMessage: '입고 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.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: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const apiData = transformFrontendToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings`,
|
||||
method: 'POST',
|
||||
body: apiData,
|
||||
transform: (d: ReceivingApiData) => transformApiToDetail(d),
|
||||
errorMessage: '입고 등록에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 입고 수정 =====
|
||||
@@ -782,66 +666,29 @@ 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: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const apiData = transformFrontendToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}`,
|
||||
method: 'PUT',
|
||||
body: apiData,
|
||||
transform: (d: ReceivingApiData) => transformApiToDetail(d),
|
||||
errorMessage: '입고 수정에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.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: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}`,
|
||||
method: 'DELETE',
|
||||
errorMessage: '입고 삭제에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 입고처리 =====
|
||||
@@ -849,34 +696,16 @@ 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: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const apiData = transformProcessDataToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}/process`,
|
||||
method: 'POST',
|
||||
body: apiData,
|
||||
transform: (d: ReceivingApiData) => transformApiToDetail(d),
|
||||
errorMessage: '입고처리에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 품목 검색 (입고 등록용) =====
|
||||
@@ -915,39 +744,24 @@ export async function searchItems(query?: string): Promise<{
|
||||
return { success: true, data: filtered };
|
||||
}
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (query) searchParams.set('search', query);
|
||||
searchParams.set('per_page', '50');
|
||||
const searchParams = new URLSearchParams();
|
||||
if (query) searchParams.set('search', query);
|
||||
searchParams.set('per_page', '50');
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/items?${searchParams.toString()}`,
|
||||
{ method: 'GET', cache: 'no-store' }
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
return { success: false, data: [] };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, data: [] };
|
||||
}
|
||||
|
||||
const items: ItemOption[] = (result.data?.data || []).map((item: Record<string, string>) => ({
|
||||
interface ItemApiData { data: Array<Record<string, string>> }
|
||||
const result = await executeServerAction<ItemApiData, ItemOption[]>({
|
||||
url: `${API_URL}/api/v1/items?${searchParams.toString()}`,
|
||||
transform: (d) => (d.data || []).map((item) => ({
|
||||
value: item.item_code,
|
||||
label: item.item_code,
|
||||
description: `${item.item_name} (${item.specification || '-'})`,
|
||||
itemName: item.item_name,
|
||||
specification: item.specification || '',
|
||||
unit: item.unit || 'EA',
|
||||
}));
|
||||
|
||||
return { success: true, data: items };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
return { success: false, data: [] };
|
||||
}
|
||||
})),
|
||||
errorMessage: '품목 검색에 실패했습니다.',
|
||||
});
|
||||
return { success: result.success, data: result.data || [] };
|
||||
}
|
||||
|
||||
// ===== 발주처 검색 (입고 등록용) =====
|
||||
@@ -977,35 +791,20 @@ export async function searchSuppliers(query?: string): Promise<{
|
||||
return { success: true, data: filtered };
|
||||
}
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (query) searchParams.set('search', query);
|
||||
searchParams.set('per_page', '50');
|
||||
const searchParams = new URLSearchParams();
|
||||
if (query) searchParams.set('search', query);
|
||||
searchParams.set('per_page', '50');
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/suppliers?${searchParams.toString()}`,
|
||||
{ method: 'GET', cache: 'no-store' }
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
return { success: false, data: [] };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, data: [] };
|
||||
}
|
||||
|
||||
const suppliers: SupplierOption[] = (result.data?.data || []).map((s: Record<string, string>) => ({
|
||||
interface SupplierApiData { data: Array<Record<string, string>> }
|
||||
const result = await executeServerAction<SupplierApiData, SupplierOption[]>({
|
||||
url: `${API_URL}/api/v1/suppliers?${searchParams.toString()}`,
|
||||
transform: (d) => (d.data || []).map((s) => ({
|
||||
value: s.name,
|
||||
label: s.name,
|
||||
}));
|
||||
|
||||
return { success: true, data: suppliers };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
return { success: false, data: [] };
|
||||
}
|
||||
})),
|
||||
errorMessage: '발주처 검색에 실패했습니다.',
|
||||
});
|
||||
return { success: result.success, data: result.data || [] };
|
||||
}
|
||||
|
||||
// ===== 수입검사 템플릿 타입 (ImportInspectionDocument와 동일) =====
|
||||
@@ -1251,7 +1050,7 @@ export async function checkInspectionTemplate(itemId?: number): Promise<{
|
||||
searchParams.append('category', 'incoming_inspection');
|
||||
searchParams.append('item_id', String(itemId));
|
||||
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/documents/resolve?${searchParams.toString()}`;
|
||||
const url = `${API_URL}/api/v1/documents/resolve?${searchParams.toString()}`;
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
@@ -1446,45 +1245,20 @@ export async function getInspectionTemplate(params: {
|
||||
return { success: false, error: '품목 ID가 필요합니다.' };
|
||||
}
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('category', 'incoming_inspection');
|
||||
searchParams.set('item_id', String(params.itemId));
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('category', 'incoming_inspection');
|
||||
searchParams.set('item_id', String(params.itemId));
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/documents/resolve?${searchParams.toString()}`,
|
||||
{ method: 'GET', cache: 'no-store' }
|
||||
);
|
||||
const result = await executeServerAction<DocumentResolveResponse>({
|
||||
url: `${API_URL}/api/v1/documents/resolve?${searchParams.toString()}`,
|
||||
errorMessage: '검사 템플릿 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
if (!result.success || !result.data) return { success: false, error: result.error };
|
||||
|
||||
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 || '검사 템플릿 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const resolveData: DocumentResolveResponse = result.data;
|
||||
|
||||
// API 응답을 기존 InspectionTemplateResponse 형식으로 변환
|
||||
const template = transformResolveToTemplate(resolveData, params);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: template,
|
||||
resolveData,
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] getInspectionTemplate error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const resolveData = result.data;
|
||||
const template = transformResolveToTemplate(resolveData, params);
|
||||
return { success: true, data: template, resolveData };
|
||||
}
|
||||
|
||||
// ===== Tolerance 타입 정의 =====
|
||||
@@ -2091,7 +1865,7 @@ export async function uploadInspectionFiles(files: File[]): Promise<{
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/files/upload`,
|
||||
`${API_URL}/api/v1/files/upload`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -2112,7 +1886,7 @@ export async function uploadInspectionFiles(files: File[]): Promise<{
|
||||
uploadedFiles.push({
|
||||
id: result.data.id,
|
||||
name: result.data.display_name || file.name,
|
||||
url: `${process.env.NEXT_PUBLIC_API_URL}/api/v1/files/${result.data.id}/download`,
|
||||
url: `${API_URL}/api/v1/files/${result.data.id}/download`,
|
||||
size: result.data.file_size,
|
||||
});
|
||||
}
|
||||
@@ -2140,62 +1914,41 @@ export async function saveInspectionData(params: {
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
// Step 1: POST /v1/documents/upsert - 검사 데이터 저장
|
||||
const upsertBody = {
|
||||
// Step 1: POST /v1/documents/upsert - 검사 데이터 저장
|
||||
const docResult = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/documents/upsert`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
template_id: params.templateId,
|
||||
item_id: params.itemId,
|
||||
title: params.title || '수입검사 성적서',
|
||||
data: params.data,
|
||||
attachments: params.attachments || [],
|
||||
};
|
||||
},
|
||||
errorMessage: '검사 데이터 저장에 실패했습니다.',
|
||||
});
|
||||
if (docResult.__authError) return { success: false, __authError: true };
|
||||
if (!docResult.success) return { success: false, error: docResult.error };
|
||||
|
||||
const { response: docResponse, error: docError } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/documents/upsert`,
|
||||
{ method: 'POST', body: JSON.stringify(upsertBody) }
|
||||
);
|
||||
// Step 2: PUT /v1/receivings/{id} - 검사 완료 후 입고대기로 상태 변경 (비필수)
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const inspectionStatus = params.inspectionResult === 'pass' ? '적' : params.inspectionResult === 'fail' ? '부적' : '-';
|
||||
const inspectionResultLabel = params.inspectionResult === 'pass' ? '합격' : params.inspectionResult === 'fail' ? '불합격' : null;
|
||||
|
||||
if (docError) {
|
||||
return { success: false, error: docError.message, __authError: docError.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!docResponse) {
|
||||
return { success: false, error: '검사 데이터 저장에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const docResult = await docResponse.json();
|
||||
if (!docResponse.ok || !docResult.success) {
|
||||
return { success: false, error: docResult.message || '검사 데이터 저장에 실패했습니다.' };
|
||||
}
|
||||
|
||||
// Step 2: PUT /v1/receivings/{id} - 검사 완료 후 입고대기로 상태 변경 + 검사 정보 저장
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const inspectionStatus = params.inspectionResult === 'pass' ? '적' : params.inspectionResult === 'fail' ? '부적' : '-';
|
||||
const inspectionResultLabel = params.inspectionResult === 'pass' ? '합격' : params.inspectionResult === 'fail' ? '불합격' : null;
|
||||
|
||||
const { response: recResponse, error: recError } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${params.receivingId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
status: 'receiving_pending',
|
||||
inspection_status: inspectionStatus,
|
||||
inspection_date: today,
|
||||
inspection_result: inspectionResultLabel,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (recError) {
|
||||
console.error('[ReceivingActions] 입고 상태 업데이트 실패 (검사 데이터는 저장됨):', recError.message);
|
||||
} else if (recResponse && !recResponse.ok) {
|
||||
console.error('[ReceivingActions] 입고 상태 업데이트 실패 (검사 데이터는 저장됨)');
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ReceivingActions] saveInspectionData error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
const recResult = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${params.receivingId}`,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
status: 'receiving_pending',
|
||||
inspection_status: inspectionStatus,
|
||||
inspection_date: today,
|
||||
inspection_result: inspectionResultLabel,
|
||||
},
|
||||
errorMessage: '입고 상태 업데이트에 실패했습니다.',
|
||||
});
|
||||
if (!recResult.success) {
|
||||
console.error('[ReceivingActions] 입고 상태 업데이트 실패 (검사 데이터는 저장됨):', recResult.error);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@@ -11,8 +11,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import { executeServerAction } from '@/lib/api/execute-server-action';
|
||||
import type {
|
||||
StockItem,
|
||||
StockDetail,
|
||||
@@ -234,312 +233,101 @@ interface PaginationMeta {
|
||||
total: number;
|
||||
}
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
// ===== 재고 목록 조회 =====
|
||||
export async function getStocks(params?: {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
search?: string;
|
||||
itemType?: string;
|
||||
status?: string;
|
||||
useStatus?: string;
|
||||
location?: string;
|
||||
sortBy?: string;
|
||||
sortDir?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
data: StockItem[];
|
||||
pagination: PaginationMeta;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
page?: number; perPage?: number; search?: string; itemType?: string;
|
||||
status?: string; useStatus?: string; location?: string;
|
||||
sortBy?: string; sortDir?: string; startDate?: string; endDate?: string;
|
||||
}): Promise<{ success: boolean; data: StockItem[]; pagination: PaginationMeta; error?: string; __authError?: boolean }> {
|
||||
const emptyPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
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?.search) searchParams.set('search', params.search);
|
||||
if (params?.itemType && params.itemType !== 'all') searchParams.set('item_type', params.itemType);
|
||||
if (params?.status && params.status !== 'all') searchParams.set('status', params.status);
|
||||
if (params?.useStatus && params.useStatus !== 'all') searchParams.set('is_active', params.useStatus === 'active' ? '1' : '0');
|
||||
if (params?.location) searchParams.set('location', params.location);
|
||||
if (params?.sortBy) searchParams.set('sort_by', params.sortBy);
|
||||
if (params?.sortDir) searchParams.set('sort_dir', params.sortDir);
|
||||
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
||||
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
||||
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.perPage) searchParams.set('per_page', String(params.perPage));
|
||||
if (params?.search) searchParams.set('search', params.search);
|
||||
if (params?.itemType && params.itemType !== 'all') {
|
||||
searchParams.set('item_type', params.itemType);
|
||||
}
|
||||
if (params?.status && params.status !== 'all') {
|
||||
searchParams.set('status', params.status);
|
||||
}
|
||||
if (params?.useStatus && params.useStatus !== 'all') {
|
||||
searchParams.set('is_active', params.useStatus === 'active' ? '1' : '0');
|
||||
}
|
||||
if (params?.location) searchParams.set('location', params.location);
|
||||
if (params?.sortBy) searchParams.set('sort_by', params.sortBy);
|
||||
if (params?.sortDir) searchParams.set('sort_dir', params.sortDir);
|
||||
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
||||
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
||||
const queryString = searchParams.toString();
|
||||
const result = await executeServerAction<ItemApiPaginatedResponse>({
|
||||
url: `${API_URL}/api/v1/stocks${queryString ? `?${queryString}` : ''}`,
|
||||
errorMessage: '재고 목록 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks${queryString ? `?${queryString}` : ''}`;
|
||||
if (result.__authError) return { success: false, data: [], pagination: emptyPagination, __authError: true };
|
||||
if (!result.success || !result.data) return { success: false, data: [], pagination: emptyPagination, error: result.error };
|
||||
|
||||
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: ItemApiPaginatedResponse = result.data || {
|
||||
data: [],
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
const stocks = (paginatedData.data || []).map(transformApiToListItem);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: stocks,
|
||||
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('[StockActions] getStocks error:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
||||
error: '서버 오류가 발생했습니다.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: (result.data.data || []).map(transformApiToListItem),
|
||||
pagination: {
|
||||
currentPage: result.data.current_page, lastPage: result.data.last_page,
|
||||
perPage: result.data.per_page, total: result.data.total,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 재고 통계 조회 =====
|
||||
export async function getStockStats(): Promise<{
|
||||
success: boolean;
|
||||
data?: StockStats;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks/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('[StockActions] getStockStats error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
export async function getStockStats(): Promise<{ success: boolean; data?: StockStats; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/stocks/stats`,
|
||||
transform: (data: StockApiStatsResponse) => transformApiToStats(data),
|
||||
errorMessage: '재고 통계 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 품목유형별 통계 조회 =====
|
||||
export async function getStockStatsByType(): Promise<{
|
||||
success: boolean;
|
||||
data?: StockApiStatsByTypeResponse;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks/stats-by-type`,
|
||||
{ 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('[StockActions] getStockStatsByType error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
export async function getStockStatsByType(): Promise<{ success: boolean; data?: StockApiStatsByTypeResponse; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction<StockApiStatsByTypeResponse>({
|
||||
url: `${API_URL}/api/v1/stocks/stats-by-type`,
|
||||
errorMessage: '품목유형별 통계 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 재고 상세 조회 (Item 기준, LOT 포함) =====
|
||||
export async function getStockById(id: string): Promise<{
|
||||
success: boolean;
|
||||
data?: StockDetail;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks/${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('[StockActions] getStockById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
export async function getStockById(id: string): Promise<{ success: boolean; data?: StockDetail; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/stocks/${id}`,
|
||||
transform: (data: ItemApiData) => transformApiToDetail(data),
|
||||
errorMessage: '재고 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 재고 단건 수정 =====
|
||||
export async function updateStock(
|
||||
id: string,
|
||||
data: {
|
||||
safetyStock: number;
|
||||
useStatus: 'active' | 'inactive';
|
||||
}
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
safety_stock: data.safetyStock,
|
||||
is_active: data.useStatus === 'active',
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
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('[StockActions] updateStock error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
id: string, data: { safetyStock: number; useStatus: 'active' | 'inactive' }
|
||||
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/stocks/${id}`,
|
||||
method: 'PUT',
|
||||
body: { safety_stock: data.safetyStock, is_active: data.useStatus === 'active' },
|
||||
errorMessage: '재고 수정에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 재고 실사 (일괄 업데이트) =====
|
||||
export async function updateStockAudit(updates: { id: string; actualQty: number }[]): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks/audit`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: updates.map((u) => ({
|
||||
item_id: u.id,
|
||||
actual_qty: u.actualQty,
|
||||
})),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
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('[StockActions] updateStockAudit error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
export async function updateStockAudit(updates: { id: string; actualQty: number }[]): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/stocks/audit`,
|
||||
method: 'POST',
|
||||
body: { items: updates.map((u) => ({ item_id: u.id, actual_qty: u.actualQty })) },
|
||||
errorMessage: '재고 실사 저장에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, error: result.error };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user