- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션 - 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts) - ApiErrorContext 추가로 전역 에러 처리 개선 - HR EmployeeForm 컴포넌트 개선 - 참조함(ReferenceBox) 기능 수정 - juil 테스트 URL 페이지 추가 - claudedocs 문서 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
754 lines
23 KiB
TypeScript
754 lines
23 KiB
TypeScript
/**
|
|
* 출하 관리 서버 액션
|
|
*
|
|
* API Endpoints:
|
|
* - GET /api/v1/shipments - 목록 조회
|
|
* - GET /api/v1/shipments/stats - 통계 조회
|
|
* - GET /api/v1/shipments/stats-by-status - 상태별 통계 조회
|
|
* - GET /api/v1/shipments/{id} - 상세 조회
|
|
* - POST /api/v1/shipments - 등록
|
|
* - PUT /api/v1/shipments/{id} - 수정
|
|
* - PATCH /api/v1/shipments/{id}/status - 상태 변경
|
|
* - DELETE /api/v1/shipments/{id} - 삭제
|
|
* - GET /api/v1/shipments/options/lots - LOT 옵션 조회
|
|
* - GET /api/v1/shipments/options/logistics - 물류사 옵션 조회
|
|
* - GET /api/v1/shipments/options/vehicle-tonnage - 차량 톤수 옵션 조회
|
|
*/
|
|
|
|
'use server';
|
|
|
|
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
|
import type {
|
|
ShipmentItem,
|
|
ShipmentDetail,
|
|
ShipmentProduct,
|
|
ShipmentStats,
|
|
ShipmentStatus,
|
|
ShipmentPriority,
|
|
DeliveryMethod,
|
|
ShipmentCreateFormData,
|
|
ShipmentEditFormData,
|
|
LotOption,
|
|
LogisticsOption,
|
|
VehicleTonnageOption,
|
|
} from './types';
|
|
|
|
// ===== API 데이터 타입 =====
|
|
interface ShipmentApiData {
|
|
id: number;
|
|
shipment_no: string;
|
|
lot_no?: string;
|
|
order_id?: number;
|
|
scheduled_date: string;
|
|
status: ShipmentStatus;
|
|
priority: ShipmentPriority;
|
|
delivery_method: DeliveryMethod;
|
|
client_id?: number;
|
|
customer_name?: string;
|
|
site_name?: string;
|
|
delivery_address?: string;
|
|
receiver?: string;
|
|
receiver_contact?: string;
|
|
can_ship: boolean;
|
|
deposit_confirmed: boolean;
|
|
invoice_issued: boolean;
|
|
customer_grade?: string;
|
|
loading_manager?: string;
|
|
loading_time?: string;
|
|
loading_completed_at?: string;
|
|
logistics_company?: string;
|
|
vehicle_tonnage?: string;
|
|
shipping_cost?: string | number;
|
|
vehicle_no?: string;
|
|
driver_name?: string;
|
|
driver_contact?: string;
|
|
expected_arrival?: string;
|
|
confirmed_arrival?: string;
|
|
remarks?: string;
|
|
created_by?: number;
|
|
updated_by?: number;
|
|
creator?: { id: number; name: string };
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
items?: ShipmentItemApiData[];
|
|
status_label?: string;
|
|
priority_label?: string;
|
|
delivery_method_label?: string;
|
|
total_quantity?: number;
|
|
item_count?: number;
|
|
}
|
|
|
|
interface ShipmentItemApiData {
|
|
id: number;
|
|
shipment_id: number;
|
|
seq: number;
|
|
item_code?: string;
|
|
item_name: string;
|
|
floor_unit?: string;
|
|
specification?: string;
|
|
quantity: string | number;
|
|
unit?: string;
|
|
lot_no?: string;
|
|
stock_lot_id?: number;
|
|
remarks?: string;
|
|
}
|
|
|
|
interface ShipmentApiPaginatedResponse {
|
|
data: ShipmentApiData[];
|
|
current_page: number;
|
|
last_page: number;
|
|
per_page: number;
|
|
total: number;
|
|
}
|
|
|
|
interface ShipmentApiStatsResponse {
|
|
today_shipment_count: number;
|
|
scheduled_count: number;
|
|
shipping_count: number;
|
|
urgent_count: number;
|
|
}
|
|
|
|
interface ShipmentApiStatsByStatusResponse {
|
|
all: number;
|
|
scheduled: number;
|
|
ready: number;
|
|
shipping: number;
|
|
completed: number;
|
|
}
|
|
|
|
// ===== API → Frontend 변환 (목록용) =====
|
|
function transformApiToListItem(data: ShipmentApiData): ShipmentItem {
|
|
return {
|
|
id: String(data.id),
|
|
shipmentNo: data.shipment_no,
|
|
lotNo: data.lot_no || '',
|
|
scheduledDate: data.scheduled_date,
|
|
status: data.status,
|
|
priority: data.priority,
|
|
deliveryMethod: data.delivery_method,
|
|
customerName: data.customer_name || '',
|
|
siteName: data.site_name || '',
|
|
manager: data.loading_manager,
|
|
canShip: data.can_ship,
|
|
depositConfirmed: data.deposit_confirmed,
|
|
invoiceIssued: data.invoice_issued,
|
|
deliveryTime: data.expected_arrival,
|
|
};
|
|
}
|
|
|
|
// ===== API → Frontend 변환 (품목용) =====
|
|
function transformApiToProduct(data: ShipmentItemApiData): ShipmentProduct {
|
|
return {
|
|
id: String(data.id),
|
|
no: data.seq,
|
|
itemCode: data.item_code || '',
|
|
itemName: data.item_name,
|
|
floorUnit: data.floor_unit || '',
|
|
specification: data.specification || '',
|
|
quantity: parseFloat(String(data.quantity)) || 0,
|
|
lotNo: data.lot_no || '',
|
|
};
|
|
}
|
|
|
|
// ===== API → Frontend 변환 (상세용) =====
|
|
function transformApiToDetail(data: ShipmentApiData): ShipmentDetail {
|
|
return {
|
|
id: String(data.id),
|
|
shipmentNo: data.shipment_no,
|
|
lotNo: data.lot_no || '',
|
|
scheduledDate: data.scheduled_date,
|
|
status: data.status,
|
|
priority: data.priority,
|
|
deliveryMethod: data.delivery_method,
|
|
depositConfirmed: data.deposit_confirmed,
|
|
invoiceIssued: data.invoice_issued,
|
|
customerGrade: data.customer_grade || '',
|
|
canShip: data.can_ship,
|
|
loadingManager: data.loading_manager,
|
|
loadingCompleted: data.loading_completed_at,
|
|
registrant: data.creator?.name,
|
|
customerName: data.customer_name || '',
|
|
siteName: data.site_name || '',
|
|
deliveryAddress: data.delivery_address || '',
|
|
receiver: data.receiver,
|
|
receiverContact: data.receiver_contact,
|
|
products: (data.items || []).map(transformApiToProduct),
|
|
logisticsCompany: data.logistics_company,
|
|
vehicleTonnage: data.vehicle_tonnage,
|
|
shippingCost: data.shipping_cost ? parseFloat(String(data.shipping_cost)) : undefined,
|
|
vehicleNo: data.vehicle_no,
|
|
driverName: data.driver_name,
|
|
driverContact: data.driver_contact,
|
|
remarks: data.remarks,
|
|
};
|
|
}
|
|
|
|
// ===== API → Frontend 변환 (통계용) =====
|
|
function transformApiToStats(data: ShipmentApiStatsResponse): ShipmentStats {
|
|
return {
|
|
todayShipmentCount: data.today_shipment_count,
|
|
scheduledCount: data.scheduled_count,
|
|
shippingCount: data.shipping_count,
|
|
urgentCount: data.urgent_count,
|
|
};
|
|
}
|
|
|
|
// ===== Frontend → API 변환 (등록용) =====
|
|
function transformCreateFormToApi(
|
|
data: ShipmentCreateFormData
|
|
): Record<string, unknown> {
|
|
return {
|
|
lot_no: data.lotNo,
|
|
scheduled_date: data.scheduledDate,
|
|
priority: data.priority,
|
|
delivery_method: data.deliveryMethod,
|
|
logistics_company: data.logisticsCompany,
|
|
vehicle_tonnage: data.vehicleTonnage,
|
|
loading_time: data.loadingTime,
|
|
loading_manager: data.loadingManager,
|
|
remarks: data.remarks,
|
|
};
|
|
}
|
|
|
|
// ===== Frontend → API 변환 (수정용) =====
|
|
function transformEditFormToApi(
|
|
data: Partial<ShipmentEditFormData>
|
|
): Record<string, unknown> {
|
|
const result: Record<string, unknown> = {};
|
|
|
|
if (data.scheduledDate !== undefined) result.scheduled_date = data.scheduledDate;
|
|
if (data.priority !== undefined) result.priority = data.priority;
|
|
if (data.deliveryMethod !== undefined) result.delivery_method = data.deliveryMethod;
|
|
if (data.loadingManager !== undefined) result.loading_manager = data.loadingManager;
|
|
if (data.logisticsCompany !== undefined) result.logistics_company = data.logisticsCompany;
|
|
if (data.vehicleTonnage !== undefined) result.vehicle_tonnage = data.vehicleTonnage;
|
|
if (data.vehicleNo !== undefined) result.vehicle_no = data.vehicleNo;
|
|
if (data.shippingCost !== undefined) result.shipping_cost = data.shippingCost;
|
|
if (data.driverName !== undefined) result.driver_name = data.driverName;
|
|
if (data.driverContact !== undefined) result.driver_contact = data.driverContact;
|
|
if (data.expectedArrival !== undefined) result.expected_arrival = data.expectedArrival;
|
|
if (data.confirmedArrival !== undefined) result.confirmed_arrival = data.confirmedArrival;
|
|
if (data.remarks !== undefined) result.remarks = data.remarks;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// ===== 페이지네이션 타입 =====
|
|
interface PaginationMeta {
|
|
currentPage: number;
|
|
lastPage: number;
|
|
perPage: number;
|
|
total: number;
|
|
}
|
|
|
|
// ===== 출하 목록 조회 =====
|
|
export async function getShipments(params?: {
|
|
page?: number;
|
|
perPage?: number;
|
|
search?: string;
|
|
status?: string;
|
|
priority?: string;
|
|
deliveryMethod?: string;
|
|
scheduledFrom?: string;
|
|
scheduledTo?: string;
|
|
canShip?: boolean;
|
|
depositConfirmed?: boolean;
|
|
sortBy?: string;
|
|
sortDir?: string;
|
|
}): Promise<{
|
|
success: boolean;
|
|
data: ShipmentItem[];
|
|
pagination: PaginationMeta;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
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?.search) searchParams.set('search', params.search);
|
|
if (params?.status && params.status !== 'all') {
|
|
searchParams.set('status', params.status);
|
|
}
|
|
if (params?.priority && params.priority !== 'all') {
|
|
searchParams.set('priority', params.priority);
|
|
}
|
|
if (params?.deliveryMethod && params.deliveryMethod !== 'all') {
|
|
searchParams.set('delivery_method', params.deliveryMethod);
|
|
}
|
|
if (params?.scheduledFrom) searchParams.set('scheduled_from', params.scheduledFrom);
|
|
if (params?.scheduledTo) searchParams.set('scheduled_to', params.scheduledTo);
|
|
if (params?.canShip !== undefined) searchParams.set('can_ship', String(params.canShip));
|
|
if (params?.depositConfirmed !== undefined) {
|
|
searchParams.set('deposit_confirmed', String(params.depositConfirmed));
|
|
}
|
|
if (params?.sortBy) searchParams.set('sort_by', params.sortBy);
|
|
if (params?.sortDir) searchParams.set('sort_dir', params.sortDir);
|
|
|
|
const queryString = searchParams.toString();
|
|
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments${queryString ? `?${queryString}` : ''}`;
|
|
|
|
console.log('[ShipmentActions] GET shipments:', url);
|
|
|
|
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 || !response.ok) {
|
|
console.warn('[ShipmentActions] GET shipments error:', response?.status);
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
error: `API 오류: ${response?.status}`,
|
|
};
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
error: result.message || '출하 목록 조회에 실패했습니다.',
|
|
};
|
|
}
|
|
|
|
const paginatedData: ShipmentApiPaginatedResponse = result.data || {
|
|
data: [],
|
|
current_page: 1,
|
|
last_page: 1,
|
|
per_page: 20,
|
|
total: 0,
|
|
};
|
|
|
|
const shipments = (paginatedData.data || []).map(transformApiToListItem);
|
|
|
|
return {
|
|
success: true,
|
|
data: shipments,
|
|
pagination: {
|
|
currentPage: paginatedData.current_page,
|
|
lastPage: paginatedData.last_page,
|
|
perPage: paginatedData.per_page,
|
|
total: paginatedData.total,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] getShipments error:', error);
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
|
error: '서버 오류가 발생했습니다.',
|
|
};
|
|
}
|
|
}
|
|
|
|
// ===== 출하 통계 조회 =====
|
|
export async function getShipmentStats(): Promise<{
|
|
success: boolean;
|
|
data?: ShipmentStats;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/stats`,
|
|
{
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response || !response.ok) {
|
|
console.warn('[ShipmentActions] GET stats 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: transformApiToStats(result.data) };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] getShipmentStats error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 상태별 통계 조회 (탭용) =====
|
|
export async function getShipmentStatsByStatus(): Promise<{
|
|
success: boolean;
|
|
data?: ShipmentApiStatsByStatusResponse;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/stats-by-status`,
|
|
{
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response || !response.ok) {
|
|
console.warn('[ShipmentActions] GET stats-by-status 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: result.data };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] getShipmentStatsByStatus error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 출하 상세 조회 =====
|
|
export async function getShipmentById(id: string): Promise<{
|
|
success: boolean;
|
|
data?: ShipmentDetail;
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${id}`,
|
|
{
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response || !response.ok) {
|
|
console.error('[ShipmentActions] GET shipment 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: transformApiToDetail(result.data) };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] getShipmentById error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 출하 등록 =====
|
|
export async function createShipment(
|
|
data: ShipmentCreateFormData
|
|
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const apiData = transformCreateFormToApi(data);
|
|
console.log('[ShipmentActions] POST shipment request:', apiData);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments`,
|
|
{
|
|
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();
|
|
console.log('[ShipmentActions] POST shipment response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '출하 등록에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToDetail(result.data) };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] createShipment error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 출하 수정 =====
|
|
export async function updateShipment(
|
|
id: string,
|
|
data: Partial<ShipmentEditFormData>
|
|
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const apiData = transformEditFormToApi(data);
|
|
console.log('[ShipmentActions] PUT shipment request:', apiData);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${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();
|
|
console.log('[ShipmentActions] PUT shipment response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '출하 수정에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToDetail(result.data) };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] updateShipment error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 출하 상태 변경 =====
|
|
export async function updateShipmentStatus(
|
|
id: string,
|
|
status: ShipmentStatus,
|
|
additionalData?: {
|
|
loadingTime?: string;
|
|
loadingCompletedAt?: string;
|
|
vehicleNo?: string;
|
|
driverName?: string;
|
|
driverContact?: string;
|
|
confirmedArrival?: string;
|
|
}
|
|
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const apiData: Record<string, unknown> = { status };
|
|
if (additionalData?.loadingTime) apiData.loading_time = additionalData.loadingTime;
|
|
if (additionalData?.loadingCompletedAt) apiData.loading_completed_at = additionalData.loadingCompletedAt;
|
|
if (additionalData?.vehicleNo) apiData.vehicle_no = additionalData.vehicleNo;
|
|
if (additionalData?.driverName) apiData.driver_name = additionalData.driverName;
|
|
if (additionalData?.driverContact) apiData.driver_contact = additionalData.driverContact;
|
|
if (additionalData?.confirmedArrival) apiData.confirmed_arrival = additionalData.confirmedArrival;
|
|
|
|
console.log('[ShipmentActions] PATCH status request:', apiData);
|
|
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${id}/status`,
|
|
{
|
|
method: 'PATCH',
|
|
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();
|
|
console.log('[ShipmentActions] PATCH status response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '상태 변경에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: transformApiToDetail(result.data) };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] updateShipmentStatus error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 출하 삭제 =====
|
|
export async function deleteShipment(
|
|
id: string
|
|
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${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();
|
|
console.log('[ShipmentActions] DELETE shipment response:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '출하 삭제에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] deleteShipment error:', error);
|
|
return { success: false, error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== LOT 옵션 조회 =====
|
|
export async function getLotOptions(): Promise<{
|
|
success: boolean;
|
|
data: LotOption[];
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/options/lots`,
|
|
{
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, data: [], error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response || !response.ok) {
|
|
console.warn('[ShipmentActions] GET lot 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 || 'LOT 옵션 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: result.data || [] };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] getLotOptions error:', error);
|
|
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 물류사 옵션 조회 =====
|
|
export async function getLogisticsOptions(): Promise<{
|
|
success: boolean;
|
|
data: LogisticsOption[];
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/options/logistics`,
|
|
{
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, data: [], error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response || !response.ok) {
|
|
console.warn('[ShipmentActions] GET logistics 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 || '물류사 옵션 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: result.data || [] };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] getLogisticsOptions error:', error);
|
|
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|
|
|
|
// ===== 차량 톤수 옵션 조회 =====
|
|
export async function getVehicleTonnageOptions(): Promise<{
|
|
success: boolean;
|
|
data: VehicleTonnageOption[];
|
|
error?: string;
|
|
__authError?: boolean;
|
|
}> {
|
|
try {
|
|
const { response, error } = await serverFetch(
|
|
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/options/vehicle-tonnage`,
|
|
{
|
|
method: 'GET',
|
|
cache: 'no-store',
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
return { success: false, data: [], error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
|
}
|
|
|
|
if (!response || !response.ok) {
|
|
console.warn('[ShipmentActions] GET vehicle tonnage 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 || '차량 톤수 옵션 조회에 실패했습니다.' };
|
|
}
|
|
|
|
return { success: true, data: result.data || [] };
|
|
} catch (error) {
|
|
console.error('[ShipmentActions] getVehicleTonnageOptions error:', error);
|
|
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
|
}
|
|
}
|