FPSMS-frontend
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 

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