Ver a proveniência

update truck X and singal relesae

production
CANCERYS\kw093 há 5 dias
ascendente
cometimento
0c97254c74
10 ficheiros alterados com 143 adições e 75 eliminações
  1. +1
    -1
      src/app/(main)/doworkbench/edit/page.tsx
  2. +21
    -3
      src/app/api/doworkbench/actions.ts
  3. +2
    -1
      src/app/api/pickOrder/actions.ts
  4. +12
    -0
      src/app/utils/formatUtil.ts
  5. +17
    -6
      src/components/DoDetail/DoDetail.tsx
  6. +4
    -1
      src/components/DoDetail/DoDetailWrapper.tsx
  7. +68
    -33
      src/components/DoWorkbench/WorkbenchFloorLanePanel.tsx
  8. +1
    -9
      src/components/DoWorkbench/WorkbenchTicketReleaseTable.tsx
  9. +1
    -13
      src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx
  10. +16
    -8
      src/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal.tsx

+ 1
- 1
src/app/(main)/doworkbench/edit/page.tsx Ver ficheiro

@@ -36,7 +36,7 @@ const Page: React.FC<Props> = async ({ searchParams }) => {
</p>
<I18nProvider namespaces={["do", "common"]}>
<Suspense fallback={<DoDetail.Loading />}>
<DoDetail id={parseInt(id)} />
<DoDetail id={parseInt(id)} workbenchRelease />
</Suspense>
</I18nProvider>
</>


+ 21
- 3
src/app/api/doworkbench/actions.ts Ver ficheiro

@@ -231,19 +231,23 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelection(
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
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" });
return response ?? [];
if (response == null) return [];
return Array.isArray(response) ? response : [];
}

/** Same body as `/doPickOrder/assign-by-lane` but resolves `delivery_order_pick_order`. */
@@ -356,7 +360,21 @@ export async function fetchWorkbenchAvailableLotsByItem(itemId: number) {
},
);
}

/** 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;


+ 2
- 1
src/app/api/pickOrder/actions.ts Ver ficheiro

@@ -1677,7 +1677,8 @@ export const fetchReleasedDoPickOrdersForSelection = async (
export const fetchReleasedDoPickOrdersForSelectionToday = async (
shopName?: string,
storeId?: string,
truck?: string
truck?: string,
_requiredDeliveryDate?: string
): Promise<ReleasedDoPickOrderListItem[]> => {
const params = new URLSearchParams();
if (shopName?.trim()) params.append("shopName", shopName.trim());


+ 12
- 0
src/app/utils/formatUtil.ts Ver ficheiro

@@ -68,6 +68,18 @@ export const arrayToDayjs = (arr: ConfigType | (number | undefined)[], showTime:
return dayjs(tempArr as ConfigType);
};

/**
* Backend `LocalDate` is often JSON `[year, month, day]` with month 1–12.
* Do not pass that array to `dayjs(arr)` — it uses JS month index 0–11 and shifts the calendar month.
*/
export function requiredDeliveryDateToDayString(value: unknown): string {
if (value == null) return "";
if (Array.isArray(value) && value.length >= 3 && value.every((x) => typeof x === "number")) {
return arrayToDayjs(value as number[]).format(OUTPUT_DATE_FORMAT);
}
return dayjs(value as string | number | Date).format(OUTPUT_DATE_FORMAT);
}

export const arrayToDateString = (arr: ConfigType | (number | undefined)[], format: "input"|"output" = "output") => {
if (format == "output") {
return arrayToDayjs(arr).format(OUTPUT_DATE_FORMAT);


+ 17
- 6
src/components/DoDetail/DoDetail.tsx Ver ficheiro

@@ -9,7 +9,12 @@ import { useCallback, useState } from "react";
import { Button, Stack, Typography, Box, Alert } from "@mui/material";
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import StartIcon from "@mui/icons-material/Start";
import { releaseDo,startBatchReleaseAsyncSingle, assignPickOrderByStore, releaseAssignedPickOrderByStore } from "@/app/api/do/actions";
import { releaseDo, startBatchReleaseAsyncSingle, assignPickOrderByStore, releaseAssignedPickOrderByStore } from "@/app/api/do/actions";
import {
getWorkbenchBatchReleaseProgress,
startWorkbenchBatchReleaseAsyncSingleV2,
} from "@/app/api/doworkbench/actions";
import Swal from "sweetalert2";
import DoInfoCard from "./DoInfoCard";
import DoLineTable from "./DoLineTable";
import { useSession } from "next-auth/react";
@@ -63,14 +68,20 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
//userId: currentUserId // Pass user ID from session
})
*/
const response = await startBatchReleaseAsyncSingle({
const response = await startWorkbenchBatchReleaseAsyncSingleV2({
doId: id,
userId: currentUserId ?? 0
})
if (response) {
formProps.setValue("status", response.entity.status)
setSuccessMessage(t("DO released successfully! Pick orders created."))
}
if (response?.code === "STARTED") {
setSuccessMessage(t("DO released successfully! Pick orders created."));
router.refresh();
} else if (response) {
setServerError(
response.message ??
response.code ??
t("An error has occurred. Please try again later."),
);
}
}
} catch (e) {
setServerError(t("An error has occurred. Please try again later."));


+ 4
- 1
src/components/DoDetail/DoDetailWrapper.tsx Ver ficheiro

@@ -9,16 +9,19 @@ interface SubComponents {

type DoDetailProps = {
id?: number;
/** When true (e.g. `/doworkbench/edit`), Release uses workbench `delivery_order_pick_order` async V2 + job progress. */
workbenchRelease?: boolean;
}

type Props = DoDetailProps

const DoDetailWrapper: React.FC<Props> & SubComponents = async ({
id,
workbenchRelease = false,
}) => {
const doDetail = id ? await fetchDoDetail(id) : undefined

return <DoDetail id={id} defaultValues={doDetail}/>
return <DoDetail id={id} defaultValues={doDetail} workbenchRelease={workbenchRelease} />
}

DoDetailWrapper.Loading = GeneralLoading;


+ 68
- 33
src/components/DoWorkbench/WorkbenchFloorLanePanel.tsx Ver ficheiro

@@ -47,6 +47,13 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSwitc
const [ticketFloor, setTicketFloor] = useState<"2/F" | "4/F">("2/F");
const defaultTruckCount = summary4F?.defaultTruckCount ?? 0;

const selectedDeliveryDateYmd = useMemo(() => {
if (selectedDate === "today") return dayjs().format("YYYY-MM-DD");
if (selectedDate === "tomorrow") return dayjs().add(1, "day").format("YYYY-MM-DD");
if (selectedDate === "dayAfterTomorrow") return dayjs().add(2, "day").format("YYYY-MM-DD");
return dayjs().format("YYYY-MM-DD");
}, [selectedDate]);

const hasLoggedRef = useRef(false);
const fullReadyLoggedRef = useRef(false);
const pendingRef = useRef(0);
@@ -54,7 +61,13 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSwitc
const workbenchReleasedListBridge = useMemo(
() => ({
loadBeforeToday: fetchWorkbenchReleasedDoPickOrdersForSelection,
loadToday: fetchWorkbenchReleasedDoPickOrdersForSelectionToday,
loadToday: (
shopName?: string,
storeId?: string,
truck?: string,
requiredDeliveryDate?: string
) =>
fetchWorkbenchReleasedDoPickOrdersForSelectionToday(shopName, storeId, truck, requiredDeliveryDate),
assignByListItemId: assignByDeliveryOrderPickOrderId,
}),
[],
@@ -358,6 +371,38 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSwitc
</Grid>
)}

<Grid item xs={12}>
<Stack direction="row" spacing={2} alignItems="flex-start">
<Stack sx={{ minWidth: 60, pt: 1 }} spacing={0.25}>
<Typography sx={{ fontWeight: 600 }}>{t("Truck X")}</Typography>
{/*
<Typography variant="caption" color="text.secondary" sx={{ lineHeight: 1.2 }}>
{t("Required Date")}: {selectedDeliveryDateYmd}
</Typography>
*/}
</Stack>
<Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
{defaultTruckCount === 0 ? (
renderNoEntry()
) : (
<Button
variant="outlined"
onClick={() => {
setSelectedStore("");
setSelectedTruck("車線-X");
setIsDefaultTruck(true);
setDefaultDateScope("today");
setModalOpen(true);
}}
>
{/*{`${selectedDeliveryDateYmd} (${defaultTruckCount})`}*/}
{t("車線-X")}({defaultTruckCount})
</Button>
)}
</Box>
</Stack>
</Grid>

<Grid item xs={12}>
<Box sx={{ py: 2, mt: 1, mb: 0.5, borderTop: "1px solid #e0e0e0" }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 0.5 }}>
@@ -431,39 +476,28 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSwitc

<Grid item xs={12}>
<Stack direction="row" spacing={2} alignItems="flex-start">
<Typography sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>{t("Truck X")}</Typography>
<Stack sx={{ minWidth: 60, pt: 1 }} spacing={0.25}>
<Typography sx={{ fontWeight: 600 }}>{t("Truck X")}</Typography>
<Typography variant="caption" color="text.secondary" sx={{ lineHeight: 1.2 }}>
{t("Before today")}
</Typography>
</Stack>
<Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
{beforeTodayTruckXCount === 0 && defaultTruckCount === 0 ? renderNoEntry() : (
<Stack direction="row" spacing={1}>
{defaultTruckCount > 0 && (
<Button
variant="outlined"
onClick={() => {
setSelectedStore("");
setSelectedTruck("車線-X");
setIsDefaultTruck(true);
setDefaultDateScope("today");
setModalOpen(true);
}}
>
{`${t("Today")} (${defaultTruckCount})`}
</Button>
)}
{beforeTodayTruckXCount > 0 && (
<Button
variant="outlined"
onClick={() => {
setSelectedStore("4/F");
setSelectedTruck("車線-X");
setIsDefaultTruck(true);
setDefaultDateScope("before");
setModalOpen(true);
}}
>
{`${t("車線-X")} (${beforeTodayTruckXCount})`}
</Button>
)}
</Stack>
{beforeTodayTruckXCount === 0 ? (
renderNoEntry()
) : (
<Button
variant="outlined"
onClick={() => {
setSelectedStore("4/F");
setSelectedTruck("車線-X");
setIsDefaultTruck(true);
setDefaultDateScope("before");
setModalOpen(true);
}}
>
{`${t("車線-X")} (${beforeTodayTruckXCount})`}
</Button>
)}
</Box>
</Stack>
@@ -475,6 +509,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSwitc
truck={selectedTruck}
isDefaultTruck={isDefaultTruck}
defaultDateScope={defaultDateScope}
defaultTruckRequiredDeliveryDate={selectedDeliveryDateYmd}
listBridge={workbenchReleasedListBridge}
onClose={() => setModalOpen(false)}
onAssigned={() => {


+ 1
- 9
src/components/DoWorkbench/WorkbenchTicketReleaseTable.tsx Ver ficheiro

@@ -30,7 +30,7 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import dayjs, { Dayjs } from "dayjs";
import { arrayToDayjs } from "@/app/utils/formatUtil";
import { arrayToDayjs, requiredDeliveryDateToDayString } from "@/app/utils/formatUtil";
import {
fetchWorkbenchTicketReleaseTable,
forceCompleteWorkbenchTicket,
@@ -41,14 +41,6 @@ import Swal from "sweetalert2";
import { AUTH } from "@/authorities";
import { SessionWithTokens } from "@/config/authConfig";

function requiredDeliveryDateToDayString(value: unknown): string {
if (value == null) return "";
if (Array.isArray(value) && value.length >= 3 && value.every((x) => typeof x === "number")) {
return arrayToDayjs(value as number[]).format("YYYY-MM-DD");
}
return dayjs(value as string | number | Date).format("YYYY-MM-DD");
}

function formatTicketDateTime(value: unknown): string {
if (!value) return "-";
if (Array.isArray(value)) {


+ 1
- 13
src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx Ver ficheiro

@@ -35,7 +35,7 @@ import {
import { useTranslation } from "react-i18next";
import { useSession } from "next-auth/react";
import dayjs, { Dayjs } from "dayjs";
import { arrayToDayjs } from "@/app/utils/formatUtil";
import { arrayToDayjs, requiredDeliveryDateToDayString } from "@/app/utils/formatUtil";
import { fetchTicketReleaseTable, getTicketReleaseTable } from "@/app/api/do/actions";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
@@ -69,18 +69,6 @@ function shouldLogTicketFilterDebug(): boolean {
return (window as unknown as { __FG_TICKET_FILTER_DEBUG__?: boolean }).__FG_TICKET_FILTER_DEBUG__ === true;
}

/**
* 後端 LocalDate 常序列化為 [year, month, day](month 為 1–12)。
* 不可使用 dayjs(array):會被當成 [年, 月索引 0–11, 日],導致月份錯一個月、篩選與畫面日期錯誤。
*/
function requiredDeliveryDateToDayString(value: unknown): string {
if (value == null) return "";
if (Array.isArray(value) && value.length >= 3 && value.every((x) => typeof x === "number")) {
return arrayToDayjs(value as number[]).format("YYYY-MM-DD");
}
return dayjs(value as string | number | Date).format("YYYY-MM-DD");
}

const FGPickOrderTicketReleaseTable: React.FC = () => {
const { t } = useTranslation("ticketReleaseTable");
const { data: session } = useSession() as { data: SessionWithTokens | null };


+ 16
- 8
src/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal.tsx Ver ficheiro

@@ -28,7 +28,7 @@ import {
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import Swal from "sweetalert2";
import dayjs from "dayjs";
import { requiredDeliveryDateToDayString } from "@/app/utils/formatUtil";

/** DO workbench 使用 workbench released API;Finished Good 不傳即可 */
export type ReleasedDoPickListBridge = {
@@ -37,10 +37,12 @@ export type ReleasedDoPickListBridge = {
storeId?: string,
truck?: string
) => Promise<ReleasedDoPickOrderListItem[]>;
/** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */
loadToday: (
shopName?: string,
storeId?: string,
truck?: string
truck?: string,
requiredDeliveryDate?: string
) => Promise<ReleasedDoPickOrderListItem[]>;
assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>;
};
@@ -55,6 +57,8 @@ interface Props {
/** Truck X only: today → released-today; before → released (歷史未完工) */
defaultDateScope?: "today" | "before";
listBridge?: ReleasedDoPickListBridge;
/** Workbench: `delivery_order_pick_order.requiredDeliveryDate` for Truck X (select day); used when [defaultDateScope] is today. */
defaultTruckRequiredDeliveryDate?: string;
}

const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
@@ -66,6 +70,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
isDefaultTruck,
defaultDateScope: defaultDateScopeProp = "today",
listBridge,
defaultTruckRequiredDeliveryDate,
}) => {
const { t } = useTranslation("pickOrder");
const { data: session } = useSession() as { data: SessionWithTokens | null };
@@ -85,7 +90,12 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({

if (isDefaultTruck) {
if (defaultDateScopeProp === "today") {
data = await loadTodayFn(undefined, undefined, "車線-X");
data = await loadTodayFn(
undefined,
undefined,
"車線-X",
defaultTruckRequiredDeliveryDate?.trim() || undefined
);
} else {
data = await loadReleased(undefined, undefined, "車線-X");
}
@@ -104,7 +114,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
} finally {
setLoading(false);
}
}, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScopeProp, listBridge]);
}, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScopeProp, listBridge, defaultTruckRequiredDeliveryDate]);

useEffect(() => {
loadList();
@@ -117,7 +127,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
title: t("Confirm Assignment"),
html: `
<div style="text-align: left;">
<p><strong>${t("Date")}:</strong> ${item.requiredDeliveryDate ?? "-"}</p>
<p><strong>${t("Date")}:</strong> ${requiredDeliveryDateToDayString(item.requiredDeliveryDate) || "-"}</p>
<p><strong>${t("Shop")}:</strong> ${item.shopName ?? item.shopCode ?? "-"}</p>
<p><strong>${t("Truck")}:</strong> ${item.truckLanceCode ?? "-"}</p>
<p><strong>${t("Delivery Order")}:</strong> ${(item.deliveryOrderCodes ?? []).join(", ")}</p>
@@ -220,9 +230,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
{list.map((row) => (
<TableRow key={row.id} hover>
<TableCell>
{row.requiredDeliveryDate
? dayjs(row.requiredDeliveryDate).format("YYYY-MM-DD")
: "-"}
{requiredDeliveryDateToDayString(row.requiredDeliveryDate) || "-"}
</TableCell>
<TableCell>{row.shopName ?? row.shopCode ?? "-"}</TableCell>
<TableCell>{row.truckLanceCode ?? "-"}</TableCell>


Carregando…
Cancelar
Guardar