|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- "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" },
- });
- };
|