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.
 
 

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