| @@ -0,0 +1,101 @@ | |||
| "use client"; | |||
| import { Box, Card, CardContent, Grid, TextField, Stack } from "@mui/material"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { FGPickOrderResponse } from "@/app/api/pickOrder/actions"; | |||
| interface Props { | |||
| fgOrder: FGPickOrderResponse; | |||
| } | |||
| const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| return ( | |||
| <Card sx={{ display: "block", mb: 2 }}> | |||
| <CardContent component={Stack} spacing={2}> | |||
| <Box> | |||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.pickOrderCode || ""} | |||
| label={t("Pick Order Code")} | |||
| fullWidth | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.deliveryNo || ""} | |||
| label={t("Delivery No")} | |||
| fullWidth | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.shopName || ""} | |||
| label={t("Shop Name")} | |||
| fullWidth | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.ticketNo || ""} | |||
| label={t("Ticket No")} | |||
| fullWidth | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.storeId || ""} | |||
| label={t("Store")} | |||
| fullWidth | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.truckLanceCode || ""} | |||
| label={t("Truck Lane Code")} | |||
| fullWidth | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.DepartureTime || ""} | |||
| label={t("Departure Time")} | |||
| fullWidth | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.shopAddress || ""} | |||
| label={t("Shop Address")} | |||
| fullWidth | |||
| disabled={true} | |||
| multiline | |||
| rows={2} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default FGPickOrderInfoCard; | |||
| @@ -63,8 +63,8 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const [totalCount, setTotalCount] = useState<number>(); | |||
| const [isAssigning, setIsAssigning] = useState(false); | |||
| const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null); | |||
| const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | |||
| // const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null); | |||
| // const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | |||
| const [isLoadingSummary, setIsLoadingSummary] = useState(false); | |||
| const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>( | |||
| typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' | |||
| @@ -83,6 +83,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| setReleasedOrderCount(0); | |||
| } | |||
| }, []); | |||
| /* | |||
| const loadSummaries = useCallback(async () => { | |||
| setIsLoadingSummary(true); | |||
| try { | |||
| @@ -104,6 +105,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| // 每30秒刷新一次 | |||
| }, [loadSummaries]); | |||
| */ | |||
| const handleDraft = useCallback(async () =>{ | |||
| try{ | |||
| if (fgPickOrdersData.length === 0) { | |||
| @@ -446,11 +448,11 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| const onAssigned = () => { | |||
| localStorage.removeItem('hideCompletedUntilNext'); | |||
| setHideCompletedUntilNext(false); | |||
| loadSummaries(); | |||
| // loadSummaries(); | |||
| }; | |||
| window.addEventListener('pickOrderAssigned', onAssigned); | |||
| return () => window.removeEventListener('pickOrderAssigned', onAssigned); | |||
| }, [loadSummaries]); | |||
| }, []); | |||
| // ... existing code ... | |||
| useEffect(() => { | |||
| @@ -479,7 +481,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| console.log("Reset print buttons for Pick Execution Record tab"); | |||
| } | |||
| }, [tabIndex]); | |||
| /* | |||
| // ... existing code ... | |||
| const handleAssignByLane = useCallback(async ( | |||
| storeId: string, | |||
| @@ -533,7 +535,7 @@ const handleAssignByLane = useCallback(async ( | |||
| } | |||
| }, [currentUserId, t, loadSummaries]); | |||
| // ✅ Manual assignment handler - uses the action function | |||
| */ | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| setTabIndex(newValue); | |||
| @@ -731,216 +733,159 @@ const handleAssignByLane = useCallback(async ( | |||
| return ( | |||
| <Box sx={{ | |||
| height: '100vh', // Full viewport height | |||
| // Full viewport height | |||
| overflow: 'auto' // Single scrollbar for the whole page | |||
| }}> | |||
| {/* Header section */} | |||
| <Box sx={{ p: 2, borderBottom: '1px solid #e0e0e0' }}> | |||
| <Stack rowGap={2}> | |||
| <Grid container alignItems="center"> | |||
| <Grid item xs={8}> | |||
| <Box mb={2}> | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Finished Good Order")} | |||
| </Typography> | |||
| </Box> | |||
| </Grid> | |||
| {/* Last 2 buttons aligned right */} | |||
| <Grid item xs={12}> | |||
| <Grid container alignItems="flex-start" spacing={1}> | |||
| {/* 2/F 楼层面板 */} | |||
| <Grid item> | |||
| <Box | |||
| sx={{ | |||
| border: '1px solid #e0e0e0', | |||
| borderRadius: 1, | |||
| p: 1, | |||
| minWidth: 320, | |||
| mr: 1, | |||
| backgroundColor: '#fafafa' | |||
| <Box sx={{ | |||
| p: 1, | |||
| borderBottom: '1px solid #e0e0e0', | |||
| minHeight: 'auto' // 确保最小高度自适应 | |||
| }}> | |||
| <Grid container alignItems="center" spacing={1}> | |||
| <Grid item xs={8}> | |||
| <Typography | |||
| variant="h5" | |||
| sx={{ | |||
| lineHeight: 1.4, // 调整行高 | |||
| m: 0, | |||
| fontWeight: 500 | |||
| }} | |||
| > | |||
| <Typography variant="subtitle2" sx={{ mb: 0.5, fontWeight: 600, textAlign: 'center' }}> | |||
| 2/F | |||
| </Typography> | |||
| {isLoadingSummary ? ( | |||
| <Typography variant="caption">Loading...</Typography> | |||
| ) : ( | |||
| <Stack spacing={0.5}> | |||
| {summary2F?.rows.map((row, rowIdx) => ( | |||
| <Box | |||
| key={rowIdx} | |||
| sx={{ | |||
| border: '1px solid #e0e0e0', | |||
| borderRadius: 0.5, | |||
| p: 0.5, | |||
| backgroundColor: '#fff' | |||
| }} | |||
| > | |||
| <Typography | |||
| variant="caption" | |||
| sx={{ | |||
| display: 'block', | |||
| mb: 0.5, | |||
| fontWeight: 500, | |||
| textAlign: 'center', | |||
| fontSize: '0.7rem' | |||
| }} | |||
| > | |||
| {row.truckDepartureTime} | |||
| </Typography> | |||
| <Stack direction="row" spacing={0.25} alignItems="center" justifyContent="center"> | |||
| {row.lanes.map((lane, laneIdx) => ( | |||
| <Button | |||
| key={laneIdx} | |||
| variant="outlined" | |||
| size="small" | |||
| disabled={lane.unassigned === 0 || isAssigning} | |||
| onClick={() => handleAssignByLane("2/F", row.truckDepartureTime, lane.truckLanceCode)} | |||
| sx={{ | |||
| minWidth: 80, | |||
| fontSize: '0.7rem', | |||
| py: 0.25, | |||
| px: 0.5, | |||
| borderWidth: 1, | |||
| borderColor: '#ccc', | |||
| '&:hover': { | |||
| borderColor: '#999', | |||
| backgroundColor: '#f5f5f5' | |||
| } | |||
| }} | |||
| > | |||
| {`${lane.truckLanceCode} (${lane.unassigned}/${lane.total})`} | |||
| </Button> | |||
| ))} | |||
| </Stack> | |||
| </Box> | |||
| ))} | |||
| </Stack> | |||
| )} | |||
| </Box> | |||
| {t("Finished Good Order")} | |||
| </Typography> | |||
| </Grid> | |||
| {/* 4/F 楼层面板 */} | |||
| <Grid item> | |||
| <Box | |||
| sx={{ | |||
| border: '1px solid #e0e0e0', | |||
| borderRadius: 1, | |||
| p: 1, | |||
| minWidth: 320, | |||
| backgroundColor: '#fafafa' | |||
| }} | |||
| > | |||
| <Typography variant="subtitle2" sx={{ mb: 0.5, fontWeight: 600, textAlign: 'center' }}> | |||
| 4/F | |||
| </Typography> | |||
| {isLoadingSummary ? ( | |||
| <Typography variant="caption">Loading...</Typography> | |||
| ) : ( | |||
| <Stack spacing={0.5}> | |||
| {summary4F?.rows.map((row, rowIdx) => ( | |||
| <Box | |||
| key={rowIdx} | |||
| sx={{ | |||
| border: '1px solid #e0e0e0', | |||
| borderRadius: 0.5, | |||
| p: 0.5, | |||
| backgroundColor: '#fff' | |||
| }} | |||
| > | |||
| <Typography | |||
| variant="caption" | |||
| sx={{ | |||
| display: 'block', | |||
| mb: 0.5, | |||
| fontWeight: 500, | |||
| textAlign: 'center', | |||
| fontSize: '0.7rem' | |||
| }} | |||
| > | |||
| {row.truckDepartureTime} | |||
| </Typography> | |||
| <Stack direction="row" spacing={0.25} alignItems="center" justifyContent="center"> | |||
| {row.lanes.map((lane, laneIdx) => ( | |||
| <Button | |||
| key={laneIdx} | |||
| variant="outlined" | |||
| size="small" | |||
| disabled={lane.unassigned === 0 || isAssigning} | |||
| onClick={() => handleAssignByLane("4/F", row.truckDepartureTime, lane.truckLanceCode)} | |||
| sx={{ | |||
| minWidth: 80, | |||
| fontSize: '0.7rem', | |||
| py: 0.25, | |||
| px: 0.5, | |||
| borderWidth: 1, | |||
| borderColor: '#ccc', | |||
| '&:hover': { | |||
| borderColor: '#999', | |||
| backgroundColor: '#f5f5f5' | |||
| } | |||
| }} | |||
| > | |||
| {`${lane.truckLanceCode} (${lane.unassigned}/${lane.total})`} | |||
| </Button> | |||
| ))} | |||
| </Stack> | |||
| </Box> | |||
| ))} | |||
| </Stack> | |||
| )} | |||
| <Grid item xs={4}> | |||
| <Box sx={{ | |||
| display: 'flex', | |||
| justifyContent: 'flex-end', | |||
| alignItems: 'center', | |||
| height: '100%' | |||
| }}> | |||
| <Stack | |||
| direction="row" | |||
| spacing={0.5} | |||
| sx={{ | |||
| alignItems: 'center', | |||
| height: '100%' | |||
| }} | |||
| > | |||
| <Button | |||
| variant="contained" | |||
| size="small" | |||
| sx={{ | |||
| py: 0.5, // 增加垂直padding | |||
| px: 1.25, // 增加水平padding | |||
| height: '40px', // 增加按钮高度 | |||
| fontSize: '0.75rem', | |||
| lineHeight: 1.2, // 添加行高控制 | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyContent: 'center', | |||
| '&.Mui-disabled': { | |||
| height: '40px' | |||
| } | |||
| }} | |||
| onClick={handleAllDraft} | |||
| > | |||
| {t("Print All Draft")} ({releasedOrderCount}) | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| size="small" | |||
| sx={{ | |||
| py: 0.5, | |||
| px: 1.25, | |||
| height: '40px', | |||
| fontSize: '0.75rem', | |||
| lineHeight: 1.2, | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyContent: 'center', | |||
| '&.Mui-disabled': { | |||
| height: '40px' | |||
| } | |||
| }} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleDraft} | |||
| > | |||
| {t("Print Draft")} | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| disabled={!printButtonsEnabled} | |||
| size="small" | |||
| sx={{ | |||
| py: 0.5, | |||
| px: 1.25, | |||
| height: '40px', | |||
| fontSize: '0.75rem', | |||
| lineHeight: 1.2, | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyContent: 'center', | |||
| '&.Mui-disabled': { | |||
| height: '40px' | |||
| } | |||
| }} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleDNandLabel} | |||
| > | |||
| {t("Print Pick Order and DN Label")} | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| disabled={!printButtonsEnabled} | |||
| size="small" | |||
| sx={{ | |||
| py: 0.5, | |||
| px: 1.25, | |||
| height: '40px', | |||
| fontSize: '0.75rem', | |||
| lineHeight: 1.2, | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyContent: 'center', | |||
| '&.Mui-disabled': { | |||
| height: '40px' | |||
| } | |||
| }} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleDN} | |||
| > | |||
| {t("Print Pick Order")} | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| disabled={!printButtonsEnabled} | |||
| size="small" | |||
| sx={{ | |||
| py: 0.5, | |||
| px: 1.25, | |||
| height: '40px', | |||
| fontSize: '0.75rem', | |||
| lineHeight: 1.2, | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyContent: 'center', | |||
| '&.Mui-disabled': { | |||
| height: '40px' | |||
| } | |||
| }} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleLabel} | |||
| > | |||
| {t("Print DN Label")} | |||
| </Button> | |||
| </Stack> | |||
| </Box> | |||
| </Grid> | |||
| {/* ✅ Updated print buttons with completion status */} | |||
| <Grid item xs> | |||
| <Box sx={{ width: '100%', display: 'flex', justifyContent: 'flex-end' }}> | |||
| <Stack direction="row" spacing={1}> | |||
| <Button variant="contained" onClick={handleAllDraft}> | |||
| {t("Print All Draft")} ({releasedOrderCount}) | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| // disabled={!printButtonsEnabled} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleDraft} | |||
| > | |||
| {t("Print Draft")} | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| disabled={!printButtonsEnabled} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleDNandLabel} | |||
| > | |||
| {t("Print Pick Order and DN Label")} | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| disabled={!printButtonsEnabled} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleDN} | |||
| > | |||
| {t("Print Pick Order")} | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| disabled={!printButtonsEnabled} | |||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||
| onClick={handleLabel} | |||
| > | |||
| {t("Print DN Label")} | |||
| </Button> | |||
| </Stack> | |||
| </Box> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| </Stack> | |||
| </Box> | |||
| </Grid> | |||
| </Box> | |||
| {/* Tabs section - ✅ Move the click handler here */} | |||
| <Box sx={{ | |||
| @@ -49,6 +49,8 @@ import { SessionWithTokens } from "@/config/authConfig"; | |||
| import { fetchStockInLineInfo } from "@/app/api/po/actions"; | |||
| import GoodPickExecutionForm from "./GoodPickExecutionForm"; | |||
| import FGPickOrderCard from "./FGPickOrderCard"; | |||
| import FinishedGoodFloorLanePanel from "./FGPickOrderCard"; | |||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | |||
| interface Props { | |||
| filterArgs: Record<string, any>; | |||
| onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | |||
| @@ -963,16 +965,24 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||
| return ( | |||
| <FormProvider {...formProps}> | |||
| {/* Search Box */} | |||
| {/* ✅ 条件渲染:没有活动订单时显示楼层选择面板 */} | |||
| {!combinedDataLoading && fgPickOrders.length === 0 ? ( | |||
| <FinishedGoodFloorLanePanel | |||
| onPickOrderAssigned={() => { | |||
| if (currentUserId) { | |||
| fetchAllCombinedLotData(currentUserId); | |||
| } | |||
| }} | |||
| /> | |||
| ) : ( | |||
| // ✅ 有活动订单时,显示 FG 订单信息卡片 | |||
| <Box> | |||
| {fgPickOrdersLoading ? ( | |||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | |||
| <CircularProgress /> | |||
| </Box> | |||
| ) : ( | |||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | |||
| <Box> | |||
| {fgPickOrders.length === 0 ? ( | |||
| <Box sx={{ p: 3, textAlign: 'center' }}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| @@ -980,276 +990,40 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||
| </Typography> | |||
| </Box> | |||
| ) : ( | |||
| // ✅ 使用新的 FGPickOrderInfoCard 组件(类似 DoInfoCard 的格式) | |||
| fgPickOrders.map((fgOrder) => ( | |||
| <FGPickOrderCard | |||
| <FGPickOrderInfoCard | |||
| key={fgOrder.pickOrderId} | |||
| fgOrder={fgOrder} | |||
| onQrCodeClick={handleQrCodeClick} | |||
| /> | |||
| )) | |||
| )} | |||
| </Box> | |||
| )} | |||
| </Box> | |||
| {/* | |||
| <Box> | |||
| <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> | |||
| <Typography variant="h6" gutterBottom sx={{ mb: 0 }}> | |||
| {t("All Pick Order Lots")} | |||
| </Typography> | |||
| </Box> | |||
| <TableContainer component={Paper}> | |||
| <Table> | |||
| <TableHead> | |||
| <TableRow> | |||
| <TableCell>{t("Index")}</TableCell> | |||
| <TableCell>{t("Route")}</TableCell> | |||
| <TableCell>{t("Item Name")}</TableCell> | |||
| <TableCell>{t("Lot#")}</TableCell> | |||
| <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> | |||
| <TableCell align="center">{t("Lot Actual Pick Qty")}</TableCell> | |||
| <TableCell align="center">{t("Action")}</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| {paginatedData.length === 0 ? ( | |||
| <TableRow> | |||
| <TableCell colSpan={11} align="center"> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("No data available")} | |||
| </Typography> | |||
| </TableCell> | |||
| </TableRow> | |||
| ) : ( | |||
| paginatedData.map((lot, index) => ( | |||
| <TableRow | |||
| key={`${lot.pickOrderLineId}-${lot.lotId}`} | |||
| sx={{ | |||
| backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit', | |||
| opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1, | |||
| '& .MuiTableCell-root': { | |||
| color: lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit' | |||
| } | |||
| }} | |||
| > | |||
| <TableCell> | |||
| <Typography variant="body2" fontWeight="bold"> | |||
| {lot.routerIndex || index + 1} | |||
| </Typography> | |||
| </TableCell> | |||
| <TableCell> | |||
| <Typography variant="body2"> | |||
| {lot.routerRoute || '-'} | |||
| </Typography> | |||
| </TableCell> | |||
| <TableCell>{lot.itemName}</TableCell> | |||
| <TableCell> | |||
| <Box> | |||
| <Typography | |||
| sx={{ | |||
| color: lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit', | |||
| opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1 | |||
| }} | |||
| > | |||
| {lot.lotNo} | |||
| </Typography> | |||
| </Box> | |||
| </TableCell> | |||
| <TableCell align="right"> | |||
| {(() => { | |||
| const inQty = lot.inQty || 0; | |||
| const outQty = lot.outQty || 0; | |||
| const result = inQty - outQty; | |||
| return result.toLocaleString(); | |||
| })()} | |||
| </TableCell> | |||
| <TableCell align="center"> | |||
| {!lot.stockOutLineId ? ( | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| onClick={() => { | |||
| setSelectedLotForQr(lot); | |||
| setQrModalOpen(true); | |||
| resetScan(); | |||
| }} | |||
| disabled={ | |||
| (lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected') | |||
| } | |||
| sx={{ | |||
| fontSize: '0.7rem', | |||
| py: 0.5, | |||
| minHeight: '40px', | |||
| whiteSpace: 'nowrap', | |||
| minWidth: '80px', | |||
| }} | |||
| startIcon={<QrCodeIcon />} | |||
| title="Click to scan QR code" | |||
| > | |||
| {t("Scan")} | |||
| </Button> | |||
| ) : ( | |||
| // ✅ When stockOutLineId exists, show TextField + Issue button | |||
| <Stack direction="row" spacing={1} alignItems="center"> | |||
| <TextField | |||
| type="number" | |||
| size="small" | |||
| value={pickQtyData[`${lot.pickOrderLineId}-${lot.lotId}`] || ''} | |||
| onChange={(e) => { | |||
| const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; | |||
| handlePickQtyChange(lotKey, parseFloat(e.target.value) || 0); | |||
| }} | |||
| disabled={ | |||
| (lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected') || | |||
| lot.stockOutLineStatus === 'completed' | |||
| } | |||
| inputProps={{ | |||
| min: 0, | |||
| max: calculateRemainingRequiredQty(lot), | |||
| step: 0.01 | |||
| }} | |||
| sx={{ | |||
| width: '60px', | |||
| height: '28px', | |||
| '& .MuiInputBase-input': { | |||
| fontSize: '0.7rem', | |||
| textAlign: 'center', | |||
| padding: '6px 8px' | |||
| } | |||
| }} | |||
| placeholder="0" | |||
| /> | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| onClick={() => handlePickExecutionForm(lot)} | |||
| sx={{ | |||
| fontSize: '0.7rem', | |||
| py: 0.5, | |||
| minHeight: '28px', | |||
| minWidth: '60px', | |||
| borderColor: 'warning.main', | |||
| color: 'warning.main' | |||
| }} | |||
| title="Report missing or bad items" | |||
| > | |||
| {t("Issue")} | |||
| </Button> | |||
| </Stack> | |||
| )} | |||
| </TableCell> | |||
| <TableCell align="center"> | |||
| <Stack direction="column" spacing={1} alignItems="center"> | |||
| <Button | |||
| variant="contained" | |||
| onClick={() => { | |||
| handleSubmitPickQty(lot); | |||
| }} | |||
| disabled={ | |||
| (lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected') || | |||
| !pickQtyData[`${lot.pickOrderLineId}-${lot.lotId}`] || | |||
| !lot.stockOutLineStatus || | |||
| !['pending','checked', 'partially_completed'].includes(lot.stockOutLineStatus.toLowerCase()) | |||
| } | |||
| sx={{ | |||
| fontSize: '0.75rem', | |||
| py: 0.5, | |||
| minHeight: '28px' | |||
| }} | |||
| > | |||
| {t("Submit")} | |||
| </Button> | |||
| </Stack> | |||
| </TableCell> | |||
| </TableRow> | |||
| )) | |||
| )} | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| */} | |||
| {/* | |||
| <TablePagination | |||
| component="div" | |||
| count={combinedLotData.length} | |||
| page={paginationController.pageNum} | |||
| rowsPerPage={paginationController.pageSize} | |||
| onPageChange={handlePageChange} | |||
| onRowsPerPageChange={handlePageSizeChange} | |||
| rowsPerPageOptions={[10, 25, 50]} | |||
| labelRowsPerPage={t("Rows per page")} | |||
| labelDisplayedRows={({ from, to, count }) => | |||
| `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` | |||
| } | |||
| /> | |||
| </Box> | |||
| </Stack> | |||
| )} | |||
| {/* Modals */} | |||
| <QrCodeModal | |||
| open={qrModalOpen} | |||
| onClose={() => setQrModalOpen(false)} | |||
| lot={selectedLotForQr} | |||
| onQrCodeSubmit={handleQrCodeSubmit} | |||
| combinedLotData={combinedLotData} | |||
| /> | |||
| <GoodPickExecutionForm | |||
| open={pickExecutionFormOpen} | |||
| onClose={() => { | |||
| setQrModalOpen(false); | |||
| setSelectedLotForQr(null); | |||
| stopScan(); | |||
| resetScan(); | |||
| setPickExecutionFormOpen(false); | |||
| setSelectedLotForExecutionForm(null); | |||
| }} | |||
| lot={selectedLotForQr} | |||
| combinedLotData={combinedLotData} // ✅ Add this prop | |||
| onQrCodeSubmit={handleQrCodeSubmitFromModal} | |||
| onSubmit={handlePickExecutionFormSubmit} | |||
| selectedLot={selectedLotForExecutionForm} | |||
| selectedPickOrderLine={null} | |||
| pickOrderId={selectedLotForExecutionForm?.pickOrderId} | |||
| pickOrderCreateDate={null} | |||
| /> | |||
| {pickExecutionFormOpen && selectedLotForExecutionForm && ( | |||
| <GoodPickExecutionForm | |||
| open={pickExecutionFormOpen} | |||
| onClose={() => { | |||
| setPickExecutionFormOpen(false); | |||
| setSelectedLotForExecutionForm(null); | |||
| }} | |||
| onSubmit={handlePickExecutionFormSubmit} | |||
| selectedLot={selectedLotForExecutionForm} | |||
| selectedPickOrderLine={{ | |||
| id: selectedLotForExecutionForm.pickOrderLineId, | |||
| itemId: selectedLotForExecutionForm.itemId, | |||
| itemCode: selectedLotForExecutionForm.itemCode, | |||
| itemName: selectedLotForExecutionForm.itemName, | |||
| pickOrderCode: selectedLotForExecutionForm.pickOrderCode, | |||
| // ✅ Add missing required properties from GetPickOrderLineInfo interface | |||
| availableQty: selectedLotForExecutionForm.availableQty || 0, | |||
| requiredQty: selectedLotForExecutionForm.requiredQty || 0, | |||
| uomCode: selectedLotForExecutionForm.uomCode || '', | |||
| uomDesc: selectedLotForExecutionForm.uomDesc || '', | |||
| pickedQty: selectedLotForExecutionForm.actualPickQty || 0, // ✅ Use pickedQty instead of actualPickQty | |||
| suggestedList: [] // ✅ Add required suggestedList property | |||
| }} | |||
| pickOrderId={selectedLotForExecutionForm.pickOrderId} | |||
| pickOrderCreateDate={new Date()} | |||
| /> | |||
| )} | |||
| */} | |||
| </FormProvider> | |||
| ); | |||
| }; | |||
| @@ -270,54 +270,68 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||
| </Typography> | |||
| </Box> | |||
| {/* FG Pick Orders 信息 */} | |||
| <Box sx={{ mb: 2 }}> | |||
| {selectedDoPickOrder.fgPickOrders.map((fgOrder, index) => ( | |||
| <FGPickOrderCard | |||
| key={index} | |||
| fgOrder={fgOrder} | |||
| onQrCodeClick={() => {}} // 只读模式 | |||
| /> | |||
| ))} | |||
| {/* 订单基本信息 */} | |||
| <Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}> | |||
| <Typography variant="h6" gutterBottom> | |||
| {t("Order Information")} | |||
| </Typography> | |||
| <Typography variant="body2"> | |||
| <strong>{t("Shop Name")}:</strong> {selectedDoPickOrder.shopName} | |||
| </Typography> | |||
| <Typography variant="body2"> | |||
| <strong>{t("Delivery No")}:</strong> {selectedDoPickOrder.deliveryNo} | |||
| </Typography> | |||
| <Typography variant="body2"> | |||
| <strong>{t("Completed Date")}:</strong> {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} | |||
| </Typography> | |||
| </Box> | |||
| {/* 类似 GoodPickExecution 的表格 */} | |||
| <TableContainer component={Paper}> | |||
| <Table> | |||
| <TableHead> | |||
| <TableRow> | |||
| <TableCell>{t("Pick Order Code")}</TableCell> | |||
| <TableCell>{t("Item Code")}</TableCell> | |||
| <TableCell>{t("Item Name")}</TableCell> | |||
| <TableCell>{t("Lot No")}</TableCell> | |||
| <TableCell>{t("Location")}</TableCell> | |||
| <TableCell>{t("Required Qty")}</TableCell> | |||
| <TableCell>{t("Actual Pick Qty")}</TableCell> | |||
| <TableCell>{t("Submitted Status")}</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| {detailLotData.map((lot, index) => ( | |||
| <TableRow key={index}> | |||
| <TableCell>{lot.pickOrderCode}</TableCell> | |||
| <TableCell>{lot.itemCode}</TableCell> | |||
| <TableCell>{lot.itemName}</TableCell> | |||
| <TableCell>{lot.lotNo}</TableCell> | |||
| <TableCell>{lot.location}</TableCell> | |||
| <TableCell>{lot.requiredQty}</TableCell> | |||
| <TableCell>{lot.actualPickQty}</TableCell> | |||
| <TableCell> | |||
| <Chip | |||
| label={t(lot.processingStatus)} | |||
| color={lot.processingStatus === 'completed' ? 'success' : 'default'} | |||
| size="small" | |||
| /> | |||
| </TableCell> | |||
| {/* ✅ 添加数据检查 */} | |||
| {detailLotData.length === 0 ? ( | |||
| <Box sx={{ p: 3, textAlign: 'center' }}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("No lot details found for this order")} | |||
| </Typography> | |||
| </Box> | |||
| ) : ( | |||
| /* 显示完成数据的表格 */ | |||
| <TableContainer component={Paper}> | |||
| <Table> | |||
| <TableHead> | |||
| <TableRow> | |||
| <TableCell>{t("Pick Order Code")}</TableCell> | |||
| <TableCell>{t("Item Code")}</TableCell> | |||
| <TableCell>{t("Item Name")}</TableCell> | |||
| <TableCell>{t("Lot No")}</TableCell> | |||
| <TableCell>{t("Location")}</TableCell> | |||
| <TableCell>{t("Required Qty")}</TableCell> | |||
| <TableCell>{t("Actual Pick Qty")}</TableCell> | |||
| <TableCell>{t("Submitted Status")}</TableCell> | |||
| </TableRow> | |||
| ))} | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| </TableHead> | |||
| <TableBody> | |||
| {detailLotData.map((lot, index) => ( | |||
| <TableRow key={index}> | |||
| <TableCell>{lot.pickOrderCode || 'N/A'}</TableCell> | |||
| <TableCell>{lot.itemCode || 'N/A'}</TableCell> | |||
| <TableCell>{lot.itemName || 'N/A'}</TableCell> | |||
| <TableCell>{lot.lotNo || 'N/A'}</TableCell> | |||
| <TableCell>{lot.location || 'N/A'}</TableCell> | |||
| <TableCell>{lot.requiredQty || 0}</TableCell> | |||
| <TableCell>{lot.actualPickQty || 0}</TableCell> | |||
| <TableCell> | |||
| <Chip | |||
| label={t(lot.processingStatus || 'unknown')} | |||
| color={lot.processingStatus === 'completed' ? 'success' : 'default'} | |||
| size="small" | |||
| /> | |||
| </TableCell> | |||
| </TableRow> | |||
| ))} | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| )} | |||
| </Box> | |||
| </FormProvider> | |||
| ); | |||