|
- "use server";
-
- import { revalidateTag } from "next/cache";
- import { BASE_API_URL } from "@/config/api";
- import { serverFetchJson } from "@/app/utils/fetchUtil";
- import type {
- PostPickOrderResponse,
- ReleasedDoPickOrderListItem,
- StoreLaneSummary,
- } from "@/app/api/pickOrder/actions";
- import dayjs from "dayjs";
-
- /** Aligns with backend MessageResponse for workbench endpoints */
- export type WorkbenchMessageResponse = {
- id?: number | null;
- code?: string | null;
- name?: string | null;
- type?: string | null;
- message?: string | null;
- errorPosition?: string | null;
- entity?: unknown;
- };
-
- export async function startWorkbenchBatchReleaseAsync(data: {
- ids: number[];
- userId: number;
- }): Promise<WorkbenchMessageResponse> {
- const { ids, userId } = data;
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/batch-release/async?userId=${userId}`,
- {
- method: "POST",
- body: JSON.stringify(ids),
- headers: { "Content-Type": "application/json" },
- }
- );
- }
-
- /** V2: no SPL/stock out at batch release; created when assigning delivery_order_pick_order. */
- export async function startWorkbenchBatchReleaseAsyncV2(data: {
- ids: number[];
- userId: number;
- }): Promise<WorkbenchMessageResponse> {
- const { ids, userId } = data;
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/batch-release/async-v2?userId=${userId}`,
- {
- method: "POST",
- body: JSON.stringify(ids),
- headers: { "Content-Type": "application/json" },
- }
- );
- }
-
- export async function workbenchBatchReleaseSyncV2(data: {
- ids: number[];
- userId: number;
- }): Promise<WorkbenchMessageResponse> {
- const { ids, userId } = data;
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/batch-release/sync-v1?userId=${userId}`,
- {
- method: "POST",
- body: JSON.stringify(ids),
- headers: { "Content-Type": "application/json" },
- }
- );
- }
-
- export async function getWorkbenchBatchReleaseProgress(
- jobId: string
- ): Promise<WorkbenchMessageResponse> {
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/batch-release/progress/${encodeURIComponent(jobId)}`,
- { method: "GET" }
- );
- }
-
- export type WorkbenchScanPickBody = {
- stockOutLineId: number;
- lotNo: string;
- /** From QR: ties to a single `inventory_lot` when lotNo is reused across stock-ins */
- stockInLineId?: number | null;
- /**
- * When set (e.g. label-print modal row), backend resolves this exact inventory lot line
- * instead of stockInLineId / newest-by-lotNo.
- */
- inventoryLotLineId?: number | null;
- /** Optional store scope (e.g. 2/F). When set, split re-suggestions must stay within the same store. */
- storeId?: string | null;
- /** Optional: exclude these warehouse codes from resuggest logic */
- excludeWarehouseCodes?: string[] | null;
- /** Optional decimal string or number serialized by JSON */
- qty?: number | string | null;
- userId: number;
- };
-
- function serializeWorkbenchQty(
- qty: number | string | null | undefined
- ): number | undefined {
- if (qty === null || qty === undefined || qty === "") return undefined;
- const n = typeof qty === "string" ? Number(qty) : qty;
- if (typeof n !== "number" || Number.isNaN(n) || !Number.isFinite(n)) return undefined;
- // 0 is a valid explicit workbench short submit (must be sent, not omitted)
- return n;
- }
-
- /**
- * DO workbench scan-pick. Omit `qty` for full remaining on this SOL chunk (backend may split if lot runs out).
- * Pass `qty` less than remaining for short submit (POL/SOL completed without `partially_completed` on POL).
- * Pass `qty` greater than remaining to overscan: backend posts up to lot availability, then rebuild/ensure SOL.
- */
- export async function workbenchScanPick(
- body: WorkbenchScanPickBody
- ): Promise<WorkbenchMessageResponse> {
- const qty = serializeWorkbenchQty(body.qty);
- const sil = body.stockInLineId;
- const stockInLineId =
- typeof sil === "number" && Number.isFinite(sil) && sil > 0 ? sil : undefined;
- const storeId =
- typeof body.storeId === "string" && body.storeId.trim() !== ""
- ? body.storeId.trim()
- : undefined;
- const excludeWarehouseCodes =
- Array.isArray(body.excludeWarehouseCodes) && body.excludeWarehouseCodes.length > 0
- ? body.excludeWarehouseCodes
- .map((c) => (typeof c === "string" ? c.trim() : ""))
- .filter((c) => c !== "")
- : undefined;
- const ill = body.inventoryLotLineId;
- const inventoryLotLineId =
- typeof ill === "number" && Number.isFinite(ill) && ill > 0 ? ill : undefined;
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/scan-pick`,
- {
- method: "POST",
- body: JSON.stringify({
- stockOutLineId: body.stockOutLineId,
- lotNo: body.lotNo,
- ...(stockInLineId !== undefined ? { stockInLineId } : {}),
- ...(inventoryLotLineId !== undefined ? { inventoryLotLineId } : {}),
- ...(storeId !== undefined ? { storeId } : {}),
- ...(excludeWarehouseCodes !== undefined ? { excludeWarehouseCodes } : {}),
- ...(qty !== undefined ? { qty } : {}),
- userId: body.userId,
- }),
- headers: { "Content-Type": "application/json" },
- }
- );
- }
-
- export type WorkbenchBatchScanPickBody = {
- lines: WorkbenchScanPickBody[];
- };
-
- /**
- * DO workbench batch scan-pick.
- * Intended for batch-submit style flows where we close multiple SOLs (commonly qty=0 for noLot/expired/unavailable).
- */
- export async function workbenchBatchScanPick(
- body: WorkbenchBatchScanPickBody,
- ): Promise<WorkbenchMessageResponse> {
- const lines = Array.isArray(body.lines) ? body.lines : [];
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/scan-pick/batch`,
- {
- method: "POST",
- body: JSON.stringify({
- lines: lines.map((l) => {
- const qty = serializeWorkbenchQty(l.qty);
- const sil = l.stockInLineId;
- const stockInLineId =
- typeof sil === "number" && Number.isFinite(sil) && sil > 0 ? sil : undefined;
- const storeId =
- typeof l.storeId === "string" && l.storeId.trim() !== "" ? l.storeId.trim() : undefined;
- const excludeWarehouseCodes =
- Array.isArray(l.excludeWarehouseCodes) && l.excludeWarehouseCodes.length > 0
- ? l.excludeWarehouseCodes
- .map((c) => (typeof c === "string" ? c.trim() : ""))
- .filter((c) => c !== "")
- : undefined;
- const ill = l.inventoryLotLineId;
- const inventoryLotLineId =
- typeof ill === "number" && Number.isFinite(ill) && ill > 0 ? ill : undefined;
- return {
- stockOutLineId: l.stockOutLineId,
- lotNo: l.lotNo ?? "",
- ...(stockInLineId !== undefined ? { stockInLineId } : {}),
- ...(inventoryLotLineId !== undefined ? { inventoryLotLineId } : {}),
- ...(storeId !== undefined ? { storeId } : {}),
- ...(excludeWarehouseCodes !== undefined ? { excludeWarehouseCodes } : {}),
- ...(qty !== undefined ? { qty } : {}),
- userId: l.userId,
- };
- }),
- }),
- headers: { "Content-Type": "application/json" },
- },
- );
- }
-
- /** Store lane grid backed by `delivery_order_pick_order` + `pick_order.deliveryOrderPickOrderId`. */
- export async function fetchWorkbenchStoreLaneSummary(
- storeId: string,
- requiredDate?: string,
- releaseType?: string
- ): Promise<StoreLaneSummary> {
- const dateToUse = requiredDate || dayjs().format("YYYY-MM-DD");
- const rt = releaseType || "all";
- const url = `${BASE_API_URL}/doPickOrder/workbench/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}&releaseType=${encodeURIComponent(rt)}`;
- return serverFetchJson<StoreLaneSummary>(url, {
- method: "GET",
- cache: "no-store",
- next: { revalidate: 0 },
- });
- }
-
- /** Past-date `delivery_order_pick_order` tickets (same shape as `/doPickOrder/released`). */
- export async function fetchWorkbenchReleasedDoPickOrdersForSelection(
- shopName?: string,
- storeId?: string,
- truck?: string
- ): Promise<ReleasedDoPickOrderListItem[]> {
- const params = new URLSearchParams();
- if (shopName?.trim()) params.append("shopName", shopName.trim());
- if (storeId?.trim()) params.append("storeId", storeId.trim());
- if (truck?.trim()) params.append("truck", truck.trim());
- const query = params.toString();
- const url = `${BASE_API_URL}/doPickOrder/workbench/released${query ? `?${query}` : ""}`;
- const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" });
- return response ?? [];
- }
-
- /** When `requiredDeliveryDate` is set (YYYY-MM-DD), filters `delivery_order_pick_order.requiredDeliveryDate`; otherwise calendar today. */
- export async function fetchWorkbenchReleasedDoPickOrdersForSelectionToday(
- shopName?: string,
- storeId?: string,
- truck?: string,
- requiredDeliveryDate?: string
- ): Promise<ReleasedDoPickOrderListItem[]> {
- const params = new URLSearchParams();
- if (shopName?.trim()) params.append("shopName", shopName.trim());
- if (storeId?.trim()) params.append("storeId", storeId.trim());
- if (truck?.trim()) params.append("truck", truck.trim());
- if (requiredDeliveryDate?.trim()) params.append("requiredDate", requiredDeliveryDate.trim());
- const query = params.toString();
- const url = `${BASE_API_URL}/doPickOrder/workbench/released-today${query ? `?${query}` : ""}`;
- const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" });
- if (response == null) return [];
- return Array.isArray(response) ? response : [];
- }
-
- /** Same body as `/doPickOrder/assign-by-lane` but resolves `delivery_order_pick_order`. */
- export async function assignWorkbenchByLane(data: {
- userId: number;
- storeId: string;
- truckLanceCode: string;
- truckDepartureTime?: string;
- loadingSequence?: number | null;
- requiredDate?: string;
- }): Promise<PostPickOrderResponse> {
- const res = await serverFetchJson<PostPickOrderResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/assign-by-lane`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(data),
- }
- );
- revalidateTag("pickorder");
- return res;
- }
-
- /** Assign V1 (legacy): old FG-style, no atomic conflict guard. */
- export async function assignWorkbenchByLaneV1(data: {
- userId: number;
- storeId: string;
- truckLanceCode: string;
- truckDepartureTime?: string;
- requiredDate?: string;
- }): Promise<PostPickOrderResponse> {
- const res = await serverFetchJson<PostPickOrderResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/assign-by-lane-v1`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(data),
- }
- );
- revalidateTag("pickorder");
- return res;
- }
-
- export async function assignByDeliveryOrderPickOrderId(
- userId: number,
- deliveryOrderPickOrderId: number
- ): Promise<PostPickOrderResponse> {
- const res = await serverFetchJson<PostPickOrderResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/assign-by-delivery-order-pick-order-id`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ userId, deliveryOrderPickOrderId }),
- }
- );
- revalidateTag("pickorder");
- return res;
- }
-
- /** Assign V1 (legacy): old FG-style, no atomic conflict guard. */
- export async function assignByDeliveryOrderPickOrderIdV1(
- userId: number,
- deliveryOrderPickOrderId: number
- ): Promise<PostPickOrderResponse> {
- const res = await serverFetchJson<PostPickOrderResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/assign-by-delivery-order-pick-order-id-v1`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ userId, deliveryOrderPickOrderId }),
- }
- );
- revalidateTag("pickorder");
- return res;
- }
-
- export async function fetchWorkbenchCompletedLotDetails(
- deliveryOrderPickOrderId: number,
- ): Promise<any> {
- return serverFetchJson<any>(
- `${BASE_API_URL}/doPickOrder/workbench/completed-lot-details/${deliveryOrderPickOrderId}`,
- { method: "GET" },
- );
- }
- export type WorkbenchScanPayload = {
- itemId: number;
- stockInLineId: number;
- };
- export async function fetchWorkbenchPrinters() {
- return serverFetchJson<any[]>(`${BASE_API_URL}/printers`, {
- method: "GET",
- cache: "no-store",
- });
- }
- export async function analyzeWorkbenchQrCode(payload: WorkbenchScanPayload) {
- return serverFetchJson<any>(`${BASE_API_URL}/inventoryLotLine/workbench/analyze-qr-code`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(payload),
- cache: "no-store",
- });
- }
-
- export async function fetchWorkbenchAvailableLotsByItem(itemId: number) {
- return serverFetchJson<any>(
- `${BASE_API_URL}/inventoryLotLine/workbench/available-lots-by-item/${itemId}`,
- {
- method: "GET",
- cache: "no-store",
- },
- );
- }
- /** Single DO; JSON body is one number (same as legacy `batch-release/async-single`). */
- export async function startWorkbenchBatchReleaseAsyncSingleV2(data: {
- doId: number;
- userId: number;
- }): Promise<WorkbenchMessageResponse> {
- const { doId, userId } = data;
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/doPickOrder/workbench/batch-release/async-single-v2?userId=${userId}`,
- {
- method: "POST",
- body: JSON.stringify(doId),
- headers: { "Content-Type": "application/json" },
- }
- );
- }
- export async function printWorkbenchLotLabel(params: {
- inventoryLotLineId: number;
- printerId: number;
- printQty: number;
- }) {
- const searchParams = new URLSearchParams();
- searchParams.set("inventoryLotLineId", String(params.inventoryLotLineId));
- searchParams.set("printerId", String(params.printerId));
- searchParams.set("printQty", String(params.printQty));
- return serverFetchJson<WorkbenchMessageResponse>(
- `${BASE_API_URL}/inventoryLotLine/workbench/print-label?${searchParams.toString()}`,
- { method: "GET", cache: "no-store" },
- );
- }
|