WorkbenchTicketReleaseTable 加 user filter DO release / FloorLanePanel:Truck X 票 + 搜尋依樓層production
| @@ -242,13 +242,16 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelection( | |||||
| shopName?: string, | shopName?: string, | ||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| releaseType?: string | |||||
| releaseType?: string, | |||||
| /** Optional `2F`/`4F`: Truck X split by DO supplier preferred floor (storeId stays null). */ | |||||
| floor?: string | |||||
| ): Promise<ReleasedDoPickOrderListItem[]> { | ): Promise<ReleasedDoPickOrderListItem[]> { | ||||
| const params = new URLSearchParams(); | const params = new URLSearchParams(); | ||||
| if (shopName?.trim()) params.append("shopName", shopName.trim()); | if (shopName?.trim()) params.append("shopName", shopName.trim()); | ||||
| if (storeId?.trim()) params.append("storeId", storeId.trim()); | if (storeId?.trim()) params.append("storeId", storeId.trim()); | ||||
| if (truck?.trim()) params.append("truck", truck.trim()); | if (truck?.trim()) params.append("truck", truck.trim()); | ||||
| if (releaseType?.trim()) params.append("releaseType", releaseType.trim()); | if (releaseType?.trim()) params.append("releaseType", releaseType.trim()); | ||||
| if (floor?.trim()) params.append("floor", floor.trim().replace("/", "").toUpperCase()); | |||||
| const query = params.toString(); | const query = params.toString(); | ||||
| const url = `${BASE_API_URL}/doPickOrder/workbench/released${query ? `?${query}` : ""}`; | const url = `${BASE_API_URL}/doPickOrder/workbench/released${query ? `?${query}` : ""}`; | ||||
| const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" }); | const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" }); | ||||
| @@ -261,7 +264,9 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelectionToday( | |||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| requiredDeliveryDate?: string, | requiredDeliveryDate?: string, | ||||
| releaseType?: string | |||||
| releaseType?: string, | |||||
| /** Optional `2F`/`4F`: Truck X split by DO supplier preferred floor (storeId stays null). */ | |||||
| floor?: string | |||||
| ): Promise<ReleasedDoPickOrderListItem[]> { | ): Promise<ReleasedDoPickOrderListItem[]> { | ||||
| const params = new URLSearchParams(); | const params = new URLSearchParams(); | ||||
| if (shopName?.trim()) params.append("shopName", shopName.trim()); | if (shopName?.trim()) params.append("shopName", shopName.trim()); | ||||
| @@ -269,6 +274,7 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelectionToday( | |||||
| if (truck?.trim()) params.append("truck", truck.trim()); | if (truck?.trim()) params.append("truck", truck.trim()); | ||||
| if (requiredDeliveryDate?.trim()) params.append("requiredDate", requiredDeliveryDate.trim()); | if (requiredDeliveryDate?.trim()) params.append("requiredDate", requiredDeliveryDate.trim()); | ||||
| if (releaseType?.trim()) params.append("releaseType", releaseType.trim()); | if (releaseType?.trim()) params.append("releaseType", releaseType.trim()); | ||||
| if (floor?.trim()) params.append("floor", floor.trim().replace("/", "").toUpperCase()); | |||||
| const query = params.toString(); | const query = params.toString(); | ||||
| const url = `${BASE_API_URL}/doPickOrder/workbench/released-today${query ? `?${query}` : ""}`; | const url = `${BASE_API_URL}/doPickOrder/workbench/released-today${query ? `?${query}` : ""}`; | ||||
| const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" }); | const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" }); | ||||
| @@ -85,7 +85,13 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| const [modalReleaseTypeFilter, setModalReleaseTypeFilter] = useState<string | undefined>(undefined); | const [modalReleaseTypeFilter, setModalReleaseTypeFilter] = useState<string | undefined>(undefined); | ||||
| const [modalFilterRequiredDeliveryDate, setModalFilterRequiredDeliveryDate] = useState<string | undefined>(undefined); | const [modalFilterRequiredDeliveryDate, setModalFilterRequiredDeliveryDate] = useState<string | undefined>(undefined); | ||||
| const [modalInitialShopSearch, setModalInitialShopSearch] = useState<string | undefined>(undefined); | const [modalInitialShopSearch, setModalInitialShopSearch] = useState<string | undefined>(undefined); | ||||
| const defaultTruckCount = summary4F?.defaultTruckCount ?? 0; | |||||
| const [modalTruckXFloor, setModalTruckXFloor] = useState<string | undefined>(undefined); | |||||
| const ticketFloorApiKey = useMemo( | |||||
| () => ticketFloor.replace("/", "").trim().toUpperCase(), | |||||
| [ticketFloor], | |||||
| ); | |||||
| const defaultTruckCount = | |||||
| (ticketFloor === "2/F" ? summary2F?.defaultTruckCount : summary4F?.defaultTruckCount) ?? 0; | |||||
| const etraEnterInFlightRef = useRef(false); | const etraEnterInFlightRef = useRef(false); | ||||
| const [etraMergeDialogOpen, setEtraMergeDialogOpen] = useState(false); | const [etraMergeDialogOpen, setEtraMergeDialogOpen] = useState(false); | ||||
| @@ -108,16 +114,25 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| shopName?: string, | shopName?: string, | ||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| releaseType?: string | |||||
| ) => fetchWorkbenchReleasedDoPickOrdersForSelection(shopName, storeId, truck, releaseType), | |||||
| releaseType?: string, | |||||
| floor?: string | |||||
| ) => fetchWorkbenchReleasedDoPickOrdersForSelection(shopName, storeId, truck, releaseType, floor), | |||||
| loadToday: ( | loadToday: ( | ||||
| shopName?: string, | shopName?: string, | ||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| requiredDeliveryDate?: string, | requiredDeliveryDate?: string, | ||||
| releaseType?: string | |||||
| releaseType?: string, | |||||
| floor?: string | |||||
| ) => | ) => | ||||
| fetchWorkbenchReleasedDoPickOrdersForSelectionToday(shopName, storeId, truck, requiredDeliveryDate, releaseType), | |||||
| fetchWorkbenchReleasedDoPickOrdersForSelectionToday( | |||||
| shopName, | |||||
| storeId, | |||||
| truck, | |||||
| requiredDeliveryDate, | |||||
| releaseType, | |||||
| floor | |||||
| ), | |||||
| assignByListItemId: assignByDeliveryOrderPickOrderId, | assignByListItemId: assignByDeliveryOrderPickOrderId, | ||||
| }), | }), | ||||
| [], | [], | ||||
| @@ -234,7 +249,14 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| pendingRef.current += 1; | pendingRef.current += 1; | ||||
| startFullTimer(); | startFullTimer(); | ||||
| try { | try { | ||||
| const list = await fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, undefined, "車線-X"); | |||||
| // storeId stays undefined (Truck X); floor splits by DO supplier preferred floor. | |||||
| const list = await fetchWorkbenchReleasedDoPickOrdersForSelection( | |||||
| undefined, | |||||
| undefined, | |||||
| "車線-X", | |||||
| undefined, | |||||
| ticketFloorApiKey | |||||
| ); | |||||
| setBeforeTodayTruckXCount(list.length); | setBeforeTodayTruckXCount(list.length); | ||||
| } catch { | } catch { | ||||
| setBeforeTodayTruckXCount(0); | setBeforeTodayTruckXCount(0); | ||||
| @@ -244,12 +266,13 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| } | } | ||||
| }; | }; | ||||
| void loadBeforeTodayTruckX(); | void loadBeforeTodayTruckX(); | ||||
| }, [inEtraUi]); | |||||
| }, [inEtraUi, ticketFloorApiKey]); | |||||
| const clearModalEtraContext = useCallback(() => { | const clearModalEtraContext = useCallback(() => { | ||||
| setModalReleaseTypeFilter(undefined); | setModalReleaseTypeFilter(undefined); | ||||
| setModalFilterRequiredDeliveryDate(undefined); | setModalFilterRequiredDeliveryDate(undefined); | ||||
| setModalInitialShopSearch(undefined); | setModalInitialShopSearch(undefined); | ||||
| setModalTruckXFloor(undefined); | |||||
| }, []); | }, []); | ||||
| const openEnterEtraView = useCallback(async () => { | const openEnterEtraView = useCallback(async () => { | ||||
| @@ -538,6 +561,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| setSelectedTruck("車線-X"); | setSelectedTruck("車線-X"); | ||||
| setIsDefaultTruck(true); | setIsDefaultTruck(true); | ||||
| setDefaultDateScope("today"); | setDefaultDateScope("today"); | ||||
| setModalTruckXFloor(ticketFloorApiKey); | |||||
| setModalOpen(true); | setModalOpen(true); | ||||
| }} | }} | ||||
| > | > | ||||
| @@ -638,10 +662,11 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| variant="outlined" | variant="outlined" | ||||
| onClick={() => { | onClick={() => { | ||||
| clearModalEtraContext(); | clearModalEtraContext(); | ||||
| setSelectedStore("4/F"); | |||||
| setSelectedStore(""); | |||||
| setSelectedTruck("車線-X"); | setSelectedTruck("車線-X"); | ||||
| setIsDefaultTruck(true); | setIsDefaultTruck(true); | ||||
| setDefaultDateScope("before"); | setDefaultDateScope("before"); | ||||
| setModalTruckXFloor(ticketFloorApiKey); | |||||
| setModalOpen(true); | setModalOpen(true); | ||||
| }} | }} | ||||
| > | > | ||||
| @@ -746,6 +771,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| releaseTypeFilter={modalReleaseTypeFilter} | releaseTypeFilter={modalReleaseTypeFilter} | ||||
| filterRequiredDeliveryDate={modalFilterRequiredDeliveryDate} | filterRequiredDeliveryDate={modalFilterRequiredDeliveryDate} | ||||
| initialShopSearch={modalInitialShopSearch} | initialShopSearch={modalInitialShopSearch} | ||||
| truckXFloor={modalTruckXFloor} | |||||
| onClose={() => { | onClose={() => { | ||||
| setModalOpen(false); | setModalOpen(false); | ||||
| clearModalEtraContext(); | clearModalEtraContext(); | ||||
| @@ -1223,13 +1223,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| }, [fetchAllCombinedLotData]); | }, [fetchAllCombinedLotData]); | ||||
| const openWorkbenchLotLabelModalForLot = useCallback( | const openWorkbenchLotLabelModalForLot = useCallback( | ||||
| (lot: any, reminderText?: string | null) => { | |||||
| ( | |||||
| lot: any, | |||||
| reminderText?: string | null, | |||||
| options?: { markAsScanIssue?: boolean }, | |||||
| ) => { | |||||
| const itemId = Number(lot?.itemId); | const itemId = Number(lot?.itemId); | ||||
| const stockInLineId = Number(lot?.stockInLineId); | const stockInLineId = Number(lot?.stockInLineId); | ||||
| const solId = Number(lot?.stockOutLineId); | const solId = Number(lot?.stockOutLineId); | ||||
| if (!Number.isFinite(itemId) || itemId <= 0 || !Number.isFinite(solId) || solId <= 0) { | if (!Number.isFinite(itemId) || itemId <= 0 || !Number.isFinite(solId) || solId <= 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| // 掃碼 issue 才標記:手動「批號二維碼」不傳 markAsScanIssue,批號不變紅 | |||||
| if (options?.markAsScanIssue) { | |||||
| rememberWorkbenchScanReject(solId, reminderText); | |||||
| } | |||||
| setWorkbenchLotLabelContextLot(lot); | setWorkbenchLotLabelContextLot(lot); | ||||
| if (Number.isFinite(stockInLineId) && stockInLineId > 0) { | if (Number.isFinite(stockInLineId) && stockInLineId > 0) { | ||||
| setWorkbenchLotLabelInitialPayload({ itemId, stockInLineId }); | setWorkbenchLotLabelInitialPayload({ itemId, stockInLineId }); | ||||
| @@ -1241,7 +1249,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| setQrScanSuccess(false); | setQrScanSuccess(false); | ||||
| setWorkbenchLotLabelModalOpen(true); | setWorkbenchLotLabelModalOpen(true); | ||||
| }, | }, | ||||
| [], | |||||
| [rememberWorkbenchScanReject], | |||||
| ); | ); | ||||
| const shouldOpenWorkbenchLotLabelModalForFailure = useCallback( | const shouldOpenWorkbenchLotLabelModalForFailure = useCallback( | ||||
| @@ -1826,6 +1834,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| openWorkbenchLotLabelModalForLot( | openWorkbenchLotLabelModalForLot( | ||||
| scannedLot, | scannedLot, | ||||
| t("This lot is not available, please scan another lot."), | t("This lot is not available, please scan another lot."), | ||||
| { markAsScanIssue: true }, | |||||
| ); | ); | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -1841,6 +1850,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| openWorkbenchLotLabelModalForLot( | openWorkbenchLotLabelModalForLot( | ||||
| scannedLot, | scannedLot, | ||||
| `Lot is expired (expiry=${scannedLot.expiryDate || "-"})`, | `Lot is expired (expiry=${scannedLot.expiryDate || "-"})`, | ||||
| { markAsScanIssue: true }, | |||||
| ); | ); | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -1963,7 +1973,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | ||||
| expectedLot | expectedLot | ||||
| ) { | ) { | ||||
| openWorkbenchLotLabelModalForLot(expectedLot, failMsg); | |||||
| openWorkbenchLotLabelModalForLot(expectedLot, failMsg, { | |||||
| markAsScanIssue: true, | |||||
| }); | |||||
| return; | return; | ||||
| } | } | ||||
| if (workbenchMode && expectedLot.stockOutLineId != null) { | if (workbenchMode && expectedLot.stockOutLineId != null) { | ||||
| @@ -2135,7 +2147,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | ||||
| expectedLot | expectedLot | ||||
| ) { | ) { | ||||
| openWorkbenchLotLabelModalForLot(expectedLot, failMsg); | |||||
| openWorkbenchLotLabelModalForLot(expectedLot, failMsg, { | |||||
| markAsScanIssue: true, | |||||
| }); | |||||
| return; | return; | ||||
| } | } | ||||
| if (workbenchMode && expectedLot.stockOutLineId != null) { | if (workbenchMode && expectedLot.stockOutLineId != null) { | ||||
| @@ -2330,7 +2344,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | ||||
| exactMatch | exactMatch | ||||
| ) { | ) { | ||||
| openWorkbenchLotLabelModalForLot(exactMatch, failMsg); | |||||
| openWorkbenchLotLabelModalForLot(exactMatch, failMsg, { | |||||
| markAsScanIssue: true, | |||||
| }); | |||||
| return; | return; | ||||
| } | } | ||||
| if (workbenchMode && exactMatch.stockOutLineId != null) { | if (workbenchMode && exactMatch.stockOutLineId != null) { | ||||
| @@ -2474,7 +2490,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && | ||||
| expectedLot | expectedLot | ||||
| ) { | ) { | ||||
| openWorkbenchLotLabelModalForLot(expectedLot, failMsg); | |||||
| openWorkbenchLotLabelModalForLot(expectedLot, failMsg, { | |||||
| markAsScanIssue: true, | |||||
| }); | |||||
| return; | return; | ||||
| } | } | ||||
| if (workbenchMode && expectedLot.stockOutLineId != null) { | if (workbenchMode && expectedLot.stockOutLineId != null) { | ||||
| @@ -4154,10 +4172,21 @@ const handleSubmitAllScanned = useCallback(async () => { | |||||
| <TableCell>{t("Item Code")}</TableCell> | <TableCell>{t("Item Code")}</TableCell> | ||||
| <TableCell>{t("Item Name")}</TableCell> | <TableCell>{t("Item Name")}</TableCell> | ||||
| <TableCell>{t("Route")}</TableCell> | |||||
| <TableCell>{t("Suggest Lot No.")}</TableCell> | |||||
| <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> | |||||
| <TableCell align="center">{t("Scan Result")}</TableCell> | |||||
| <TableCell | |||||
| align="center" | |||||
| sx={{ minWidth: 220, whiteSpace: "nowrap" }} | |||||
| > | |||||
| {`${t("Route")} / ${t("Suggest Lot No.")}`} | |||||
| </TableCell> | |||||
| <TableCell align="right" sx={{ whiteSpace: "nowrap", width: 88, px: 1 }}> | |||||
| {t("Lot Required Pick Qty")} | |||||
| </TableCell> | |||||
| <TableCell | |||||
| align="center" | |||||
| sx={{ width: 88, minWidth: 88, px: 0.5, whiteSpace: "nowrap" }} | |||||
| > | |||||
| {t("Scan Result")} | |||||
| </TableCell> | |||||
| {/*<TableCell align="center">{t("Qty will submit")}</TableCell>*/} | {/*<TableCell align="center">{t("Qty will submit")}</TableCell>*/} | ||||
| <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell> | <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| @@ -4165,7 +4194,7 @@ const handleSubmitAllScanned = useCallback(async () => { | |||||
| <TableBody> | <TableBody> | ||||
| {paginatedData.length === 0 ? ( | {paginatedData.length === 0 ? ( | ||||
| <TableRow> | <TableRow> | ||||
| <TableCell colSpan={11} align="center"> | |||||
| <TableCell colSpan={7} align="center"> | |||||
| <Typography variant="body2" color="text.secondary"> | <Typography variant="body2" color="text.secondary"> | ||||
| {t("No data available")} | {t("No data available")} | ||||
| </Typography> | </Typography> | ||||
| @@ -4226,36 +4255,49 @@ paginatedData.map((row, index) => { | |||||
| {row.isGroupFirst ? lot.itemName + '(' + lot.stockUnit + ')' : ""} | {row.isGroupFirst ? lot.itemName + '(' + lot.stockUnit + ')' : ""} | ||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell> | |||||
| <Typography variant="body2"> | |||||
| {lot.routerRoute || '-'} | |||||
| </Typography> | |||||
| </TableCell> | |||||
| <TableCell> | |||||
| <Stack direction="row" spacing={1} alignItems="center"> | |||||
| <Box sx={{ flex: 1, minWidth: 0 }}> | |||||
| <TableCell align="center" sx={{ minWidth: 220 }}> | |||||
| <Stack | |||||
| direction="row" | |||||
| spacing={1} | |||||
| alignItems="center" | |||||
| justifyContent="center" | |||||
| sx={{ width: "100%" }} | |||||
| > | |||||
| <Box sx={{ textAlign: "center" }}> | |||||
| <Typography variant="body2" sx={{ whiteSpace: "nowrap", textAlign: "center" }}> | |||||
| {lot.routerRoute || "-"} | |||||
| </Typography> | |||||
| {(() => { | {(() => { | ||||
| const hasLotNo = Boolean(lot.lotNo); | const hasLotNo = Boolean(lot.lotNo); | ||||
| const isExpired = lot.lotAvailability === 'expired'; | const isExpired = lot.lotAvailability === 'expired'; | ||||
| const isUnavailable = isInventoryLotLineUnavailable(lot); | const isUnavailable = isInventoryLotLineUnavailable(lot); | ||||
| const isNoLotHint = !hasLotNo && !rejectDisplay; // 顯示「請檢查周圍…」那種 | const isNoLotHint = !hasLotNo && !rejectDisplay; // 顯示「請檢查周圍…」那種 | ||||
| const isIssueText = | |||||
| Boolean(rejectDisplay) || !hasLotNo || isExpired || isUnavailable; | |||||
| const textColor = | |||||
| isNoLotHint | |||||
| ? 'error.main' // 或 'text.primary':固定黑,不受 handled / unavailable 影響 | |||||
| : rejectDisplay || isSolRejected || isUnavailable | |||||
| ? 'error.main' | |||||
| : isExpired | |||||
| ? 'warning.main' | |||||
| : 'inherit'; | |||||
| const solStatus = String(lot.stockOutLineStatus ?? "").toLowerCase(); | |||||
| const isComplete = | |||||
| solStatus === "completed" || | |||||
| solStatus === "checked" || | |||||
| solStatus === "partially_completed" || | |||||
| solStatus === "partially_complete"; | |||||
| const textColor = | |||||
| isNoLotHint | |||||
| ? "error.main" | |||||
| : rejectDisplay || isSolRejected || isUnavailable | |||||
| ? "error.main" | |||||
| : isExpired | |||||
| ? "warning.main" | |||||
| : isComplete | |||||
| ? "success.main" | |||||
| : "inherit"; | |||||
| const lotOnly = | |||||
| hasLotNo && !rejectDisplay && !isExpired && !isUnavailable; | |||||
| return ( | return ( | ||||
| <Typography | <Typography | ||||
| variant={isIssueText ? "body2" : "body2"} | |||||
| variant="body2" | |||||
| sx={{ | sx={{ | ||||
| color: textColor, | color: textColor, | ||||
| whiteSpace: 'pre-wrap', | |||||
| wordBreak: 'break-word', | |||||
| whiteSpace: lotOnly ? "nowrap" : "pre-wrap", | |||||
| wordBreak: lotOnly ? "normal" : "break-word", | |||||
| textAlign: "center", | |||||
| }} | }} | ||||
| > | > | ||||
| {hasLotNo ? ( | {hasLotNo ? ( | ||||
| @@ -4299,7 +4341,6 @@ paginatedData.map((row, index) => { | |||||
| sx={{ | sx={{ | ||||
| flexShrink: 0, | flexShrink: 0, | ||||
| fontSize: "0.75rem", | fontSize: "0.75rem", | ||||
| //py: 0.25, | |||||
| minWidth: "auto", | minWidth: "auto", | ||||
| px: 1, | px: 1, | ||||
| whiteSpace: "nowrap", | whiteSpace: "nowrap", | ||||
| @@ -4310,23 +4351,34 @@ paginatedData.map((row, index) => { | |||||
| )} | )} | ||||
| </Stack> | </Stack> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="right"> | |||||
| {(() => { | |||||
| const requiredQty = lot.requiredQty || 0; | |||||
| return requiredQty.toLocaleString() + '(' + lot.uomShortDesc + ')'; | |||||
| })()} | |||||
| <TableCell align="right" sx={{ whiteSpace: "nowrap", width: 88, px: 1 }}> | |||||
| <Typography variant="body1"> | |||||
| {(() => { | |||||
| const requiredQty = lot.requiredQty || 0; | |||||
| return requiredQty.toLocaleString() + '(' + lot.uomShortDesc + ')'; | |||||
| })()} | |||||
| </Typography> | |||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | |||||
| <TableCell align="center" sx={{ width: 88, minWidth: 88, px: 0.5 }}> | |||||
| {(() => { | {(() => { | ||||
| const status = lot.stockOutLineStatus?.toLowerCase(); | const status = lot.stockOutLineStatus?.toLowerCase(); | ||||
| const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected'; | const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected'; | ||||
| const isNoLot = !lot.lotNo; | const isNoLot = !lot.lotNo; | ||||
| const scanResultSlotSx = { | |||||
| width: 42, | |||||
| height: 42, | |||||
| display: 'flex', | |||||
| justifyContent: 'center', | |||||
| alignItems: 'center', | |||||
| mx: 'auto', | |||||
| } as const; | |||||
| // rejected lot:显示红色勾选(已扫描但被拒绝) | // rejected lot:显示红色勾选(已扫描但被拒绝) | ||||
| if (isRejected && !isNoLot) { | if (isRejected && !isNoLot) { | ||||
| return ( | return ( | ||||
| <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> | |||||
| <Box sx={scanResultSlotSx}> | |||||
| <Checkbox | <Checkbox | ||||
| checked={true} | checked={true} | ||||
| disabled={true} | disabled={true} | ||||
| @@ -4350,13 +4402,13 @@ paginatedData.map((row, index) => { | |||||
| status !== "partially_completed" && | status !== "partially_completed" && | ||||
| status !== "partially_complete" | status !== "partially_complete" | ||||
| ) { | ) { | ||||
| return null; | |||||
| return <Box sx={scanResultSlotSx} />; | |||||
| } | } | ||||
| // 正常 lot:已扫描(checked/partially_completed/completed) | // 正常 lot:已扫描(checked/partially_completed/completed) | ||||
| if (!isNoLot && status !== 'pending' && status !== 'rejected') { | if (!isNoLot && status !== 'pending' && status !== 'rejected') { | ||||
| return ( | return ( | ||||
| <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> | |||||
| <Box sx={scanResultSlotSx}> | |||||
| <Checkbox | <Checkbox | ||||
| checked={true} | checked={true} | ||||
| disabled={true} | disabled={true} | ||||
| @@ -4375,7 +4427,7 @@ paginatedData.map((row, index) => { | |||||
| // noLot 且已完成/部分完成:显示红色勾选 | // noLot 且已完成/部分完成:显示红色勾选 | ||||
| if (isNoLot && (status === 'partially_completed' || status === 'completed')) { | if (isNoLot && (status === 'partially_completed' || status === 'completed')) { | ||||
| return ( | return ( | ||||
| <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> | |||||
| <Box sx={scanResultSlotSx}> | |||||
| <Checkbox | <Checkbox | ||||
| checked={true} | checked={true} | ||||
| disabled={true} | disabled={true} | ||||
| @@ -4391,7 +4443,7 @@ paginatedData.map((row, index) => { | |||||
| ); | ); | ||||
| } | } | ||||
| return null; | |||||
| return <Box sx={scanResultSlotSx} />; | |||||
| })()} | })()} | ||||
| </TableCell> | </TableCell> | ||||
| {/* | {/* | ||||
| @@ -4477,11 +4529,10 @@ paginatedData.map((row, index) => { | |||||
| <Stack direction="row" spacing={1} alignItems="center" justifyContent="center"> | <Stack direction="row" spacing={1} alignItems="center" justifyContent="center"> | ||||
| {isRowPicked ? ( | {isRowPicked ? ( | ||||
| <Typography | <Typography | ||||
| variant="body2" | |||||
| variant="body1" | |||||
| sx={{ | sx={{ | ||||
| width: 96, | width: 96, | ||||
| textAlign: "center", | textAlign: "center", | ||||
| fontSize: "1rem", | |||||
| fontWeight: 500, | fontWeight: 500, | ||||
| }} | }} | ||||
| > | > | ||||
| @@ -4512,7 +4563,16 @@ paginatedData.map((row, index) => { | |||||
| inputProps={{ min: 0, step: 1 }} | inputProps={{ min: 0, step: 1 }} | ||||
| sx={{ | sx={{ | ||||
| width: 96, | width: 96, | ||||
| "& .MuiInputBase-input": { fontSize: "0.75rem", py: 0.5, textAlign: "center" }, | |||||
| "& .MuiInputBase-input": { | |||||
| typography: "body1", | |||||
| py: 0.5, | |||||
| textAlign: "center", | |||||
| "&::-webkit-outer-spin-button, &::-webkit-inner-spin-button": { | |||||
| WebkitAppearance: "none", | |||||
| margin: 0, | |||||
| }, | |||||
| MozAppearance: "textfield", | |||||
| }, | |||||
| }} | }} | ||||
| /> | /> | ||||
| )} | )} | ||||
| @@ -4557,7 +4617,14 @@ paginatedData.map((row, index) => { | |||||
| } | } | ||||
| sx={{ fontSize: '0.7rem', py: 0.5, minHeight: '28px', minWidth: '60px' }} | |||||
| sx={{ | |||||
| fontSize: "0.7rem", | |||||
| py: 0.5, | |||||
| minHeight: "28px", | |||||
| minWidth: "72px", | |||||
| whiteSpace: "nowrap", | |||||
| flexShrink: 0, | |||||
| }} | |||||
| > | > | ||||
| {t("Just Completed")} | {t("Just Completed")} | ||||
| </Button> | </Button> | ||||
| @@ -5,13 +5,16 @@ import { | |||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| CardContent, | CardContent, | ||||
| Checkbox, | |||||
| Chip, | Chip, | ||||
| CircularProgress, | CircularProgress, | ||||
| FormControl, | FormControl, | ||||
| InputLabel, | InputLabel, | ||||
| ListItemText, | |||||
| MenuItem, | MenuItem, | ||||
| Paper, | Paper, | ||||
| Select, | Select, | ||||
| SelectChangeEvent, | |||||
| Stack, | Stack, | ||||
| Table, | Table, | ||||
| TableBody, | TableBody, | ||||
| @@ -65,6 +68,14 @@ function showDoPickOpsButtons(row: WorkbenchTicketReleaseTable): boolean { | |||||
| ); | ); | ||||
| } | } | ||||
| const HANDLER_UNASSIGNED = "__UNASSIGNED__"; | |||||
| const HANDLER_SELECT_ALL = "__ALL__"; | |||||
| function handlerFilterKey(handlerName: string | null | undefined): string { | |||||
| const name = handlerName?.trim() ?? ""; | |||||
| return name || HANDLER_UNASSIGNED; | |||||
| } | |||||
| const WorkbenchTicketReleaseTableTab: React.FC = () => { | const WorkbenchTicketReleaseTableTab: React.FC = () => { | ||||
| const { t } = useTranslation("ticketReleaseTable"); | const { t } = useTranslation("ticketReleaseTable"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| @@ -73,6 +84,7 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => { | |||||
| const [queryDate, setQueryDate] = useState<Dayjs>(() => dayjs()); | const [queryDate, setQueryDate] = useState<Dayjs>(() => dayjs()); | ||||
| const [selectedFloor, setSelectedFloor] = useState<string>(""); | const [selectedFloor, setSelectedFloor] = useState<string>(""); | ||||
| const [selectedStatus, setSelectedStatus] = useState<string>("released"); | const [selectedStatus, setSelectedStatus] = useState<string>("released"); | ||||
| const [selectedHandlers, setSelectedHandlers] = useState<string[]>([]); | |||||
| const [data, setData] = useState<WorkbenchTicketReleaseTable[]>([]); | const [data, setData] = useState<WorkbenchTicketReleaseTable[]>([]); | ||||
| const [loading, setLoading] = useState<boolean>(true); | const [loading, setLoading] = useState<boolean>(true); | ||||
| const [now, setNow] = useState(dayjs()); | const [now, setNow] = useState(dayjs()); | ||||
| @@ -111,6 +123,28 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => { | |||||
| }, []); | }, []); | ||||
| const dayStr = queryDate.format("YYYY-MM-DD"); | const dayStr = queryDate.format("YYYY-MM-DD"); | ||||
| const handlerOptions = useMemo(() => { | |||||
| const names = new Set<string>(); | |||||
| let hasUnassigned = false; | |||||
| for (const item of data) { | |||||
| if (selectedFloor && item.storeId !== selectedFloor) continue; | |||||
| const name = item.handlerName?.trim(); | |||||
| if (name) names.add(name); | |||||
| else hasUnassigned = true; | |||||
| } | |||||
| const sorted = Array.from(names).sort((a, b) => a.localeCompare(b, "zh-Hant")); | |||||
| return hasUnassigned ? [HANDLER_UNASSIGNED, ...sorted] : sorted; | |||||
| }, [data, selectedFloor]); | |||||
| useEffect(() => { | |||||
| setSelectedHandlers((prev) => { | |||||
| if (prev.length === 0) return prev; | |||||
| const next = prev.filter((name) => handlerOptions.includes(name)); | |||||
| return next.length === prev.length ? prev : next; | |||||
| }); | |||||
| }, [handlerOptions]); | |||||
| const filteredData = useMemo(() => { | const filteredData = useMemo(() => { | ||||
| return data.filter((item) => { | return data.filter((item) => { | ||||
| if (selectedFloor && item.storeId !== selectedFloor) return false; | if (selectedFloor && item.storeId !== selectedFloor) return false; | ||||
| @@ -119,9 +153,27 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => { | |||||
| if (itemDate !== dayStr) return false; | if (itemDate !== dayStr) return false; | ||||
| } | } | ||||
| if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) return false; | if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) return false; | ||||
| if (selectedHandlers.length > 0 && !selectedHandlers.includes(handlerFilterKey(item.handlerName))) { | |||||
| return false; | |||||
| } | |||||
| return true; | return true; | ||||
| }); | }); | ||||
| }, [data, dayStr, selectedFloor, selectedStatus]); | |||||
| }, [data, dayStr, selectedFloor, selectedStatus, selectedHandlers]); | |||||
| const allHandlersSelected = | |||||
| handlerOptions.length > 0 && selectedHandlers.length === handlerOptions.length; | |||||
| const handleHandlersChange = (event: SelectChangeEvent<string[]>) => { | |||||
| const value = event.target.value; | |||||
| const next = typeof value === "string" ? value.split(",") : value; | |||||
| if (next.includes(HANDLER_SELECT_ALL)) { | |||||
| setSelectedHandlers(allHandlersSelected ? [] : handlerOptions); | |||||
| setPaginationController((prev) => ({ ...prev, pageNum: 0 })); | |||||
| return; | |||||
| } | |||||
| setSelectedHandlers(next.filter((v) => v !== HANDLER_SELECT_ALL)); | |||||
| setPaginationController((prev) => ({ ...prev, pageNum: 0 })); | |||||
| }; | |||||
| const paginatedData = useMemo(() => { | const paginatedData = useMemo(() => { | ||||
| const startIndex = paginationController.pageNum * paginationController.pageSize; | const startIndex = paginationController.pageNum * paginationController.pageSize; | ||||
| @@ -247,6 +299,39 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => { | |||||
| <MenuItem value="completed">{t("completed")}</MenuItem> | <MenuItem value="completed">{t("completed")}</MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <FormControl sx={{ minWidth: 200 }} size="small"> | |||||
| <InputLabel id="workbench-handler-select-label" shrink> | |||||
| {t("Handler Name")} | |||||
| </InputLabel> | |||||
| <Select | |||||
| labelId="workbench-handler-select-label" | |||||
| multiple | |||||
| value={selectedHandlers} | |||||
| label={t("Handler Name")} | |||||
| onChange={handleHandlersChange} | |||||
| displayEmpty | |||||
| renderValue={(selected) => { | |||||
| if (selected.length === 0) return t("All Handlers"); | |||||
| return selected | |||||
| .map((v) => (v === HANDLER_UNASSIGNED ? t("Unassigned") : v)) | |||||
| .join(", "); | |||||
| }} | |||||
| > | |||||
| <MenuItem value={HANDLER_SELECT_ALL}> | |||||
| <Checkbox | |||||
| checked={allHandlersSelected} | |||||
| indeterminate={selectedHandlers.length > 0 && !allHandlersSelected} | |||||
| /> | |||||
| <ListItemText primary={t("Select All")} /> | |||||
| </MenuItem> | |||||
| {handlerOptions.map((name) => ( | |||||
| <MenuItem key={name} value={name}> | |||||
| <Checkbox checked={selectedHandlers.includes(name)} /> | |||||
| <ListItemText primary={name === HANDLER_UNASSIGNED ? t("Unassigned") : name} /> | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | |||||
| </FormControl> | |||||
| <Box sx={{ flexGrow: 1 }} /> | <Box sx={{ flexGrow: 1 }} /> | ||||
| <Stack direction="row" spacing={2} sx={{ flexShrink: 0, alignSelf: "center" }}> | <Stack direction="row" spacing={2} sx={{ flexShrink: 0, alignSelf: "center" }}> | ||||
| <Typography variant="body2" sx={{ color: "text.secondary" }} suppressHydrationWarning> | <Typography variant="body2" sx={{ color: "text.secondary" }} suppressHydrationWarning> | ||||
| @@ -36,7 +36,9 @@ export type ReleasedDoPickListBridge = { | |||||
| shopName?: string, | shopName?: string, | ||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| releaseType?: string | |||||
| releaseType?: string, | |||||
| /** Optional `2F`/`4F` for Truck X supplier-floor split */ | |||||
| floor?: string | |||||
| ) => Promise<ReleasedDoPickOrderListItem[]>; | ) => Promise<ReleasedDoPickOrderListItem[]>; | ||||
| /** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */ | /** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */ | ||||
| loadToday: ( | loadToday: ( | ||||
| @@ -44,7 +46,9 @@ export type ReleasedDoPickListBridge = { | |||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| requiredDeliveryDate?: string, | requiredDeliveryDate?: string, | ||||
| releaseType?: string | |||||
| releaseType?: string, | |||||
| /** Optional `2F`/`4F` for Truck X supplier-floor split */ | |||||
| floor?: string | |||||
| ) => Promise<ReleasedDoPickOrderListItem[]>; | ) => Promise<ReleasedDoPickOrderListItem[]>; | ||||
| assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>; | assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>; | ||||
| }; | }; | ||||
| @@ -70,6 +74,10 @@ interface Props { | |||||
| * requiredDate instead of historical released (delivery date before calendar today). | * requiredDate instead of historical released (delivery date before calendar today). | ||||
| */ | */ | ||||
| filterRequiredDeliveryDate?: string; | filterRequiredDeliveryDate?: string; | ||||
| /** | |||||
| * Truck X only: `2F`/`4F` (or `2/F`/`4/F`) — filter by DO supplier preferred floor; storeId stays null. | |||||
| */ | |||||
| truckXFloor?: string; | |||||
| } | } | ||||
| const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | ||||
| @@ -85,6 +93,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| releaseTypeFilter, | releaseTypeFilter, | ||||
| initialShopSearch, | initialShopSearch, | ||||
| filterRequiredDeliveryDate, | filterRequiredDeliveryDate, | ||||
| truckXFloor, | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| @@ -102,6 +111,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| const loadReleased = listBridge?.loadBeforeToday ?? fetchReleasedDoPickOrdersForSelection; | const loadReleased = listBridge?.loadBeforeToday ?? fetchReleasedDoPickOrdersForSelection; | ||||
| const loadTodayFn = listBridge?.loadToday ?? fetchReleasedDoPickOrdersForSelectionToday; | const loadTodayFn = listBridge?.loadToday ?? fetchReleasedDoPickOrdersForSelectionToday; | ||||
| const floorArg = truckXFloor?.trim() || undefined; | |||||
| if (isDefaultTruck) { | if (isDefaultTruck) { | ||||
| if (defaultDateScopeProp === "today") { | if (defaultDateScopeProp === "today") { | ||||
| data = await loadTodayFn( | data = await loadTodayFn( | ||||
| @@ -109,14 +119,16 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| undefined, | undefined, | ||||
| "車線-X", | "車線-X", | ||||
| defaultTruckRequiredDeliveryDate?.trim() || undefined, | defaultTruckRequiredDeliveryDate?.trim() || undefined, | ||||
| releaseTypeFilter?.trim() || undefined | |||||
| releaseTypeFilter?.trim() || undefined, | |||||
| floorArg | |||||
| ); | ); | ||||
| } else { | } else { | ||||
| data = await loadReleased( | data = await loadReleased( | ||||
| undefined, | undefined, | ||||
| undefined, | undefined, | ||||
| "車線-X", | "車線-X", | ||||
| releaseTypeFilter?.trim() || undefined | |||||
| releaseTypeFilter?.trim() || undefined, | |||||
| floorArg | |||||
| ); | ); | ||||
| } | } | ||||
| } else if (filterRequiredDeliveryDate?.trim() && listBridge?.loadToday) { | } else if (filterRequiredDeliveryDate?.trim() && listBridge?.loadToday) { | ||||
| @@ -143,7 +155,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| } finally { | } finally { | ||||
| setLoading(false); | setLoading(false); | ||||
| } | } | ||||
| }, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScopeProp, listBridge, defaultTruckRequiredDeliveryDate, releaseTypeFilter, filterRequiredDeliveryDate]); | |||||
| }, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScopeProp, listBridge, defaultTruckRequiredDeliveryDate, releaseTypeFilter, filterRequiredDeliveryDate, truckXFloor]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| loadList(); | loadList(); | ||||
| @@ -1655,6 +1655,11 @@ const WorkbenchPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| const isRowExpired = | const isRowExpired = | ||||
| isWorkbenchSourceLotExpired(r) && !isRowRejected; | isWorkbenchSourceLotExpired(r) && !isRowRejected; | ||||
| const isRowUnavailable = isInventoryLotLineUnavailable(r); | const isRowUnavailable = isInventoryLotLineUnavailable(r); | ||||
| const isRowComplete = | |||||
| rowStatus === "completed" || | |||||
| rowStatus === "checked" || | |||||
| rowStatus === "partially_completed" || | |||||
| rowStatus === "partially_complete"; | |||||
| return ( | return ( | ||||
| <TableRow key={r.key}> | <TableRow key={r.key}> | ||||
| @@ -1678,7 +1683,9 @@ const WorkbenchPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| ? "error.main" | ? "error.main" | ||||
| : isRowExpired || isLotAvailabilityExpired(r) | : isRowExpired || isLotAvailabilityExpired(r) | ||||
| ? "warning.main" | ? "warning.main" | ||||
| : "inherit", | |||||
| : isRowComplete | |||||
| ? "success.main" | |||||
| : "inherit", | |||||
| }} | }} | ||||
| > | > | ||||
| {r.lotNo ? ( | {r.lotNo ? ( | ||||
| @@ -1833,7 +1840,19 @@ const WorkbenchPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| return { ...prev, [r.stockOutLineId]: n }; | return { ...prev, [r.stockOutLineId]: n }; | ||||
| }); | }); | ||||
| }} | }} | ||||
| sx={{ width: 96 }} | |||||
| sx={{ | |||||
| width: 96, | |||||
| "& .MuiInputBase-input": { | |||||
| typography: "body1", | |||||
| py: 0.5, | |||||
| textAlign: "center", | |||||
| "&::-webkit-outer-spin-button, &::-webkit-inner-spin-button": { | |||||
| WebkitAppearance: "none", | |||||
| margin: 0, | |||||
| }, | |||||
| MozAppearance: "textfield", | |||||
| }, | |||||
| }} | |||||
| disabled={!r.stockOutLineId || qtyEditableBySolId[r.stockOutLineId] !== true} | disabled={!r.stockOutLineId || qtyEditableBySolId[r.stockOutLineId] !== true} | ||||
| inputProps={{ min: 0, step: 1 }} | inputProps={{ min: 0, step: 1 }} | ||||
| /> | /> | ||||
| @@ -1,6 +1,7 @@ | |||||
| { | { | ||||
| "Actions": "Actions", | "Actions": "Actions", | ||||
| "All Floors": "All Floors", | "All Floors": "All Floors", | ||||
| "All Handlers": "All Handlers", | |||||
| "All Statuses": "All Statuses", | "All Statuses": "All Statuses", | ||||
| "Auto-refresh every 1 minute": "Auto-refresh every 1 minute", | "Auto-refresh every 1 minute": "Auto-refresh every 1 minute", | ||||
| "Auto-refresh every 10 minutes": "Auto-refresh every 10 minutes", | "Auto-refresh every 10 minutes": "Auto-refresh every 10 minutes", | ||||
| @@ -30,9 +31,11 @@ | |||||
| "Revert assignment": "Revert assignment", | "Revert assignment": "Revert assignment", | ||||
| "Revert assignment hint": "Revert assignment hint", | "Revert assignment hint": "Revert assignment hint", | ||||
| "Rows per page": "Rows per page", | "Rows per page": "Rows per page", | ||||
| "Select All": "Select All", | |||||
| "Select Date": "Select Date", | "Select Date": "Select Date", | ||||
| "Shop Name": "Shop Name", | "Shop Name": "Shop Name", | ||||
| "Status": "Status", | "Status": "Status", | ||||
| "Unassigned": "Unassigned", | |||||
| "Store ID": "Store ID", | "Store ID": "Store ID", | ||||
| "Target Date": "Target Date", | "Target Date": "Target Date", | ||||
| "Ticket Information": "Ticket Information", | "Ticket Information": "Ticket Information", | ||||
| @@ -12,7 +12,7 @@ | |||||
| "N/A": "不適用", | "N/A": "不適用", | ||||
| "Release Pick Orders": "放單", | "Release Pick Orders": "放單", | ||||
| "released": "已放單", | "released": "已放單", | ||||
| "is unavailable. Please check around have available QR code or not.": "不可用。請檢查周圍是否有可用的 QR 碼。", | |||||
| "is unavailable. Please check around have available QR code or not.": "不可用。請檢查周圍是否有可用的二維碼。", | |||||
| "No lot rows. Select a pick order above.": "沒有批次行。請選擇一個提料單。", | "No lot rows. Select a pick order above.": "沒有批次行。請選擇一個提料單。", | ||||
| "Loading...": "載入中...", | "Loading...": "載入中...", | ||||
| "Suggestion success": "建議成功", | "Suggestion success": "建議成功", | ||||
| @@ -65,7 +65,7 @@ | |||||
| "items": "項目", | "items": "項目", | ||||
| "Select Pick Order:": "選擇提料單:", | "Select Pick Order:": "選擇提料單:", | ||||
| "No Stock Available": "沒有庫存", | "No Stock Available": "沒有庫存", | ||||
| "is expired. Please check around have available QR code or not.": "已過期。請檢查周圍是否有可用的 QR 碼。", | |||||
| "is expired. Please check around have available QR code or not.": "已過期。請檢查周圍是否有可用的二維碼。", | |||||
| "Start Fail": "開始失敗", | "Start Fail": "開始失敗", | ||||
| "Start PO": "開始採購訂單", | "Start PO": "開始採購訂單", | ||||
| "Do you want to complete?": "確定完成嗎?", | "Do you want to complete?": "確定完成嗎?", | ||||
| @@ -136,7 +136,7 @@ | |||||
| "LotNo": "批號", | "LotNo": "批號", | ||||
| "Po Code": "採購訂單編號", | "Po Code": "採購訂單編號", | ||||
| "No Warehouse": "沒有倉庫", | "No Warehouse": "沒有倉庫", | ||||
| "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | |||||
| "Please scan warehouse qr code.": "請掃描倉庫二維碼。", | |||||
| "Reject": "拒絕", | "Reject": "拒絕", | ||||
| "submit": "確認提交", | "submit": "確認提交", | ||||
| @@ -270,7 +270,7 @@ | |||||
| "Pick order completed successfully!": "提料單完成成功!", | "Pick order completed successfully!": "提料單完成成功!", | ||||
| "Lot has been rejected and marked as unavailable.": "批號已拒絕並標記為不可用。", | "Lot has been rejected and marked as unavailable.": "批號已拒絕並標記為不可用。", | ||||
| "This order is insufficient, please pick another lot.": "此訂單不足,請選擇其他批號。", | "This order is insufficient, please pick another lot.": "此訂單不足,請選擇其他批號。", | ||||
| "Please finish QR code scan, QC check and pick order.": "請完成 QR 碼掃描、QC 檢查和提料。", | |||||
| "Please finish QR code scan, QC check and pick order.": "請完成二維碼掃描、QC 檢查和提料。", | |||||
| "No data available": "沒有資料", | "No data available": "沒有資料", | ||||
| "Please submit the pick order.": "請提交提料單。", | "Please submit the pick order.": "請提交提料單。", | ||||
| "Item lot to be Pick:": "批次貨品提料:", | "Item lot to be Pick:": "批次貨品提料:", | ||||
| @@ -293,13 +293,13 @@ | |||||
| "Original Available Qty": "原可用數", | "Original Available Qty": "原可用數", | ||||
| "Remaining Available Qty": "剩餘可用數", | "Remaining Available Qty": "剩餘可用數", | ||||
| "Please submit pick order.": "請提交提料單。", | "Please submit pick order.": "請提交提料單。", | ||||
| "Please finish QR code scan and pick order.": "請完成 QR 碼掃描和提料。", | |||||
| "Please finish QR code scanand pick order.": "請完成 QR 碼掃描和提料。", | |||||
| "Please finish QR code scan and pick order.": "請完成二維碼掃描和提料。", | |||||
| "Please finish QR code scanand pick order.": "請完成二維碼掃描和提料。", | |||||
| "First created group": "首次建立分組", | "First created group": "首次建立分組", | ||||
| "Latest created group": "最新建立分組", | "Latest created group": "最新建立分組", | ||||
| "Manual Input": "手動輸入", | "Manual Input": "手動輸入", | ||||
| "QR Code Scan for Lot": " QR 碼掃描批次", | |||||
| "Processing QR code...": "處理 QR 碼...", | |||||
| "QR Code Scan for Lot": "二維碼掃描批次", | |||||
| "Processing QR code...": "處理二維碼...", | |||||
| "The input is not the same as the expected lot number.": "輸入的批次號碼與預期的不符。", | "The input is not the same as the expected lot number.": "輸入的批次號碼與預期的不符。", | ||||
| "Verified successfully!": "驗證成功!", | "Verified successfully!": "驗證成功!", | ||||
| "Cancel": "取消", | "Cancel": "取消", | ||||
| @@ -370,7 +370,7 @@ | |||||
| "Scanning...":"掃描中...", | "Scanning...":"掃描中...", | ||||
| "Print DN/Label":"列印送貨單/標籤", | "Print DN/Label":"列印送貨單/標籤", | ||||
| "Store ID":"儲存編號", | "Store ID":"儲存編號", | ||||
| "QR code does not match any item in current orders.":"QR 碼不符合當前訂單中的任何貨品。", | |||||
| "QR code does not match any item in current orders.":"二維碼不符合當前訂單中的任何貨品。", | |||||
| "Lot Number Mismatch":"批次號碼不符", | "Lot Number Mismatch":"批次號碼不符", | ||||
| "The scanned item matches the expected item, but the lot number is different. Do you want to proceed with this different lot?":"掃描的貨品與預期的貨品相同,但批次號碼不同。您是否要繼續使用不同的批次?", | "The scanned item matches the expected item, but the lot number is different. Do you want to proceed with this different lot?":"掃描的貨品與預期的貨品相同,但批次號碼不同。您是否要繼續使用不同的批次?", | ||||
| "The scanned item matches the expected item, but the lot number is different. Scan again to confirm: scan the expected lot QR to keep the suggested lot, or scan the other lot QR again to switch.":"掃描貨品相同但批次不同。請再掃描一次以確認:掃描「建議批次」的 QR 可沿用該批次;再掃描「另一批次」的 QR 則切換為該批次。", | "The scanned item matches the expected item, but the lot number is different. Scan again to confirm: scan the expected lot QR to keep the suggested lot, or scan the other lot QR again to switch.":"掃描貨品相同但批次不同。請再掃描一次以確認:掃描「建議批次」的 QR 可沿用該批次;再掃描「另一批次」的 QR 則切換為該批次。", | ||||
| @@ -389,7 +389,7 @@ | |||||
| "Lot switch failed":"批次切換失敗", | "Lot switch failed":"批次切換失敗", | ||||
| "The system could not switch to the scanned lot. Review the lots below, then tap Confirm to retry.":"系統無法切換至掃描的批次。請核對下方批次後按「確認」重試。", | "The system could not switch to the scanned lot. Review the lots below, then tap Confirm to retry.":"系統無法切換至掃描的批次。請核對下方批次後按「確認」重試。", | ||||
| "You can also scan again: expected lot QR keeps the suggested line; scanned lot QR retries the switch.":"您也可以再掃描:掃描建議批次 QR 可保留該行;掃描欲切換批次 QR 可再次嘗試切換。", | "You can also scan again: expected lot QR keeps the suggested line; scanned lot QR retries the switch.":"您也可以再掃描:掃描建議批次 QR 可保留該行;掃描欲切換批次 QR 可再次嘗試切換。", | ||||
| "QR code verified.":"QR 碼驗證成功。", | |||||
| "QR code verified.":"二維碼驗證成功。", | |||||
| "Order Finished":"訂單完成", | "Order Finished":"訂單完成", | ||||
| "Submitted Status":"提交狀態", | "Submitted Status":"提交狀態", | ||||
| "Pick Execution Record":"提料執行記錄", | "Pick Execution Record":"提料執行記錄", | ||||
| @@ -507,16 +507,17 @@ | |||||
| "packaging": "提料中", | "packaging": "提料中", | ||||
| "No Stock Available": "沒有庫存可用", | "No Stock Available": "沒有庫存可用", | ||||
| "This lot is not available, please scan another lot.": "此批號不可用,請掃描其他批號。", | "This lot is not available, please scan another lot.": "此批號不可用,請掃描其他批號。", | ||||
| "This lot UOM does not match the pick line. Please scan another lot.": "此批號單位與提貨行不符,請掃描其他批號。", | |||||
| "Scanned lot UOM does not match pick line UOM": "此批號單位與提貨行不符,請掃描其他批號。", | |||||
| "This lot UOM does not match the pick line. Please scan another lot.": "此批號單位不符,請掃描其他批號。", | |||||
| "Scanned lot UOM does not match pick line UOM": "此批號單位不符,請掃描其他批號。", | |||||
| "This lot has already been picked. Please scan another lot.": "此批號已提貨,請掃描其他批號。", | "This lot has already been picked. Please scan another lot.": "此批號已提貨,請掃描其他批號。", | ||||
| "Scanned lot is already completed or checked": "此物料已提貨,請掃描其他批號。", | "Scanned lot is already completed or checked": "此物料已提貨,請掃描其他批號。", | ||||
| "Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.": "請檢查周圍是否有其他可用 QR 碼。", | |||||
| "Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.": "請檢查周圍是否有其他可用二維碼。", | |||||
| "Lot is expired (expiry={{expiry}})": "掃描批號已過期(到期日={{expiry}})", | "Lot is expired (expiry={{expiry}})": "掃描批號已過期(到期日={{expiry}})", | ||||
| "Day After Tomorrow": "後日", | "Day After Tomorrow": "後日", | ||||
| "Lot line is unavailable": "掃描批次不可用", | "Lot line is unavailable": "掃描批次不可用", | ||||
| "Select Date": "請選擇日期", | "Select Date": "請選擇日期", | ||||
| "Suggest Lot No.": "推薦批號", | |||||
| "Suggest Lot No.": "推薦批號/實際批號", | |||||
| "Search by Shop": "搜索商店", | "Search by Shop": "搜索商店", | ||||
| "Search by Truck": "搜索貨車", | "Search by Truck": "搜索貨車", | ||||
| "Print DN & Label": "列印提料單和送貨單標籤", | "Print DN & Label": "列印提料單和送貨單標籤", | ||||
| @@ -545,13 +546,13 @@ | |||||
| "4F ticket": "4/F 票", | "4F ticket": "4/F 票", | ||||
| "4F lane panel legend": "貨車班次 — 裝載序(未撳數/總單數)", | "4F lane panel legend": "貨車班次 — 裝載序(未撳數/總單數)", | ||||
| "Loading sequence n": "板{{n}}", | "Loading sequence n": "板{{n}}", | ||||
| "lot QR code": "批號 QR 碼", | |||||
| "lot QR code": "批號二維碼", | |||||
| "label Printer" : "標籤打印機", | "label Printer" : "標籤打印機", | ||||
| "A4 Printer" : "A4 打印機", | "A4 Printer" : "A4 打印機", | ||||
| "Loading Sequence": "裝載序", | "Loading Sequence": "裝載序", | ||||
| "Ticket No": "提票號碼", | "Ticket No": "提票號碼", | ||||
| "The scanned lot inventory line is unavailable. Cannot switch or bind; pick line was not updated.": "掃描的庫存批行為「不可用」,無法換批或綁定;揀貨行未更新。", | "The scanned lot inventory line is unavailable. Cannot switch or bind; pick line was not updated.": "掃描的庫存批行為「不可用」,無法換批或綁定;揀貨行未更新。", | ||||
| "is unavable. Please check around have available QR code or not.": "此批號不可用,請檢查周圍是否有可用的 QR 碼。", | |||||
| "is unavable. Please check around have available QR code or not.": "此批號不可用,請檢查周圍是否有可用的二維碼。", | |||||
| "Lot switch failed; pick line was not marked as checked.": "換批失敗;揀貨行未標為已核對。", | "Lot switch failed; pick line was not marked as checked.": "換批失敗;揀貨行未標為已核對。", | ||||
| "Lot confirmation failed. Please try again.": "確認批號失敗,請重試。", | "Lot confirmation failed. Please try again.": "確認批號失敗,請重試。", | ||||
| "Powder Mixture": "箱料粉", | "Powder Mixture": "箱料粉", | ||||
| @@ -25,6 +25,9 @@ | |||||
| "Released Time": "開始時間", | "Released Time": "開始時間", | ||||
| "Completed Time": "完成時間", | "Completed Time": "完成時間", | ||||
| "Handler Name": "負責員工", | "Handler Name": "負責員工", | ||||
| "All Handlers": "所有員工", | |||||
| "Select All": "全選", | |||||
| "Unassigned": "未分配", | |||||
| "Number of FG Items (Order Item(s) Count)": "訂單項目數量", | "Number of FG Items (Order Item(s) Count)": "訂單項目數量", | ||||
| "No data available": "沒有資料", | "No data available": "沒有資料", | ||||
| "Rows per page": "每頁行數", | "Rows per page": "每頁行數", | ||||