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

GoodPickExecutiondetail.tsx 56 KiB

3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551
  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;