|
- "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<string, string | number | null>) => {
- 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<ShopAndTruck[]>(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<Truck[]>(endpoint, {
- method: "GET",
- headers: { "Content-Type": "application/json" },
- });
- });
-
- export const updateTruckLaneAction = async (data: SaveTruckLane) => {
- const endpoint = `${BASE_API_URL}/truck/updateTruckLane`;
-
- return serverFetchJson<MessageResponse>(endpoint, {
- method: "POST",
- body: JSON.stringify(data),
- headers: { "Content-Type": "application/json" },
- });
- };
-
- export const updateLaneLogisticAction = async (
- data: UpdateLaneLogisticRequest,
- ): Promise<MessageResponse> => {
- const endpoint = `${BASE_API_URL}/truck/updateLaneLogistic`;
- return serverFetchJson<MessageResponse>(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<MessageResponse>(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<MessageResponse>(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<Truck[]>(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<Truck[]>(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<ShopAndTruck[]>(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<ShopAndTruck[]>(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<Truck[]>(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<Truck[]>(url, {
- method: "GET",
- headers: { "Content-Type": "application/json" },
- });
- },
- );
-
- export const updateTruckShopDetailsAction = async (data: UpdateTruckShopDetailsRequest) => {
- const endpoint = `${BASE_API_URL}/truck/updateTruckShopDetails`;
-
- return serverFetchJson<MessageResponse>(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<MessageResponse>(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<MessageResponse> => {
- 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<ParseRouteLanesExcelResponse> => {
- 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<Array<{ name: string; code: string }>>(endpoint, {
- method: "GET",
- headers: { "Content-Type": "application/json" },
- });
- });
-
- export const findAllUniqueRemarksFromTrucksAction = cache(async () => {
- const endpoint = `${BASE_API_URL}/truck/findAllUniqueRemarksFromTrucks`;
-
- return serverFetchJson<string[]>(endpoint, {
- method: "GET",
- headers: { "Content-Type": "application/json" },
- });
- });
-
- export const findAllUniqueShopCodesFromTrucksAction = cache(async () => {
- const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopCodesFromTrucks`;
-
- return serverFetchJson<string[]>(endpoint, {
- method: "GET",
- headers: { "Content-Type": "application/json" },
- });
- });
-
- export const findAllUniqueShopNamesFromTrucksAction = cache(async () => {
- const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopNamesFromTrucks`;
-
- return serverFetchJson<string[]>(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;
- /** 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[];
- };
-
- 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<TruckLaneVersionResponse>(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<TruckLaneVersionResponse[]>(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<TruckLaneVersionLineResponse[]>(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<TruckLaneVersionDiffResponse>(url, {
- method: "GET",
- headers: { "Content-Type": "application/json" },
- });
- };
-
- export const restoreTruckLaneVersionAction = async (versionId: number) => {
- const endpoint = `${BASE_API_URL}/truckLaneVersion/${versionId}/restore`;
- return serverFetchJson<MessageResponse>(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<TruckLaneVersionResponse>(endpoint, {
- method: "PATCH",
- body: JSON.stringify(data),
- headers: { "Content-Type": "application/json" },
- });
- };
|