FPSMS-frontend
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 

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