FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1411 lines
48 KiB

  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. Checkbox,
  18. TablePagination,
  19. Modal,
  20. } from "@mui/material";
  21. import TestQrCodeProvider from '../QrCodeScannerProvider/TestQrCodeProvider';
  22. import { useCallback, useEffect, useState, useRef, useMemo } from "react";
  23. import { useTranslation } from "react-i18next";
  24. import { useRouter } from "next/navigation";
  25. // 修改:使用 Job Order API
  26. import {
  27. fetchCompletedJobOrderPickOrders,
  28. fetchUnassignedJobOrderPickOrders,
  29. assignJobOrderPickOrder,
  30. updateSecondQrScanStatus,
  31. submitSecondScanQuantity,
  32. recordSecondScanIssue
  33. } from "@/app/api/jo/actions";
  34. import { fetchNameList, NameList } from "@/app/api/user/actions";
  35. import {
  36. FormProvider,
  37. useForm,
  38. } from "react-hook-form";
  39. import SearchBox, { Criterion } from "../SearchBox";
  40. import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
  41. import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
  42. import QrCodeIcon from '@mui/icons-material/QrCode';
  43. import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
  44. import { useSession } from "next-auth/react";
  45. import { SessionWithTokens } from "@/config/authConfig";
  46. import { fetchStockInLineInfo } from "@/app/api/po/actions";
  47. import GoodPickExecutionForm from "./JobmatchForm";
  48. import FGPickOrderCard from "./FGPickOrderCard";
  49. interface Props {
  50. filterArgs: Record<string, any>;
  51. }
  52. // QR Code Modal Component (from GoodPickExecution)
  53. const QrCodeModal: React.FC<{
  54. open: boolean;
  55. onClose: () => void;
  56. lot: any | null;
  57. onQrCodeSubmit: (lotNo: string) => void;
  58. combinedLotData: any[];
  59. }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => {
  60. const { t } = useTranslation("jo");
  61. const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  62. const [manualInput, setManualInput] = useState<string>('');
  63. const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false);
  64. const [manualInputError, setManualInputError] = useState<boolean>(false);
  65. const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false);
  66. const [qrScanFailed, setQrScanFailed] = useState<boolean>(false);
  67. const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
  68. const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
  69. const [scannedQrResult, setScannedQrResult] = useState<string>('');
  70. const { data: session } = useSession() as { data: SessionWithTokens | null };
  71. const currentUserId = session?.id ? parseInt(session.id) :
  72. // Process scanned QR codes
  73. useEffect(() => {
  74. if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) {
  75. const latestQr = qrValues[qrValues.length - 1];
  76. if (processedQrCodes.has(latestQr)) {
  77. console.log("QR code already processed, skipping...");
  78. return;
  79. }
  80. setProcessedQrCodes(prev => new Set(prev).add(latestQr));
  81. try {
  82. const qrData = JSON.parse(latestQr);
  83. if (qrData.stockInLineId && qrData.itemId) {
  84. setIsProcessingQr(true);
  85. setQrScanFailed(false);
  86. fetchStockInLineInfo(qrData.stockInLineId)
  87. .then((stockInLineInfo) => {
  88. console.log("Stock in line info:", stockInLineInfo);
  89. setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
  90. if (stockInLineInfo.lotNo === lot.lotNo) {
  91. console.log(` QR Code verified for lot: ${lot.lotNo}`);
  92. setQrScanSuccess(true);
  93. onQrCodeSubmit(lot.lotNo);
  94. onClose();
  95. resetScan();
  96. } else {
  97. console.log(`❌ QR Code mismatch. Expected: ${lot.lotNo}, Got: ${stockInLineInfo.lotNo}`);
  98. setQrScanFailed(true);
  99. setManualInputError(true);
  100. setManualInputSubmitted(true);
  101. }
  102. })
  103. .catch((error) => {
  104. console.error("Error fetching stock in line info:", error);
  105. setScannedQrResult('Error fetching data');
  106. setQrScanFailed(true);
  107. setManualInputError(true);
  108. setManualInputSubmitted(true);
  109. })
  110. .finally(() => {
  111. setIsProcessingQr(false);
  112. });
  113. } else {
  114. const qrContent = latestQr.replace(/[{}]/g, '');
  115. setScannedQrResult(qrContent);
  116. if (qrContent === lot.lotNo) {
  117. setQrScanSuccess(true);
  118. onQrCodeSubmit(lot.lotNo);
  119. onClose();
  120. resetScan();
  121. } else {
  122. setQrScanFailed(true);
  123. setManualInputError(true);
  124. setManualInputSubmitted(true);
  125. }
  126. }
  127. } catch (error) {
  128. console.log("QR code is not JSON format, trying direct comparison");
  129. const qrContent = latestQr.replace(/[{}]/g, '');
  130. setScannedQrResult(qrContent);
  131. if (qrContent === lot.lotNo) {
  132. setQrScanSuccess(true);
  133. onQrCodeSubmit(lot.lotNo);
  134. onClose();
  135. resetScan();
  136. } else {
  137. setQrScanFailed(true);
  138. setManualInputError(true);
  139. setManualInputSubmitted(true);
  140. }
  141. }
  142. }
  143. }, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes]);
  144. // Clear states when modal opens
  145. useEffect(() => {
  146. if (open) {
  147. setManualInput('');
  148. setManualInputSubmitted(false);
  149. setManualInputError(false);
  150. setIsProcessingQr(false);
  151. setQrScanFailed(false);
  152. setQrScanSuccess(false);
  153. setScannedQrResult('');
  154. setProcessedQrCodes(new Set());
  155. }
  156. }, [open]);
  157. useEffect(() => {
  158. if (lot) {
  159. setManualInput('');
  160. setManualInputSubmitted(false);
  161. setManualInputError(false);
  162. setIsProcessingQr(false);
  163. setQrScanFailed(false);
  164. setQrScanSuccess(false);
  165. setScannedQrResult('');
  166. setProcessedQrCodes(new Set());
  167. }
  168. }, [lot]);
  169. // Auto-submit manual input when it matches
  170. useEffect(() => {
  171. if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '' && !qrScanFailed && !qrScanSuccess) {
  172. console.log(' Auto-submitting manual input:', manualInput.trim());
  173. const timer = setTimeout(() => {
  174. setQrScanSuccess(true);
  175. onQrCodeSubmit(lot.lotNo);
  176. onClose();
  177. setManualInput('');
  178. setManualInputError(false);
  179. setManualInputSubmitted(false);
  180. }, 200);
  181. return () => clearTimeout(timer);
  182. }
  183. }, [manualInput, lot, onQrCodeSubmit, onClose, qrScanFailed, qrScanSuccess]);
  184. const handleManualSubmit = () => {
  185. if (manualInput.trim() === lot?.lotNo) {
  186. setQrScanSuccess(true);
  187. onQrCodeSubmit(lot.lotNo);
  188. onClose();
  189. setManualInput('');
  190. } else {
  191. setQrScanFailed(true);
  192. setManualInputError(true);
  193. setManualInputSubmitted(true);
  194. }
  195. };
  196. useEffect(() => {
  197. if (open) {
  198. startScan();
  199. }
  200. }, [open, startScan]);
  201. return (
  202. <Modal open={open} onClose={onClose}>
  203. <Box sx={{
  204. position: 'absolute',
  205. top: '50%',
  206. left: '50%',
  207. transform: 'translate(-50%, -50%)',
  208. bgcolor: 'background.paper',
  209. p: 3,
  210. borderRadius: 2,
  211. minWidth: 400,
  212. }}>
  213. <Typography variant="h6" gutterBottom>
  214. {t("QR Code Scan for Lot")}: {lot?.lotNo}
  215. </Typography>
  216. {isProcessingQr && (
  217. <Box sx={{ mb: 2, p: 2, backgroundColor: '#e3f2fd', borderRadius: 1 }}>
  218. <Typography variant="body2" color="primary">
  219. {t("Processing QR code...")}
  220. </Typography>
  221. </Box>
  222. )}
  223. <Box sx={{ mb: 2 }}>
  224. <Typography variant="body2" gutterBottom>
  225. <strong>{t("Manual Input")}:</strong>
  226. </Typography>
  227. <TextField
  228. fullWidth
  229. size="small"
  230. value={manualInput}
  231. onChange={(e) => {
  232. setManualInput(e.target.value);
  233. if (qrScanFailed || manualInputError) {
  234. setQrScanFailed(false);
  235. setManualInputError(false);
  236. setManualInputSubmitted(false);
  237. }
  238. }}
  239. sx={{ mb: 1 }}
  240. error={manualInputSubmitted && manualInputError}
  241. helperText={
  242. manualInputSubmitted && manualInputError
  243. ? `${t("The input is not the same as the expected lot number.")}`
  244. : ''
  245. }
  246. />
  247. <Button
  248. variant="contained"
  249. onClick={handleManualSubmit}
  250. disabled={!manualInput.trim()}
  251. size="small"
  252. color="primary"
  253. >
  254. {t("Submit")}
  255. </Button>
  256. </Box>
  257. {qrValues.length > 0 && (
  258. <Box sx={{
  259. mb: 2,
  260. p: 2,
  261. backgroundColor: qrScanFailed ? '#ffebee' : qrScanSuccess ? '#e8f5e8' : '#f5f5f5',
  262. borderRadius: 1
  263. }}>
  264. <Typography variant="body2" color={qrScanFailed ? 'error' : qrScanSuccess ? 'success' : 'text.secondary'}>
  265. <strong>{t("QR Scan Result:")}</strong> {scannedQrResult}
  266. </Typography>
  267. {qrScanSuccess && (
  268. <Typography variant="caption" color="success" display="block">
  269. {t("Verified successfully!")}
  270. </Typography>
  271. )}
  272. </Box>
  273. )}
  274. <Box sx={{ mt: 2, textAlign: 'right' }}>
  275. <Button onClick={onClose} variant="outlined">
  276. {t("Cancel")}
  277. </Button>
  278. </Box>
  279. </Box>
  280. </Modal>
  281. );
  282. };
  283. const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
  284. const { t } = useTranslation("jo");
  285. const router = useRouter();
  286. const { data: session } = useSession() as { data: SessionWithTokens | null };
  287. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  288. // 修改:使用 Job Order 数据结构
  289. const [jobOrderData, setJobOrderData] = useState<any>(null);
  290. const [combinedLotData, setCombinedLotData] = useState<any[]>([]);
  291. const [combinedDataLoading, setCombinedDataLoading] = useState(false);
  292. const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]);
  293. // 添加未分配订单状态
  294. const [unassignedOrders, setUnassignedOrders] = useState<any[]>([]);
  295. const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false);
  296. const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  297. const [qrScanInput, setQrScanInput] = useState<string>('');
  298. const [qrScanError, setQrScanError] = useState<boolean>(false);
  299. const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
  300. const [pickQtyData, setPickQtyData] = useState<Record<string, number>>({});
  301. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  302. const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false);
  303. const [paginationController, setPaginationController] = useState({
  304. pageNum: 0,
  305. pageSize: 10,
  306. });
  307. const [usernameList, setUsernameList] = useState<NameList[]>([]);
  308. const initializationRef = useRef(false);
  309. const autoAssignRef = useRef(false);
  310. const formProps = useForm();
  311. const errors = formProps.formState.errors;
  312. // Add QR modal states
  313. const [qrModalOpen, setQrModalOpen] = useState(false);
  314. const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);
  315. // Add GoodPickExecutionForm states
  316. const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
  317. const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
  318. // Add these missing state variables
  319. const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
  320. const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
  321. const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
  322. const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false);
  323. // 修改:加载未分配的 Job Order 订单
  324. const loadUnassignedOrders = useCallback(async () => {
  325. setIsLoadingUnassigned(true);
  326. try {
  327. const orders = await fetchUnassignedJobOrderPickOrders();
  328. setUnassignedOrders(orders);
  329. } catch (error) {
  330. console.error("Error loading unassigned orders:", error);
  331. } finally {
  332. setIsLoadingUnassigned(false);
  333. }
  334. }, []);
  335. // 修改:分配订单给当前用户
  336. const handleAssignOrder = useCallback(async (pickOrderId: number) => {
  337. if (!currentUserId) {
  338. console.error("Missing user id in session");
  339. return;
  340. }
  341. try {
  342. const result = await assignJobOrderPickOrder(pickOrderId, currentUserId);
  343. if (result.message === "Successfully assigned") {
  344. console.log(" Successfully assigned pick order");
  345. // 刷新数据
  346. window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
  347. // 重新加载未分配订单列表
  348. loadUnassignedOrders();
  349. } else {
  350. console.warn("⚠️ Assignment failed:", result.message);
  351. alert(`Assignment failed: ${result.message}`);
  352. }
  353. } catch (error) {
  354. console.error("❌ Error assigning order:", error);
  355. alert("Error occurred during assignment");
  356. }
  357. }, [currentUserId, loadUnassignedOrders]);
  358. // Handle QR code button click
  359. const handleQrCodeClick = (pickOrderId: number) => {
  360. console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
  361. // TODO: Implement QR code functionality
  362. };
  363. // 修改:使用 Job Order API 获取数据
  364. const fetchJobOrderData = useCallback(async (userId?: number) => {
  365. setCombinedDataLoading(true);
  366. try {
  367. const userIdToUse = userId || currentUserId;
  368. console.log(" fetchJobOrderData called with userId:", userIdToUse);
  369. if (!userIdToUse) {
  370. console.warn("⚠️ No userId available, skipping API call");
  371. setJobOrderData(null);
  372. setCombinedLotData([]);
  373. setOriginalCombinedData([]);
  374. return;
  375. }
  376. window.dispatchEvent(new CustomEvent('jobOrderDataStatus', {
  377. detail: {
  378. hasData: false,
  379. tabIndex: 1
  380. }
  381. }));
  382. // 使用 Job Order API
  383. const jobOrderData = await fetchCompletedJobOrderPickOrders(userIdToUse);
  384. console.log(" Job Order data:", jobOrderData);
  385. console.log(" Pick Order Code from API:", jobOrderData.pickOrder?.code);
  386. console.log(" Expected Pick Order Code: P-20251003-001");
  387. setJobOrderData(jobOrderData);
  388. // Transform hierarchical data to flat structure for the table
  389. const flatLotData: any[] = [];
  390. if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) {
  391. jobOrderData.pickOrderLines.forEach((line: any) => {
  392. if (line.lots && line.lots.length > 0) {
  393. line.lots.forEach((lot: any) => {
  394. flatLotData.push({
  395. // Pick order info
  396. pickOrderId: jobOrderData.pickOrder.id,
  397. pickOrderCode: jobOrderData.pickOrder.code,
  398. pickOrderConsoCode: jobOrderData.pickOrder.consoCode,
  399. pickOrderTargetDate: jobOrderData.pickOrder.targetDate,
  400. pickOrderType: jobOrderData.pickOrder.type,
  401. pickOrderStatus: jobOrderData.pickOrder.status,
  402. pickOrderAssignTo: jobOrderData.pickOrder.assignTo,
  403. // Pick order line info
  404. pickOrderLineId: line.id,
  405. pickOrderLineRequiredQty: line.requiredQty,
  406. pickOrderLineStatus: line.status,
  407. // Item info
  408. itemId: line.itemId,
  409. itemCode: line.itemCode,
  410. itemName: line.itemName,
  411. uomCode: line.uomCode,
  412. uomDesc: line.uomDesc,
  413. // Lot info
  414. lotId: lot.lotId,
  415. lotNo: lot.lotNo,
  416. expiryDate: lot.expiryDate,
  417. location: lot.location,
  418. availableQty: lot.availableQty,
  419. requiredQty: lot.requiredQty,
  420. actualPickQty: lot.actualPickQty,
  421. lotStatus: lot.lotStatus,
  422. lotAvailability: lot.lotAvailability,
  423. processingStatus: lot.processingStatus,
  424. stockOutLineId: lot.stockOutLineId,
  425. stockOutLineStatus: lot.stockOutLineStatus,
  426. stockOutLineQty: lot.stockOutLineQty,
  427. // Router info
  428. routerIndex: lot.routerIndex,
  429. matchStatus: lot.matchStatus,
  430. routerArea: lot.routerArea,
  431. routerRoute: lot.routerRoute,
  432. uomShortDesc: lot.uomShortDesc
  433. });
  434. });
  435. }
  436. });
  437. }
  438. console.log(" Transformed flat lot data:", flatLotData);
  439. setCombinedLotData(flatLotData);
  440. setOriginalCombinedData(flatLotData);
  441. const hasData = flatLotData.length > 0;
  442. window.dispatchEvent(new CustomEvent('jobOrderDataStatus', {
  443. detail: {
  444. hasData: hasData,
  445. tabIndex: 1
  446. }
  447. }));
  448. // 计算完成状态并发送事件
  449. const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) =>
  450. lot.processingStatus === 'completed'
  451. );
  452. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  453. detail: {
  454. allLotsCompleted: allCompleted,
  455. tabIndex: 1
  456. }
  457. }));
  458. // 发送完成状态事件,包含标签页信息
  459. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  460. detail: {
  461. allLotsCompleted: allCompleted,
  462. tabIndex: 0 // 明确指定这是来自标签页 0 的事件
  463. }
  464. }));
  465. } catch (error) {
  466. console.error("❌ Error fetching job order data:", error);
  467. setJobOrderData(null);
  468. setCombinedLotData([]);
  469. setOriginalCombinedData([]);
  470. // 如果加载失败,禁用打印按钮
  471. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  472. detail: {
  473. allLotsCompleted: false,
  474. tabIndex: 0
  475. }
  476. }));
  477. } finally {
  478. setCombinedDataLoading(false);
  479. }
  480. }, [currentUserId]);
  481. const handleSubmitAllScanned = useCallback(async () => {
  482. const scannedLots = combinedLotData.filter(lot =>
  483. lot.matchStatus === 'scanned' // Only submit items that are scanned but not yet submitted
  484. );
  485. if (scannedLots.length === 0) {
  486. console.log("No scanned items to submit");
  487. return;
  488. }
  489. setIsSubmittingAll(true);
  490. console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
  491. try {
  492. // Submit all items in parallel using Promise.all
  493. const submitPromises = scannedLots.map(async (lot) => {
  494. const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
  495. console.log(`Submitting item ${lot.itemCode}: qty=${submitQty}`);
  496. const result = await submitSecondScanQuantity(
  497. lot.pickOrderId,
  498. lot.itemId,
  499. {
  500. qty: submitQty,
  501. isMissing: false,
  502. isBad: false,
  503. reason: undefined
  504. }
  505. );
  506. return { success: result.code === "SUCCESS", itemCode: lot.itemCode };
  507. });
  508. // Wait for all submissions to complete
  509. const results = await Promise.all(submitPromises);
  510. const successCount = results.filter(r => r.success).length;
  511. console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
  512. // Refresh data once after all submissions
  513. await fetchJobOrderData();
  514. if (successCount > 0) {
  515. setQrScanSuccess(true);
  516. setTimeout(() => setQrScanSuccess(false), 2000);
  517. }
  518. } catch (error) {
  519. console.error("Error submitting all scanned items:", error);
  520. setQrScanError(true);
  521. } finally {
  522. setIsSubmittingAll(false);
  523. }
  524. }, [combinedLotData, fetchJobOrderData]);
  525. // Calculate scanned items count
  526. const scannedItemsCount = useMemo(() => {
  527. return combinedLotData.filter(lot => lot.matchStatus === 'scanned').length;
  528. }, [combinedLotData]);
  529. // 修改:初始化时加载数据
  530. useEffect(() => {
  531. if (session && currentUserId && !initializationRef.current) {
  532. console.log(" Session loaded, initializing job order...");
  533. initializationRef.current = true;
  534. // 加载 Job Order 数据
  535. fetchJobOrderData();
  536. // 加载未分配订单
  537. loadUnassignedOrders();
  538. }
  539. }, [session, currentUserId, fetchJobOrderData, loadUnassignedOrders]);
  540. // Add event listener for manual assignment
  541. useEffect(() => {
  542. const handlePickOrderAssigned = () => {
  543. console.log("🔄 Pick order assigned event received, refreshing data...");
  544. fetchJobOrderData();
  545. };
  546. window.addEventListener('pickOrderAssigned', handlePickOrderAssigned);
  547. return () => {
  548. window.removeEventListener('pickOrderAssigned', handlePickOrderAssigned);
  549. };
  550. }, [fetchJobOrderData]);
  551. // Handle QR code submission for matched lot (external scanning)
  552. const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
  553. console.log(` Processing Second QR Code for lot: ${lotNo}`);
  554. // Check if this lot was already processed recently
  555. const lotKey = `${lotNo}_${Date.now()}`;
  556. if (processedQrCodes.has(lotNo)) {
  557. console.log(`⏭️ Lot ${lotNo} already processed, skipping...`);
  558. return;
  559. }
  560. const currentLotData = combinedLotData;
  561. const matchingLots = currentLotData.filter(lot =>
  562. lot.lotNo === lotNo ||
  563. lot.lotNo?.toLowerCase() === lotNo.toLowerCase()
  564. );
  565. if (matchingLots.length === 0) {
  566. console.error(`❌ Lot not found: ${lotNo}`);
  567. setQrScanError(true);
  568. setQrScanSuccess(false);
  569. return;
  570. }
  571. try {
  572. let successCount = 0;
  573. for (const matchingLot of matchingLots) {
  574. // Check if this specific item was already processed
  575. const itemKey = `${matchingLot.pickOrderId}_${matchingLot.itemId}`;
  576. if (processedQrCodes.has(itemKey)) {
  577. console.log(`⏭️ Item ${matchingLot.itemId} already processed, skipping...`);
  578. continue;
  579. }
  580. // Use the new second scan API
  581. const result = await updateSecondQrScanStatus(
  582. matchingLot.pickOrderId,
  583. matchingLot.itemId,
  584. currentUserId || 0,
  585. matchingLot.requiredQty || 1 // 传递实际的 required quantity
  586. );
  587. if (result.code === "SUCCESS") {
  588. successCount++;
  589. // Mark this item as processed
  590. setProcessedQrCodes(prev => new Set(prev).add(itemKey));
  591. console.log(` Second QR scan status updated for item ${matchingLot.itemId}`);
  592. } else {
  593. console.error(`❌ Failed to update second QR scan status: ${result.message}`);
  594. }
  595. }
  596. if (successCount > 0) {
  597. setQrScanSuccess(true);
  598. setQrScanError(false);
  599. // Set refreshing flag briefly to prevent duplicate processing
  600. setIsRefreshingData(true);
  601. await fetchJobOrderData(); // Refresh data
  602. // Clear refresh flag and success message after a short delay
  603. setTimeout(() => {
  604. setQrScanSuccess(false);
  605. setIsRefreshingData(false);
  606. }, 500);
  607. } else {
  608. setQrScanError(true);
  609. setQrScanSuccess(false);
  610. }
  611. } catch (error) {
  612. console.error("❌ Error processing second QR code:", error);
  613. setQrScanError(true);
  614. setQrScanSuccess(false);
  615. }
  616. }, [combinedLotData, fetchJobOrderData, processedQrCodes]);
  617. useEffect(() => {
  618. // Add isManualScanning and isRefreshingData checks
  619. if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
  620. return;
  621. }
  622. const latestQr = qrValues[qrValues.length - 1];
  623. // Check if this QR was already processed recently
  624. if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) {
  625. console.log("⏭️ QR code already processed, skipping...");
  626. return;
  627. }
  628. // Mark as processed
  629. setProcessedQrCodes(prev => new Set(prev).add(latestQr));
  630. setLastProcessedQr(latestQr);
  631. // Extract lot number from QR code
  632. let lotNo = '';
  633. try {
  634. const qrData = JSON.parse(latestQr);
  635. if (qrData.stockInLineId && qrData.itemId) {
  636. // For JSON QR codes, we need to fetch the lot number
  637. fetchStockInLineInfo(qrData.stockInLineId)
  638. .then((stockInLineInfo) => {
  639. console.log("Outside QR scan - Stock in line info:", stockInLineInfo);
  640. const extractedLotNo = stockInLineInfo.lotNo;
  641. if (extractedLotNo) {
  642. console.log(`Outside QR scan detected (JSON): ${extractedLotNo}`);
  643. handleQrCodeSubmit(extractedLotNo);
  644. }
  645. })
  646. .catch((error) => {
  647. console.error("Outside QR scan - Error fetching stock in line info:", error);
  648. });
  649. return; // Exit early for JSON QR codes
  650. }
  651. } catch (error) {
  652. // Not JSON format, treat as direct lot number
  653. lotNo = latestQr.replace(/[{}]/g, '');
  654. }
  655. // For direct lot number QR codes
  656. if (lotNo) {
  657. console.log(`Outside QR scan detected (direct): ${lotNo}`);
  658. handleQrCodeSubmit(lotNo);
  659. }
  660. }, [qrValues, combinedLotData, handleQrCodeSubmit, processedQrCodes, lastProcessedQr, isManualScanning, isRefreshingData]);
  661. // ADD THIS: Cleanup effect
  662. useEffect(() => {
  663. return () => {
  664. // Cleanup when component unmounts (e.g., when switching tabs)
  665. if (isManualScanning) {
  666. console.log("🧹 Second scan component unmounting, stopping QR scanner...");
  667. stopScan();
  668. resetScan();
  669. }
  670. };
  671. }, [isManualScanning, stopScan, resetScan]);
  672. const handleManualInputSubmit = useCallback(() => {
  673. if (qrScanInput.trim() !== '') {
  674. handleQrCodeSubmit(qrScanInput.trim());
  675. }
  676. }, [qrScanInput, handleQrCodeSubmit]);
  677. // Handle QR code submission from modal (internal scanning)
  678. const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
  679. if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
  680. console.log(` QR Code verified for lot: ${lotNo}`);
  681. const requiredQty = selectedLotForQr.requiredQty;
  682. const lotId = selectedLotForQr.lotId;
  683. // Create stock out line
  684. const stockOutLineData: CreateStockOutLine = {
  685. consoCode: selectedLotForQr.pickOrderConsoCode,
  686. pickOrderLineId: selectedLotForQr.pickOrderLineId,
  687. inventoryLotLineId: selectedLotForQr.lotId,
  688. qty: 0.0
  689. };
  690. try {
  691. // Close modal
  692. setQrModalOpen(false);
  693. setSelectedLotForQr(null);
  694. // Set pick quantity
  695. const lotKey = `${selectedLotForQr.pickOrderLineId}-${lotId}`;
  696. setTimeout(() => {
  697. setPickQtyData(prev => ({
  698. ...prev,
  699. [lotKey]: requiredQty
  700. }));
  701. console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
  702. }, 500);
  703. // Refresh data
  704. await fetchJobOrderData();
  705. } catch (error) {
  706. console.error("Error creating stock out line:", error);
  707. }
  708. }
  709. }, [selectedLotForQr, fetchJobOrderData]);
  710. // Outside QR scanning - process QR codes from outside the page automatically
  711. const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => {
  712. if (value === '' || value === null || value === undefined) {
  713. setPickQtyData(prev => ({
  714. ...prev,
  715. [lotKey]: 0
  716. }));
  717. return;
  718. }
  719. const numericValue = typeof value === 'string' ? parseFloat(value) : value;
  720. if (isNaN(numericValue)) {
  721. setPickQtyData(prev => ({
  722. ...prev,
  723. [lotKey]: 0
  724. }));
  725. return;
  726. }
  727. setPickQtyData(prev => ({
  728. ...prev,
  729. [lotKey]: numericValue
  730. }));
  731. }, []);
  732. const [autoAssignStatus, setAutoAssignStatus] = useState<'idle' | 'checking' | 'assigned' | 'no_orders'>('idle');
  733. const [autoAssignMessage, setAutoAssignMessage] = useState<string>('');
  734. const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => {
  735. try {
  736. // Use the new second scan submit API
  737. const result = await submitSecondScanQuantity(
  738. lot.pickOrderId,
  739. lot.itemId,
  740. {
  741. qty: submitQty,
  742. isMissing: false,
  743. isBad: false,
  744. reason: undefined // Fix TypeScript error
  745. }
  746. );
  747. if (result.code === "SUCCESS") {
  748. console.log(` Second scan quantity submitted: ${submitQty}`);
  749. await fetchJobOrderData(); // Refresh data
  750. } else {
  751. console.error(`❌ Failed to submit second scan quantity: ${result.message}`);
  752. }
  753. } catch (error) {
  754. console.error("Error submitting second scan quantity:", error);
  755. }
  756. }, [fetchJobOrderData]);
  757. // Handle reject lot
  758. // Handle pick execution form
  759. const handlePickExecutionForm = useCallback((lot: any) => {
  760. console.log("=== Pick Execution Form ===");
  761. console.log("Lot data:", lot);
  762. console.log("lot.pickOrderCode:", lot.pickOrderCode); // 添加
  763. console.log("lot.pickOrderId:", lot.pickOrderId);
  764. if (!lot) {
  765. console.warn("No lot data provided for pick execution form");
  766. return;
  767. }
  768. console.log("Opening pick execution form for lot:", lot.lotNo);
  769. setSelectedLotForExecutionForm(lot);
  770. setPickExecutionFormOpen(true);
  771. console.log("Pick execution form opened for lot ID:", lot.lotId);
  772. }, []);
  773. const handlePickExecutionFormSubmit = useCallback(async (data: any) => {
  774. try {
  775. console.log("Pick execution form submitted:", data);
  776. if (!currentUserId) {
  777. console.error("❌ No current user ID available");
  778. return;
  779. }
  780. const result = await recordSecondScanIssue(
  781. selectedLotForExecutionForm.pickOrderId,
  782. selectedLotForExecutionForm.itemId,
  783. {
  784. qty: data.actualPickQty, // verified qty
  785. missQty: data.missQty || 0, // 添加:实际的 miss qty
  786. badItemQty: data.badItemQty || 0, // 添加:实际的 bad item qty
  787. isMissing: data.missQty > 0,
  788. isBad: data.badItemQty > 0,
  789. reason: data.issueRemark || '',
  790. createdBy: currentUserId,
  791. type: "match"
  792. }
  793. );
  794. console.log("Pick execution issue recorded:", result);
  795. if (result && result.code === "SUCCESS") {
  796. console.log(" Pick execution issue recorded successfully");
  797. } else {
  798. console.error("❌ Failed to record pick execution issue:", result);
  799. }
  800. setPickExecutionFormOpen(false);
  801. setSelectedLotForExecutionForm(null);
  802. await fetchJobOrderData();
  803. } catch (error) {
  804. console.error("Error submitting pick execution form:", error);
  805. }
  806. }, [currentUserId, selectedLotForExecutionForm, fetchJobOrderData,]);
  807. // Calculate remaining required quantity
  808. const calculateRemainingRequiredQty = useCallback((lot: any) => {
  809. const requiredQty = lot.requiredQty || 0;
  810. const stockOutLineQty = lot.stockOutLineQty || 0;
  811. return Math.max(0, requiredQty - stockOutLineQty);
  812. }, []);
  813. // Search criteria
  814. const searchCriteria: Criterion<any>[] = [
  815. {
  816. label: t("Pick Order Code"),
  817. paramName: "pickOrderCode",
  818. type: "text",
  819. },
  820. {
  821. label: t("Item Code"),
  822. paramName: "itemCode",
  823. type: "text",
  824. },
  825. {
  826. label: t("Item Name"),
  827. paramName: "itemName",
  828. type: "text",
  829. },
  830. {
  831. label: t("Lot No"),
  832. paramName: "lotNo",
  833. type: "text",
  834. },
  835. ];
  836. const handleSearch = useCallback((query: Record<string, any>) => {
  837. setSearchQuery({ ...query });
  838. console.log("Search query:", query);
  839. if (!originalCombinedData) return;
  840. const filtered = originalCombinedData.filter((lot: any) => {
  841. const pickOrderCodeMatch = !query.pickOrderCode ||
  842. lot.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase());
  843. const itemCodeMatch = !query.itemCode ||
  844. lot.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase());
  845. const itemNameMatch = !query.itemName ||
  846. lot.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase());
  847. const lotNoMatch = !query.lotNo ||
  848. lot.lotNo?.toLowerCase().includes((query.lotNo || "").toLowerCase());
  849. return pickOrderCodeMatch && itemCodeMatch && itemNameMatch && lotNoMatch;
  850. });
  851. setCombinedLotData(filtered);
  852. console.log("Filtered lots count:", filtered.length);
  853. }, [originalCombinedData]);
  854. const handleReset = useCallback(() => {
  855. setSearchQuery({});
  856. if (originalCombinedData) {
  857. setCombinedLotData(originalCombinedData);
  858. }
  859. }, [originalCombinedData]);
  860. const handlePageChange = useCallback((event: unknown, newPage: number) => {
  861. setPaginationController(prev => ({
  862. ...prev,
  863. pageNum: newPage,
  864. }));
  865. }, []);
  866. const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  867. const newPageSize = parseInt(event.target.value, 10);
  868. setPaginationController({
  869. pageNum: 0,
  870. pageSize: newPageSize,
  871. });
  872. }, []);
  873. // Pagination data with sorting by routerIndex
  874. const paginatedData = useMemo(() => {
  875. // Sort by routerIndex first, then by other criteria
  876. const sortedData = [...combinedLotData].sort((a, b) => {
  877. const aIndex = a.routerIndex || 0;
  878. const bIndex = b.routerIndex || 0;
  879. // Primary sort: by routerIndex
  880. if (aIndex !== bIndex) {
  881. return aIndex - bIndex;
  882. }
  883. // Secondary sort: by pickOrderCode if routerIndex is the same
  884. if (a.pickOrderCode !== b.pickOrderCode) {
  885. return a.pickOrderCode.localeCompare(b.pickOrderCode);
  886. }
  887. // Tertiary sort: by lotNo if everything else is the same
  888. return (a.lotNo || '').localeCompare(b.lotNo || '');
  889. });
  890. const startIndex = paginationController.pageNum * paginationController.pageSize;
  891. const endIndex = startIndex + paginationController.pageSize;
  892. return sortedData.slice(startIndex, endIndex);
  893. }, [combinedLotData, paginationController]);
  894. // Add these functions for manual scanning
  895. const handleStartScan = useCallback(() => {
  896. console.log(" Starting manual QR scan...");
  897. setIsManualScanning(true);
  898. setProcessedQrCodes(new Set());
  899. setLastProcessedQr('');
  900. setQrScanError(false);
  901. setQrScanSuccess(false);
  902. startScan();
  903. }, [startScan]);
  904. const handleStopScan = useCallback(() => {
  905. console.log("⏹️ Stopping manual QR scan...");
  906. setIsManualScanning(false);
  907. setQrScanError(false);
  908. setQrScanSuccess(false);
  909. stopScan();
  910. resetScan();
  911. }, [stopScan, resetScan]);
  912. useEffect(() => {
  913. if (isManualScanning && combinedLotData.length === 0) {
  914. console.log("⏹️ No data available, auto-stopping QR scan...");
  915. handleStopScan();
  916. }
  917. }, [combinedLotData.length, isManualScanning, handleStopScan]);
  918. // Cleanup effect
  919. useEffect(() => {
  920. return () => {
  921. // Cleanup when component unmounts (e.g., when switching tabs)
  922. if (isManualScanning) {
  923. console.log("🧹 Second scan component unmounting, stopping QR scanner...");
  924. stopScan();
  925. resetScan();
  926. }
  927. };
  928. }, [isManualScanning, stopScan, resetScan]);
  929. const getStatusMessage = useCallback((lot: any) => {
  930. switch (lot.stockOutLineStatus?.toLowerCase()) {
  931. case 'pending':
  932. return t("Please finish QR code scan and pick order.");
  933. case 'checked':
  934. return t("Please submit the pick order.");
  935. case 'partially_completed':
  936. return t("Partial quantity submitted. Please submit more or complete the order.");
  937. case 'completed':
  938. return t("Pick order completed successfully!");
  939. case 'rejected':
  940. return t("Lot has been rejected and marked as unavailable.");
  941. case 'unavailable':
  942. return t("This order is insufficient, please pick another lot.");
  943. default:
  944. return t("Please finish QR code scan and pick order.");
  945. }
  946. }, [t]);
  947. return (
  948. <TestQrCodeProvider
  949. lotData={combinedLotData}
  950. onScanLot={handleQrCodeSubmit}
  951. filterActive={(lot) => (
  952. lot.matchStatus !== 'completed' &&
  953. lot.lotAvailability !== 'rejected'
  954. )}
  955. >
  956. <FormProvider {...formProps}>
  957. <Stack spacing={2}>
  958. {/* Job Order Header */}
  959. {jobOrderData && (
  960. <Paper sx={{ p: 2 }}>
  961. <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap">
  962. <Typography variant="subtitle1">
  963. <strong>{t("Job Order")}:</strong> {jobOrderData.pickOrder?.jobOrder?.code || '-'}
  964. </Typography>
  965. <Typography variant="subtitle1">
  966. <strong>{t("Pick Order Code")}:</strong> {jobOrderData.pickOrder?.code || '-'}
  967. </Typography>
  968. <Typography variant="subtitle1">
  969. <strong>{t("Target Date")}:</strong> {jobOrderData.pickOrder?.targetDate || '-'}
  970. </Typography>
  971. </Stack>
  972. </Paper>
  973. )}
  974. {/* Combined Lot Table */}
  975. <Box>
  976. <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
  977. <Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
  978. {!isManualScanning ? (
  979. <Button
  980. variant="contained"
  981. startIcon={<QrCodeIcon />}
  982. onClick={handleStartScan}
  983. color="primary"
  984. sx={{ minWidth: '120px' }}
  985. >
  986. {t("Start QR Scan")}
  987. </Button>
  988. ) : (
  989. <Button
  990. variant="outlined"
  991. startIcon={<QrCodeIcon />}
  992. onClick={handleStopScan}
  993. color="secondary"
  994. sx={{ minWidth: '120px' }}
  995. >
  996. {t("Stop QR Scan")}
  997. </Button>
  998. )}
  999. {/* ADD THIS: Submit All Scanned Button */}
  1000. <Button
  1001. variant="contained"
  1002. color="success"
  1003. onClick={handleSubmitAllScanned}
  1004. disabled={scannedItemsCount === 0 || isSubmittingAll}
  1005. sx={{ minWidth: '160px' }}
  1006. >
  1007. {isSubmittingAll ? (
  1008. <>
  1009. <CircularProgress size={16} sx={{ mr: 1 }} />
  1010. {t("Submitting...")}
  1011. </>
  1012. ) : (
  1013. `${t("Submit All Scanned")} (${scannedItemsCount})`
  1014. )}
  1015. </Button>
  1016. </Box>
  1017. </Box>
  1018. {qrScanError && !qrScanSuccess && (
  1019. <Alert severity="error" sx={{ mb: 2 }}>
  1020. {t("QR code does not match any item in current orders.")}
  1021. </Alert>
  1022. )}
  1023. {qrScanSuccess && (
  1024. <Alert severity="success" sx={{ mb: 2 }}>
  1025. {t("QR code verified.")}
  1026. </Alert>
  1027. )}
  1028. <TableContainer component={Paper}>
  1029. <Table>
  1030. <TableHead>
  1031. <TableRow>
  1032. <TableCell>{t("Index")}</TableCell>
  1033. <TableCell>{t("Route")}</TableCell>
  1034. <TableCell>{t("Item Code")}</TableCell>
  1035. <TableCell>{t("Item Name")}</TableCell>
  1036. <TableCell>{t("Lot No")}</TableCell>
  1037. <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
  1038. <TableCell align="center">{t("Scan Result")}</TableCell>
  1039. <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell>
  1040. </TableRow>
  1041. </TableHead>
  1042. <TableBody>
  1043. {paginatedData.length === 0 ? (
  1044. <TableRow>
  1045. <TableCell colSpan={8} align="center">
  1046. <Typography variant="body2" color="text.secondary">
  1047. {t("No data available")}
  1048. </Typography>
  1049. </TableCell>
  1050. </TableRow>
  1051. ) : (
  1052. paginatedData.map((lot, index) => (
  1053. <TableRow
  1054. key={`${lot.pickOrderLineId}-${lot.lotId}`}
  1055. sx={{
  1056. backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit',
  1057. opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1,
  1058. '& .MuiTableCell-root': {
  1059. color: lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit'
  1060. }
  1061. }}
  1062. >
  1063. <TableCell>
  1064. <Typography variant="body2" fontWeight="bold">
  1065. {index + 1}
  1066. </Typography>
  1067. </TableCell>
  1068. <TableCell>
  1069. <Typography variant="body2">
  1070. {lot.routerRoute || '-'}
  1071. </Typography>
  1072. </TableCell>
  1073. <TableCell>{lot.itemCode}</TableCell>
  1074. <TableCell>{lot.itemName+'('+lot.uomDesc+')'}</TableCell>
  1075. <TableCell>
  1076. <Box>
  1077. <Typography
  1078. sx={{
  1079. color: lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit',
  1080. opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1
  1081. }}
  1082. >
  1083. {lot.lotNo}
  1084. </Typography>
  1085. </Box>
  1086. </TableCell>
  1087. <TableCell align="right">
  1088. {(() => {
  1089. const requiredQty = lot.requiredQty || 0;
  1090. return requiredQty.toLocaleString()+'('+lot.uomShortDesc+')';
  1091. })()}
  1092. </TableCell>
  1093. <TableCell align="center">
  1094. {lot.matchStatus?.toLowerCase() === 'scanned' ||
  1095. lot.matchStatus?.toLowerCase() === 'completed' ? (
  1096. <Box sx={{
  1097. display: 'flex',
  1098. justifyContent: 'center',
  1099. alignItems: 'center',
  1100. width: '100%',
  1101. height: '100%'
  1102. }}>
  1103. <Checkbox
  1104. checked={true} // 改为 true
  1105. disabled={true}
  1106. readOnly={true}
  1107. size="large"
  1108. sx={{
  1109. color: 'success.main', // 固定为绿色
  1110. '&.Mui-checked': {
  1111. color: 'success.main',
  1112. },
  1113. transform: 'scale(1.3)',
  1114. '& .MuiSvgIcon-root': {
  1115. fontSize: '1.5rem',
  1116. }
  1117. }}
  1118. />
  1119. </Box>
  1120. ) : (
  1121. <Typography variant="body2" color="text.secondary">
  1122. {t(" ")}
  1123. </Typography>
  1124. )}
  1125. </TableCell>
  1126. <TableCell align="center">
  1127. <Box sx={{ display: 'flex', justifyContent: 'center' }}>
  1128. <Stack direction="row" spacing={1} alignItems="center">
  1129. <Button
  1130. variant="contained"
  1131. onClick={() => {
  1132. const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
  1133. const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
  1134. // Submit with default lot required pick qty
  1135. handlePickQtyChange(lotKey, submitQty);
  1136. handleSubmitPickQtyWithQty(lot, submitQty);
  1137. }}
  1138. disabled={
  1139. // 修复:只有扫描过但未完成的才能提交
  1140. lot.matchStatus !== 'scanned' || // 只有 scanned 状态才能提交
  1141. lot.lotAvailability === 'expired' ||
  1142. lot.lotAvailability === 'status_unavailable' ||
  1143. lot.lotAvailability === 'rejected'
  1144. }
  1145. sx={{
  1146. fontSize: '0.75rem',
  1147. py: 0.5,
  1148. minHeight: '28px',
  1149. minWidth: '70px'
  1150. }}
  1151. >
  1152. {t("Submit")}
  1153. </Button>
  1154. <Button
  1155. variant="outlined"
  1156. size="small"
  1157. onClick={() => handlePickExecutionForm(lot)}
  1158. disabled={
  1159. // 修复:只有扫描过但未完成的才能报告问题
  1160. lot.matchStatus !== 'scanned' || // 只有 scanned 状态才能报告问题
  1161. lot.lotAvailability === 'expired' ||
  1162. lot.lotAvailability === 'status_unavailable' ||
  1163. lot.lotAvailability === 'rejected'
  1164. }
  1165. sx={{
  1166. fontSize: '0.7rem',
  1167. py: 0.5,
  1168. minHeight: '28px',
  1169. minWidth: '60px',
  1170. borderColor: 'warning.main',
  1171. color: 'warning.main'
  1172. }}
  1173. title="Report missing or bad items"
  1174. >
  1175. {t("Issue")}
  1176. </Button>
  1177. </Stack>
  1178. </Box>
  1179. </TableCell>
  1180. </TableRow>
  1181. ))
  1182. )}
  1183. </TableBody>
  1184. </Table>
  1185. </TableContainer>
  1186. <TablePagination
  1187. component="div"
  1188. count={combinedLotData.length}
  1189. page={paginationController.pageNum}
  1190. rowsPerPage={paginationController.pageSize}
  1191. onPageChange={handlePageChange}
  1192. onRowsPerPageChange={handlePageSizeChange}
  1193. rowsPerPageOptions={[10, 25, 50]}
  1194. labelRowsPerPage={t("Rows per page")}
  1195. labelDisplayedRows={({ from, to, count }) =>
  1196. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  1197. }
  1198. />
  1199. </Box>
  1200. </Stack>
  1201. {/* QR Code Modal */}
  1202. <QrCodeModal
  1203. open={qrModalOpen}
  1204. onClose={() => {
  1205. setQrModalOpen(false);
  1206. setSelectedLotForQr(null);
  1207. stopScan();
  1208. resetScan();
  1209. }}
  1210. lot={selectedLotForQr}
  1211. combinedLotData={combinedLotData}
  1212. onQrCodeSubmit={handleQrCodeSubmitFromModal}
  1213. />
  1214. {/* Pick Execution Form Modal */}
  1215. {pickExecutionFormOpen && selectedLotForExecutionForm && (
  1216. <GoodPickExecutionForm
  1217. open={pickExecutionFormOpen}
  1218. onClose={() => {
  1219. setPickExecutionFormOpen(false);
  1220. setSelectedLotForExecutionForm(null);
  1221. }}
  1222. onSubmit={handlePickExecutionFormSubmit}
  1223. selectedLot={selectedLotForExecutionForm}
  1224. selectedPickOrderLine={{
  1225. id: selectedLotForExecutionForm.pickOrderLineId,
  1226. itemId: selectedLotForExecutionForm.itemId,
  1227. itemCode: selectedLotForExecutionForm.itemCode,
  1228. itemName: selectedLotForExecutionForm.itemName,
  1229. pickOrderCode: selectedLotForExecutionForm.pickOrderCode,
  1230. // Add missing required properties from GetPickOrderLineInfo interface
  1231. availableQty: selectedLotForExecutionForm.availableQty || 0,
  1232. requiredQty: selectedLotForExecutionForm.requiredQty || 0,
  1233. uomCode: selectedLotForExecutionForm.uomCode || '',
  1234. uomDesc: selectedLotForExecutionForm.uomDesc || '',
  1235. pickedQty: selectedLotForExecutionForm.actualPickQty || 0, // Use pickedQty instead of actualPickQty
  1236. suggestedList: [] // Add required suggestedList property
  1237. }}
  1238. pickOrderId={selectedLotForExecutionForm.pickOrderId}
  1239. pickOrderCreateDate={new Date()}
  1240. />
  1241. )}
  1242. </FormProvider>
  1243. </TestQrCodeProvider>
  1244. );
  1245. };
  1246. export default JobPickExecution