"use server"; // import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; // import { BASE_API_URL } from "@/config/api"; import { serverFetch, serverFetchJson, serverFetchWithNoContent, ServerFetchError, } from "../../utils/fetchUtil"; import { BASE_API_URL } from "../../../config/api"; import { revalidateTag } from "next/cache"; import { cache } from "react"; export interface ShopAndTruck{ id: number; name: String; code: String; addr1: String; addr2: String; addr3: String; contactNo: number; type: String; contactEmail: String; contactName: String; truckLanceCode: String; DepartureTime: String; LoadingSequence?: number | null; districtReference: string | null; Store_id: Number; remark?: String | null; truckId?: number; } export interface Shop{ id: number; name: String; code: String; addr3: String; } export interface Truck{ id?: number; truckLanceCode: String; departureTime: String | number[]; loadingSequence: number; districtReference: string | null; storeId: Number | String; remark?: String | null; shopName?: String | null; shopCode?: String | null; } export interface SaveTruckLane { id: number; truckLanceCode: string; departureTime: string; loadingSequence: number; districtReference: string | null; storeId: string; remark?: string | null; logisticId?: number | null; /** When true, set truck.logistic to logisticId (null clears). When false/omit, do not change logistic. */ updateLogistic?: boolean; } /** POST /truck/updateLaneLogistic — 同線桶內 truck 列一次更新 logistic(單一 transaction) */ export interface UpdateLaneLogisticRequest { truckLanceCode: string; remark?: string | null; logisticId?: number | null; } export interface DeleteTruckLane { id: number; } export interface UpdateTruckShopDetailsRequest { id: number; shopId?: number | null; shopName: string | null; shopCode: string | null; loadingSequence: number; remark?: string | null; } export interface SaveTruckRequest { id?: number | null; store_id: string; truckLanceCode: string; departureTime: string; shopId: number; shopName: string; shopCode: string; loadingSequence: number; districtReference?: string | null; remark?: string | null; logisticId?: number | null; } export interface CreateTruckWithoutShopRequest { store_id: string; truckLanceCode: string; departureTime: string; loadingSequence?: number; districtReference?: string | null; logisticId?: number | null; remark?: string | null; } export interface MessageResponse { id: number | null; name: string | null; code: string | null; type: string; message: string; errorPosition: string | null; entity: Truck | null; } export const fetchAllShopsAction = cache(async (params?: Record) => { const endpoint = `${BASE_API_URL}/shop/combo/allShop`; const qs = params ? Object.entries(params) .filter(([, v]) => v !== null && v !== undefined && String(v).trim() !== "") .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`) .join("&") : ""; const url = qs ? `${endpoint}?${qs}` : endpoint; return serverFetchJson(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const findTruckLaneByShopIdAction = cache(async (shopId: number | string) => { const endpoint = `${BASE_API_URL}/truck/findTruckLane/${shopId}`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const updateTruckLaneAction = async (data: SaveTruckLane) => { const endpoint = `${BASE_API_URL}/truck/updateTruckLane`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; export const updateLaneLogisticAction = async ( data: UpdateLaneLogisticRequest, ): Promise => { const endpoint = `${BASE_API_URL}/truck/updateLaneLogistic`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; export const deleteTruckLaneAction = async (data: DeleteTruckLane) => { const endpoint = `${BASE_API_URL}/truck/deleteTruckLane`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; export const createTruckAction = async (data: SaveTruckRequest) => { const endpoint = `${BASE_API_URL}/truck/createTruckInShop`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; export const findAllUniqueTruckLaneCombinationsAction = cache(async () => { const endpoint = `${BASE_API_URL}/truck/findAllUniqueTruckLanceCodeAndRemarkCombinations`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); /** O(1) 取整個 RouteBoard 所需 truck rows;前端自行按 (truckLanceCode, remark) 分桶。 */ export const findAllForRouteBoardAction = cache(async () => { const endpoint = `${BASE_API_URL}/truck/findAllForRouteBoard`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const findAllShopsByTruckLanceCodeAndRemarkAction = cache(async (truckLanceCode: string, remark: string) => { const endpoint = `${BASE_API_URL}/truck/findAllFromShopAndTruckByTruckLanceCodeAndRemarkAndDeletedFalse`; const url = `${endpoint}?truckLanceCode=${encodeURIComponent(truckLanceCode)}&remark=${encodeURIComponent(remark)}`; return serverFetchJson(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const findAllShopsByTruckLanceCodeAction = cache(async (truckLanceCode: string) => { const endpoint = `${BASE_API_URL}/truck/findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse`; const url = `${endpoint}?truckLanceCode=${encodeURIComponent(truckLanceCode)}`; return serverFetchJson(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const findAllByTruckLanceCodeAndDeletedFalseAction = cache(async (truckLanceCode: string) => { const endpoint = `${BASE_API_URL}/truck/findAllByTruckLanceCodeAndDeletedFalse`; const url = `${endpoint}?truckLanceCode=${encodeURIComponent(truckLanceCode)}`; return serverFetchJson(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); /** 與 `findAllUniqueTruckLanceCodeAndRemarkCombinations` 同一 (code, remark) 桶;remark 空則不帶參數。 */ export const findAllByTruckLanceCodeAndRemarkAndDeletedFalseAction = cache( async (truckLanceCode: string, remark: string | null | undefined) => { const endpoint = `${BASE_API_URL}/truck/findAllByTruckLanceCodeAndRemarkAndDeletedFalse`; const params = new URLSearchParams(); params.set("truckLanceCode", truckLanceCode); const r = remark != null && String(remark).trim() !== "" ? String(remark).trim() : ""; if (r !== "") params.set("remark", r); const url = `${endpoint}?${params.toString()}`; return serverFetchJson(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); }, ); export const updateTruckShopDetailsAction = async (data: UpdateTruckShopDetailsRequest) => { const endpoint = `${BASE_API_URL}/truck/updateTruckShopDetails`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; export const createTruckWithoutShopAction = async (data: CreateTruckWithoutShopRequest) => { const endpoint = `${BASE_API_URL}/truck/createTruckWithoutShop`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; /** PDF 圖1:每車線一個 worksheet(MTMS_ROUTE_V1)。回傳 base64 方便 client 下載。 */ export const exportRouteLanesExcelAction = async ( laneIds: string[], ): Promise<{ base64: string; filename: string }> => { const response = await serverFetch(`${BASE_API_URL}/truck/exportRouteLanesExcel`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }, body: JSON.stringify({ laneIds }), }); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Export failed: ${response.status} ${text}`.trim(), response, ); } const cd = response.headers.get("content-disposition") ?? ""; let filename = `MTMS_車線_${Date.now()}.xlsx`; const quoted = /filename="([^"]+)"/i.exec(cd); const star = /filename\*=UTF-8''([^;\s]+)/i.exec(cd); const raw = (star?.[1] || quoted?.[1])?.trim(); if (raw) { try { filename = decodeURIComponent(raw); } catch { filename = raw; } } const buf = await response.arrayBuffer(); return { base64: Buffer.from(buf).toString("base64"), filename, }; }; /** 圖2:車線 Report(單一 sheet;每間物流公司一個水平區塊)。 */ export const exportRouteReportExcelAction = async ( laneIds: string[], ): Promise<{ base64: string; filename: string }> => { const response = await serverFetch(`${BASE_API_URL}/truck/exportRouteReportExcel`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }, body: JSON.stringify({ laneIds }), }); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Export failed: ${response.status} ${text}`.trim(), response, ); } const cd = response.headers.get("content-disposition") ?? ""; let filename = `車線Report_${Date.now()}.xlsx`; const quoted = /filename="([^"]+)"/i.exec(cd); const star = /filename\*=UTF-8''([^;\s]+)/i.exec(cd); const raw = (star?.[1] || quoted?.[1])?.trim(); if (raw) { try { filename = decodeURIComponent(raw); } catch { filename = raw; } } const buf = await response.arrayBuffer(); return { base64: Buffer.from(buf).toString("base64"), filename, }; }; export const exportTruckLaneVersionReportExcelAction = async ( fromVersionId: number, toVersionId: number, ): Promise<{ base64: string; filename: string }> => { const response = await serverFetch( `${BASE_API_URL}/truck/exportTruckLaneVersionReportExcel`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }, body: JSON.stringify({ fromVersionId, toVersionId }), }, ); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Export failed: ${response.status} ${text}`.trim(), response, ); } const cd = response.headers.get("content-disposition") ?? ""; let filename = `車線版本報告_${Date.now()}.xlsx`; const quoted = /filename="([^"]+)"/i.exec(cd); const star = /filename\*=UTF-8''([^;\s]+)/i.exec(cd); const raw = (star?.[1] || quoted?.[1])?.trim(); if (raw) { try { filename = decodeURIComponent(raw); } catch { filename = raw; } } const buf = await response.arrayBuffer(); return { base64: Buffer.from(buf).toString("base64"), filename, }; }; export const importRouteLanesExcelAction = async ( formData: FormData, ): Promise => { const response = await serverFetch(`${BASE_API_URL}/truck/importRouteLanesExcel`, { method: "POST", body: formData, }); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Import failed: ${response.status} ${text}`.trim(), response, ); } return (await response.json()) as MessageResponse; }; export type RouteLaneImportPreviewRow = { truckRowId: number | null; truckLanceCode: string; remark: string | null; storeId: string; departureTime: string; shopId: number; shopName: string; shopCode: string; loadingSequence: number; districtReference: string | null; logisticId: number | null; }; export type ParseRouteLanesExcelResponse = { sheetCount: number; rowCount: number; rows: RouteLaneImportPreviewRow[]; }; export const parseRouteLanesExcelAction = async ( formData: FormData, ): Promise => { const response = await serverFetch(`${BASE_API_URL}/truck/parseRouteLanesExcel`, { method: "POST", body: formData, }); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Parse import failed: ${response.status} ${text}`.trim(), response, ); } return (await response.json()) as ParseRouteLanesExcelResponse; }; export const findAllUniqueShopNamesAndCodesFromTrucksAction = cache(async () => { const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopNamesAndCodesFromTrucks`; return serverFetchJson>(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const findAllUniqueRemarksFromTrucksAction = cache(async () => { const endpoint = `${BASE_API_URL}/truck/findAllUniqueRemarksFromTrucks`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const findAllUniqueShopCodesFromTrucksAction = cache(async () => { const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopCodesFromTrucks`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const findAllUniqueShopNamesFromTrucksAction = cache(async () => { const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopNamesFromTrucks`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); // ---- Truck lane version snapshot (DB snapshot) ---- export interface CreateTruckLaneSnapshotRequest { truckLanceCode?: string | null; note?: string | null; } export interface TruckLaneVersionResponse { id: number; truckLanceCode: string; note: string | null; created: string | null; createdBy?: string | null; /** truck_lane_version.modifiedBy(BaseEntity) */ modifiedBy?: string | null; } export interface TruckLaneVersionLineResponse { truckRowId: number; truckLanceCode: string | null; shopCode: string | null; branchName: string | null; districtReference: string | null; loadingSequence: number | null; departureTime: string | null; storeId: string; remark: string | null; logisticId: number | null; } export type DiffFieldChange = { field: string; from: string | null; to: string | null; }; export type TruckLaneVersionDiffLine = { truckRowId: number; shopCode: string | null; changes: DiffFieldChange[]; /** 快照車線(僅欄位異動時 changes 不含 truckLanceCode/remark) */ truckLanceCode?: string | null; remark?: string | null; }; export type LogisticMasterDiffLine = { logisticId: number; type: string; logisticName: string; carPlate: string; changeText: string; }; export type TruckLaneVersionDiffResponse = { fromVersionId: number; toVersionId: number; changed: TruckLaneVersionDiffLine[]; logisticMasterChanges?: LogisticMasterDiffLine[]; }; export const createTruckLaneSnapshotAction = async (data: CreateTruckLaneSnapshotRequest) => { const endpoint = `${BASE_API_URL}/truckLaneVersion/snapshot`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; export const listTruckLaneVersionsAction = cache(async (truckLanceCode?: string | null) => { const endpoint = `${BASE_API_URL}/truckLaneVersion`; const url = truckLanceCode != null && String(truckLanceCode).trim() !== "" ? `${endpoint}?truckLanceCode=${encodeURIComponent(String(truckLanceCode))}` : endpoint; return serverFetchJson(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const getTruckLaneVersionLinesAction = cache(async (versionId: number) => { const endpoint = `${BASE_API_URL}/truckLaneVersion/${versionId}/lines`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }); export const diffTruckLaneVersionsAction = async ( fromVersionId: number, toVersionId: number, ) => { const endpoint = `${BASE_API_URL}/truckLaneVersion/diff`; const url = `${endpoint}?fromVersionId=${encodeURIComponent(String(fromVersionId))}&toVersionId=${encodeURIComponent(String(toVersionId))}`; return serverFetchJson(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); }; export const restoreTruckLaneVersionAction = async (versionId: number) => { const endpoint = `${BASE_API_URL}/truckLaneVersion/${versionId}/restore`; return serverFetchJson(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, }); }; export type UpdateTruckLaneVersionNoteRequest = { note: string | null; }; export const updateTruckLaneVersionNoteAction = async ( versionId: number, data: UpdateTruckLaneVersionNoteRequest, ) => { const endpoint = `${BASE_API_URL}/truckLaneVersion/${versionId}/note`; return serverFetchJson(endpoint, { method: "PATCH", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; // ---- Truck lane schedule (server-side apply) ---- export type TruckLaneScheduleLineAction = | "MOVE" | "CREATE" | "DELETE" | "ENSURE_LANE"; export type TruckLaneMoveTargetRequest = { truckRowId: number; toTruckLanceCode: string; toRemark?: string | null; toStoreId: string; toLoadingSequence: number; toDistrictReference?: string | null; departureTime?: string | null; }; export type TruckLaneScheduleLineRequest = { action: TruckLaneScheduleLineAction; truckRowId?: number | null; toTruckLanceCode: string; toRemark?: string | null; toStoreId: string; toLoadingSequence?: number | null; toDistrictReference?: string | null; shopId?: number | null; shopCode?: string | null; shopName?: string | null; departureTime?: string | null; logisticId?: number | null; }; export type CreateTruckLaneScheduleRequest = { executeAt: string; note?: string | null; lines?: TruckLaneScheduleLineRequest[] | null; moves?: TruckLaneMoveTargetRequest[] | null; }; export type TruckLaneScheduleLineResponse = { id: number; action: TruckLaneScheduleLineAction; truckRowId: number | null; shopCode: string | null; shopName: string | null; fromTruckLanceCode: string | null; fromRemark: string | null; fromStoreId: string | null; fromLoadingSequence?: number | null; fromDistrictReference?: string | null; fromDepartureTime?: string | null; toTruckLanceCode: string; toRemark: string | null; toStoreId: string; toDistrictReference?: string | null; toLoadingSequence?: number | null; departureTime?: string | null; lineStatus: string; errorMessage: string | null; appliedAt: string | null; }; export type RouteExcelSchedulePlanPreviewRow = { action: TruckLaneScheduleLineAction; truckRowId: number | null; shopCode: string | null; shopName: string | null; toTruckLanceCode: string; toRemark: string | null; toStoreId: string; toLoadingSequence: number | null; }; export type RouteExcelSchedulePlanError = { shopCode: string; shopName: string; message: string; }; export type RouteExcelSchedulePlanCounts = { moves: number; creates: number; deletes: number; ensureLanes: number; }; export type RouteExcelSchedulePlanResponse = { sheetCount: number; rowCount: number; lines: TruckLaneScheduleLineRequest[]; previews: RouteExcelSchedulePlanPreviewRow[]; errors: RouteExcelSchedulePlanError[]; counts: RouteExcelSchedulePlanCounts; }; export type TruckLaneScheduleResponse = { id: number; executeAt: string; status: string; source: string; note: string | null; appliedAt: string | null; errorMessage: string | null; snapshotVersionId: number | null; preApplySnapshotVersionId?: number | null; created: string | null; createdBy?: string | null; modifiedBy: string | null; lines?: TruckLaneScheduleLineResponse[] | null; lineCounts?: { total: number; applied: number; failed: number; pending: number; } | null; }; export type PendingTruckRowIdsResponse = { /** 所有開放排程(PENDING/APPLYING)涉及的 truck rows(標記、排程驗證用) */ truckRowIds: number[]; /** 已進入鎖定時間窗(或 APPLYING)的 truck rows,看板不可手改 */ lockedTruckRowIds?: number[]; }; export type TruckLaneScheduleExcelPreviewRow = { rowIndex: number; shopCode: string; toTruckLanceCode: string; toRemark: string | null; toStoreId: string; executeAt: string | null; truckRowId: number | null; }; export type TruckLaneScheduleExcelRowError = { rowIndex: number; message: string; }; export type ParseTruckLaneScheduleExcelResponse = { rowCount: number; validCount: number; errorCount: number; rows: TruckLaneScheduleExcelPreviewRow[]; errors: TruckLaneScheduleExcelRowError[]; }; export const createTruckLaneScheduleAction = async ( data: CreateTruckLaneScheduleRequest, ): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; export const planTruckLaneScheduleFromRouteExcelAction = async ( formData: FormData, ): Promise => { const response = await serverFetch( `${BASE_API_URL}/truckLaneSchedule/planFromRouteExcel`, { method: "POST", body: formData, }, ); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Plan import failed: ${response.status} ${text}`.trim(), response, ); } return (await response.json()) as RouteExcelSchedulePlanResponse; }; export const listTruckLaneSchedulesAction = async ( status?: string[], limit: number = 200, ): Promise => { const params = new URLSearchParams(); for (const s of status ?? []) { params.append("status", s); } params.set("limit", String(limit)); const qs = params.toString(); const base = `${BASE_API_URL}/truckLaneSchedule`; return serverFetchJson( `${base}${qs ? `?${qs}` : ""}`, { method: "GET", headers: { "Content-Type": "application/json" }, }, ); }; export const getTruckLaneScheduleAction = async ( id: number, ): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }; export const pendingTruckLaneScheduleShopIdsAction = async (): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule/pendingShopIds`; return serverFetchJson(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, }); }; export const cancelTruckLaneScheduleAction = async ( id: number, ): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/cancel`; return serverFetchJson(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, }); }; export const applyNowTruckLaneScheduleAction = async ( id: number, ): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/applyNow`; return serverFetchJson(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, }); }; export type RetryFailedTruckLaneScheduleRequest = { executeAt?: string | null; }; export const retryFailedTruckLaneScheduleAction = async ( id: number, body?: RetryFailedTruckLaneScheduleRequest, ): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/retry-failed`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(body ?? {}), headers: { "Content-Type": "application/json" }, }); }; export const reactivateCancelledTruckLaneScheduleAction = async ( id: number, body?: RetryFailedTruckLaneScheduleRequest, ): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/reactivate`; return serverFetchJson(endpoint, { method: "POST", body: JSON.stringify(body ?? {}), headers: { "Content-Type": "application/json" }, }); }; export const ignoreTruckLaneScheduleAction = async ( id: number, ): Promise => { const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/ignore`; return serverFetchJson(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, }); }; export const parseTruckLaneScheduleExcelAction = async ( formData: FormData, defaultExecuteAt?: string | null, ): Promise => { const qs = defaultExecuteAt != null && defaultExecuteAt !== "" ? `?defaultExecuteAt=${encodeURIComponent(defaultExecuteAt)}` : ""; const response = await serverFetch( `${BASE_API_URL}/truckLaneSchedule/parseExcel${qs}`, { method: "POST", body: formData }, ); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Parse schedule Excel failed: ${response.status} ${text}`.trim(), response, ); } return (await response.json()) as ParseTruckLaneScheduleExcelResponse; }; export const importTruckLaneScheduleExcelAction = async ( formData: FormData, defaultExecuteAt?: string | null, note?: string | null, ): Promise => { const params = new URLSearchParams(); if (defaultExecuteAt) params.set("defaultExecuteAt", defaultExecuteAt); if (note) params.set("note", note); const qs = params.toString() ? `?${params.toString()}` : ""; const response = await serverFetch( `${BASE_API_URL}/truckLaneSchedule/importExcel${qs}`, { method: "POST", body: formData }, ); if (!response.ok) { const text = await response.text().catch(() => ""); throw new ServerFetchError( `Import schedule Excel failed: ${response.status} ${text}`.trim(), response, ); } return (await response.json()) as TruckLaneScheduleResponse[]; };