Browse Source

do fix, ticket release table update(done), document printing

master
kelvin.yau 1 month ago
parent
commit
9974a820dd
12 changed files with 242 additions and 94 deletions
  1. +1
    -1
      src/app/(main)/finishedGood/detail/page.tsx
  2. +1
    -1
      src/app/(main)/finishedGood/page.tsx
  3. +37
    -8
      src/app/api/do/actions.tsx
  4. +1
    -0
      src/app/api/pickOrder/actions.ts
  5. +72
    -21
      src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx
  6. +48
    -30
      src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx
  7. +22
    -7
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  8. +4
    -0
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  9. +13
    -21
      src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx
  10. +11
    -3
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  11. +3
    -2
      src/i18n/zh/pickOrder.json
  12. +29
    -0
      src/i18n/zh/ticketReleaseTable.json

+ 1
- 1
src/app/(main)/finishedGood/detail/page.tsx View File

@@ -18,7 +18,7 @@ const PickOrder: React.FC<Props> = async ({ searchParams }) => {


return ( return (
<> <>
<I18nProvider namespaces={["pickOrder", "common"]}>
<I18nProvider namespaces={["pickOrder", "common", "ticketReleaseTable"]}>
<Suspense fallback={<FinishedGoodSearchWrapper.Loading />}> <Suspense fallback={<FinishedGoodSearchWrapper.Loading />}>
<FinishedGoodSearchWrapper /> <FinishedGoodSearchWrapper />
</Suspense> </Suspense>


+ 1
- 1
src/app/(main)/finishedGood/page.tsx View File

@@ -17,7 +17,7 @@ const PickOrder: React.FC = async () => {


return ( return (
<> <>
<I18nProvider namespaces={["pickOrder", "common"]}>
<I18nProvider namespaces={["pickOrder", "common", "ticketReleaseTable"]}>
<Suspense fallback={<FinishedGoodSearch.Loading />}> <Suspense fallback={<FinishedGoodSearch.Loading />}>
<FinishedGoodSearch /> <FinishedGoodSearch />
</Suspense> </Suspense>


+ 37
- 8
src/app/api/do/actions.tsx View File

@@ -3,7 +3,7 @@ import { BASE_API_URL } from "@/config/api";
// import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { revalidateTag } from "next/cache"; import { revalidateTag } from "next/cache";
import { cache } from "react"; import { cache } from "react";
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { serverFetch, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { QcItemResult } from "../settings/qcItem"; import { QcItemResult } from "../settings/qcItem";
import { RecordsRes } from "../utils"; import { RecordsRes } from "../utils";
import { DoResult } from "."; import { DoResult } from ".";
@@ -129,11 +129,22 @@ export interface getTicketReleaseTable {
shopName: string | null; shopName: string | null;
requiredDeliveryDate: string | null; requiredDeliveryDate: string | null;
handlerName: string | null; handlerName: string | null;
numberOfFGItems: number;
} }


export const fetchTicketReleaseTable = cache(async ()=> {
export const fetchTicketReleaseTable = cache(async (startDate?: string, endDate?: string) => {
const params = new URLSearchParams();
if (startDate) {
params.append('startDate', startDate);
}
if (endDate) {
params.append('endDate', endDate);
}
const url = `${BASE_API_URL}/doPickOrder/ticket-release-table${params.toString() ? `?${params.toString()}` : ''}`;
return await serverFetchJson<getTicketReleaseTable[]>( return await serverFetchJson<getTicketReleaseTable[]>(
`${BASE_API_URL}/doPickOrder/ticket-release-table`,
url,
{ {
method: "GET", method: "GET",
} }
@@ -226,11 +237,29 @@ export async function printDN(request: PrintDeliveryNoteRequest){
params.append('numOfCarton', request.numOfCarton.toString()); params.append('numOfCarton', request.numOfCarton.toString());
params.append('isDraft', request.isDraft.toString()); params.append('isDraft', request.isDraft.toString());


const response = await serverFetchWithNoContent(`${BASE_API_URL}/do/print-DN?${params.toString()}`,{
method: "GET",
});
return { success: true, message: "Print job sent successfully (DN)" } as PrintDeliveryNoteResponse;
try {
const response = await serverFetch(`${BASE_API_URL}/do/print-DN?${params.toString()}`, {
method: "GET",
});

if (response.ok) {
return { success: true, message: "Print job sent successfully (DN)" } as PrintDeliveryNoteResponse;
}

const errorText = await response.text();
console.error("Print DN error:", errorText);
return {
success: false,
message: "No data found for this pick order."
} as PrintDeliveryNoteResponse;

} catch (error) {
console.error("Error in printDN:", error);
return {
success: false,
message: "No data found for this pick order."
} as PrintDeliveryNoteResponse;
}
} }


export async function printDNLabels(request: PrintDNLabelsRequest){ export async function printDNLabels(request: PrintDNLabelsRequest){


+ 1
- 0
src/app/api/pickOrder/actions.ts View File

@@ -357,6 +357,7 @@ export interface CompletedDoPickOrderResponse {
storeId: string; storeId: string;
completedDate: string; completedDate: string;
fgPickOrders: FGPickOrderResponse[]; fgPickOrders: FGPickOrderResponse[];
deliveryNoteCode: number;
} }


// 新增:搜索参数接口 // 新增:搜索参数接口


+ 72
- 21
src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx View File

@@ -24,13 +24,15 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { arrayToDayjs } from '@/app/utils/formatUtil';
import { fetchTicketReleaseTable, getTicketReleaseTable } from '@/app/api/do/actions'; import { fetchTicketReleaseTable, getTicketReleaseTable } from '@/app/api/do/actions';
import { time } from 'console';


const FGPickOrderTicketReleaseTable: React.FC = () => { const FGPickOrderTicketReleaseTable: React.FC = () => {
const { t } = useTranslation("pickOrder");
const { t } = useTranslation("ticketReleaseTable");
const [selectedDate, setSelectedDate] = useState<string>("today"); const [selectedDate, setSelectedDate] = useState<string>("today");
const [selectedFloor, setSelectedFloor] = useState<string>(""); const [selectedFloor, setSelectedFloor] = useState<string>("");
const [selectedStatus, setSelectedStatus] = useState<string>("released");

const [data, setData] = useState<getTicketReleaseTable[]>([]); const [data, setData] = useState<getTicketReleaseTable[]>([]);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [paginationController, setPaginationController] = useState({ const [paginationController, setPaginationController] = useState({
@@ -62,16 +64,22 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
const formattedMinute = minute.toString().padStart(2, '0'); const formattedMinute = minute.toString().padStart(2, '0');
return `${formattedHour}:${formattedMinute}`; return `${formattedHour}:${formattedMinute}`;
}; };

const getDateLabel = (offset: number) => { const getDateLabel = (offset: number) => {
return dayjs().add(offset, 'day').format('YYYY-MM-DD'); return dayjs().add(offset, 'day').format('YYYY-MM-DD');
}; };


const getDateRange = () => {
const today = dayjs().format('YYYY-MM-DD');
const dayAfterTomorrow = dayjs().add(2, 'day').format('YYYY-MM-DD');
return { startDate: today, endDate: dayAfterTomorrow };
};

useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
setLoading(true); setLoading(true);
try { try {
const result = await fetchTicketReleaseTable();
const { startDate, endDate } = getDateRange();
const result = await fetchTicketReleaseTable(startDate, endDate);
setData(result); setData(result);
} catch (error) { } catch (error) {
console.error('Error fetching ticket release table:', error); console.error('Error fetching ticket release table:', error);
@@ -99,8 +107,14 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
} }
} }


// Filter by status if selected
if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) {
return false;
}


return true; return true;
},[data, selectedDate, selectedFloor]);
},[data, selectedDate, selectedFloor, selectedStatus]);


const handlePageChange = useCallback((event: unknown, newPage: number) => { const handlePageChange = useCallback((event: unknown, newPage: number) => {
setPaginationController(prev => ({ setPaginationController(prev => ({
@@ -125,19 +139,18 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {


useEffect(() => { useEffect(() => {
setPaginationController(prev => ({ ...prev, pageNum: 0 })); setPaginationController(prev => ({ ...prev, pageNum: 0 }));
}, [selectedDate, selectedFloor]);
}, [selectedDate, selectedFloor, selectedStatus]);


return ( return (
<Card sx={{ mb: 2 }}> <Card sx={{ mb: 2 }}>
<CardContent> <CardContent>
{/* Title */} {/* Title */}
<Typography variant="h5" sx={{ mb: 3, fontWeight: 600 }}> <Typography variant="h5" sx={{ mb: 3, fontWeight: 600 }}>
Ticket Release Table
{t("Ticket Release Table")}
</Typography> </Typography>


{/* Dropdown Menus */} {/* Dropdown Menus */}
<Stack direction="row" spacing={2} sx={{ mb: 3 }}> <Stack direction="row" spacing={2} sx={{ mb: 3 }}>
{/* Date Selection Dropdown */}
<FormControl sx={{ minWidth: 250 }} size="small"> <FormControl sx={{ minWidth: 250 }} size="small">
<InputLabel id="date-select-label">{t("Select Date")}</InputLabel> <InputLabel id="date-select-label">{t("Select Date")}</InputLabel>
<Select <Select
@@ -174,13 +187,33 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
onChange={(e) => setSelectedFloor(e.target.value)} onChange={(e) => setSelectedFloor(e.target.value)}
displayEmpty displayEmpty
> >
<MenuItem value="">
<em>{t("All Floors")}</em>
</MenuItem>
<MenuItem value="">{t("All Floors")}</MenuItem>
<MenuItem value="2/F">2/F</MenuItem> <MenuItem value="2/F">2/F</MenuItem>
<MenuItem value="4/F">4/F</MenuItem> <MenuItem value="4/F">4/F</MenuItem>
</Select> </Select>
</FormControl> </FormControl>

<FormControl sx={{ minWidth: 150 }} size="small">
<InputLabel
id="status-select-label"
shrink={true}
>
{t("Status")}
</InputLabel>
<Select
labelId="status-select-label"
id="status-select"
value={selectedStatus}
label={t("Status")}
onChange={(e) => setSelectedStatus(e.target.value)}
displayEmpty
>
<MenuItem value="">{t("All Statuses")}</MenuItem>
<MenuItem value="pending">{t("pending")}</MenuItem>
<MenuItem value="released">{t("released")}</MenuItem>
<MenuItem value="completed">{t("completed")}</MenuItem>
</Select>
</FormControl>
</Stack> </Stack>


<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
@@ -202,13 +235,13 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
{t("Truck Information")} {t("Truck Information")}
</Typography> </Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}> <Typography variant="caption" sx={{ color: 'text.secondary' }}>
{t("Departure Time")} {t("Truck Lane Code")}
{t("Truck Lane Code")} - {t("Departure Time")}
</Typography> </Typography>
</Box> </Box>
</TableCell> </TableCell>
{/*<TableCell>{t("Truck Departure Time")}</TableCell> {/*<TableCell>{t("Truck Departure Time")}</TableCell>
<TableCell>{t("Truck Lane Code")}</TableCell>*/} <TableCell>{t("Truck Lane Code")}</TableCell>*/}
<TableCell>{t("Shop Name")}</TableCell>
<TableCell sx={{ minWidth: 200, width: '20%' }}>{t("Shop Name")}</TableCell>
<TableCell>{t("Loading Sequence")}</TableCell> <TableCell>{t("Loading Sequence")}</TableCell>
{/*<TableCell>{t("Delivery Order Code(s)")}</TableCell> {/*<TableCell>{t("Delivery Order Code(s)")}</TableCell>
<TableCell>{t("Pick Order Code(s)")}</TableCell> <TableCell>{t("Pick Order Code(s)")}</TableCell>
@@ -230,7 +263,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
</Box> </Box>
</TableCell> </TableCell>
<TableCell>{t("Handler Name")}</TableCell> <TableCell>{t("Handler Name")}</TableCell>
<TableCell>{t("Number of FG Items")}</TableCell>
<TableCell sx={{ minWidth: 100, width: '8%', whiteSpace: 'nowrap' }}>{t("Number of FG Items (Order Item(s) Count)")}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -275,7 +308,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
</Box> </Box>
</TableCell> </TableCell>


<TableCell>{row.shopName || '-'}</TableCell>
<TableCell sx={{ minWidth: 200, width: '20%' }}>{row.shopName || '-'}</TableCell>
<TableCell>{row.loadingSequence || '-'}</TableCell> <TableCell>{row.loadingSequence || '-'}</TableCell>
{/*<TableCell>{row.deliveryOrderCode || '-'}</TableCell> {/*<TableCell>{row.deliveryOrderCode || '-'}</TableCell>
<TableCell>{row.pickOrderCode || '-'}</TableCell> <TableCell>{row.pickOrderCode || '-'}</TableCell>
@@ -295,22 +328,40 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
<TableCell> <TableCell>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<Typography variant="body2"> <Typography variant="body2">
{row.ticketNo || '-'} ({row.ticketStatus || '-'})
{row.ticketNo || '-'} ({row.ticketStatus ? t(row.ticketStatus.toLowerCase()) : '-'})
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
{row.ticketReleaseTime
? dayjs(row.ticketReleaseTime, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm')
{t("Released Time")}: {row.ticketReleaseTime
? (() => {
if (Array.isArray(row.ticketReleaseTime)) {
return arrayToDayjs(row.ticketReleaseTime, true).format('HH:mm');
}
const parsedDate = dayjs(row.ticketReleaseTime, 'YYYYMMDDHHmmss');
if (!parsedDate.isValid()) {
return dayjs(row.ticketReleaseTime).format('HH:mm');
}
return parsedDate.format('HH:mm');
})()
: '-'} : '-'}
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
{row.ticketCompleteDateTime
? dayjs(row.ticketCompleteDateTime, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm')
{t("Completed Time")}: {row.ticketCompleteDateTime
? (() => {
if (Array.isArray(row.ticketCompleteDateTime)) {
return arrayToDayjs(row.ticketCompleteDateTime, true).format('HH:mm');
}
const parsedDate = dayjs(row.ticketCompleteDateTime, 'YYYYMMDDHHmmss');
if (!parsedDate.isValid()) {
return dayjs(row.ticketCompleteDateTime).format('HH:mm');
}
return parsedDate.format('HH:mm');
})()
: '-'} : '-'}
</Typography> </Typography>
</Box> </Box>
</TableCell> </TableCell>
<TableCell>{row.handlerName || '-'}</TableCell> <TableCell>{row.handlerName || '-'}</TableCell>
<TableCell>-</TableCell>
<TableCell sx={{ minWidth: 100, width: '8%' }}>{row.numberOfFGItems ?? 0}</TableCell>
</TableRow> </TableRow>
); );
}) })


+ 48
- 30
src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx View File

@@ -140,36 +140,54 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw


return ( return (
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>
{/* Date Selector Dropdown */}
<Box sx={{ maxWidth: 300, mb: 2 }}>
<FormControl fullWidth size="small">
<InputLabel id="date-select-label">{t("Select Date")}</InputLabel>
<Select
labelId="date-select-label"
id="date-select"
value={selectedDate}
label={t("Select Date")}
onChange={(e) => { {
setSelectedDate(e.target.value);
loadSummaries();
}}}
>
{/* Date Selector Dropdown and Legend */}
<Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: 'flex-start' }}>
<Box sx={{ maxWidth: 300 }}>
<FormControl fullWidth size="small">
<InputLabel id="date-select-label">{t("Select Date")}</InputLabel>
<Select
labelId="date-select-label"
id="date-select"
value={selectedDate}
label={t("Select Date")}
onChange={(e) => { {
setSelectedDate(e.target.value);
loadSummaries();
}}}
>

<MenuItem value="today">
{t("Today")} ({getDateLabel(0)})
</MenuItem>
<MenuItem value="tomorrow">
{t("Tomorrow")} ({getDateLabel(1)})
</MenuItem>
<MenuItem value="dayAfterTomorrow">
{t("Day After Tomorrow")} ({getDateLabel(2)})
</MenuItem>
</Select>
</FormControl>
</Box>

<Box
sx={{
p: 1,
backgroundColor: '#fafafa',
borderRadius: 1,
border: '1px solid #e0e0e0',
flex: 1,
maxWidth: 400
}}
>
<Typography variant="body2" sx={{ display: 'block', color: 'text.secondary', fontWeight: 600 }}>
{t("EDT - Lane Code (Unassigned/Total)")}
</Typography>
</Box>
</Stack>


<MenuItem value="today">
{t("Today")} ({getDateLabel(0)})
</MenuItem>
<MenuItem value="tomorrow">
{t("Tomorrow")} ({getDateLabel(1)})
</MenuItem>
<MenuItem value="dayAfterTomorrow">
{t("Day After Tomorrow")} ({getDateLabel(2)})
</MenuItem>
</Select>
</FormControl>
</Box>


{/* Grid containing both floors */} {/* Grid containing both floors */}
<Grid container spacing={2}> <Grid container spacing={2}>
@@ -299,7 +317,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw
}} }}
> >
{isLoadingSummary ? ( {isLoadingSummary ? (
<Typography variant="caption">Loading...</Typography>
<Typography variant="caption">{t("Loading...")}</Typography>
) : !summary4F?.rows || summary4F.rows.length === 0 ? ( ) : !summary4F?.rows || summary4F.rows.length === 0 ? (
<Typography <Typography
variant="body2" variant="body2"


+ 22
- 7
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx View File

@@ -145,6 +145,11 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
}); });
} else { } else {
console.error("Print failed: ", response.message); console.error("Print failed: ", response.message);
Swal.fire({
title: "",
text: t("Please take one pick order before printing the draft."),
icon: "info"
})
} }
} catch(error){ } catch(error){
console.error("error: ", error) console.error("error: ", error)
@@ -247,7 +252,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
const handleCompletionStatusChange = (event: CustomEvent) => { const handleCompletionStatusChange = (event: CustomEvent) => {
const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail; const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail;
// 修复:根据标签页和事件来源决定是否更新打印按钮状态
// 修复:根据标签页和事件来源决定是否更新打印按钮状态
if (eventTabIndex === undefined || eventTabIndex === tabIndex) { if (eventTabIndex === undefined || eventTabIndex === tabIndex) {
setPrintButtonsEnabled(allLotsCompleted); setPrintButtonsEnabled(allLotsCompleted);
console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted); console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted);
@@ -259,9 +264,9 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
return () => { return () => {
window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
}; };
}, [tabIndex]); // 添加 tabIndex 依赖
}, [tabIndex]); // 添加 tabIndex 依赖


// 新增:处理标签页切换时的打印按钮状态重置
// 新增:处理标签页切换时的打印按钮状态重置
useEffect(() => { useEffect(() => {
// 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态 // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态
if (tabIndex === 2) { if (tabIndex === 2) {
@@ -286,7 +291,7 @@ const handleAssignByLane = useCallback(async (
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
if (res.code === "SUCCESS") { if (res.code === "SUCCESS") {
console.log(" Successfully assigned pick order from lane", truckLanceCode);
console.log(" Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态 loadSummaries(); // 刷新按钮状态
} else if (res.code === "USER_BUSY") { } else if (res.code === "USER_BUSY") {
@@ -322,7 +327,7 @@ const handleAssignByLane = useCallback(async (
setIsAssigning(false); setIsAssigning(false);
} }
}, [currentUserId, t, loadSummaries]); }, [currentUserId, t, loadSummaries]);
// Manual assignment handler - uses the action function
// Manual assignment handler - uses the action function
*/ */
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => { (_e, newValue) => {
@@ -334,6 +339,10 @@ const handleAssignByLane = useCallback(async (
const handleSwitchToDetailTab = useCallback(() => { const handleSwitchToDetailTab = useCallback(() => {
setTabIndex(1); setTabIndex(1);
}, []); }, []);

const handleSwtitchToRecordTab = useCallback(() =>{
setTabIndex(2);
}, []);
const openCreateModal = useCallback(async () => { const openCreateModal = useCallback(async () => {
console.log("testing") console.log("testing")
@@ -611,7 +620,7 @@ const handleAssignByLane = useCallback(async (
</Grid> </Grid>
</Box> </Box>


{/* Tabs section - Move the click handler here */}
{/* Tabs section - Move the click handler here */}
<Box sx={{ <Box sx={{
borderBottom: '1px solid #e0e0e0' borderBottom: '1px solid #e0e0e0'
}}> }}>
@@ -634,7 +643,13 @@ const handleAssignByLane = useCallback(async (
onSwitchToDetailTab={handleSwitchToDetailTab} onSwitchToDetailTab={handleSwitchToDetailTab}
/> />
)} )}
{tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />}
{tabIndex === 1 && (
<PickExecutionDetail
filterArgs={filterArgs}
onSwitchToRecordTab={handleSwtitchToRecordTab}
onRefreshReleasedOrderCount={fetchReleasedOrderCount}
/>
) }
{tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />} {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />}
{tabIndex === 3 && <FGPickOrderTicketReleaseTable/>} {tabIndex === 3 && <FGPickOrderTicketReleaseTable/>}
</Box> </Box>


+ 4
- 0
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx View File

@@ -190,7 +190,11 @@ const validateForm = (): boolean => {
} }
// 2. 检查 actualPickQty 不能超过可用数量或需求数量 // 2. 检查 actualPickQty 不能超过可用数量或需求数量
<<<<<<< Updated upstream
if (ap > Math.min( req)) { if (ap > Math.min( req)) {
=======
if (ap > Math.min(req)) {
>>>>>>> Stashed changes
newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty'); newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty');
} }


+ 13
- 21
src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx View File

@@ -564,28 +564,20 @@ if (showDetailView && selectedDoPickOrder) {
<Typography variant="subtitle1"> <Typography variant="subtitle1">
<strong>{t("Completed Date")}:</strong> {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} <strong>{t("Completed Date")}:</strong> {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)}
</Typography> </Typography>
{selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && (
<Typography variant="subtitle1">
<strong>{t("Pick Order Code(s)")}:</strong>{" "}
{(typeof selectedDoPickOrder.pickOrderCodes === 'string'
? selectedDoPickOrder.pickOrderCodes.split(',').map(code => code.trim())
: Array.isArray(selectedDoPickOrder.pickOrderCodes)
? selectedDoPickOrder.pickOrderCodes
: []
).filter(Boolean).join(', ')}
</Typography>
)}
</Stack> </Stack>
</Paper> </Paper>


{/* 添加:多个 Pick Orders 信息(如果有) */}
{selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && (
<Paper sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5' }}>
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
{t("This ticket contains")} {selectedDoPickOrder.pickOrderIds.length} {t("pick orders")}:
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{selectedDoPickOrder.pickOrderCodes?.split(', ').map((code, idx) => (
<Chip
key={idx}
label={code}
size="small"
variant="outlined"
/>
))}
</Box>
</Paper>
)}

{/* 数据检查 */} {/* 数据检查 */}
{detailLotData.length === 0 ? ( {detailLotData.length === 0 ? (
<Box sx={{ p: 3, textAlign: 'center' }}> <Box sx={{ p: 3, textAlign: 'center' }}>
@@ -705,11 +697,11 @@ if (showDetailView && selectedDoPickOrder) {
<Stack direction="row" justifyContent="space-between" alignItems="center"> <Stack direction="row" justifyContent="space-between" alignItems="center">
<Box> <Box>
<Typography variant="h6"> <Typography variant="h6">
{doPickOrder.pickOrderCode}
{doPickOrder.deliveryNoteCode}
</Typography> </Typography>


<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{doPickOrder.shopName} - {doPickOrder.deliveryNo}
{doPickOrder.shopName}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{t("Completed")}: {dayjs(doPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} {t("Completed")}: {dayjs(doPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)}


+ 11
- 3
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx View File

@@ -68,6 +68,8 @@ import GoodPickExecutionForm from "./GoodPickExecutionForm";
import FGPickOrderCard from "./FGPickOrderCard"; import FGPickOrderCard from "./FGPickOrderCard";
interface Props { interface Props {
filterArgs: Record<string, any>; filterArgs: Record<string, any>;
onSwitchToRecordTab?: () => void;
onRefreshReleasedOrderCount?: () => void;
} }


// QR Code Modal Component (from LotTable) // QR Code Modal Component (from LotTable)
@@ -324,7 +326,7 @@ const QrCodeModal: React.FC<{
); );
}; };


const PickExecution: React.FC<Props> = ({ filterArgs }) => {
const PickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab, onRefreshReleasedOrderCount }) => {
const { t } = useTranslation("pickOrder"); const { t } = useTranslation("pickOrder");
const router = useRouter(); const router = useRouter();
const { data: session } = useSession() as { data: SessionWithTokens | null }; const { data: session } = useSession() as { data: SessionWithTokens | null };
@@ -579,7 +581,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
pickOrderTargetDate: mergedPickOrder.targetDate, pickOrderTargetDate: mergedPickOrder.targetDate,
pickOrderStatus: mergedPickOrder.status, pickOrderStatus: mergedPickOrder.status,
pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId
pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
pickOrderLineId: line.id, pickOrderLineId: line.id,
pickOrderLineRequiredQty: line.requiredQty, pickOrderLineRequiredQty: line.requiredQty,
pickOrderLineStatus: line.status, pickOrderLineStatus: line.status,
@@ -1678,6 +1680,12 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
setTimeout(() => { setTimeout(() => {
setQrScanSuccess(false); setQrScanSuccess(false);
checkAndAutoAssignNext(); checkAndAutoAssignNext();
if (onSwitchToRecordTab) {
onSwitchToRecordTab();
}
if (onRefreshReleasedOrderCount) {
onRefreshReleasedOrderCount();
}
}, 2000); }, 2000);
} }
@@ -1687,7 +1695,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
} finally { } finally {
setIsSubmittingAll(false); setIsSubmittingAll(false);
} }
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]);
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull, onSwitchToRecordTab, onRefreshReleasedOrderCount]);


// Calculate scanned items count // Calculate scanned items count
// Calculate scanned items count (should match handleSubmitAllScanned filter logic) // Calculate scanned items count (should match handleSubmitAllScanned filter logic)


+ 3
- 2
src/i18n/zh/pickOrder.json View File

@@ -405,6 +405,7 @@
"Print DN & Label": "列印提料單和送貨單標籤", "Print DN & Label": "列印提料單和送貨單標籤",
"Print Label": "列印送貨單標籤", "Print Label": "列印送貨單標籤",
"Ticket Release Table": "查看提貨情況", "Ticket Release Table": "查看提貨情況",
"Please take one pick order before printing the draft.": "請先從下方選取提料單,再列印草稿。",
"No released pick order records found.": "目前沒有可用的提料單。"
"Please take one pick order before printing the draft.": "請先從「撳單/提料單詳情」頁面下方選取提料單,再列印草稿。",
"No released pick order records found.": "目前沒有可用的提料單。",
"EDT - Lane Code (Unassigned/Total)": "預計出發時間 - 貨車班次(未撳數/總單數)"
} }

+ 29
- 0
src/i18n/zh/ticketReleaseTable.json View File

@@ -0,0 +1,29 @@
{
"Ticket Release Table": "查看提貨情況",
"Select Date": "請選擇日期",
"Today": "是日",
"Tomorrow": "翌日",
"Day After Tomorrow": "後日",
"Floor": "樓層",
"All Floors": "所有樓層",
"Store ID": "樓層",
"Required Delivery Date": "需求送貨日期",
"Truck Information": "貨車資訊",
"Departure Time": "出發時間",
"Truck Lane Code": "車綫編號",
"Shop Name": "店鋪名稱",
"Loading Sequence": "裝載順序",
"Ticket Information": "提票資訊",
"Ticket No.": "提票號碼",
"Status": "狀態",
"Released Time": "開始時間",
"Completed Time": "完成時間",
"Handler Name": "負責員工",
"Number of FG Items (Order Item(s) Count)": "訂單項目數量",
"No data available": "沒有資料",
"Rows per page": "每頁行數",
"pending": "待撳單",
"released": "提貨中",
"completed": "已完成",
"All Statuses": "所有提貨狀態"
}

Loading…
Cancel
Save