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.
 
 

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