| @@ -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 [tabIndex, setTabIndex] = useState(0); | ||||
| const [totalCount, setTotalCount] = useState<number>(); | const [totalCount, setTotalCount] = useState<number>(); | ||||
| const [isAssigning, setIsAssigning] = useState(false); | 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 [isLoadingSummary, setIsLoadingSummary] = useState(false); | ||||
| const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>( | const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>( | ||||
| typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' | typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' | ||||
| @@ -83,6 +83,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| setReleasedOrderCount(0); | setReleasedOrderCount(0); | ||||
| } | } | ||||
| }, []); | }, []); | ||||
| /* | |||||
| const loadSummaries = useCallback(async () => { | const loadSummaries = useCallback(async () => { | ||||
| setIsLoadingSummary(true); | setIsLoadingSummary(true); | ||||
| try { | try { | ||||
| @@ -104,6 +105,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| // 每30秒刷新一次 | // 每30秒刷新一次 | ||||
| }, [loadSummaries]); | }, [loadSummaries]); | ||||
| */ | |||||
| const handleDraft = useCallback(async () =>{ | const handleDraft = useCallback(async () =>{ | ||||
| try{ | try{ | ||||
| if (fgPickOrdersData.length === 0) { | if (fgPickOrdersData.length === 0) { | ||||
| @@ -446,11 +448,11 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| const onAssigned = () => { | const onAssigned = () => { | ||||
| localStorage.removeItem('hideCompletedUntilNext'); | localStorage.removeItem('hideCompletedUntilNext'); | ||||
| setHideCompletedUntilNext(false); | setHideCompletedUntilNext(false); | ||||
| loadSummaries(); | |||||
| // loadSummaries(); | |||||
| }; | }; | ||||
| window.addEventListener('pickOrderAssigned', onAssigned); | window.addEventListener('pickOrderAssigned', onAssigned); | ||||
| return () => window.removeEventListener('pickOrderAssigned', onAssigned); | return () => window.removeEventListener('pickOrderAssigned', onAssigned); | ||||
| }, [loadSummaries]); | |||||
| }, []); | |||||
| // ... existing code ... | // ... existing code ... | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -479,7 +481,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| console.log("Reset print buttons for Pick Execution Record tab"); | console.log("Reset print buttons for Pick Execution Record tab"); | ||||
| } | } | ||||
| }, [tabIndex]); | }, [tabIndex]); | ||||
| /* | |||||
| // ... existing code ... | // ... existing code ... | ||||
| const handleAssignByLane = useCallback(async ( | const handleAssignByLane = useCallback(async ( | ||||
| storeId: string, | storeId: string, | ||||
| @@ -533,7 +535,7 @@ const handleAssignByLane = useCallback(async ( | |||||
| } | } | ||||
| }, [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) => { | ||||
| setTabIndex(newValue); | setTabIndex(newValue); | ||||
| @@ -731,216 +733,159 @@ const handleAssignByLane = useCallback(async ( | |||||
| return ( | return ( | ||||
| <Box sx={{ | <Box sx={{ | ||||
| height: '100vh', // Full viewport height | |||||
| // Full viewport height | |||||
| overflow: 'auto' // Single scrollbar for the whole page | overflow: 'auto' // Single scrollbar for the whole page | ||||
| }}> | }}> | ||||
| {/* Header section */} | {/* 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> | </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> | </Box> | ||||
| </Grid> | </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 */} | {/* Tabs section - ✅ Move the click handler here */} | ||||
| <Box sx={{ | <Box sx={{ | ||||
| @@ -49,6 +49,8 @@ import { SessionWithTokens } from "@/config/authConfig"; | |||||
| import { fetchStockInLineInfo } from "@/app/api/po/actions"; | import { fetchStockInLineInfo } from "@/app/api/po/actions"; | ||||
| import GoodPickExecutionForm from "./GoodPickExecutionForm"; | import GoodPickExecutionForm from "./GoodPickExecutionForm"; | ||||
| import FGPickOrderCard from "./FGPickOrderCard"; | import FGPickOrderCard from "./FGPickOrderCard"; | ||||
| import FinishedGoodFloorLanePanel from "./FGPickOrderCard"; | |||||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | |||||
| interface Props { | interface Props { | ||||
| filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
| onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | ||||
| @@ -963,16 +965,24 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||||
| return ( | return ( | ||||
| <FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
| {/* Search Box */} | |||||
| {/* ✅ 条件渲染:没有活动订单时显示楼层选择面板 */} | |||||
| {!combinedDataLoading && fgPickOrders.length === 0 ? ( | |||||
| <FinishedGoodFloorLanePanel | |||||
| onPickOrderAssigned={() => { | |||||
| if (currentUserId) { | |||||
| fetchAllCombinedLotData(currentUserId); | |||||
| } | |||||
| }} | |||||
| /> | |||||
| ) : ( | |||||
| // ✅ 有活动订单时,显示 FG 订单信息卡片 | |||||
| <Box> | <Box> | ||||
| {fgPickOrdersLoading ? ( | {fgPickOrdersLoading ? ( | ||||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | ||||
| <CircularProgress /> | <CircularProgress /> | ||||
| </Box> | </Box> | ||||
| ) : ( | ) : ( | ||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | |||||
| <Box> | |||||
| {fgPickOrders.length === 0 ? ( | {fgPickOrders.length === 0 ? ( | ||||
| <Box sx={{ p: 3, textAlign: 'center' }}> | <Box sx={{ p: 3, textAlign: 'center' }}> | ||||
| <Typography variant="body2" color="text.secondary"> | <Typography variant="body2" color="text.secondary"> | ||||
| @@ -980,276 +990,40 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| ) : ( | ) : ( | ||||
| // ✅ 使用新的 FGPickOrderInfoCard 组件(类似 DoInfoCard 的格式) | |||||
| fgPickOrders.map((fgOrder) => ( | fgPickOrders.map((fgOrder) => ( | ||||
| <FGPickOrderCard | |||||
| <FGPickOrderInfoCard | |||||
| key={fgOrder.pickOrderId} | key={fgOrder.pickOrderId} | ||||
| fgOrder={fgOrder} | fgOrder={fgOrder} | ||||
| onQrCodeClick={handleQrCodeClick} | |||||
| /> | /> | ||||
| )) | )) | ||||
| )} | )} | ||||
| </Box> | </Box> | ||||
| )} | )} | ||||
| </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 | <QrCodeModal | ||||
| open={qrModalOpen} | open={qrModalOpen} | ||||
| onClose={() => setQrModalOpen(false)} | |||||
| lot={selectedLotForQr} | |||||
| onQrCodeSubmit={handleQrCodeSubmit} | |||||
| combinedLotData={combinedLotData} | |||||
| /> | |||||
| <GoodPickExecutionForm | |||||
| open={pickExecutionFormOpen} | |||||
| onClose={() => { | 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> | </FormProvider> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -270,54 +270,68 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| </Typography> | </Typography> | ||||
| </Box> | </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> | </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> | </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> | </Box> | ||||
| </FormProvider> | </FormProvider> | ||||
| ); | ); | ||||