Files
sam-react-prod/src/components/outbound/ShipmentManagement/actions.ts
유병철 ca6247286a feat(WEB): 수입검사 관리 대폭 개선, 캘린더 DayTimeView 추가 및 출고 기능 보완
- 수입검사: InspectionCreate/Detail/List 대폭 개선, OrderSelectModal/문서 컴포넌트 신규 추가
- 수입검사: actions/types/mockData/inspectionConfig 전면 리팩토링
- QMS: InspectionModalV2/ImportInspectionDocument 개선
- 캘린더: DayTimeView 신규 추가, CalendarHeader/ScheduleCalendar/utils 확장
- 출고: ShipmentDetail/List/actions 개선, ShipmentOrderDocument/ShippingSlip 수정

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:46:52 +09:00

831 lines
27 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 { isNextRedirectError } from '@/lib/utils/redirect-error';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type {
ShipmentItem,
ShipmentDetail,
ShipmentProduct,
ShipmentStats,
ShipmentStatusStats,
ShipmentStatus,
ShipmentPriority,
DeliveryMethod,
FreightCostType,
ShipmentCreateFormData,
ShipmentEditFormData,
LotOption,
LogisticsOption,
VehicleTonnageOption,
} from './types';
// ===== API 데이터 타입 =====
// 수주 연동 정보 (Order → Shipment)
interface OrderInfoApiData {
order_id?: number;
order_no?: string;
order_status?: string;
client_id?: number;
customer_name?: string;
site_name?: string;
delivery_address?: string;
contact?: string;
}
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;
// 수주 연동 정보 (order_info accessor)
order_info?: OrderInfoApiData;
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,
deliveryMethodLabel: data.delivery_method_label || data.delivery_method,
// 발주처/배송 정보: order_info 우선 참조 (Order가 Single Source of Truth)
customerName: data.order_info?.customer_name || data.customer_name || '',
siteName: data.order_info?.site_name || 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,
shipmentDate: (data as unknown as Record<string, unknown>).shipment_date as string | undefined,
status: data.status,
priority: data.priority,
deliveryMethod: data.delivery_method,
freightCost: (data as unknown as Record<string, unknown>).freight_cost as FreightCostType | undefined,
freightCostLabel: (data as unknown as Record<string, unknown>).freight_cost_label as string | undefined,
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,
// 발주처/배송 정보: order_info 우선 참조 (Order가 Single Source of Truth)
customerName: data.order_info?.customer_name || data.customer_name || '',
siteName: data.order_info?.site_name || data.site_name || '',
deliveryAddress: data.order_info?.delivery_address || data.delivery_address || '',
receiver: data.receiver,
receiverContact: data.order_info?.contact || data.receiver_contact,
zipCode: (data as unknown as Record<string, unknown>).zip_code as string | undefined,
address: (data as unknown as Record<string, unknown>).address as string | undefined,
addressDetail: (data as unknown as Record<string, unknown>).address_detail as string | undefined,
// 배차 정보 - 기존 단일 필드에서 구성 (다중 행 API 준비 전까지)
vehicleDispatches: data.vehicle_no || data.logistics_company || data.driver_contact
? [{
id: `vd-${data.id}`,
logisticsCompany: data.logistics_company || '-',
arrivalDateTime: data.confirmed_arrival || data.expected_arrival || '-',
tonnage: data.vehicle_tonnage || '-',
vehicleNo: data.vehicle_no || '-',
driverContact: data.driver_contact || '-',
remarks: '',
}]
: [],
// 제품내용 (그룹핑) - 프론트엔드에서 그룹핑 처리
productGroups: [],
otherParts: [],
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 & { total_count?: number }): ShipmentStats {
return {
todayShipmentCount: data.today_shipment_count,
scheduledCount: data.scheduled_count,
shippingCount: data.shipping_count,
urgentCount: data.urgent_count,
totalCount: data.total_count || 0,
};
}
// ===== API → Frontend 변환 (상태별 통계용) =====
const STATUS_TAB_LABELS: Record<string, string> = {
all: '전체',
scheduled: '출고예정',
ready: '출고대기',
shipping: '배송중',
completed: '배송완료',
};
function transformApiToStatsByStatus(data: ShipmentApiStatsByStatusResponse): ShipmentStatusStats {
const result: ShipmentStatusStats = {};
for (const [key, count] of Object.entries(data)) {
if (key !== 'all') { // all은 탭에서 제외 (전체 탭은 별도 처리)
result[key] = {
label: STATUS_TAB_LABELS[key] || key,
count: count as number,
};
}
}
return result;
}
// ===== 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw error;
console.error('[ShipmentActions] getShipmentStats error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
}
// ===== 상태별 통계 조회 (탭용) =====
export async function getShipmentStatsByStatus(): Promise<{
success: boolean;
data?: ShipmentStatusStats;
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: transformApiToStatsByStatus(result.data) };
} catch (error) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw 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) {
if (isNextRedirectError(error)) throw error;
console.error('[ShipmentActions] getVehicleTonnageOptions error:', error);
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
}
}