# Conflicts: # src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsxmaster
| @@ -18,7 +18,7 @@ const PickOrder: React.FC<Props> = async ({ searchParams }) => { | |||
| return ( | |||
| <> | |||
| <I18nProvider namespaces={["pickOrder", "common"]}> | |||
| <I18nProvider namespaces={["pickOrder", "common", "ticketReleaseTable"]}> | |||
| <Suspense fallback={<FinishedGoodSearchWrapper.Loading />}> | |||
| <FinishedGoodSearchWrapper /> | |||
| </Suspense> | |||
| @@ -17,7 +17,7 @@ const PickOrder: React.FC = async () => { | |||
| return ( | |||
| <> | |||
| <I18nProvider namespaces={["pickOrder", "common"]}> | |||
| <I18nProvider namespaces={["pickOrder", "common", "ticketReleaseTable"]}> | |||
| <Suspense fallback={<FinishedGoodSearch.Loading />}> | |||
| <FinishedGoodSearch /> | |||
| </Suspense> | |||
| @@ -3,7 +3,7 @@ import { BASE_API_URL } from "@/config/api"; | |||
| // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
| import { revalidateTag } from "next/cache"; | |||
| 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 { RecordsRes } from "../utils"; | |||
| import { DoResult } from "."; | |||
| @@ -129,11 +129,22 @@ export interface getTicketReleaseTable { | |||
| shopName: string | null; | |||
| requiredDeliveryDate: 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[]>( | |||
| `${BASE_API_URL}/doPickOrder/ticket-release-table`, | |||
| url, | |||
| { | |||
| method: "GET", | |||
| } | |||
| @@ -226,11 +237,29 @@ export async function printDN(request: PrintDeliveryNoteRequest){ | |||
| params.append('numOfCarton', request.numOfCarton.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){ | |||
| @@ -357,6 +357,7 @@ export interface CompletedDoPickOrderResponse { | |||
| storeId: string; | |||
| completedDate: string; | |||
| fgPickOrders: FGPickOrderResponse[]; | |||
| deliveryNoteCode: number; | |||
| } | |||
| // 新增:搜索参数接口 | |||
| @@ -24,13 +24,15 @@ import { | |||
| } from '@mui/material'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import dayjs from 'dayjs'; | |||
| import { arrayToDayjs } from '@/app/utils/formatUtil'; | |||
| import { fetchTicketReleaseTable, getTicketReleaseTable } from '@/app/api/do/actions'; | |||
| import { time } from 'console'; | |||
| const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| const { t } = useTranslation("ticketReleaseTable"); | |||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | |||
| const [selectedFloor, setSelectedFloor] = useState<string>(""); | |||
| const [selectedStatus, setSelectedStatus] = useState<string>("released"); | |||
| const [data, setData] = useState<getTicketReleaseTable[]>([]); | |||
| const [loading, setLoading] = useState<boolean>(true); | |||
| const [paginationController, setPaginationController] = useState({ | |||
| @@ -62,16 +64,22 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| const formattedMinute = minute.toString().padStart(2, '0'); | |||
| return `${formattedHour}:${formattedMinute}`; | |||
| }; | |||
| const getDateLabel = (offset: number) => { | |||
| 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(() => { | |||
| const loadData = async () => { | |||
| setLoading(true); | |||
| try { | |||
| const result = await fetchTicketReleaseTable(); | |||
| const { startDate, endDate } = getDateRange(); | |||
| const result = await fetchTicketReleaseTable(startDate, endDate); | |||
| setData(result); | |||
| } catch (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; | |||
| },[data, selectedDate, selectedFloor]); | |||
| },[data, selectedDate, selectedFloor, selectedStatus]); | |||
| const handlePageChange = useCallback((event: unknown, newPage: number) => { | |||
| setPaginationController(prev => ({ | |||
| @@ -125,19 +139,18 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| useEffect(() => { | |||
| setPaginationController(prev => ({ ...prev, pageNum: 0 })); | |||
| }, [selectedDate, selectedFloor]); | |||
| }, [selectedDate, selectedFloor, selectedStatus]); | |||
| return ( | |||
| <Card sx={{ mb: 2 }}> | |||
| <CardContent> | |||
| {/* Title */} | |||
| <Typography variant="h5" sx={{ mb: 3, fontWeight: 600 }}> | |||
| Ticket Release Table | |||
| {t("Ticket Release Table")} | |||
| </Typography> | |||
| {/* Dropdown Menus */} | |||
| <Stack direction="row" spacing={2} sx={{ mb: 3 }}> | |||
| {/* Date Selection Dropdown */} | |||
| <FormControl sx={{ minWidth: 250 }} size="small"> | |||
| <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | |||
| <Select | |||
| @@ -174,13 +187,33 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| onChange={(e) => setSelectedFloor(e.target.value)} | |||
| displayEmpty | |||
| > | |||
| <MenuItem value=""> | |||
| <em>{t("All Floors")}</em> | |||
| </MenuItem> | |||
| <MenuItem value="">{t("All Floors")}</MenuItem> | |||
| <MenuItem value="2/F">2/F</MenuItem> | |||
| <MenuItem value="4/F">4/F</MenuItem> | |||
| </Select> | |||
| </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> | |||
| <Box sx={{ mt: 2 }}> | |||
| @@ -202,13 +235,13 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| {t("Truck Information")} | |||
| </Typography> | |||
| <Typography variant="caption" sx={{ color: 'text.secondary' }}> | |||
| {t("Departure Time")} {t("Truck Lane Code")} | |||
| {t("Truck Lane Code")} - {t("Departure Time")} | |||
| </Typography> | |||
| </Box> | |||
| </TableCell> | |||
| {/*<TableCell>{t("Truck Departure Time")}</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("Delivery Order Code(s)")}</TableCell> | |||
| <TableCell>{t("Pick Order Code(s)")}</TableCell> | |||
| @@ -230,7 +263,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| </Box> | |||
| </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> | |||
| </TableHead> | |||
| <TableBody> | |||
| @@ -275,7 +308,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| </Box> | |||
| </TableCell> | |||
| <TableCell>{row.shopName || '-'}</TableCell> | |||
| <TableCell sx={{ minWidth: 200, width: '20%' }}>{row.shopName || '-'}</TableCell> | |||
| <TableCell>{row.loadingSequence || '-'}</TableCell> | |||
| {/*<TableCell>{row.deliveryOrderCode || '-'}</TableCell> | |||
| <TableCell>{row.pickOrderCode || '-'}</TableCell> | |||
| @@ -295,22 +328,40 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||
| <TableCell> | |||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | |||
| <Typography variant="body2"> | |||
| {row.ticketNo || '-'} ({row.ticketStatus || '-'}) | |||
| {row.ticketNo || '-'} ({row.ticketStatus ? t(row.ticketStatus.toLowerCase()) : '-'}) | |||
| </Typography> | |||
| <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 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> | |||
| </Box> | |||
| </TableCell> | |||
| <TableCell>{row.handlerName || '-'}</TableCell> | |||
| <TableCell>-</TableCell> | |||
| <TableCell sx={{ minWidth: 100, width: '8%' }}>{row.numberOfFGItems ?? 0}</TableCell> | |||
| </TableRow> | |||
| ); | |||
| }) | |||
| @@ -140,36 +140,54 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||
| return ( | |||
| <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 container spacing={2}> | |||
| @@ -299,7 +317,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||
| }} | |||
| > | |||
| {isLoadingSummary ? ( | |||
| <Typography variant="caption">Loading...</Typography> | |||
| <Typography variant="caption">{t("Loading...")}</Typography> | |||
| ) : !summary4F?.rows || summary4F.rows.length === 0 ? ( | |||
| <Typography | |||
| variant="body2" | |||
| @@ -145,6 +145,11 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| }); | |||
| } else { | |||
| console.error("Print failed: ", response.message); | |||
| Swal.fire({ | |||
| title: "", | |||
| text: t("Please take one pick order before printing the draft."), | |||
| icon: "info" | |||
| }) | |||
| } | |||
| } catch(error){ | |||
| console.error("error: ", error) | |||
| @@ -247,7 +252,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| const handleCompletionStatusChange = (event: CustomEvent) => { | |||
| const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail; | |||
| // 修复:根据标签页和事件来源决定是否更新打印按钮状态 | |||
| // ✅ 修复:根据标签页和事件来源决定是否更新打印按钮状态 | |||
| if (eventTabIndex === undefined || eventTabIndex === tabIndex) { | |||
| setPrintButtonsEnabled(allLotsCompleted); | |||
| console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted); | |||
| @@ -259,9 +264,9 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| return () => { | |||
| window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); | |||
| }; | |||
| }, [tabIndex]); // 添加 tabIndex 依赖 | |||
| }, [tabIndex]); // ✅ 添加 tabIndex 依赖 | |||
| // 新增:处理标签页切换时的打印按钮状态重置 | |||
| // ✅ 新增:处理标签页切换时的打印按钮状态重置 | |||
| useEffect(() => { | |||
| // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态 | |||
| if (tabIndex === 2) { | |||
| @@ -286,7 +291,7 @@ const handleAssignByLane = useCallback(async ( | |||
| const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); | |||
| 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')); | |||
| loadSummaries(); // 刷新按钮状态 | |||
| } else if (res.code === "USER_BUSY") { | |||
| @@ -322,7 +327,7 @@ const handleAssignByLane = useCallback(async ( | |||
| setIsAssigning(false); | |||
| } | |||
| }, [currentUserId, t, loadSummaries]); | |||
| // Manual assignment handler - uses the action function | |||
| // ✅ Manual assignment handler - uses the action function | |||
| */ | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| @@ -334,6 +339,10 @@ const handleAssignByLane = useCallback(async ( | |||
| const handleSwitchToDetailTab = useCallback(() => { | |||
| setTabIndex(1); | |||
| }, []); | |||
| const handleSwtitchToRecordTab = useCallback(() =>{ | |||
| setTabIndex(2); | |||
| }, []); | |||
| const openCreateModal = useCallback(async () => { | |||
| console.log("testing") | |||
| @@ -611,7 +620,7 @@ const handleAssignByLane = useCallback(async ( | |||
| </Grid> | |||
| </Box> | |||
| {/* Tabs section - Move the click handler here */} | |||
| {/* Tabs section - ✅ Move the click handler here */} | |||
| <Box sx={{ | |||
| borderBottom: '1px solid #e0e0e0' | |||
| }}> | |||
| @@ -634,7 +643,13 @@ const handleAssignByLane = useCallback(async ( | |||
| onSwitchToDetailTab={handleSwitchToDetailTab} | |||
| /> | |||
| )} | |||
| {tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />} | |||
| {tabIndex === 1 && ( | |||
| <PickExecutionDetail | |||
| filterArgs={filterArgs} | |||
| onSwitchToRecordTab={handleSwtitchToRecordTab} | |||
| onRefreshReleasedOrderCount={fetchReleasedOrderCount} | |||
| /> | |||
| ) } | |||
| {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />} | |||
| {tabIndex === 3 && <FGPickOrderTicketReleaseTable/>} | |||
| </Box> | |||
| @@ -190,7 +190,11 @@ const validateForm = (): boolean => { | |||
| } | |||
| // 2. 检查 actualPickQty 不能超过可用数量或需求数量 | |||
| <<<<<<< Updated upstream | |||
| 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'); | |||
| } | |||
| @@ -564,33 +564,20 @@ if (showDetailView && selectedDoPickOrder) { | |||
| <Typography variant="subtitle1"> | |||
| <strong>{t("Completed Date")}:</strong> {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} | |||
| </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> | |||
| </Paper> | |||
| {/* 添加:多个 Pick Orders 信息(如果有) */} | |||
| {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 0 && ( | |||
| <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' }}> | |||
| {(typeof selectedDoPickOrder.pickOrderCodes === 'string' | |||
| ? selectedDoPickOrder.pickOrderCodes.split(',').map(code => code.trim()) | |||
| : Array.isArray(selectedDoPickOrder.pickOrderCodes) | |||
| ? selectedDoPickOrder.pickOrderCodes | |||
| : [] | |||
| ).filter(Boolean).map((code, idx) => ( | |||
| <Chip | |||
| key={idx} | |||
| label={code} | |||
| size="small" | |||
| variant="outlined" | |||
| /> | |||
| ))} | |||
| </Box> | |||
| </Paper> | |||
| )} | |||
| {/* 数据检查 */} | |||
| {detailLotData.length === 0 ? ( | |||
| <Box sx={{ p: 3, textAlign: 'center' }}> | |||
| @@ -710,11 +697,11 @@ if (showDetailView && selectedDoPickOrder) { | |||
| <Stack direction="row" justifyContent="space-between" alignItems="center"> | |||
| <Box> | |||
| <Typography variant="h6"> | |||
| {doPickOrder.pickOrderCode} | |||
| {doPickOrder.deliveryNoteCode} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {doPickOrder.shopName} - {doPickOrder.deliveryNo} | |||
| {doPickOrder.shopName} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Completed")}: {dayjs(doPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} | |||
| @@ -68,6 +68,8 @@ import GoodPickExecutionForm from "./GoodPickExecutionForm"; | |||
| import FGPickOrderCard from "./FGPickOrderCard"; | |||
| interface Props { | |||
| filterArgs: Record<string, any>; | |||
| onSwitchToRecordTab?: () => void; | |||
| onRefreshReleasedOrderCount?: () => void; | |||
| } | |||
| // 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 router = useRouter(); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| @@ -579,7 +581,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); | |||
| pickOrderTargetDate: mergedPickOrder.targetDate, | |||
| pickOrderStatus: mergedPickOrder.status, | |||
| pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId | |||
| pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", | |||
| pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", | |||
| pickOrderLineId: line.id, | |||
| pickOrderLineRequiredQty: line.requiredQty, | |||
| pickOrderLineStatus: line.status, | |||
| @@ -1678,6 +1680,12 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||
| setTimeout(() => { | |||
| setQrScanSuccess(false); | |||
| checkAndAutoAssignNext(); | |||
| if (onSwitchToRecordTab) { | |||
| onSwitchToRecordTab(); | |||
| } | |||
| if (onRefreshReleasedOrderCount) { | |||
| onRefreshReleasedOrderCount(); | |||
| } | |||
| }, 2000); | |||
| } | |||
| @@ -1687,7 +1695,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||
| } finally { | |||
| setIsSubmittingAll(false); | |||
| } | |||
| }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]); | |||
| }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull, onSwitchToRecordTab, onRefreshReleasedOrderCount]); | |||
| // Calculate scanned items count | |||
| // Calculate scanned items count (should match handleSubmitAllScanned filter logic) | |||
| @@ -405,6 +405,7 @@ | |||
| "Print DN & Label": "列印提料單和送貨單標籤", | |||
| "Print Label": "列印送貨單標籤", | |||
| "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)": "預計出發時間 - 貨車班次(未撳數/總單數)" | |||
| } | |||
| @@ -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": "所有提貨狀態" | |||
| } | |||