- ShipmentApiData에 vehicle_dispatches 타입 추가 - transformApiToDetail: vehicle_dispatches 배열 매핑 (레거시 단일필드 fallback 유지) - transformCreateFormToApi/transformEditFormToApi: vehicleDispatches → vehicle_dispatches 변환 추가 - transformApiToListItem: 첫 번째 배차의 arrival_datetime 반영
521 lines
20 KiB
TypeScript
521 lines
20 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 { executeServerAction } from '@/lib/api/execute-server-action';
|
|
import { executePaginatedAction } from '@/lib/api/execute-paginated-action';
|
|
import { buildApiUrl } from '@/lib/api/query-params';
|
|
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;
|
|
delivery_date?: string;
|
|
writer_id?: number;
|
|
writer_name?: 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[];
|
|
vehicle_dispatches?: Array<{
|
|
id: number;
|
|
seq: number;
|
|
logistics_company?: string;
|
|
arrival_datetime?: string;
|
|
tonnage?: string;
|
|
vehicle_no?: string;
|
|
driver_contact?: string;
|
|
remarks?: string;
|
|
}>;
|
|
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 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.vehicle_dispatches?.[0]?.arrival_datetime || data.expected_arrival,
|
|
// 수신/작성자/출고일 매핑
|
|
receiver: data.receiver || '',
|
|
receiverAddress: data.order_info?.delivery_address || data.delivery_address || '',
|
|
receiverCompany: data.order_info?.customer_name || data.customer_name || '',
|
|
writer: data.order_info?.writer_name || data.creator?.name || '',
|
|
shipmentDate: data.scheduled_date || '',
|
|
};
|
|
}
|
|
|
|
// ===== 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,
|
|
// 배차 정보 - vehicle_dispatches 테이블에서 조회, 없으면 레거시 단일 필드 fallback
|
|
vehicleDispatches: data.vehicle_dispatches && data.vehicle_dispatches.length > 0
|
|
? data.vehicle_dispatches.map((vd) => ({
|
|
id: String(vd.id),
|
|
logisticsCompany: vd.logistics_company || '-',
|
|
arrivalDateTime: vd.arrival_datetime || '-',
|
|
tonnage: vd.tonnage || '-',
|
|
vehicleNo: vd.vehicle_no || '-',
|
|
driverContact: vd.driver_contact || '-',
|
|
remarks: vd.remarks || '',
|
|
}))
|
|
: (data.vehicle_no || data.logistics_company || data.driver_contact
|
|
? [{
|
|
id: `vd-legacy-${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> {
|
|
const result: Record<string, unknown> = {
|
|
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,
|
|
};
|
|
|
|
if (data.vehicleDispatches && data.vehicleDispatches.length > 0) {
|
|
result.vehicle_dispatches = data.vehicleDispatches.map((vd, idx) => ({
|
|
seq: idx + 1,
|
|
logistics_company: vd.logisticsCompany || null,
|
|
arrival_datetime: vd.arrivalDateTime || null,
|
|
tonnage: vd.tonnage || null,
|
|
vehicle_no: vd.vehicleNo || null,
|
|
driver_contact: vd.driverContact || null,
|
|
remarks: vd.remarks || null,
|
|
}));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ===== 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.receiver !== undefined) result.receiver = data.receiver;
|
|
if (data.receiverContact !== undefined) result.receiver_contact = data.receiverContact;
|
|
// 주소: zipCode + address + addressDetail → delivery_address로 결합
|
|
if (data.address !== undefined || data.zipCode !== undefined || data.addressDetail !== undefined) {
|
|
const parts = [
|
|
data.zipCode ? `[${data.zipCode}]` : '',
|
|
data.address || '',
|
|
data.addressDetail || '',
|
|
].filter(Boolean);
|
|
result.delivery_address = parts.join(' ');
|
|
}
|
|
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.changeReason !== undefined) result.change_reason = data.changeReason;
|
|
if (data.remarks !== undefined) result.remarks = data.remarks;
|
|
|
|
if (data.vehicleDispatches) {
|
|
result.vehicle_dispatches = data.vehicleDispatches.map((vd, idx) => ({
|
|
seq: idx + 1,
|
|
logistics_company: vd.logisticsCompany || null,
|
|
arrival_datetime: vd.arrivalDateTime || null,
|
|
tonnage: vd.tonnage || null,
|
|
vehicle_no: vd.vehicleNo || null,
|
|
driver_contact: vd.driverContact || null,
|
|
remarks: vd.remarks || null,
|
|
}));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// ===== 출고 목록 조회 =====
|
|
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;
|
|
}) {
|
|
return executePaginatedAction<ShipmentApiData, ShipmentItem>({
|
|
url: buildApiUrl('/api/v1/shipments', {
|
|
page: params?.page,
|
|
per_page: params?.perPage,
|
|
search: params?.search,
|
|
status: params?.status !== 'all' ? params?.status : undefined,
|
|
priority: params?.priority !== 'all' ? params?.priority : undefined,
|
|
delivery_method: params?.deliveryMethod !== 'all' ? params?.deliveryMethod : undefined,
|
|
scheduled_from: params?.scheduledFrom,
|
|
scheduled_to: params?.scheduledTo,
|
|
can_ship: params?.canShip,
|
|
deposit_confirmed: params?.depositConfirmed,
|
|
sort_by: params?.sortBy,
|
|
sort_dir: params?.sortDir,
|
|
}),
|
|
transform: transformApiToListItem,
|
|
errorMessage: '출고 목록 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
// ===== 출고 통계 조회 =====
|
|
export async function getShipmentStats(): Promise<{ success: boolean; data?: ShipmentStats; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl('/api/v1/shipments/stats'),
|
|
transform: (data: ShipmentApiStatsResponse & { total_count?: number }) => transformApiToStats(data),
|
|
errorMessage: '출고 통계 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
// ===== 상태별 통계 조회 (탭용) =====
|
|
export async function getShipmentStatsByStatus(): Promise<{ success: boolean; data?: ShipmentStatusStats; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl('/api/v1/shipments/stats-by-status'),
|
|
transform: (data: ShipmentApiStatsByStatusResponse) => transformApiToStatsByStatus(data),
|
|
errorMessage: '상태별 통계 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
// ===== 출고 상세 조회 =====
|
|
export async function getShipmentById(id: string): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/shipments/${id}`),
|
|
transform: (data: ShipmentApiData) => transformApiToDetail(data),
|
|
errorMessage: '출고 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
// ===== 출고 등록 =====
|
|
export async function createShipment(
|
|
data: ShipmentCreateFormData
|
|
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
|
const apiData = transformCreateFormToApi(data);
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl('/api/v1/shipments'),
|
|
method: 'POST',
|
|
body: apiData,
|
|
transform: (d: ShipmentApiData) => transformApiToDetail(d),
|
|
errorMessage: '출고 등록에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
// ===== 출고 수정 =====
|
|
export async function updateShipment(
|
|
id: string, data: Partial<ShipmentEditFormData>
|
|
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
|
const apiData = transformEditFormToApi(data);
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/shipments/${id}`),
|
|
method: 'PUT',
|
|
body: apiData,
|
|
transform: (d: ShipmentApiData) => transformApiToDetail(d),
|
|
errorMessage: '출고 수정에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.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 }> {
|
|
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;
|
|
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/shipments/${id}/status`),
|
|
method: 'PATCH',
|
|
body: apiData,
|
|
transform: (d: ShipmentApiData) => transformApiToDetail(d),
|
|
errorMessage: '상태 변경에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, data: result.data, error: result.error };
|
|
}
|
|
|
|
// ===== 출고 삭제 =====
|
|
export async function deleteShipment(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction({
|
|
url: buildApiUrl(`/api/v1/shipments/${id}`),
|
|
method: 'DELETE',
|
|
errorMessage: '출고 삭제에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, __authError: true };
|
|
return { success: result.success, error: result.error };
|
|
}
|
|
|
|
// ===== LOT 옵션 조회 =====
|
|
export async function getLotOptions(): Promise<{ success: boolean; data: LotOption[]; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction<LotOption[]>({
|
|
url: buildApiUrl('/api/v1/shipments/options/lots'),
|
|
errorMessage: 'LOT 옵션 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, data: [], __authError: true };
|
|
return { success: result.success, data: result.data || [], error: result.error };
|
|
}
|
|
|
|
// ===== 물류사 옵션 조회 =====
|
|
export async function getLogisticsOptions(): Promise<{ success: boolean; data: LogisticsOption[]; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction<LogisticsOption[]>({
|
|
url: buildApiUrl('/api/v1/shipments/options/logistics'),
|
|
errorMessage: '물류사 옵션 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, data: [], __authError: true };
|
|
return { success: result.success, data: result.data || [], error: result.error };
|
|
}
|
|
|
|
// ===== 차량 톤수 옵션 조회 =====
|
|
export async function getVehicleTonnageOptions(): Promise<{ success: boolean; data: VehicleTonnageOption[]; error?: string; __authError?: boolean }> {
|
|
const result = await executeServerAction<VehicleTonnageOption[]>({
|
|
url: buildApiUrl('/api/v1/shipments/options/vehicle-tonnage'),
|
|
errorMessage: '차량 톤수 옵션 조회에 실패했습니다.',
|
|
});
|
|
if (result.__authError) return { success: false, data: [], __authError: true };
|
|
return { success: result.success, data: result.data || [], error: result.error };
|
|
}
|