FPSMS-frontend
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

GoodPickExecution.tsx 40 KiB

7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
2ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
4ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
5ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
2ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
6ヶ月前
2ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
2ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
4ヶ月前
6ヶ月前
5ヶ月前
7ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
4ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
7ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
5ヶ月前
6ヶ月前
7ヶ月前
6ヶ月前
2ヶ月前
5ヶ月前
2ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
6ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
5ヶ月前
7ヶ月前
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  1. "use client";
  2. import {
  3. Box,
  4. Button,
  5. Stack,
  6. TextField,
  7. Typography,
  8. Alert,
  9. CircularProgress,
  10. Table,
  11. TableBody,
  12. TableCell,
  13. TableContainer,
  14. TableHead,
  15. TableRow,
  16. Paper,
  17. TablePagination,
  18. Modal,
  19. } from "@mui/material";
  20. import { useCallback, useEffect, useState, useRef, useMemo } from "react";
  21. import { useTranslation } from "react-i18next";
  22. import { useRouter } from "next/navigation";
  23. import {
  24. fetchAllPickOrderLotsHierarchical,
  25. updateStockOutLineStatus,
  26. createStockOutLine,
  27. recordPickExecutionIssue,
  28. fetchFGPickOrdersByUserId, // Add this import
  29. FGPickOrderResponse,
  30. autoAssignAndReleasePickOrder,
  31. AutoAssignReleaseResponse,
  32. checkPickOrderCompletion,
  33. PickOrderCompletionResponse,
  34. checkAndCompletePickOrderByConsoCode,
  35. fetchDoPickOrderDetail,
  36. DoPickOrderDetail,
  37. batchQrSubmit,
  38. } from "@/app/api/pickOrder/actions";
  39. import { fetchNameList, NameList } from "@/app/api/user/actions";
  40. import {
  41. FormProvider,
  42. useForm,
  43. } from "react-hook-form";
  44. import SearchBox, { Criterion } from "../SearchBox";
  45. import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
  46. import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
  47. import QrCodeIcon from '@mui/icons-material/QrCode';
  48. import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
  49. import { useSession } from "next-auth/react";
  50. import { SessionWithTokens } from "@/config/authConfig";
  51. import { fetchStockInLineInfo } from "@/app/api/po/actions";
  52. import GoodPickExecutionForm from "./GoodPickExecutionForm";
  53. import FGPickOrderCard from "./FGPickOrderCard";
  54. import FinishedGoodFloorLanePanel from "./FinishedGoodFloorLanePanel";
  55. import FGPickOrderInfoCard from "./FGPickOrderInfoCard";
  56. import GoodPickExecutiondetail from "./GoodPickExecutiondetail";
  57. interface Props {
  58. filterArgs: Record<string, any>;
  59. onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void;
  60. onSwitchToDetailTab?: () => void;
  61. }
  62. // QR Code Modal Component (from LotTable)
  63. const QrCodeModal: React.FC<{
  64. open: boolean;
  65. onClose: () => void;
  66. lot: any | null;
  67. onQrCodeSubmit: (lotNo: string) => void;
  68. combinedLotData: any[]; // Add this prop
  69. }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => {
  70. const { t } = useTranslation("pickOrder");
  71. const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  72. const [manualInput, setManualInput] = useState<string>('');
  73. const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false);
  74. const [manualInputError, setManualInputError] = useState<boolean>(false);
  75. const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false);
  76. const [qrScanFailed, setQrScanFailed] = useState<boolean>(false);
  77. const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
  78. const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
  79. const [scannedQrResult, setScannedQrResult] = useState<string>('');
  80. const [fgPickOrder, setFgPickOrder] = useState<FGPickOrderResponse | null>(null);
  81. // Process scanned QR codes
  82. useEffect(() => {
  83. if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) {
  84. const latestQr = qrValues[qrValues.length - 1];
  85. if (processedQrCodes.has(latestQr)) {
  86. console.log("QR code already processed, skipping...");
  87. return;
  88. }
  89. setProcessedQrCodes(prev => new Set(prev).add(latestQr));
  90. try {
  91. const qrData = JSON.parse(latestQr);
  92. if (qrData.stockInLineId && qrData.itemId) {
  93. setIsProcessingQr(true);
  94. setQrScanFailed(false);
  95. fetchStockInLineInfo(qrData.stockInLineId)
  96. .then((stockInLineInfo) => {
  97. console.log("Stock in line info:", stockInLineInfo);
  98. setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
  99. if (stockInLineInfo.lotNo === lot.lotNo) {
  100. console.log(` QR Code verified for lot: ${lot.lotNo}`);
  101. setQrScanSuccess(true);
  102. onQrCodeSubmit(lot.lotNo);
  103. onClose();
  104. resetScan();
  105. } else {
  106. console.log(`❌ QR Code mismatch. Expected: ${lot.lotNo}, Got: ${stockInLineInfo.lotNo}`);
  107. setQrScanFailed(true);
  108. setManualInputError(true);
  109. setManualInputSubmitted(true);
  110. }
  111. })
  112. .catch((error) => {
  113. console.error("Error fetching stock in line info:", error);
  114. setScannedQrResult('Error fetching data');
  115. setQrScanFailed(true);
  116. setManualInputError(true);
  117. setManualInputSubmitted(true);
  118. })
  119. .finally(() => {
  120. setIsProcessingQr(false);
  121. });
  122. } else {
  123. const qrContent = latestQr.replace(/[{}]/g, '');
  124. setScannedQrResult(qrContent);
  125. if (qrContent === lot.lotNo) {
  126. setQrScanSuccess(true);
  127. onQrCodeSubmit(lot.lotNo);
  128. onClose();
  129. resetScan();
  130. } else {
  131. setQrScanFailed(true);
  132. setManualInputError(true);
  133. setManualInputSubmitted(true);
  134. }
  135. }
  136. } catch (error) {
  137. console.log("QR code is not JSON format, trying direct comparison");
  138. const qrContent = latestQr.replace(/[{}]/g, '');
  139. setScannedQrResult(qrContent);
  140. if (qrContent === lot.lotNo) {
  141. setQrScanSuccess(true);
  142. onQrCodeSubmit(lot.lotNo);
  143. onClose();
  144. resetScan();
  145. } else {
  146. setQrScanFailed(true);
  147. setManualInputError(true);
  148. setManualInputSubmitted(true);
  149. }
  150. }
  151. }
  152. }, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes]);
  153. // Clear states when modal opens
  154. useEffect(() => {
  155. if (open) {
  156. setManualInput('');
  157. setManualInputSubmitted(false);
  158. setManualInputError(false);
  159. setIsProcessingQr(false);
  160. setQrScanFailed(false);
  161. setQrScanSuccess(false);
  162. setScannedQrResult('');
  163. setProcessedQrCodes(new Set());
  164. }
  165. }, [open]);
  166. useEffect(() => {
  167. if (lot) {
  168. setManualInput('');
  169. setManualInputSubmitted(false);
  170. setManualInputError(false);
  171. setIsProcessingQr(false);
  172. setQrScanFailed(false);
  173. setQrScanSuccess(false);
  174. setScannedQrResult('');
  175. setProcessedQrCodes(new Set());
  176. }
  177. }, [lot]);
  178. // Auto-submit manual input when it matches
  179. useEffect(() => {
  180. if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '' && !qrScanFailed && !qrScanSuccess) {
  181. console.log(' Auto-submitting manual input:', manualInput.trim());
  182. const timer = setTimeout(() => {
  183. setQrScanSuccess(true);
  184. onQrCodeSubmit(lot.lotNo);
  185. onClose();
  186. setManualInput('');
  187. setManualInputError(false);
  188. setManualInputSubmitted(false);
  189. }, 200);
  190. return () => clearTimeout(timer);
  191. }
  192. }, [manualInput, lot, onQrCodeSubmit, onClose, qrScanFailed, qrScanSuccess]);
  193. const handleManualSubmit = () => {
  194. if (manualInput.trim() === lot?.lotNo) {
  195. setQrScanSuccess(true);
  196. onQrCodeSubmit(lot.lotNo);
  197. onClose();
  198. setManualInput('');
  199. } else {
  200. setQrScanFailed(true);
  201. setManualInputError(true);
  202. setManualInputSubmitted(true);
  203. }
  204. };
  205. useEffect(() => {
  206. if (open) {
  207. startScan();
  208. }
  209. }, [open, startScan]);
  210. return (
  211. <Modal open={open} onClose={onClose}>
  212. <Box sx={{
  213. position: 'absolute',
  214. top: '50%',
  215. left: '50%',
  216. transform: 'translate(-50%, -50%)',
  217. bgcolor: 'background.paper',
  218. p: 3,
  219. borderRadius: 2,
  220. minWidth: 400,
  221. }}>
  222. <Typography variant="h6" gutterBottom>
  223. {t("QR Code Scan for Lot")}: {lot?.lotNo}
  224. </Typography>
  225. {isProcessingQr && (
  226. <Box sx={{ mb: 2, p: 2, backgroundColor: '#e3f2fd', borderRadius: 1 }}>
  227. <Typography variant="body2" color="primary">
  228. {t("Processing QR code...")}
  229. </Typography>
  230. </Box>
  231. )}
  232. <Box sx={{ mb: 2 }}>
  233. <Typography variant="body2" gutterBottom>
  234. <strong>{t("Manual Input")}:</strong>
  235. </Typography>
  236. <TextField
  237. fullWidth
  238. size="small"
  239. value={manualInput}
  240. onChange={(e) => {
  241. setManualInput(e.target.value);
  242. if (qrScanFailed || manualInputError) {
  243. setQrScanFailed(false);
  244. setManualInputError(false);
  245. setManualInputSubmitted(false);
  246. }
  247. }}
  248. sx={{ mb: 1 }}
  249. error={manualInputSubmitted && manualInputError}
  250. helperText={
  251. manualInputSubmitted && manualInputError
  252. ? `${t("The input is not the same as the expected lot number.")}`
  253. : ''
  254. }
  255. />
  256. <Button
  257. variant="contained"
  258. onClick={handleManualSubmit}
  259. disabled={!manualInput.trim()}
  260. size="small"
  261. color="primary"
  262. >
  263. {t("Submit")}
  264. </Button>
  265. </Box>
  266. {qrValues.length > 0 && (
  267. <Box sx={{
  268. mb: 2,
  269. p: 2,
  270. backgroundColor: qrScanFailed ? '#ffebee' : qrScanSuccess ? '#e8f5e8' : '#f5f5f5',
  271. borderRadius: 1
  272. }}>
  273. <Typography variant="body2" color={qrScanFailed ? 'error' : qrScanSuccess ? 'success' : 'text.secondary'}>
  274. <strong>{t("QR Scan Result:")}</strong> {scannedQrResult}
  275. </Typography>
  276. {qrScanSuccess && (
  277. <Typography variant="caption" color="success" display="block">
  278. {t("Verified successfully!")}
  279. </Typography>
  280. )}
  281. </Box>
  282. )}
  283. <Box sx={{ mt: 2, textAlign: 'right' }}>
  284. <Button onClick={onClose} variant="outlined">
  285. {t("Cancel")}
  286. </Button>
  287. </Box>
  288. </Box>
  289. </Modal>
  290. );
  291. };
  292. const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange, onSwitchToDetailTab }) => {
  293. const { t } = useTranslation("pickOrder");
  294. const router = useRouter();
  295. const { data: session } = useSession() as { data: SessionWithTokens | null };
  296. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  297. const [combinedLotData, setCombinedLotData] = useState<any[]>([]);
  298. const [combinedDataLoading, setCombinedDataLoading] = useState(false);
  299. const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]);
  300. const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  301. const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null);
  302. const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null);
  303. const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
  304. const [qrScanInput, setQrScanInput] = useState<string>('');
  305. const [qrScanError, setQrScanError] = useState<boolean>(false);
  306. const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
  307. const [pickQtyData, setPickQtyData] = useState<Record<string, number>>({});
  308. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  309. const [paginationController, setPaginationController] = useState({
  310. pageNum: 0,
  311. pageSize: 10,
  312. });
  313. const [usernameList, setUsernameList] = useState<NameList[]>([]);
  314. const initializationRef = useRef(false);
  315. const autoAssignRef = useRef(false);
  316. const formProps = useForm();
  317. const errors = formProps.formState.errors;
  318. // Add QR modal states
  319. const [qrModalOpen, setQrModalOpen] = useState(false);
  320. const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);
  321. // Add GoodPickExecutionForm states
  322. const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
  323. const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
  324. const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);
  325. const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false);
  326. // 在 GoodPickExecutiondetail.tsx 中修改 fetchFgPickOrdersData
  327. // 修改 fetchFgPickOrdersData 函数:
  328. const fetchFgPickOrdersData = useCallback(async () => {
  329. if (!currentUserId) return;
  330. setFgPickOrdersLoading(true);
  331. try {
  332. const fgPickOrders = await fetchFGPickOrdersByUserId(currentUserId);
  333. console.log("🔍 DEBUG: Fetched FG pick orders:", fgPickOrders);
  334. console.log("🔍 DEBUG: First order numberOfPickOrders:", fgPickOrders[0]?.numberOfPickOrders);
  335. setFgPickOrders(fgPickOrders);
  336. if (onFgPickOrdersChange) {
  337. onFgPickOrdersChange(fgPickOrders);
  338. }
  339. } catch (error) {
  340. console.error("❌ Error fetching FG pick orders:", error);
  341. setFgPickOrders([]);
  342. if (onFgPickOrdersChange) {
  343. onFgPickOrdersChange([]);
  344. }
  345. } finally {
  346. setFgPickOrdersLoading(false);
  347. }
  348. }, [currentUserId, selectedPickOrderId]);
  349. // 简化:移除复杂的 useEffect 依赖
  350. useEffect(() => {
  351. if (currentUserId) {
  352. fetchFgPickOrdersData();
  353. }
  354. }, [currentUserId, fetchFgPickOrdersData]);
  355. // Handle QR code button click
  356. const handleQrCodeClick = (pickOrderId: number) => {
  357. console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
  358. // TODO: Implement QR code functionality
  359. };
  360. useEffect(() => {
  361. startScan();
  362. return () => {
  363. stopScan();
  364. resetScan();
  365. };
  366. }, [startScan, stopScan, resetScan]);
  367. const fetchAllCombinedLotData = useCallback(async (userId?: number) => {
  368. setCombinedDataLoading(true);
  369. try {
  370. const userIdToUse = userId || currentUserId;
  371. console.log(" fetchAllCombinedLotData called with userId:", userIdToUse);
  372. if (!userIdToUse) {
  373. console.warn("⚠️ No userId available, skipping API call");
  374. setCombinedLotData([]);
  375. setOriginalCombinedData([]);
  376. return;
  377. }
  378. // ✅ Fix: fetchAllPickOrderLotsHierarchical returns hierarchical data, not a flat array
  379. const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse);
  380. console.log(" Hierarchical data:", hierarchicalData);
  381. // ✅ Fix: Ensure we always set an array
  382. // If hierarchicalData is not in the expected format, default to empty array
  383. let allLotDetails: any[] = [];
  384. if (hierarchicalData && Array.isArray(hierarchicalData)) {
  385. // If it's already an array, use it directly
  386. allLotDetails = hierarchicalData;
  387. } else if (hierarchicalData?.pickOrders && Array.isArray(hierarchicalData.pickOrders)) {
  388. // Process hierarchical data into flat array (similar to GoodPickExecutiondetail.tsx)
  389. const mergedPickOrder = hierarchicalData.pickOrders[0];
  390. if (mergedPickOrder?.pickOrderLines) {
  391. mergedPickOrder.pickOrderLines.forEach((line: any) => {
  392. if (line.lots && line.lots.length > 0) {
  393. line.lots.forEach((lot: any) => {
  394. allLotDetails.push({
  395. pickOrderConsoCode: mergedPickOrder.consoCode,
  396. pickOrderTargetDate: mergedPickOrder.targetDate,
  397. pickOrderStatus: mergedPickOrder.status,
  398. pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0,
  399. pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
  400. pickOrderLineId: line.id,
  401. pickOrderLineRequiredQty: line.requiredQty,
  402. pickOrderLineStatus: line.status,
  403. itemId: line.item?.id,
  404. itemCode: line.item?.code,
  405. itemName: line.item?.name,
  406. uomDesc: line.item?.uomDesc,
  407. uomShortDesc: line.item?.uomShortDesc,
  408. lotId: lot.id,
  409. lotNo: lot.lotNo,
  410. expiryDate: lot.expiryDate,
  411. location: lot.location,
  412. stockUnit: lot.stockUnit,
  413. availableQty: lot.availableQty,
  414. requiredQty: lot.requiredQty,
  415. actualPickQty: lot.actualPickQty,
  416. lotStatus: lot.lotStatus,
  417. lotAvailability: lot.lotAvailability,
  418. processingStatus: lot.processingStatus,
  419. stockOutLineId: lot.stockOutLineId,
  420. stockOutLineStatus: lot.stockOutLineStatus,
  421. stockOutLineQty: lot.stockOutLineQty,
  422. routerId: lot.router?.id,
  423. routerIndex: lot.router?.index,
  424. routerRoute: lot.router?.route,
  425. routerArea: lot.router?.area,
  426. });
  427. });
  428. }
  429. });
  430. }
  431. }
  432. console.log(" All combined lot details:", allLotDetails);
  433. setCombinedLotData(allLotDetails);
  434. setOriginalCombinedData(allLotDetails);
  435. // ✅ Fix: Add safety check - ensure allLotDetails is an array before using .every()
  436. const allCompleted = Array.isArray(allLotDetails) && allLotDetails.length > 0 && allLotDetails.every((lot: any) =>
  437. lot.processingStatus === 'completed'
  438. );
  439. // 发送完成状态事件,包含标签页信息
  440. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  441. detail: {
  442. allLotsCompleted: allCompleted,
  443. tabIndex: 0 // 明确指定这是来自标签页 0 的事件
  444. }
  445. }));
  446. } catch (error) {
  447. console.error("❌ Error fetching combined lot data:", error);
  448. setCombinedLotData([]);
  449. setOriginalCombinedData([]);
  450. // 如果加载失败,禁用打印按钮
  451. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  452. detail: {
  453. allLotsCompleted: false,
  454. tabIndex: 0
  455. }
  456. }));
  457. } finally {
  458. setCombinedDataLoading(false);
  459. }
  460. }, [currentUserId, combinedLotData]);
  461. // Only fetch existing data when session is ready, no auto-assignment
  462. useEffect(() => {
  463. if (session && currentUserId && !initializationRef.current) {
  464. console.log(" Session loaded, initializing pick order...");
  465. initializationRef.current = true;
  466. // Only fetch existing data, no auto-assignment
  467. fetchAllCombinedLotData();
  468. }
  469. }, [session, currentUserId, fetchAllCombinedLotData]);
  470. // Add event listener for manual assignment
  471. useEffect(() => {
  472. const handlePickOrderAssigned = () => {
  473. console.log("🔄 Pick order assigned event received, refreshing data...");
  474. fetchAllCombinedLotData();
  475. };
  476. window.addEventListener('pickOrderAssigned', handlePickOrderAssigned);
  477. return () => {
  478. window.removeEventListener('pickOrderAssigned', handlePickOrderAssigned);
  479. };
  480. }, [fetchAllCombinedLotData]);
  481. // Handle QR code submission for matched lot (external scanning)
  482. // Handle QR code submission for matched lot (external scanning)
  483. /*
  484. const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
  485. console.log(` Processing QR Code for lot: ${lotNo}`);
  486. // Use current data without refreshing to avoid infinite loop
  487. const currentLotData = combinedLotData;
  488. console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo));
  489. const matchingLots = currentLotData.filter(lot =>
  490. lot.lotNo === lotNo ||
  491. lot.lotNo?.toLowerCase() === lotNo.toLowerCase()
  492. );
  493. if (matchingLots.length === 0) {
  494. console.error(`❌ Lot not found: ${lotNo}`);
  495. setQrScanError(true);
  496. setQrScanSuccess(false);
  497. return;
  498. }
  499. console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
  500. setQrScanError(false);
  501. try {
  502. let successCount = 0;
  503. let existsCount = 0;
  504. let errorCount = 0;
  505. for (const matchingLot of matchingLots) {
  506. console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`);
  507. if (matchingLot.stockOutLineId) {
  508. console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
  509. existsCount++;
  510. } else {
  511. const stockOutLineData: CreateStockOutLine = {
  512. consoCode: matchingLot.pickOrderConsoCode,
  513. pickOrderLineId: matchingLot.pickOrderLineId,
  514. inventoryLotLineId: matchingLot.lotId,
  515. qty: 0.0
  516. };
  517. console.log(`Creating stock out line for pick order line ${matchingLot.pickOrderLineId}:`, stockOutLineData);
  518. const result = await createStockOutLine(stockOutLineData);
  519. console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, result);
  520. if (result && result.code === "EXISTS") {
  521. console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
  522. existsCount++;
  523. } else if (result && result.code === "SUCCESS") {
  524. console.log(` Stock out line created successfully for line ${matchingLot.pickOrderLineId}`);
  525. successCount++;
  526. } else {
  527. console.error(`❌ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, result);
  528. errorCount++;
  529. }
  530. }
  531. }
  532. // Always refresh data after processing (success or failure)
  533. console.log("🔄 Refreshing data after QR code processing...");
  534. await fetchAllCombinedLotData();
  535. if (successCount > 0 || existsCount > 0) {
  536. console.log(` QR Code processing completed: ${successCount} created, ${existsCount} already existed`);
  537. setQrScanSuccess(true);
  538. setQrScanInput(''); // Clear input after successful processing
  539. // Clear success state after a delay
  540. setTimeout(() => {
  541. setQrScanSuccess(false);
  542. }, 2000);
  543. } else {
  544. console.error(`❌ QR Code processing failed: ${errorCount} errors`);
  545. setQrScanError(true);
  546. setQrScanSuccess(false);
  547. // Clear error state after a delay
  548. setTimeout(() => {
  549. setQrScanError(false);
  550. }, 3000);
  551. }
  552. } catch (error) {
  553. console.error("❌ Error processing QR code:", error);
  554. setQrScanError(true);
  555. setQrScanSuccess(false);
  556. // Still refresh data even on error
  557. await fetchAllCombinedLotData();
  558. // Clear error state after a delay
  559. setTimeout(() => {
  560. setQrScanError(false);
  561. }, 3000);
  562. }
  563. }, [combinedLotData, fetchAllCombinedLotData]);
  564. */ const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
  565. console.log(` Processing QR Code for lot: ${lotNo}`);
  566. // Use current data without refreshing to avoid infinite loop
  567. const currentLotData = combinedLotData;
  568. console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo));
  569. const matchingLots = currentLotData.filter(lot =>
  570. lot.lotNo === lotNo ||
  571. lot.lotNo?.toLowerCase() === lotNo.toLowerCase()
  572. );
  573. if (matchingLots.length === 0) {
  574. console.error(`❌ Lot not found: ${lotNo}`);
  575. setQrScanError(true);
  576. setQrScanSuccess(false);
  577. return;
  578. }
  579. console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
  580. setQrScanError(false);
  581. try {
  582. let successCount = 0;
  583. let existsCount = 0;
  584. let errorCount = 0;
  585. for (const matchingLot of matchingLots) {
  586. console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`);
  587. if (matchingLot.stockOutLineId) {
  588. console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
  589. existsCount++;
  590. } else {
  591. const stockOutLineData: CreateStockOutLine = {
  592. consoCode: matchingLot.pickOrderConsoCode,
  593. pickOrderLineId: matchingLot.pickOrderLineId,
  594. inventoryLotLineId: matchingLot.lotId,
  595. qty: 0.0
  596. };
  597. console.log(`Creating stock out line for pick order line ${matchingLot.pickOrderLineId}:`, stockOutLineData);
  598. const result = await createStockOutLine(stockOutLineData);
  599. console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, result);
  600. if (result && result.code === "EXISTS") {
  601. console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
  602. existsCount++;
  603. } else if (result && result.code === "SUCCESS") {
  604. console.log(` Stock out line created successfully for line ${matchingLot.pickOrderLineId}`);
  605. successCount++;
  606. } else {
  607. console.error(`❌ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, result);
  608. errorCount++;
  609. }
  610. }
  611. }
  612. // Always refresh data after processing (success or failure)
  613. console.log("🔄 Refreshing data after QR code processing...");
  614. await fetchAllCombinedLotData();
  615. if (successCount > 0 || existsCount > 0) {
  616. console.log(` QR Code processing completed: ${successCount} created, ${existsCount} already existed`);
  617. setQrScanSuccess(true);
  618. setQrScanInput(''); // Clear input after successful processing
  619. // Clear success state after a delay
  620. setTimeout(() => {
  621. setQrScanSuccess(false);
  622. }, 2000);
  623. } else {
  624. console.error(`❌ QR Code processing failed: ${errorCount} errors`);
  625. setQrScanError(true);
  626. setQrScanSuccess(false);
  627. // Clear error state after a delay
  628. setTimeout(() => {
  629. setQrScanError(false);
  630. }, 3000);
  631. }
  632. } catch (error) {
  633. console.error("❌ Error processing QR code:", error);
  634. setQrScanError(true);
  635. setQrScanSuccess(false);
  636. // Still refresh data even on error
  637. await fetchAllCombinedLotData();
  638. // Clear error state after a delay
  639. setTimeout(() => {
  640. setQrScanError(false);
  641. }, 3000);
  642. }
  643. }, [combinedLotData, fetchAllCombinedLotData]);
  644. const handleManualInputSubmit = useCallback(() => {
  645. if (qrScanInput.trim() !== '') {
  646. handleQrCodeSubmit(qrScanInput.trim());
  647. }
  648. }, [qrScanInput, handleQrCodeSubmit]);
  649. // Handle QR code submission from modal (internal scanning)
  650. const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
  651. if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
  652. console.log(` QR Code verified for lot: ${lotNo}`);
  653. const requiredQty = selectedLotForQr.requiredQty;
  654. const lotId = selectedLotForQr.lotId;
  655. // Create stock out line
  656. const stockOutLineData: CreateStockOutLine = {
  657. consoCode: selectedLotForQr.pickOrderConsoCode, // Use pickOrderConsoCode instead of pickOrderCode
  658. pickOrderLineId: selectedLotForQr.pickOrderLineId,
  659. inventoryLotLineId: selectedLotForQr.lotId,
  660. qty: 0.0
  661. };
  662. try {
  663. await createStockOutLine(stockOutLineData);
  664. console.log("Stock out line created successfully!");
  665. // Close modal
  666. setQrModalOpen(false);
  667. setSelectedLotForQr(null);
  668. // Set pick quantity
  669. const lotKey = `${selectedLotForQr.pickOrderLineId}-${lotId}`;
  670. setTimeout(() => {
  671. setPickQtyData(prev => ({
  672. ...prev,
  673. [lotKey]: requiredQty
  674. }));
  675. console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
  676. }, 500);
  677. // Refresh data
  678. await fetchAllCombinedLotData();
  679. } catch (error) {
  680. console.error("Error creating stock out line:", error);
  681. }
  682. }
  683. }, [selectedLotForQr, fetchAllCombinedLotData]);
  684. // Outside QR scanning - process QR codes from outside the page automatically
  685. useEffect(() => {
  686. if (qrValues.length > 0 && combinedLotData.length > 0) {
  687. const latestQr = qrValues[qrValues.length - 1];
  688. // Extract lot number from QR code
  689. let lotNo = '';
  690. try {
  691. const qrData = JSON.parse(latestQr);
  692. if (qrData.stockInLineId && qrData.itemId) {
  693. // For JSON QR codes, we need to fetch the lot number
  694. fetchStockInLineInfo(qrData.stockInLineId)
  695. .then((stockInLineInfo) => {
  696. console.log("Outside QR scan - Stock in line info:", stockInLineInfo);
  697. const extractedLotNo = stockInLineInfo.lotNo;
  698. if (extractedLotNo) {
  699. console.log(`Outside QR scan detected (JSON): ${extractedLotNo}`);
  700. handleQrCodeSubmit(extractedLotNo);
  701. }
  702. })
  703. .catch((error) => {
  704. console.error("Outside QR scan - Error fetching stock in line info:", error);
  705. });
  706. return; // Exit early for JSON QR codes
  707. }
  708. } catch (error) {
  709. // Not JSON format, treat as direct lot number
  710. lotNo = latestQr.replace(/[{}]/g, '');
  711. }
  712. // For direct lot number QR codes
  713. if (lotNo) {
  714. console.log(`Outside QR scan detected (direct): ${lotNo}`);
  715. handleQrCodeSubmit(lotNo);
  716. }
  717. }
  718. }, [qrValues, combinedLotData, handleQrCodeSubmit]);
  719. const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => {
  720. if (value === '' || value === null || value === undefined) {
  721. setPickQtyData(prev => ({
  722. ...prev,
  723. [lotKey]: 0
  724. }));
  725. return;
  726. }
  727. const numericValue = typeof value === 'string' ? parseFloat(value) : value;
  728. if (isNaN(numericValue)) {
  729. setPickQtyData(prev => ({
  730. ...prev,
  731. [lotKey]: 0
  732. }));
  733. return;
  734. }
  735. setPickQtyData(prev => ({
  736. ...prev,
  737. [lotKey]: numericValue
  738. }));
  739. }, []);
  740. const [autoAssignStatus, setAutoAssignStatus] = useState<'idle' | 'checking' | 'assigned' | 'no_orders'>('idle');
  741. const [autoAssignMessage, setAutoAssignMessage] = useState<string>('');
  742. const [completionStatus, setCompletionStatus] = useState<PickOrderCompletionResponse | null>(null);
  743. const checkAndAutoAssignNext = useCallback(async () => {
  744. if (!currentUserId) return;
  745. try {
  746. const completionResponse = await checkPickOrderCompletion(currentUserId);
  747. if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) {
  748. console.log("Found completed pick orders, auto-assigning next...");
  749. // 移除前端的自动分配逻辑,因为后端已经处理了
  750. // await handleAutoAssignAndRelease(); // 删除这个函数
  751. }
  752. } catch (error) {
  753. console.error("Error checking pick order completion:", error);
  754. }
  755. }, [currentUserId]);
  756. // Handle submit pick quantity
  757. const handleSubmitPickQty = useCallback(async (lot: any) => {
  758. const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
  759. const newQty = pickQtyData[lotKey] || 0;
  760. if (!lot.stockOutLineId) {
  761. console.error("No stock out line found for this lot");
  762. return;
  763. }
  764. try {
  765. const currentActualPickQty = lot.actualPickQty || 0;
  766. const cumulativeQty = currentActualPickQty + newQty;
  767. let newStatus = 'partially_completed';
  768. if (cumulativeQty >= lot.requiredQty) {
  769. newStatus = 'completed';
  770. }
  771. console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`);
  772. console.log(`Lot: ${lot.lotNo}`);
  773. console.log(`Required Qty: ${lot.requiredQty}`);
  774. console.log(`Current Actual Pick Qty: ${currentActualPickQty}`);
  775. console.log(`New Submitted Qty: ${newQty}`);
  776. console.log(`Cumulative Qty: ${cumulativeQty}`);
  777. console.log(`New Status: ${newStatus}`);
  778. console.log(`=====================================`);
  779. await updateStockOutLineStatus({
  780. id: lot.stockOutLineId,
  781. status: newStatus,
  782. qty: cumulativeQty
  783. });
  784. if (newQty > 0) {
  785. await updateInventoryLotLineQuantities({
  786. inventoryLotLineId: lot.lotId,
  787. qty: newQty,
  788. status: 'available',
  789. operation: 'pick'
  790. });
  791. }
  792. // FIXED: Use the proper API function instead of direct fetch
  793. if (newStatus === 'completed' && lot.pickOrderConsoCode) {
  794. console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
  795. try {
  796. // Use the imported API function instead of direct fetch
  797. const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
  798. console.log(` Pick order completion check result:`, completionResponse);
  799. if (completionResponse.code === "SUCCESS") {
  800. console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
  801. } else if (completionResponse.message === "not completed") {
  802. console.log(`⏳ Pick order not completed yet, more lines remaining`);
  803. } else {
  804. console.error(`❌ Error checking completion: ${completionResponse.message}`);
  805. }
  806. } catch (error) {
  807. console.error("Error checking pick order completion:", error);
  808. }
  809. }
  810. await fetchAllCombinedLotData();
  811. console.log("Pick quantity submitted successfully!");
  812. setTimeout(() => {
  813. checkAndAutoAssignNext();
  814. }, 1000);
  815. } catch (error) {
  816. console.error("Error submitting pick quantity:", error);
  817. }
  818. }, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]);
  819. // Handle reject lot
  820. const handleRejectLot = useCallback(async (lot: any) => {
  821. if (!lot.stockOutLineId) {
  822. console.error("No stock out line found for this lot");
  823. return;
  824. }
  825. try {
  826. await updateStockOutLineStatus({
  827. id: lot.stockOutLineId,
  828. status: 'rejected',
  829. qty: 0
  830. });
  831. await fetchAllCombinedLotData();
  832. console.log("Lot rejected successfully!");
  833. setTimeout(() => {
  834. checkAndAutoAssignNext();
  835. }, 1000);
  836. } catch (error) {
  837. console.error("Error rejecting lot:", error);
  838. }
  839. }, [fetchAllCombinedLotData, checkAndAutoAssignNext]);
  840. // Handle pick execution form
  841. const handlePickExecutionForm = useCallback((lot: any) => {
  842. console.log("=== Pick Execution Form ===");
  843. console.log("Lot data:", lot);
  844. if (!lot) {
  845. console.warn("No lot data provided for pick execution form");
  846. return;
  847. }
  848. console.log("Opening pick execution form for lot:", lot.lotNo);
  849. setSelectedLotForExecutionForm(lot);
  850. setPickExecutionFormOpen(true);
  851. console.log("Pick execution form opened for lot ID:", lot.lotId);
  852. }, []);
  853. const handlePickExecutionFormSubmit = useCallback(async (data: any) => {
  854. try {
  855. console.log("Pick execution form submitted:", data);
  856. const issueData = {
  857. ...data,
  858. type: "Do", // Delivery Order Record 类型
  859. };
  860. const result = await recordPickExecutionIssue(issueData);
  861. console.log("Pick execution issue recorded:", result);
  862. if (result && result.code === "SUCCESS") {
  863. console.log(" Pick execution issue recorded successfully");
  864. } else {
  865. console.error("❌ Failed to record pick execution issue:", result);
  866. }
  867. setPickExecutionFormOpen(false);
  868. setSelectedLotForExecutionForm(null);
  869. await fetchAllCombinedLotData();
  870. } catch (error) {
  871. console.error("Error submitting pick execution form:", error);
  872. }
  873. }, [fetchAllCombinedLotData]);
  874. // Calculate remaining required quantity
  875. const calculateRemainingRequiredQty = useCallback((lot: any) => {
  876. const requiredQty = lot.requiredQty || 0;
  877. const stockOutLineQty = lot.stockOutLineQty || 0;
  878. return Math.max(0, requiredQty - stockOutLineQty);
  879. }, []);
  880. // Search criteria
  881. const searchCriteria: Criterion<any>[] = [
  882. {
  883. label: t("Pick Order Code"),
  884. paramName: "pickOrderCode",
  885. type: "text",
  886. },
  887. {
  888. label: t("Item Code"),
  889. paramName: "itemCode",
  890. type: "text",
  891. },
  892. {
  893. label: t("Item Name"),
  894. paramName: "itemName",
  895. type: "text",
  896. },
  897. {
  898. label: t("Lot No"),
  899. paramName: "lotNo",
  900. type: "text",
  901. },
  902. ];
  903. const handleSearch = useCallback((query: Record<string, any>) => {
  904. setSearchQuery({ ...query });
  905. console.log("Search query:", query);
  906. if (!originalCombinedData) return;
  907. const filtered = originalCombinedData.filter((lot: any) => {
  908. const pickOrderCodeMatch = !query.pickOrderCode ||
  909. lot.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase());
  910. const itemCodeMatch = !query.itemCode ||
  911. lot.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase());
  912. const itemNameMatch = !query.itemName ||
  913. lot.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase());
  914. const lotNoMatch = !query.lotNo ||
  915. lot.lotNo?.toLowerCase().includes((query.lotNo || "").toLowerCase());
  916. return pickOrderCodeMatch && itemCodeMatch && itemNameMatch && lotNoMatch;
  917. });
  918. setCombinedLotData(filtered);
  919. console.log("Filtered lots count:", filtered.length);
  920. }, [originalCombinedData]);
  921. const handleReset = useCallback(() => {
  922. setSearchQuery({});
  923. if (originalCombinedData) {
  924. setCombinedLotData(originalCombinedData);
  925. }
  926. }, [originalCombinedData]);
  927. const handlePageChange = useCallback((event: unknown, newPage: number) => {
  928. setPaginationController(prev => ({
  929. ...prev,
  930. pageNum: newPage,
  931. }));
  932. }, []);
  933. const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  934. const newPageSize = parseInt(event.target.value, 10);
  935. setPaginationController({
  936. pageNum: 0,
  937. pageSize: newPageSize,
  938. });
  939. }, []);
  940. const paginatedData = useMemo(() => {
  941. // ✅ Fix: Add safety check to ensure combinedLotData is an array
  942. if (!Array.isArray(combinedLotData)) {
  943. console.warn("⚠️ combinedLotData is not an array:", combinedLotData);
  944. return [];
  945. }
  946. // Sort by routerIndex first, then by other criteria
  947. const sortedData = [...combinedLotData].sort((a: any, b: any) => {
  948. const aIndex = a.routerIndex || 0;
  949. const bIndex = b.routerIndex || 0;
  950. // Primary sort: by routerIndex
  951. if (aIndex !== bIndex) {
  952. return aIndex - bIndex;
  953. }
  954. // Secondary sort: by pickOrderCode if routerIndex is the same
  955. if (a.pickOrderCode !== b.pickOrderCode) {
  956. return a.pickOrderCode.localeCompare(b.pickOrderCode);
  957. }
  958. // Tertiary sort: by lotNo if everything else is the same
  959. return (a.lotNo || '').localeCompare(b.lotNo || '');
  960. });
  961. const startIndex = paginationController.pageNum * paginationController.pageSize;
  962. const endIndex = startIndex + paginationController.pageSize;
  963. return sortedData.slice(startIndex, endIndex);
  964. }, [combinedLotData, paginationController]);
  965. return (
  966. <FormProvider {...formProps}>
  967. {/* 修复:改进条件渲染逻辑 */}
  968. {combinedDataLoading || fgPickOrdersLoading ? (
  969. // 数据加载中,显示加载指示器
  970. <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
  971. <CircularProgress />
  972. </Box>
  973. ) : fgPickOrders.length === 0 ? (
  974. // 没有活动订单,显示楼层选择面板
  975. <FinishedGoodFloorLanePanel
  976. onPickOrderAssigned={() => {
  977. if (currentUserId) {
  978. fetchAllCombinedLotData(currentUserId);
  979. fetchFgPickOrdersData();
  980. }
  981. }}
  982. onSwitchToDetailTab={onSwitchToDetailTab}
  983. />
  984. ) : (
  985. // 有活动订单,显示 FG 订单信息
  986. <Box>
  987. {fgPickOrders.map((fgOrder) => (
  988. <Box key={fgOrder.pickOrderId} sx={{ mb: 2 }}>
  989. <FGPickOrderInfoCard
  990. fgOrder={fgOrder}
  991. />
  992. </Box>
  993. ))}
  994. </Box>
  995. )}
  996. {/* Modals */}
  997. <QrCodeModal
  998. open={qrModalOpen}
  999. onClose={() => setQrModalOpen(false)}
  1000. lot={selectedLotForQr}
  1001. onQrCodeSubmit={handleQrCodeSubmit}
  1002. combinedLotData={combinedLotData}
  1003. />
  1004. <GoodPickExecutionForm
  1005. open={pickExecutionFormOpen}
  1006. onClose={() => {
  1007. setPickExecutionFormOpen(false);
  1008. setSelectedLotForExecutionForm(null);
  1009. }}
  1010. onSubmit={handlePickExecutionFormSubmit}
  1011. selectedLot={selectedLotForExecutionForm}
  1012. selectedPickOrderLine={null}
  1013. pickOrderId={selectedLotForExecutionForm?.pickOrderId}
  1014. pickOrderCreateDate={null}
  1015. />
  1016. </FormProvider>
  1017. );
  1018. };
  1019. export default PickExecution;