FPSMS-frontend
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

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