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

GoodPickExecutiondetail.tsx 78 KiB

3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
3ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
3ヶ月前
1ヶ月前
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228
  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. Chip,
  21. } from "@mui/material";
  22. import dayjs from 'dayjs';
  23. import TestQrCodeProvider from '../QrCodeScannerProvider/TestQrCodeProvider';
  24. import { fetchLotDetail } from "@/app/api/inventory/actions";
  25. import { useCallback, useEffect, useState, useRef, useMemo } from "react";
  26. import { useTranslation } from "react-i18next";
  27. import { useRouter } from "next/navigation";
  28. import {
  29. fetchALLPickOrderLineLotDetails,
  30. updateStockOutLineStatus,
  31. createStockOutLine,
  32. updateStockOutLine,
  33. recordPickExecutionIssue,
  34. fetchFGPickOrders, // Add this import
  35. FGPickOrderResponse,
  36. stockReponse,
  37. PickExecutionIssueData,
  38. checkPickOrderCompletion,
  39. fetchAllPickOrderLotsHierarchical,
  40. PickOrderCompletionResponse,
  41. checkAndCompletePickOrderByConsoCode,
  42. updateSuggestedLotLineId,
  43. confirmLotSubstitution,
  44. fetchDoPickOrderDetail, // 必须添加
  45. DoPickOrderDetail, // 必须添加
  46. fetchFGPickOrdersByUserId
  47. } from "@/app/api/pickOrder/actions";
  48. import FGPickOrderInfoCard from "./FGPickOrderInfoCard";
  49. import LotConfirmationModal from "./LotConfirmationModal";
  50. //import { fetchItem } from "@/app/api/settings/item";
  51. import { updateInventoryLotLineStatus, analyzeQrCode } from "@/app/api/inventory/actions";
  52. import { fetchNameList, NameList } from "@/app/api/user/actions";
  53. import {
  54. FormProvider,
  55. useForm,
  56. } from "react-hook-form";
  57. import SearchBox, { Criterion } from "../SearchBox";
  58. import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
  59. import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
  60. import QrCodeIcon from '@mui/icons-material/QrCode';
  61. import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
  62. import { useSession } from "next-auth/react";
  63. import { SessionWithTokens } from "@/config/authConfig";
  64. import { fetchStockInLineInfo } from "@/app/api/po/actions";
  65. import GoodPickExecutionForm from "./GoodPickExecutionForm";
  66. import FGPickOrderCard from "./FGPickOrderCard";
  67. interface Props {
  68. filterArgs: Record<string, any>;
  69. }
  70. // QR Code Modal Component (from LotTable)
  71. const QrCodeModal: React.FC<{
  72. open: boolean;
  73. onClose: () => void;
  74. lot: any | null;
  75. onQrCodeSubmit: (lotNo: string) => void;
  76. combinedLotData: any[]; // Add this prop
  77. }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => {
  78. const { t } = useTranslation("pickOrder");
  79. const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  80. const [manualInput, setManualInput] = useState<string>('');
  81. const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false);
  82. const [manualInputError, setManualInputError] = useState<boolean>(false);
  83. const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false);
  84. const [qrScanFailed, setQrScanFailed] = useState<boolean>(false);
  85. const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
  86. const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
  87. const [scannedQrResult, setScannedQrResult] = useState<string>('');
  88. const [fgPickOrder, setFgPickOrder] = useState<FGPickOrderResponse | null>(null);
  89. // Process scanned QR codes
  90. useEffect(() => {
  91. if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) {
  92. const latestQr = qrValues[qrValues.length - 1];
  93. if (processedQrCodes.has(latestQr)) {
  94. console.log("QR code already processed, skipping...");
  95. return;
  96. }
  97. setProcessedQrCodes(prev => new Set(prev).add(latestQr));
  98. try {
  99. const qrData = JSON.parse(latestQr);
  100. if (qrData.stockInLineId && qrData.itemId) {
  101. setIsProcessingQr(true);
  102. setQrScanFailed(false);
  103. fetchStockInLineInfo(qrData.stockInLineId)
  104. .then((stockInLineInfo) => {
  105. console.log("Stock in line info:", stockInLineInfo);
  106. setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
  107. if (stockInLineInfo.lotNo === lot.lotNo) {
  108. console.log(` QR Code verified for lot: ${lot.lotNo}`);
  109. setQrScanSuccess(true);
  110. onQrCodeSubmit(lot.lotNo);
  111. onClose();
  112. resetScan();
  113. } else {
  114. console.log(`❌ QR Code mismatch. Expected: ${lot.lotNo}, Got: ${stockInLineInfo.lotNo}`);
  115. setQrScanFailed(true);
  116. setManualInputError(true);
  117. setManualInputSubmitted(true);
  118. }
  119. })
  120. .catch((error) => {
  121. console.error("Error fetching stock in line info:", error);
  122. setScannedQrResult('Error fetching data');
  123. setQrScanFailed(true);
  124. setManualInputError(true);
  125. setManualInputSubmitted(true);
  126. })
  127. .finally(() => {
  128. setIsProcessingQr(false);
  129. });
  130. } else {
  131. const qrContent = latestQr.replace(/[{}]/g, '');
  132. setScannedQrResult(qrContent);
  133. if (qrContent === lot.lotNo) {
  134. setQrScanSuccess(true);
  135. onQrCodeSubmit(lot.lotNo);
  136. onClose();
  137. resetScan();
  138. } else {
  139. setQrScanFailed(true);
  140. setManualInputError(true);
  141. setManualInputSubmitted(true);
  142. }
  143. }
  144. } catch (error) {
  145. console.log("QR code is not JSON format, trying direct comparison");
  146. const qrContent = latestQr.replace(/[{}]/g, '');
  147. setScannedQrResult(qrContent);
  148. if (qrContent === lot.lotNo) {
  149. setQrScanSuccess(true);
  150. onQrCodeSubmit(lot.lotNo);
  151. onClose();
  152. resetScan();
  153. } else {
  154. setQrScanFailed(true);
  155. setManualInputError(true);
  156. setManualInputSubmitted(true);
  157. }
  158. }
  159. }
  160. }, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes]);
  161. // Clear states when modal opens
  162. useEffect(() => {
  163. if (open) {
  164. setManualInput('');
  165. setManualInputSubmitted(false);
  166. setManualInputError(false);
  167. setIsProcessingQr(false);
  168. setQrScanFailed(false);
  169. setQrScanSuccess(false);
  170. setScannedQrResult('');
  171. setProcessedQrCodes(new Set());
  172. }
  173. }, [open]);
  174. useEffect(() => {
  175. if (lot) {
  176. setManualInput('');
  177. setManualInputSubmitted(false);
  178. setManualInputError(false);
  179. setIsProcessingQr(false);
  180. setQrScanFailed(false);
  181. setQrScanSuccess(false);
  182. setScannedQrResult('');
  183. setProcessedQrCodes(new Set());
  184. }
  185. }, [lot]);
  186. // Auto-submit manual input when it matches
  187. useEffect(() => {
  188. if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '' && !qrScanFailed && !qrScanSuccess) {
  189. console.log(' Auto-submitting manual input:', manualInput.trim());
  190. const timer = setTimeout(() => {
  191. setQrScanSuccess(true);
  192. onQrCodeSubmit(lot.lotNo);
  193. onClose();
  194. setManualInput('');
  195. setManualInputError(false);
  196. setManualInputSubmitted(false);
  197. }, 200);
  198. return () => clearTimeout(timer);
  199. }
  200. }, [manualInput, lot, onQrCodeSubmit, onClose, qrScanFailed, qrScanSuccess]);
  201. const handleManualSubmit = () => {
  202. if (manualInput.trim() === lot?.lotNo) {
  203. setQrScanSuccess(true);
  204. onQrCodeSubmit(lot.lotNo);
  205. onClose();
  206. setManualInput('');
  207. } else {
  208. setQrScanFailed(true);
  209. setManualInputError(true);
  210. setManualInputSubmitted(true);
  211. }
  212. };
  213. useEffect(() => {
  214. if (open) {
  215. startScan();
  216. }
  217. }, [open, startScan]);
  218. return (
  219. <Modal open={open} onClose={onClose}>
  220. <Box sx={{
  221. position: 'absolute',
  222. top: '50%',
  223. left: '50%',
  224. transform: 'translate(-50%, -50%)',
  225. bgcolor: 'background.paper',
  226. p: 3,
  227. borderRadius: 2,
  228. minWidth: 400,
  229. }}>
  230. <Typography variant="h6" gutterBottom>
  231. {t("QR Code Scan for Lot")}: {lot?.lotNo}
  232. </Typography>
  233. {isProcessingQr && (
  234. <Box sx={{ mb: 2, p: 2, backgroundColor: '#e3f2fd', borderRadius: 1 }}>
  235. <Typography variant="body2" color="primary">
  236. {t("Processing QR code...")}
  237. </Typography>
  238. </Box>
  239. )}
  240. <Box sx={{ mb: 2 }}>
  241. <Typography variant="body2" gutterBottom>
  242. <strong>{t("Manual Input")}:</strong>
  243. </Typography>
  244. <TextField
  245. fullWidth
  246. size="small"
  247. value={manualInput}
  248. onChange={(e) => {
  249. setManualInput(e.target.value);
  250. if (qrScanFailed || manualInputError) {
  251. setQrScanFailed(false);
  252. setManualInputError(false);
  253. setManualInputSubmitted(false);
  254. }
  255. }}
  256. sx={{ mb: 1 }}
  257. error={manualInputSubmitted && manualInputError}
  258. helperText={
  259. manualInputSubmitted && manualInputError
  260. ? `${t("The input is not the same as the expected lot number.")}`
  261. : ''
  262. }
  263. />
  264. <Button
  265. variant="contained"
  266. onClick={handleManualSubmit}
  267. disabled={!manualInput.trim()}
  268. size="small"
  269. color="primary"
  270. >
  271. {t("Submit")}
  272. </Button>
  273. </Box>
  274. {qrValues.length > 0 && (
  275. <Box sx={{
  276. mb: 2,
  277. p: 2,
  278. backgroundColor: qrScanFailed ? '#ffebee' : qrScanSuccess ? '#e8f5e8' : '#f5f5f5',
  279. borderRadius: 1
  280. }}>
  281. <Typography variant="body2" color={qrScanFailed ? 'error' : qrScanSuccess ? 'success' : 'text.secondary'}>
  282. <strong>{t("QR Scan Result:")}</strong> {scannedQrResult}
  283. </Typography>
  284. {qrScanSuccess && (
  285. <Typography variant="caption" color="success" display="block">
  286. {t("Verified successfully!")}
  287. </Typography>
  288. )}
  289. </Box>
  290. )}
  291. <Box sx={{ mt: 2, textAlign: 'right' }}>
  292. <Button onClick={onClose} variant="outlined">
  293. {t("Cancel")}
  294. </Button>
  295. </Box>
  296. </Box>
  297. </Modal>
  298. );
  299. };
  300. const PickExecution: React.FC<Props> = ({ filterArgs }) => {
  301. const { t } = useTranslation("pickOrder");
  302. const router = useRouter();
  303. const { data: session } = useSession() as { data: SessionWithTokens | null };
  304. const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null);
  305. const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null);
  306. const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
  307. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  308. const [allLotsCompleted, setAllLotsCompleted] = useState(false);
  309. const [combinedLotData, setCombinedLotData] = useState<any[]>([]);
  310. const [combinedDataLoading, setCombinedDataLoading] = useState(false);
  311. const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]);
  312. const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  313. const [qrScanInput, setQrScanInput] = useState<string>('');
  314. const [qrScanError, setQrScanError] = useState<boolean>(false);
  315. const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
  316. const [pickQtyData, setPickQtyData] = useState<Record<string, number>>({});
  317. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  318. const [paginationController, setPaginationController] = useState({
  319. pageNum: 0,
  320. pageSize: 10,
  321. });
  322. const [usernameList, setUsernameList] = useState<NameList[]>([]);
  323. const initializationRef = useRef(false);
  324. const autoAssignRef = useRef(false);
  325. const formProps = useForm();
  326. const errors = formProps.formState.errors;
  327. // Add QR modal states
  328. const [qrModalOpen, setQrModalOpen] = useState(false);
  329. const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);
  330. const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false);
  331. const [expectedLotData, setExpectedLotData] = useState<any>(null);
  332. const [scannedLotData, setScannedLotData] = useState<any>(null);
  333. const [isConfirmingLot, setIsConfirmingLot] = useState(false);
  334. // Add GoodPickExecutionForm states
  335. const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
  336. const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
  337. const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);
  338. const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false);
  339. // Add these missing state variables after line 352
  340. const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
  341. const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
  342. const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
  343. const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false);
  344. const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false);
  345. // Handle QR code button click
  346. const handleQrCodeClick = (pickOrderId: number) => {
  347. console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
  348. // TODO: Implement QR code functionality
  349. };
  350. const handleLotMismatch = useCallback((expectedLot: any, scannedLot: any) => {
  351. console.log("Lot mismatch detected:", { expectedLot, scannedLot });
  352. setExpectedLotData(expectedLot);
  353. setScannedLotData(scannedLot);
  354. setLotConfirmationOpen(true);
  355. }, []);
  356. const checkAllLotsCompleted = useCallback((lotData: any[]) => {
  357. if (lotData.length === 0) {
  358. setAllLotsCompleted(false);
  359. return false;
  360. }
  361. // Filter out rejected lots
  362. const nonRejectedLots = lotData.filter(lot =>
  363. lot.lotAvailability !== 'rejected' &&
  364. lot.stockOutLineStatus !== 'rejected'
  365. );
  366. if (nonRejectedLots.length === 0) {
  367. setAllLotsCompleted(false);
  368. return false;
  369. }
  370. // Check if all non-rejected lots are completed
  371. const allCompleted = nonRejectedLots.every(lot =>
  372. lot.stockOutLineStatus === 'completed'
  373. );
  374. setAllLotsCompleted(allCompleted);
  375. return allCompleted;
  376. }, []);
  377. // 在 fetchAllCombinedLotData 函数中(约 446-684 行)
  378. const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => {
  379. setCombinedDataLoading(true);
  380. try {
  381. const userIdToUse = userId || currentUserId;
  382. console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse);
  383. if (!userIdToUse) {
  384. console.warn("⚠️ No userId available, skipping API call");
  385. setCombinedLotData([]);
  386. setOriginalCombinedData([]);
  387. setAllLotsCompleted(false);
  388. return;
  389. }
  390. // 获取新结构的层级数据
  391. const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse);
  392. console.log(" Hierarchical data (new structure):", hierarchicalData);
  393. // 检查数据结构
  394. if (!hierarchicalData.fgInfo || !hierarchicalData.pickOrders || hierarchicalData.pickOrders.length === 0) {
  395. console.warn("⚠️ No FG info or pick orders found");
  396. setCombinedLotData([]);
  397. setOriginalCombinedData([]);
  398. setAllLotsCompleted(false);
  399. return;
  400. }
  401. // 使用合并后的 pick order 对象(现在只有一个对象,包含所有数据)
  402. const mergedPickOrder = hierarchicalData.pickOrders[0];
  403. // 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片)
  404. // 修改第 478-509 行的 fgOrder 构建逻辑:
  405. const fgOrder: FGPickOrderResponse = {
  406. doPickOrderId: hierarchicalData.fgInfo.doPickOrderId,
  407. ticketNo: hierarchicalData.fgInfo.ticketNo,
  408. storeId: hierarchicalData.fgInfo.storeId,
  409. shopCode: hierarchicalData.fgInfo.shopCode,
  410. shopName: hierarchicalData.fgInfo.shopName,
  411. truckLanceCode: hierarchicalData.fgInfo.truckLanceCode,
  412. DepartureTime: hierarchicalData.fgInfo.departureTime,
  413. shopAddress: "",
  414. pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
  415. // 兼容字段
  416. pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0,
  417. pickOrderConsoCode: mergedPickOrder.consoCode || "",
  418. pickOrderTargetDate: mergedPickOrder.targetDate || "",
  419. pickOrderStatus: mergedPickOrder.status || "",
  420. deliveryOrderId: mergedPickOrder.doOrderIds?.[0] || 0,
  421. deliveryNo: mergedPickOrder.deliveryOrderCodes?.[0] || "",
  422. deliveryDate: "",
  423. shopId: 0,
  424. shopPoNo: "",
  425. numberOfCartons: mergedPickOrder.pickOrderLines?.length || 0,
  426. qrCodeData: hierarchicalData.fgInfo.doPickOrderId,
  427. // 新增:多个 pick orders 信息 - 保持数组格式,不要 join
  428. numberOfPickOrders: mergedPickOrder.pickOrderIds?.length || 0,
  429. pickOrderIds: mergedPickOrder.pickOrderIds || [],
  430. pickOrderCodes: Array.isArray(mergedPickOrder.pickOrderCodes)
  431. ? mergedPickOrder.pickOrderCodes
  432. : [], // 改:保持数组
  433. deliveryOrderIds: mergedPickOrder.doOrderIds || [],
  434. deliveryNos: Array.isArray(mergedPickOrder.deliveryOrderCodes)
  435. ? mergedPickOrder.deliveryOrderCodes
  436. : [], // 改:保持数组
  437. lineCountsPerPickOrder: Array.isArray(mergedPickOrder.lineCountsPerPickOrder)
  438. ? mergedPickOrder.lineCountsPerPickOrder
  439. : []
  440. };
  441. setFgPickOrders([fgOrder]);
  442. console.log("🔍 DEBUG fgOrder.lineCountsPerPickOrder:", fgOrder.lineCountsPerPickOrder);
  443. console.log("🔍 DEBUG fgOrder.pickOrderCodes:", fgOrder.pickOrderCodes);
  444. console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
  445. // ❌ 移除:不需要 doPickOrderDetail 和 switcher 逻辑
  446. // if (hierarchicalData.pickOrders.length > 1) { ... }
  447. // 直接使用合并后的 pickOrderLines
  448. console.log("🎯 Displaying merged pick order lines");
  449. // 将层级数据转换为平铺格式(用于表格显示)
  450. const flatLotData: any[] = [];
  451. mergedPickOrder.pickOrderLines.forEach((line: any) => {
  452. if (line.lots && line.lots.length > 0) {
  453. // 修复:先对 lots 按 lotId 去重并合并 requiredQty
  454. const lotMap = new Map<number, any>();
  455. line.lots.forEach((lot: any) => {
  456. const lotId = lot.id;
  457. if (lotMap.has(lotId)) {
  458. // 如果已存在,合并 requiredQty
  459. const existingLot = lotMap.get(lotId);
  460. existingLot.requiredQty = (existingLot.requiredQty || 0) + (lot.requiredQty || 0);
  461. // 保留其他字段(使用第一个遇到的 lot 的字段)
  462. } else {
  463. // 首次遇到,添加到 map
  464. lotMap.set(lotId, { ...lot });
  465. }
  466. });
  467. // 遍历去重后的 lots
  468. lotMap.forEach((lot: any) => {
  469. flatLotData.push({
  470. // 使用合并后的数据
  471. pickOrderConsoCode: mergedPickOrder.consoCode,
  472. pickOrderTargetDate: mergedPickOrder.targetDate,
  473. pickOrderStatus: mergedPickOrder.status,
  474. pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId
  475. pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
  476. pickOrderLineId: line.id,
  477. pickOrderLineRequiredQty: line.requiredQty,
  478. pickOrderLineStatus: line.status,
  479. itemId: line.item.id,
  480. itemCode: line.item.code,
  481. itemName: line.item.name,
  482. uomDesc: line.item.uomDesc,
  483. uomShortDesc: line.item.uomShortDesc,
  484. lotId: lot.id,
  485. lotNo: lot.lotNo,
  486. expiryDate: lot.expiryDate,
  487. location: lot.location,
  488. stockUnit: lot.stockUnit,
  489. availableQty: lot.availableQty,
  490. requiredQty: lot.requiredQty, // 使用合并后的 requiredQty
  491. actualPickQty: lot.actualPickQty,
  492. inQty: lot.inQty,
  493. outQty: lot.outQty,
  494. holdQty: lot.holdQty,
  495. lotStatus: lot.lotStatus,
  496. lotAvailability: lot.lotAvailability,
  497. processingStatus: lot.processingStatus,
  498. suggestedPickLotId: lot.suggestedPickLotId,
  499. stockOutLineId: lot.stockOutLineId,
  500. stockOutLineStatus: lot.stockOutLineStatus,
  501. stockOutLineQty: lot.stockOutLineQty,
  502. routerId: lot.router?.id,
  503. routerIndex: lot.router?.index,
  504. routerRoute: lot.router?.route,
  505. routerArea: lot.router?.area,
  506. noLot: false,
  507. });
  508. });
  509. } else {
  510. // 没有 lots 的情况(null stock)- 从 stockouts 数组中获取 id
  511. const firstStockout = line.stockouts && line.stockouts.length > 0
  512. ? line.stockouts[0]
  513. : null;
  514. flatLotData.push({
  515. pickOrderConsoCode: mergedPickOrder.consoCodes?.[0] || "", // 修复:consoCodes 是数组
  516. pickOrderTargetDate: mergedPickOrder.targetDate,
  517. pickOrderStatus: mergedPickOrder.status,
  518. pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId
  519. pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
  520. pickOrderLineId: line.id,
  521. pickOrderLineRequiredQty: line.requiredQty,
  522. pickOrderLineStatus: line.status,
  523. itemId: line.item.id,
  524. itemCode: line.item.code,
  525. itemName: line.item.name,
  526. uomDesc: line.item.uomDesc,
  527. uomShortDesc: line.item.uomShortDesc,
  528. // Null stock 字段 - 从 stockouts 数组中获取
  529. lotId: firstStockout?.lotId || null,
  530. lotNo: firstStockout?.lotNo || null,
  531. expiryDate: null,
  532. location: firstStockout?.location || null,
  533. stockUnit: line.item.uomDesc,
  534. availableQty: firstStockout?.availableQty || 0,
  535. requiredQty: line.requiredQty,
  536. actualPickQty: firstStockout?.qty || 0,
  537. inQty: 0,
  538. outQty: 0,
  539. holdQty: 0,
  540. lotStatus: 'unavailable',
  541. lotAvailability: 'insufficient_stock',
  542. processingStatus: firstStockout?.status || 'pending',
  543. suggestedPickLotId: null,
  544. stockOutLineId: firstStockout?.id || null, // 使用 stockouts 数组中的 id
  545. stockOutLineStatus: firstStockout?.status || null,
  546. stockOutLineQty: firstStockout?.qty || 0,
  547. routerId: null,
  548. routerIndex: 999999,
  549. routerRoute: null,
  550. routerArea: null,
  551. noLot: true,
  552. });
  553. }
  554. });
  555. console.log(" Transformed flat lot data:", flatLotData);
  556. console.log("🔍 Total items (including null stock):", flatLotData.length);
  557. setCombinedLotData(flatLotData);
  558. setOriginalCombinedData(flatLotData);
  559. checkAllLotsCompleted(flatLotData);
  560. } catch (error) {
  561. console.error("❌ Error fetching combined lot data:", error);
  562. setCombinedLotData([]);
  563. setOriginalCombinedData([]);
  564. setAllLotsCompleted(false);
  565. } finally {
  566. setCombinedDataLoading(false);
  567. }
  568. }, [currentUserId, checkAllLotsCompleted]); // ❌ 移除 selectedPickOrderId 依赖
  569. // Add effect to check completion when lot data changes
  570. useEffect(() => {
  571. if (combinedLotData.length > 0) {
  572. checkAllLotsCompleted(combinedLotData);
  573. }
  574. }, [combinedLotData, checkAllLotsCompleted]);
  575. // Add function to expose completion status to parent
  576. const getCompletionStatus = useCallback(() => {
  577. return allLotsCompleted;
  578. }, [allLotsCompleted]);
  579. // Expose completion status to parent component
  580. useEffect(() => {
  581. // Dispatch custom event with completion status
  582. const event = new CustomEvent('pickOrderCompletionStatus', {
  583. detail: {
  584. allLotsCompleted,
  585. tabIndex: 1 // 明确指定这是来自标签页 1 的事件
  586. }
  587. });
  588. window.dispatchEvent(event);
  589. }, [allLotsCompleted]);
  590. const handleLotConfirmation = useCallback(async () => {
  591. if (!expectedLotData || !scannedLotData || !selectedLotForQr) return;
  592. setIsConfirmingLot(true);
  593. try {
  594. let newLotLineId = scannedLotData?.inventoryLotLineId;
  595. if (!newLotLineId && scannedLotData?.stockInLineId) {
  596. const ld = await fetchLotDetail(scannedLotData.stockInLineId);
  597. newLotLineId = ld.inventoryLotLineId;
  598. }
  599. if (!newLotLineId) {
  600. console.error("No inventory lot line id for scanned lot");
  601. return;
  602. }
  603. await confirmLotSubstitution({
  604. pickOrderLineId: selectedLotForQr.pickOrderLineId,
  605. stockOutLineId: selectedLotForQr.stockOutLineId,
  606. originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId,
  607. newInventoryLotLineId: newLotLineId
  608. });
  609. setQrScanError(false);
  610. setQrScanSuccess(false);
  611. setQrScanInput('');
  612. //setIsManualScanning(false);
  613. //stopScan();
  614. //resetScan();
  615. setProcessedQrCodes(new Set());
  616. setLastProcessedQr('');
  617. setQrModalOpen(false);
  618. setPickExecutionFormOpen(false);
  619. if(selectedLotForQr?.stockOutLineId){
  620. const stockOutLineUpdate = await updateStockOutLineStatus({
  621. id: selectedLotForQr.stockOutLineId,
  622. status: 'checked',
  623. qty: 0
  624. });
  625. }
  626. setLotConfirmationOpen(false);
  627. setExpectedLotData(null);
  628. setScannedLotData(null);
  629. setSelectedLotForQr(null);
  630. await fetchAllCombinedLotData();
  631. } catch (error) {
  632. console.error("Error confirming lot substitution:", error);
  633. } finally {
  634. setIsConfirmingLot(false);
  635. }
  636. }, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData]);
  637. const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
  638. console.log(` Processing QR Code for lot: ${lotNo}`);
  639. // 检查 lotNo 是否为 null 或 undefined(包括字符串 "null")
  640. if (!lotNo || lotNo === 'null' || lotNo.trim() === '') {
  641. console.error("❌ Invalid lotNo: null, undefined, or empty");
  642. return;
  643. }
  644. // Use current data without refreshing to avoid infinite loop
  645. const currentLotData = combinedLotData;
  646. console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo));
  647. // 修复:在比较前确保 lotNo 不为 null
  648. const lotNoLower = lotNo.toLowerCase();
  649. const matchingLots = currentLotData.filter(lot => {
  650. if (!lot.lotNo) return false; // 跳过 null lotNo
  651. return lot.lotNo === lotNo || lot.lotNo.toLowerCase() === lotNoLower;
  652. });
  653. if (matchingLots.length === 0) {
  654. console.error(`❌ Lot not found: ${lotNo}`);
  655. setQrScanError(true);
  656. setQrScanSuccess(false);
  657. const availableLotNos = currentLotData.map(lot => lot.lotNo).join(', ');
  658. console.log(`❌ QR Code "${lotNo}" does not match any expected lots. Available lots: ${availableLotNos}`);
  659. return;
  660. }
  661. console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
  662. setQrScanError(false);
  663. try {
  664. let successCount = 0;
  665. let errorCount = 0;
  666. for (const matchingLot of matchingLots) {
  667. console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`);
  668. if (matchingLot.stockOutLineId) {
  669. const stockOutLineUpdate = await updateStockOutLineStatus({
  670. id: matchingLot.stockOutLineId,
  671. status: 'checked',
  672. qty: 0
  673. });
  674. console.log(`Update stock out line result for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate);
  675. // Treat multiple backend shapes as success (type-safe via any)
  676. const r: any = stockOutLineUpdate as any;
  677. const updateOk =
  678. r?.code === 'SUCCESS' ||
  679. typeof r?.id === 'number' ||
  680. r?.type === 'checked' ||
  681. r?.status === 'checked' ||
  682. typeof r?.entity?.id === 'number' ||
  683. r?.entity?.status === 'checked';
  684. if (updateOk) {
  685. successCount++;
  686. } else {
  687. errorCount++;
  688. }
  689. } else {
  690. const createStockOutLineData = {
  691. consoCode: matchingLot.pickOrderConsoCode,
  692. pickOrderLineId: matchingLot.pickOrderLineId,
  693. inventoryLotLineId: matchingLot.lotId,
  694. qty: 0
  695. };
  696. const createResult = await createStockOutLine(createStockOutLineData);
  697. console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, createResult);
  698. if (createResult && createResult.code === "SUCCESS") {
  699. // Immediately set status to checked for new line
  700. let newSolId: number | undefined;
  701. const anyRes: any = createResult as any;
  702. if (typeof anyRes?.id === 'number') {
  703. newSolId = anyRes.id;
  704. } else if (anyRes?.entity) {
  705. newSolId = Array.isArray(anyRes.entity) ? anyRes.entity[0]?.id : anyRes.entity?.id;
  706. }
  707. if (newSolId) {
  708. const setChecked = await updateStockOutLineStatus({
  709. id: newSolId,
  710. status: 'checked',
  711. qty: 0
  712. });
  713. if (setChecked && setChecked.code === "SUCCESS") {
  714. successCount++;
  715. } else {
  716. errorCount++;
  717. }
  718. } else {
  719. console.warn("Created stock out line but no ID returned; cannot set to checked");
  720. errorCount++;
  721. }
  722. } else {
  723. errorCount++;
  724. }
  725. }
  726. }
  727. // FIXED: Set refresh flag before refreshing data
  728. setIsRefreshingData(true);
  729. console.log("🔄 Refreshing data after QR code processing...");
  730. await fetchAllCombinedLotData();
  731. if (successCount > 0) {
  732. console.log(` QR Code processing completed: ${successCount} updated/created`);
  733. setQrScanSuccess(true);
  734. setQrScanError(false);
  735. setQrScanInput(''); // Clear input after successful processing
  736. //setIsManualScanning(false);
  737. // stopScan();
  738. // resetScan();
  739. // Clear success state after a delay
  740. //setTimeout(() => {
  741. //setQrScanSuccess(false);
  742. //}, 2000);
  743. } else {
  744. console.error(`❌ QR Code processing failed: ${errorCount} errors`);
  745. setQrScanError(true);
  746. setQrScanSuccess(false);
  747. // Clear error state after a delay
  748. // setTimeout(() => {
  749. // setQrScanError(false);
  750. //}, 3000);
  751. }
  752. } catch (error) {
  753. console.error("❌ Error processing QR code:", error);
  754. setQrScanError(true);
  755. setQrScanSuccess(false);
  756. // Still refresh data even on error
  757. setIsRefreshingData(true);
  758. await fetchAllCombinedLotData();
  759. // Clear error state after a delay
  760. setTimeout(() => {
  761. setQrScanError(false);
  762. }, 3000);
  763. } finally {
  764. // Clear refresh flag after a short delay
  765. setTimeout(() => {
  766. setIsRefreshingData(false);
  767. }, 1000);
  768. }
  769. }, [combinedLotData, fetchAllCombinedLotData]);
  770. const processOutsideQrCode = useCallback(async (latestQr: string) => {
  771. // 1) Parse JSON safely
  772. let qrData: any = null;
  773. try {
  774. qrData = JSON.parse(latestQr);
  775. } catch {
  776. console.log("QR content is not JSON; skipping lotNo direct submit to avoid false matches.");
  777. setQrScanError(true);
  778. setQrScanSuccess(false);
  779. return;
  780. }
  781. try {
  782. // Only use the new API when we have JSON with stockInLineId + itemId
  783. if (!(qrData?.stockInLineId && qrData?.itemId)) {
  784. console.log("QR JSON missing required fields (itemId, stockInLineId).");
  785. setQrScanError(true);
  786. setQrScanSuccess(false);
  787. return;
  788. }
  789. // Call new analyze-qr-code API
  790. const analysis = await analyzeQrCode({
  791. itemId: qrData.itemId,
  792. stockInLineId: qrData.stockInLineId
  793. });
  794. if (!analysis) {
  795. console.error("analyzeQrCode returned no data");
  796. setQrScanError(true);
  797. setQrScanSuccess(false);
  798. return;
  799. }
  800. const {
  801. itemId: analyzedItemId,
  802. itemCode: analyzedItemCode,
  803. itemName: analyzedItemName,
  804. scanned,
  805. } = analysis || {};
  806. // 1) Find all lots for the same item from current expected list
  807. const sameItemLotsInExpected = combinedLotData.filter(l =>
  808. (l.itemId && analyzedItemId && l.itemId === analyzedItemId) ||
  809. (l.itemCode && analyzedItemCode && l.itemCode === analyzedItemCode)
  810. );
  811. if (!sameItemLotsInExpected || sameItemLotsInExpected.length === 0) {
  812. // Case 3: No item code match
  813. console.error("No item match in expected lots for scanned code");
  814. setQrScanError(true);
  815. setQrScanSuccess(false);
  816. return;
  817. }
  818. // FIXED: Find the ACTIVE suggested lot (not rejected lots)
  819. const activeSuggestedLots = sameItemLotsInExpected.filter(lot =>
  820. lot.lotAvailability !== 'rejected' &&
  821. lot.stockOutLineStatus !== 'rejected' &&
  822. lot.processingStatus !== 'rejected'
  823. );
  824. if (activeSuggestedLots.length === 0) {
  825. console.error("No active suggested lots found for this item");
  826. setQrScanError(true);
  827. setQrScanSuccess(false);
  828. return;
  829. }
  830. // 2) Check if scanned lot is exactly in active suggested lots
  831. const exactLotMatch = activeSuggestedLots.find(l =>
  832. (scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) ||
  833. (scanned?.lotNo && l.lotNo === scanned.lotNo)
  834. );
  835. if (exactLotMatch && scanned?.lotNo) {
  836. // Case 1: Normal case - item matches AND lot matches -> proceed
  837. console.log(`Exact lot match found for ${scanned.lotNo}, submitting QR`);
  838. handleQrCodeSubmit(scanned.lotNo);
  839. return;
  840. }
  841. // Case 2: Item matches but lot number differs -> open confirmation modal
  842. // FIXED: Use the first ACTIVE suggested lot, not just any lot
  843. const expectedLot = activeSuggestedLots[0];
  844. if (!expectedLot) {
  845. console.error("Could not determine expected lot for confirmation");
  846. setQrScanError(true);
  847. setQrScanSuccess(false);
  848. return;
  849. }
  850. // Check if the expected lot is already the scanned lot (after substitution)
  851. if (expectedLot.lotNo === scanned?.lotNo) {
  852. console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`);
  853. handleQrCodeSubmit(scanned.lotNo);
  854. return;
  855. }
  856. console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`);
  857. setSelectedLotForQr(expectedLot);
  858. handleLotMismatch(
  859. {
  860. lotNo: expectedLot.lotNo,
  861. itemCode: analyzedItemCode || expectedLot.itemCode,
  862. itemName: analyzedItemName || expectedLot.itemName
  863. },
  864. {
  865. lotNo: scanned?.lotNo || '',
  866. itemCode: analyzedItemCode || expectedLot.itemCode,
  867. itemName: analyzedItemName || expectedLot.itemName,
  868. inventoryLotLineId: scanned?.inventoryLotLineId,
  869. stockInLineId: qrData.stockInLineId
  870. }
  871. );
  872. } catch (error) {
  873. console.error("Error during analyzeQrCode flow:", error);
  874. setQrScanError(true);
  875. setQrScanSuccess(false);
  876. return;
  877. }
  878. }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]);
  879. // Update the outside QR scanning effect to use enhanced processing
  880. // Update the outside QR scanning effect to use enhanced processing
  881. useEffect(() => {
  882. if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
  883. return;
  884. }
  885. const latestQr = qrValues[qrValues.length - 1];
  886. if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) {
  887. console.log("QR code already processed, skipping...");
  888. return;
  889. }
  890. if (latestQr && latestQr !== lastProcessedQr) {
  891. console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`);
  892. setLastProcessedQr(latestQr);
  893. setProcessedQrCodes(prev => new Set(prev).add(latestQr));
  894. processOutsideQrCode(latestQr);
  895. }
  896. }, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]);
  897. // Only fetch existing data when session is ready, no auto-assignment
  898. useEffect(() => {
  899. if (session && currentUserId && !initializationRef.current) {
  900. console.log(" Session loaded, initializing pick order...");
  901. initializationRef.current = true;
  902. // Only fetch existing data, no auto-assignment
  903. fetchAllCombinedLotData();
  904. }
  905. }, [session, currentUserId, fetchAllCombinedLotData]);
  906. // Add event listener for manual assignment
  907. useEffect(() => {
  908. const handlePickOrderAssigned = () => {
  909. console.log("🔄 Pick order assigned event received, refreshing data...");
  910. fetchAllCombinedLotData();
  911. };
  912. window.addEventListener('pickOrderAssigned', handlePickOrderAssigned);
  913. return () => {
  914. window.removeEventListener('pickOrderAssigned', handlePickOrderAssigned);
  915. };
  916. }, [fetchAllCombinedLotData]);
  917. const handleManualInputSubmit = useCallback(() => {
  918. if (qrScanInput.trim() !== '') {
  919. handleQrCodeSubmit(qrScanInput.trim());
  920. }
  921. }, [qrScanInput, handleQrCodeSubmit]);
  922. // Handle QR code submission from modal (internal scanning)
  923. const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
  924. if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
  925. console.log(` QR Code verified for lot: ${lotNo}`);
  926. const requiredQty = selectedLotForQr.requiredQty;
  927. const lotId = selectedLotForQr.lotId;
  928. // Create stock out line
  929. try {
  930. const stockOutLineUpdate = await updateStockOutLineStatus({
  931. id: selectedLotForQr.stockOutLineId,
  932. status: 'checked',
  933. qty: selectedLotForQr.stockOutLineQty || 0
  934. });
  935. console.log("Stock out line updated successfully!");
  936. setQrScanSuccess(true);
  937. setQrScanError(false);
  938. // Close modal
  939. setQrModalOpen(false);
  940. setSelectedLotForQr(null);
  941. // Set pick quantity
  942. const lotKey = `${selectedLotForQr.pickOrderLineId}-${lotId}`;
  943. setTimeout(() => {
  944. setPickQtyData(prev => ({
  945. ...prev,
  946. [lotKey]: requiredQty
  947. }));
  948. console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
  949. }, 500);
  950. // Refresh data
  951. await fetchAllCombinedLotData();
  952. } catch (error) {
  953. console.error("Error creating stock out line:", error);
  954. }
  955. }
  956. }, [selectedLotForQr, fetchAllCombinedLotData]);
  957. const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => {
  958. if (value === '' || value === null || value === undefined) {
  959. setPickQtyData(prev => ({
  960. ...prev,
  961. [lotKey]: 0
  962. }));
  963. return;
  964. }
  965. const numericValue = typeof value === 'string' ? parseFloat(value) : value;
  966. if (isNaN(numericValue)) {
  967. setPickQtyData(prev => ({
  968. ...prev,
  969. [lotKey]: 0
  970. }));
  971. return;
  972. }
  973. setPickQtyData(prev => ({
  974. ...prev,
  975. [lotKey]: numericValue
  976. }));
  977. }, []);
  978. const [autoAssignStatus, setAutoAssignStatus] = useState<'idle' | 'checking' | 'assigned' | 'no_orders'>('idle');
  979. const [autoAssignMessage, setAutoAssignMessage] = useState<string>('');
  980. const [completionStatus, setCompletionStatus] = useState<PickOrderCompletionResponse | null>(null);
  981. const checkAndAutoAssignNext = useCallback(async () => {
  982. if (!currentUserId) return;
  983. try {
  984. const completionResponse = await checkPickOrderCompletion(currentUserId);
  985. if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) {
  986. console.log("Found completed pick orders, auto-assigning next...");
  987. // 移除前端的自动分配逻辑,因为后端已经处理了
  988. // await handleAutoAssignAndRelease(); // 删除这个函数
  989. }
  990. } catch (error) {
  991. console.error("Error checking pick order completion:", error);
  992. }
  993. }, [currentUserId]);
  994. // Handle submit pick quantity
  995. const handleSubmitPickQty = useCallback(async (lot: any) => {
  996. const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
  997. const newQty = pickQtyData[lotKey] || 0;
  998. if (!lot.stockOutLineId) {
  999. console.error("No stock out line found for this lot");
  1000. return;
  1001. }
  1002. try {
  1003. // FIXED: Calculate cumulative quantity correctly
  1004. const currentActualPickQty = lot.actualPickQty || 0;
  1005. const cumulativeQty = currentActualPickQty + newQty;
  1006. // FIXED: Determine status based on cumulative quantity vs required quantity
  1007. let newStatus = 'partially_completed';
  1008. if (cumulativeQty >= lot.requiredQty) {
  1009. newStatus = 'completed';
  1010. } else if (cumulativeQty > 0) {
  1011. newStatus = 'partially_completed';
  1012. } else {
  1013. newStatus = 'checked'; // QR scanned but no quantity submitted yet
  1014. }
  1015. console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`);
  1016. console.log(`Lot: ${lot.lotNo}`);
  1017. console.log(`Required Qty: ${lot.requiredQty}`);
  1018. console.log(`Current Actual Pick Qty: ${currentActualPickQty}`);
  1019. console.log(`New Submitted Qty: ${newQty}`);
  1020. console.log(`Cumulative Qty: ${cumulativeQty}`);
  1021. console.log(`New Status: ${newStatus}`);
  1022. console.log(`=====================================`);
  1023. await updateStockOutLineStatus({
  1024. id: lot.stockOutLineId,
  1025. status: newStatus,
  1026. qty: cumulativeQty // Use cumulative quantity
  1027. });
  1028. if (newQty > 0) {
  1029. await updateInventoryLotLineQuantities({
  1030. inventoryLotLineId: lot.lotId,
  1031. qty: newQty,
  1032. status: 'available',
  1033. operation: 'pick'
  1034. });
  1035. }
  1036. // Check if pick order is completed when lot status becomes 'completed'
  1037. if (newStatus === 'completed' && lot.pickOrderConsoCode) {
  1038. console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
  1039. try {
  1040. const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
  1041. console.log(` Pick order completion check result:`, completionResponse);
  1042. if (completionResponse.code === "SUCCESS") {
  1043. console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
  1044. } else if (completionResponse.message === "not completed") {
  1045. console.log(`⏳ Pick order not completed yet, more lines remaining`);
  1046. } else {
  1047. console.error(`❌ Error checking completion: ${completionResponse.message}`);
  1048. }
  1049. } catch (error) {
  1050. console.error("Error checking pick order completion:", error);
  1051. }
  1052. }
  1053. await fetchAllCombinedLotData();
  1054. console.log("Pick quantity submitted successfully!");
  1055. setTimeout(() => {
  1056. checkAndAutoAssignNext();
  1057. }, 1000);
  1058. } catch (error) {
  1059. console.error("Error submitting pick quantity:", error);
  1060. }
  1061. }, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]);
  1062. // Handle reject lot
  1063. const handleRejectLot = useCallback(async (lot: any) => {
  1064. if (!lot.stockOutLineId) {
  1065. console.error("No stock out line found for this lot");
  1066. return;
  1067. }
  1068. try {
  1069. await updateStockOutLineStatus({
  1070. id: lot.stockOutLineId,
  1071. status: 'rejected',
  1072. qty: 0
  1073. });
  1074. await fetchAllCombinedLotData();
  1075. console.log("Lot rejected successfully!");
  1076. setTimeout(() => {
  1077. checkAndAutoAssignNext();
  1078. }, 1000);
  1079. } catch (error) {
  1080. console.error("Error rejecting lot:", error);
  1081. }
  1082. }, [fetchAllCombinedLotData, checkAndAutoAssignNext]);
  1083. // Handle pick execution form
  1084. const handlePickExecutionForm = useCallback((lot: any) => {
  1085. console.log("=== Pick Execution Form ===");
  1086. console.log("Lot data:", lot);
  1087. if (!lot) {
  1088. console.warn("No lot data provided for pick execution form");
  1089. return;
  1090. }
  1091. console.log("Opening pick execution form for lot:", lot.lotNo);
  1092. setSelectedLotForExecutionForm(lot);
  1093. setPickExecutionFormOpen(true);
  1094. console.log("Pick execution form opened for lot ID:", lot.lotId);
  1095. }, []);
  1096. const handlePickExecutionFormSubmit = useCallback(async (data: any) => {
  1097. try {
  1098. console.log("Pick execution form submitted:", data);
  1099. const issueData = {
  1100. ...data,
  1101. type: "Do", // Delivery Order Record 类型
  1102. pickerName: session?.user?.name || '',
  1103. };
  1104. const result = await recordPickExecutionIssue(issueData);
  1105. console.log("Pick execution issue recorded:", result);
  1106. if (result && result.code === "SUCCESS") {
  1107. console.log(" Pick execution issue recorded successfully");
  1108. } else {
  1109. console.error("❌ Failed to record pick execution issue:", result);
  1110. }
  1111. setPickExecutionFormOpen(false);
  1112. setSelectedLotForExecutionForm(null);
  1113. setQrScanError(false);
  1114. setQrScanSuccess(false);
  1115. setQrScanInput('');
  1116. setIsManualScanning(false);
  1117. stopScan();
  1118. resetScan();
  1119. setProcessedQrCodes(new Set());
  1120. setLastProcessedQr('');
  1121. await fetchAllCombinedLotData();
  1122. } catch (error) {
  1123. console.error("Error submitting pick execution form:", error);
  1124. }
  1125. }, [fetchAllCombinedLotData]);
  1126. // Calculate remaining required quantity
  1127. const calculateRemainingRequiredQty = useCallback((lot: any) => {
  1128. const requiredQty = lot.requiredQty || 0;
  1129. const stockOutLineQty = lot.stockOutLineQty || 0;
  1130. return Math.max(0, requiredQty - stockOutLineQty);
  1131. }, []);
  1132. // Search criteria
  1133. const searchCriteria: Criterion<any>[] = [
  1134. {
  1135. label: t("Pick Order Code"),
  1136. paramName: "pickOrderCode",
  1137. type: "text",
  1138. },
  1139. {
  1140. label: t("Item Code"),
  1141. paramName: "itemCode",
  1142. type: "text",
  1143. },
  1144. {
  1145. label: t("Item Name"),
  1146. paramName: "itemName",
  1147. type: "text",
  1148. },
  1149. {
  1150. label: t("Lot No"),
  1151. paramName: "lotNo",
  1152. type: "text",
  1153. },
  1154. ];
  1155. const handleSearch = useCallback((query: Record<string, any>) => {
  1156. setSearchQuery({ ...query });
  1157. console.log("Search query:", query);
  1158. if (!originalCombinedData) return;
  1159. const filtered = originalCombinedData.filter((lot: any) => {
  1160. const pickOrderCodeMatch = !query.pickOrderCode ||
  1161. lot.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase());
  1162. const itemCodeMatch = !query.itemCode ||
  1163. lot.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase());
  1164. const itemNameMatch = !query.itemName ||
  1165. lot.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase());
  1166. const lotNoMatch = !query.lotNo ||
  1167. lot.lotNo?.toLowerCase().includes((query.lotNo || "").toLowerCase());
  1168. return pickOrderCodeMatch && itemCodeMatch && itemNameMatch && lotNoMatch;
  1169. });
  1170. setCombinedLotData(filtered);
  1171. console.log("Filtered lots count:", filtered.length);
  1172. }, [originalCombinedData]);
  1173. const handleReset = useCallback(() => {
  1174. setSearchQuery({});
  1175. if (originalCombinedData) {
  1176. setCombinedLotData(originalCombinedData);
  1177. }
  1178. }, [originalCombinedData]);
  1179. const handlePageChange = useCallback((event: unknown, newPage: number) => {
  1180. setPaginationController(prev => ({
  1181. ...prev,
  1182. pageNum: newPage,
  1183. }));
  1184. }, []);
  1185. const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  1186. const newPageSize = parseInt(event.target.value, 10);
  1187. setPaginationController({
  1188. pageNum: 0,
  1189. pageSize: newPageSize,
  1190. });
  1191. }, []);
  1192. // Pagination data with sorting by routerIndex
  1193. // Remove the sorting logic and just do pagination
  1194. const paginatedData = useMemo(() => {
  1195. const startIndex = paginationController.pageNum * paginationController.pageSize;
  1196. const endIndex = startIndex + paginationController.pageSize;
  1197. return combinedLotData.slice(startIndex, endIndex); // No sorting needed
  1198. }, [combinedLotData, paginationController]);
  1199. const allItemsReady = useMemo(() => {
  1200. if (combinedLotData.length === 0) return false;
  1201. return combinedLotData.every((lot: any) => {
  1202. const status = lot.stockOutLineStatus?.toLowerCase();
  1203. const isRejected =
  1204. status === 'rejected' || lot.lotAvailability === 'rejected';
  1205. const isCompleted =
  1206. status === 'completed' || status === 'partially_completed' || status === 'partially_complete';
  1207. const isChecked = status === 'checked';
  1208. // 无库存(noLot)行:只要状态不是 pending/rejected 即视为已处理
  1209. if (lot.noLot === true) {
  1210. return isChecked || isCompleted || isRejected;
  1211. }
  1212. // 正常 lot:必须已扫描/提交或者被拒收
  1213. return isChecked || isCompleted || isRejected;
  1214. });
  1215. }, [combinedLotData]);
  1216. const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => {
  1217. if (!lot.stockOutLineId) {
  1218. console.error("No stock out line found for this lot");
  1219. return;
  1220. }
  1221. try {
  1222. // FIXED: Calculate cumulative quantity correctly
  1223. const currentActualPickQty = lot.actualPickQty || 0;
  1224. const cumulativeQty = currentActualPickQty + submitQty;
  1225. // FIXED: Determine status based on cumulative quantity vs required quantity
  1226. let newStatus = 'partially_completed';
  1227. if (cumulativeQty >= lot.requiredQty) {
  1228. newStatus = 'completed';
  1229. } else if (cumulativeQty > 0) {
  1230. newStatus = 'partially_completed';
  1231. } else {
  1232. newStatus = 'checked'; // QR scanned but no quantity submitted yet
  1233. }
  1234. console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`);
  1235. console.log(`Lot: ${lot.lotNo}`);
  1236. console.log(`Required Qty: ${lot.requiredQty}`);
  1237. console.log(`Current Actual Pick Qty: ${currentActualPickQty}`);
  1238. console.log(`New Submitted Qty: ${submitQty}`);
  1239. console.log(`Cumulative Qty: ${cumulativeQty}`);
  1240. console.log(`New Status: ${newStatus}`);
  1241. console.log(`=====================================`);
  1242. await updateStockOutLineStatus({
  1243. id: lot.stockOutLineId,
  1244. status: newStatus,
  1245. qty: cumulativeQty // Use cumulative quantity
  1246. });
  1247. if (submitQty > 0) {
  1248. await updateInventoryLotLineQuantities({
  1249. inventoryLotLineId: lot.lotId,
  1250. qty: submitQty,
  1251. status: 'available',
  1252. operation: 'pick'
  1253. });
  1254. }
  1255. // Check if pick order is completed when lot status becomes 'completed'
  1256. if (newStatus === 'completed' && lot.pickOrderConsoCode) {
  1257. console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
  1258. try {
  1259. const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
  1260. console.log(` Pick order completion check result:`, completionResponse);
  1261. if (completionResponse.code === "SUCCESS") {
  1262. console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
  1263. } else if (completionResponse.message === "not completed") {
  1264. console.log(`⏳ Pick order not completed yet, more lines remaining`);
  1265. } else {
  1266. console.error(`❌ Error checking completion: ${completionResponse.message}`);
  1267. }
  1268. } catch (error) {
  1269. console.error("Error checking pick order completion:", error);
  1270. }
  1271. }
  1272. await fetchAllCombinedLotData();
  1273. console.log("Pick quantity submitted successfully!");
  1274. setTimeout(() => {
  1275. checkAndAutoAssignNext();
  1276. }, 1000);
  1277. } catch (error) {
  1278. console.error("Error submitting pick quantity:", error);
  1279. }
  1280. }, [fetchAllCombinedLotData, checkAndAutoAssignNext]);
  1281. // Add these functions after line 395
  1282. const handleStartScan = useCallback(() => {
  1283. console.log(" Starting manual QR scan...");
  1284. setIsManualScanning(true);
  1285. setProcessedQrCodes(new Set());
  1286. setLastProcessedQr('');
  1287. setQrScanError(false);
  1288. setQrScanSuccess(false);
  1289. startScan();
  1290. }, [startScan]);
  1291. const handlePickOrderSwitch = useCallback(async (pickOrderId: number) => {
  1292. if (pickOrderSwitching) return;
  1293. setPickOrderSwitching(true);
  1294. try {
  1295. console.log("🔍 Switching to pick order:", pickOrderId);
  1296. setSelectedPickOrderId(pickOrderId);
  1297. // 强制刷新数据,确保显示正确的 pick order 数据
  1298. await fetchAllCombinedLotData(currentUserId, pickOrderId);
  1299. } catch (error) {
  1300. console.error("Error switching pick order:", error);
  1301. } finally {
  1302. setPickOrderSwitching(false);
  1303. }
  1304. }, [pickOrderSwitching, currentUserId, fetchAllCombinedLotData]);
  1305. const handleStopScan = useCallback(() => {
  1306. console.log("⏹️ Stopping manual QR scan...");
  1307. setIsManualScanning(false);
  1308. setQrScanError(false);
  1309. setQrScanSuccess(false);
  1310. stopScan();
  1311. resetScan();
  1312. }, [stopScan, resetScan]);
  1313. // ... existing code around line 1469 ...
  1314. const handlelotnull = useCallback(async (lot: any) => {
  1315. // 优先使用 stockouts 中的 id,如果没有则使用 stockOutLineId
  1316. const stockOutLineId = lot.stockOutLineId;
  1317. if (!stockOutLineId) {
  1318. console.error("❌ No stockOutLineId found for lot:", lot);
  1319. return;
  1320. }
  1321. try {
  1322. // Step 1: Update stock out line status
  1323. await updateStockOutLineStatus({
  1324. id: stockOutLineId,
  1325. status: 'completed',
  1326. qty: 0
  1327. });
  1328. // Step 2: Create pick execution issue for no-lot case
  1329. // Get pick order ID from fgPickOrders or use 0 if not available
  1330. const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0;
  1331. const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || '';
  1332. const issueData: PickExecutionIssueData = {
  1333. type: "Do", // Delivery Order type
  1334. pickOrderId: pickOrderId,
  1335. pickOrderCode: pickOrderCode,
  1336. pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format
  1337. pickExecutionDate: dayjs().format('YYYY-MM-DD'),
  1338. pickOrderLineId: lot.pickOrderLineId,
  1339. itemId: lot.itemId,
  1340. itemCode: lot.itemCode || '',
  1341. itemDescription: lot.itemName || '',
  1342. lotId: null, // No lot available
  1343. lotNo: null, // No lot number
  1344. storeLocation: lot.location || '',
  1345. requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
  1346. actualPickQty: 0, // No items picked (no lot available)
  1347. missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, // All quantity is missing
  1348. badItemQty: 0,
  1349. issueRemark: `No lot available for this item. Handled via handlelotnull.`,
  1350. pickerName: session?.user?.name || '',
  1351. };
  1352. const result = await recordPickExecutionIssue(issueData);
  1353. console.log(" Pick execution issue created for no-lot item:", result);
  1354. if (result && result.code === "SUCCESS") {
  1355. console.log(" No-lot item handled and issue recorded successfully");
  1356. } else {
  1357. console.error("❌ Failed to record pick execution issue:", result);
  1358. }
  1359. // Step 3: Refresh data
  1360. await fetchAllCombinedLotData();
  1361. } catch (error) {
  1362. console.error("❌ Error in handlelotnull:", error);
  1363. }
  1364. }, [fetchAllCombinedLotData, session, currentUserId, fgPickOrders]);
  1365. // ... existing code ...
  1366. const handleSubmitAllScanned = useCallback(async () => {
  1367. const scannedLots = combinedLotData.filter(lot => {
  1368. // 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete
  1369. if (lot.noLot === true) {
  1370. return lot.stockOutLineStatus === 'checked' ||
  1371. lot.stockOutLineStatus === 'pending' ||
  1372. lot.stockOutLineStatus === 'partially_completed' ||
  1373. lot.stockOutLineStatus === 'PARTIALLY_COMPLETE';
  1374. }
  1375. // 正常情况:只包含 checked 状态
  1376. return lot.stockOutLineStatus === 'checked';
  1377. });
  1378. if (scannedLots.length === 0) {
  1379. console.log("No scanned items to submit");
  1380. return;
  1381. }
  1382. setIsSubmittingAll(true);
  1383. console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
  1384. try {
  1385. // Submit all items in parallel using Promise.all
  1386. const submitPromises = scannedLots.map(async (lot) => {
  1387. // 检查是否是 noLot 情况
  1388. if (lot.noLot === true) {
  1389. // 使用 handlelotnull 处理无 lot 的情况
  1390. console.log(`Submitting no-lot item: ${lot.itemName || lot.itemCode}`);
  1391. await updateStockOutLineStatus({
  1392. id: lot.stockOutLineId,
  1393. status: 'completed',
  1394. qty: 0
  1395. });
  1396. console.log(` No-lot item completed: ${lot.itemName || lot.itemCode}`);
  1397. const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0;
  1398. const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || '';
  1399. const issueData: PickExecutionIssueData = {
  1400. type: "Do", // Delivery Order type
  1401. pickOrderId: pickOrderId,
  1402. pickOrderCode: pickOrderCode,
  1403. pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format
  1404. pickExecutionDate: dayjs().format('YYYY-MM-DD'),
  1405. pickOrderLineId: lot.pickOrderLineId,
  1406. itemId: lot.itemId,
  1407. itemCode: lot.itemCode || '',
  1408. itemDescription: lot.itemName || '',
  1409. lotId: null, // No lot available
  1410. lotNo: null, // No lot number
  1411. storeLocation: lot.location || '',
  1412. requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
  1413. actualPickQty: 0, // No items picked (no lot available)
  1414. missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
  1415. badItemQty: 0,
  1416. issueRemark: `No lot available for this item. Handled via handlelotnull.`,
  1417. pickerName: session?.user?.name || '',
  1418. };
  1419. const result = await recordPickExecutionIssue(issueData);
  1420. return { success: true, lotNo: lot.lotNo || 'No Lot', isNoLot: true };
  1421. }
  1422. // 正常情况:有 lot 的处理逻辑
  1423. const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
  1424. const currentActualPickQty = lot.actualPickQty || 0;
  1425. const cumulativeQty = currentActualPickQty + submitQty;
  1426. let newStatus = 'partially_completed';
  1427. if (cumulativeQty >= lot.requiredQty) {
  1428. newStatus = 'completed';
  1429. }
  1430. console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`);
  1431. // Update stock out line
  1432. await updateStockOutLineStatus({
  1433. id: lot.stockOutLineId,
  1434. status: newStatus,
  1435. qty: cumulativeQty
  1436. });
  1437. // Update inventory
  1438. if (submitQty > 0 && lot.lotId) {
  1439. await updateInventoryLotLineQuantities({
  1440. inventoryLotLineId: lot.lotId,
  1441. qty: submitQty,
  1442. status: 'available',
  1443. operation: 'pick'
  1444. });
  1445. }
  1446. // Check if pick order is completed
  1447. if (newStatus === 'completed' && lot.pickOrderConsoCode) {
  1448. await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
  1449. }
  1450. return { success: true, lotNo: lot.lotNo };
  1451. });
  1452. // Wait for all submissions to complete
  1453. const results = await Promise.all(submitPromises);
  1454. const successCount = results.filter(r => r.success).length;
  1455. console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
  1456. // Refresh data once after all submissions
  1457. await fetchAllCombinedLotData();
  1458. if (successCount > 0) {
  1459. setQrScanSuccess(true);
  1460. setTimeout(() => {
  1461. setQrScanSuccess(false);
  1462. checkAndAutoAssignNext();
  1463. }, 2000);
  1464. }
  1465. } catch (error) {
  1466. console.error("Error submitting all scanned items:", error);
  1467. setQrScanError(true);
  1468. } finally {
  1469. setIsSubmittingAll(false);
  1470. }
  1471. }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]);
  1472. // Calculate scanned items count
  1473. // Calculate scanned items count (should match handleSubmitAllScanned filter logic)
  1474. const scannedItemsCount = useMemo(() => {
  1475. const filtered = combinedLotData.filter(lot => {
  1476. // 如果是 noLot 情况,只要状态不是 completed 或 rejected,就包含
  1477. if (lot.noLot === true) {
  1478. const status = lot.stockOutLineStatus?.toLowerCase();
  1479. const include = status !== 'completed' && status !== 'rejected';
  1480. if (include) {
  1481. console.log(`📊 Including noLot item: ${lot.itemName || lot.itemCode}, status: ${lot.stockOutLineStatus}`);
  1482. }
  1483. return include;
  1484. }
  1485. // 正常情况:只包含 checked 状态
  1486. return lot.stockOutLineStatus === 'checked';
  1487. });
  1488. // 添加调试日志
  1489. const noLotCount = filtered.filter(l => l.noLot === true).length;
  1490. const normalCount = filtered.filter(l => l.noLot !== true).length;
  1491. console.log(`📊 scannedItemsCount calculation: total=${filtered.length}, noLot=${noLotCount}, normal=${normalCount}`);
  1492. console.log(`📊 All items breakdown:`, {
  1493. total: combinedLotData.length,
  1494. noLot: combinedLotData.filter(l => l.noLot === true).length,
  1495. normal: combinedLotData.filter(l => l.noLot !== true).length
  1496. });
  1497. return filtered.length;
  1498. }, [combinedLotData]);
  1499. // ADD THIS: Auto-stop scan when no data available
  1500. useEffect(() => {
  1501. if (isManualScanning && combinedLotData.length === 0) {
  1502. console.log("⏹️ No data available, auto-stopping QR scan...");
  1503. handleStopScan();
  1504. }
  1505. }, [combinedLotData.length, isManualScanning, handleStopScan]);
  1506. // Cleanup effect
  1507. useEffect(() => {
  1508. return () => {
  1509. // Cleanup when component unmounts (e.g., when switching tabs)
  1510. if (isManualScanning) {
  1511. console.log("🧹 Pick execution component unmounting, stopping QR scanner...");
  1512. stopScan();
  1513. resetScan();
  1514. }
  1515. };
  1516. }, [isManualScanning, stopScan, resetScan]);
  1517. const getStatusMessage = useCallback((lot: any) => {
  1518. switch (lot.stockOutLineStatus?.toLowerCase()) {
  1519. case 'pending':
  1520. return t("Please finish QR code scan and pick order.");
  1521. case 'checked':
  1522. return t("Please submit the pick order.");
  1523. case 'partially_completed':
  1524. return t("Partial quantity submitted. Please submit more or complete the order.");
  1525. case 'completed':
  1526. return t("Pick order completed successfully!");
  1527. case 'rejected':
  1528. return t("Lot has been rejected and marked as unavailable.");
  1529. case 'unavailable':
  1530. return t("This order is insufficient, please pick another lot.");
  1531. default:
  1532. return t("Please finish QR code scan and pick order.");
  1533. }
  1534. }, [t]);
  1535. return (
  1536. <TestQrCodeProvider
  1537. lotData={combinedLotData}
  1538. onScanLot={handleQrCodeSubmit}
  1539. filterActive={(lot) => (
  1540. lot.lotAvailability !== 'rejected' &&
  1541. lot.stockOutLineStatus !== 'rejected' &&
  1542. lot.stockOutLineStatus !== 'completed'
  1543. )}
  1544. >
  1545. <FormProvider {...formProps}>
  1546. <Stack spacing={2}>
  1547. {/* DO Header */}
  1548. {/* 保留:Combined Lot Table - 包含所有 QR 扫描功能 */}
  1549. <Box>
  1550. <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
  1551. <Typography variant="h6" gutterBottom sx={{ mb: 0 }}>
  1552. {t("All Pick Order Lots")}
  1553. </Typography>
  1554. <Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
  1555. {!isManualScanning ? (
  1556. <Button
  1557. variant="contained"
  1558. startIcon={<QrCodeIcon />}
  1559. onClick={handleStartScan}
  1560. color="primary"
  1561. sx={{ minWidth: '120px' }}
  1562. >
  1563. {t("Start QR Scan")}
  1564. </Button>
  1565. ) : (
  1566. <Button
  1567. variant="outlined"
  1568. startIcon={<QrCodeIcon />}
  1569. onClick={handleStopScan}
  1570. color="secondary"
  1571. sx={{ minWidth: '120px' }}
  1572. >
  1573. {t("Stop QR Scan")}
  1574. </Button>
  1575. )}
  1576. {/* 保留:Submit All Scanned Button */}
  1577. <Button
  1578. variant="contained"
  1579. color="success"
  1580. onClick={handleSubmitAllScanned}
  1581. disabled={
  1582. // scannedItemsCount === 0
  1583. !allItemsReady
  1584. || isSubmittingAll}
  1585. sx={{ minWidth: '160px' }}
  1586. >
  1587. {isSubmittingAll ? (
  1588. <>
  1589. <CircularProgress size={16} sx={{ mr: 1, color: 'white' }} />
  1590. {t("Submitting...")}
  1591. </>
  1592. ) : (
  1593. `${t("Submit All Scanned")} (${scannedItemsCount})`
  1594. )}
  1595. </Button>
  1596. </Box>
  1597. </Box>
  1598. {fgPickOrders.length > 0 && (
  1599. <Paper sx={{ p: 2, mb: 2 }}>
  1600. <Stack spacing={2}>
  1601. {/* 基本信息 */}
  1602. <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap">
  1603. <Typography variant="subtitle1">
  1604. <strong>{t("Shop Name")}:</strong> {fgPickOrders[0].shopName || '-'}
  1605. </Typography>
  1606. <Typography variant="subtitle1">
  1607. <strong>{t("Store ID")}:</strong> {fgPickOrders[0].storeId || '-'}
  1608. </Typography>
  1609. <Typography variant="subtitle1">
  1610. <strong>{t("Ticket No.")}:</strong> {fgPickOrders[0].ticketNo || '-'}
  1611. </Typography>
  1612. <Typography variant="subtitle1">
  1613. <strong>{t("Departure Time")}:</strong> {fgPickOrders[0].DepartureTime || '-'}
  1614. </Typography>
  1615. </Stack>
  1616. {/* 改进:三个字段显示在一起,使用表格式布局 */}
  1617. {/* 改进:三个字段合并显示 */}
  1618. {/* 改进:表格式显示每个 pick order */}
  1619. <Box sx={{
  1620. p: 2,
  1621. backgroundColor: '#f5f5f5',
  1622. borderRadius: 1
  1623. }}>
  1624. <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
  1625. {t("Pick Orders Details")}:
  1626. </Typography>
  1627. {(() => {
  1628. const pickOrderCodes = fgPickOrders[0].pickOrderCodes as string[] | string | undefined;
  1629. const deliveryNos = fgPickOrders[0].deliveryNos as string[] | string | undefined;
  1630. const lineCounts = fgPickOrders[0].lineCountsPerPickOrder;
  1631. const pickOrderCodesArray = Array.isArray(pickOrderCodes)
  1632. ? pickOrderCodes
  1633. : (typeof pickOrderCodes === 'string' ? pickOrderCodes.split(', ') : []);
  1634. const deliveryNosArray = Array.isArray(deliveryNos)
  1635. ? deliveryNos
  1636. : (typeof deliveryNos === 'string' ? deliveryNos.split(', ') : []);
  1637. const lineCountsArray = Array.isArray(lineCounts) ? lineCounts : [];
  1638. const maxLength = Math.max(
  1639. pickOrderCodesArray.length,
  1640. deliveryNosArray.length,
  1641. lineCountsArray.length
  1642. );
  1643. if (maxLength === 0) {
  1644. return <Typography variant="body2" color="text.secondary">-</Typography>;
  1645. }
  1646. // 使用与外部基本信息相同的样式
  1647. return Array.from({ length: maxLength }, (_, idx) => (
  1648. <Stack
  1649. key={idx}
  1650. direction="row"
  1651. spacing={4}
  1652. useFlexGap
  1653. flexWrap="wrap"
  1654. sx={{ mb: idx < maxLength - 1 ? 1 : 0 }} // 除了最后一行,都添加底部间距
  1655. >
  1656. <Typography variant="subtitle1">
  1657. <strong>{t("Delivery Order")}:</strong> {deliveryNosArray[idx] || '-'}
  1658. </Typography>
  1659. <Typography variant="subtitle1">
  1660. <strong>{t("Pick Order")}:</strong> {pickOrderCodesArray[idx] || '-'}
  1661. </Typography>
  1662. <Typography variant="subtitle1">
  1663. <strong>{t("Finsihed good items")}:</strong> {lineCountsArray[idx] || '-'}<strong>{t("kinds")}</strong>
  1664. </Typography>
  1665. </Stack>
  1666. ));
  1667. })()}
  1668. </Box>
  1669. </Stack>
  1670. </Paper>
  1671. )}
  1672. <TableContainer component={Paper}>
  1673. <Table>
  1674. <TableHead>
  1675. <TableRow>
  1676. <TableCell>{t("Index")}</TableCell>
  1677. <TableCell>{t("Route")}</TableCell>
  1678. <TableCell>{t("Item Code")}</TableCell>
  1679. <TableCell>{t("Item Name")}</TableCell>
  1680. <TableCell>{t("Lot#")}</TableCell>
  1681. <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
  1682. <TableCell align="center">{t("Scan Result")}</TableCell>
  1683. <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell>
  1684. </TableRow>
  1685. </TableHead>
  1686. <TableBody>
  1687. {paginatedData.length === 0 ? (
  1688. <TableRow>
  1689. <TableCell colSpan={11} align="center">
  1690. <Typography variant="body2" color="text.secondary">
  1691. {t("No data available")}
  1692. </Typography>
  1693. </TableCell>
  1694. </TableRow>
  1695. ) : (
  1696. // 在第 1797-1938 行之间,将整个 map 函数修改为:
  1697. paginatedData.map((lot, index) => {
  1698. // 检查是否是 issue lot
  1699. const isIssueLot = lot.stockOutLineStatus === 'rejected' || !lot.lotNo;
  1700. return (
  1701. <TableRow
  1702. key={`${lot.pickOrderLineId}-${lot.lotId || 'null'}`}
  1703. sx={{
  1704. //backgroundColor: isIssueLot ? '#fff3e0' : 'inherit',
  1705. // opacity: isIssueLot ? 0.6 : 1,
  1706. '& .MuiTableCell-root': {
  1707. //color: isIssueLot ? 'warning.main' : 'inherit'
  1708. }
  1709. }}
  1710. >
  1711. <TableCell>
  1712. <Typography variant="body2" fontWeight="bold">
  1713. {index + 1}
  1714. </Typography>
  1715. </TableCell>
  1716. <TableCell>
  1717. <Typography variant="body2">
  1718. {lot.routerRoute || '-'}
  1719. </Typography>
  1720. </TableCell>
  1721. <TableCell>{lot.itemCode}</TableCell>
  1722. <TableCell>{lot.itemName + '(' + lot.stockUnit + ')'}</TableCell>
  1723. <TableCell>
  1724. <Box>
  1725. <Typography
  1726. sx={{
  1727. // color: isIssueLot ? 'warning.main' : lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit',
  1728. }}
  1729. >
  1730. {lot.lotNo ||
  1731. t('⚠️ No Stock Available')}
  1732. </Typography>
  1733. </Box>
  1734. </TableCell>
  1735. <TableCell align="right">
  1736. {(() => {
  1737. const requiredQty = lot.requiredQty || 0;
  1738. return requiredQty.toLocaleString() + '(' + lot.uomShortDesc + ')';
  1739. })()}
  1740. </TableCell>
  1741. <TableCell align="center">
  1742. {(() => {
  1743. const status = lot.stockOutLineStatus?.toLowerCase();
  1744. const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected';
  1745. const isNoLot = !lot.lotNo;
  1746. // rejected lot:显示红色勾选(已扫描但被拒绝)
  1747. if (isRejected && !isNoLot) {
  1748. return (
  1749. <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  1750. <Checkbox
  1751. checked={true}
  1752. disabled={true}
  1753. readOnly={true}
  1754. size="large"
  1755. sx={{
  1756. color: 'error.main',
  1757. '&.Mui-checked': { color: 'error.main' },
  1758. transform: 'scale(1.3)',
  1759. }}
  1760. />
  1761. </Box>
  1762. );
  1763. }
  1764. // 正常 lot:已扫描(checked/partially_completed/completed)
  1765. if (!isNoLot && status !== 'pending' && status !== 'rejected') {
  1766. return (
  1767. <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  1768. <Checkbox
  1769. checked={true}
  1770. disabled={true}
  1771. readOnly={true}
  1772. size="large"
  1773. sx={{
  1774. color: 'success.main',
  1775. '&.Mui-checked': { color: 'success.main' },
  1776. transform: 'scale(1.3)',
  1777. }}
  1778. />
  1779. </Box>
  1780. );
  1781. }
  1782. // noLot 且已完成/部分完成:显示红色勾选
  1783. if (isNoLot && (status === 'partially_completed' || status === 'completed')) {
  1784. return (
  1785. <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  1786. <Checkbox
  1787. checked={true}
  1788. disabled={true}
  1789. readOnly={true}
  1790. size="large"
  1791. sx={{
  1792. color: 'error.main',
  1793. '&.Mui-checked': { color: 'error.main' },
  1794. transform: 'scale(1.3)',
  1795. }}
  1796. />
  1797. </Box>
  1798. );
  1799. }
  1800. return null;
  1801. })()}
  1802. </TableCell>
  1803. <TableCell align="center">
  1804. <Box sx={{ display: 'flex', justifyContent: 'center' }}>
  1805. {(() => {
  1806. const status = lot.stockOutLineStatus?.toLowerCase();
  1807. const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected';
  1808. const isNoLot = !lot.lotNo;
  1809. // rejected lot:不显示任何按钮
  1810. if (isRejected && !isNoLot) {
  1811. return null;
  1812. }
  1813. // noLot 情况:只显示 Issue 按钮
  1814. if (isNoLot) {
  1815. return (
  1816. <Button
  1817. variant="outlined"
  1818. size="small"
  1819. onClick={() => handlelotnull(lot)}
  1820. disabled={status === 'completed'}
  1821. sx={{
  1822. fontSize: '0.7rem',
  1823. py: 0.5,
  1824. minHeight: '28px',
  1825. minWidth: '60px',
  1826. borderColor: 'warning.main',
  1827. color: 'warning.main'
  1828. }}
  1829. >
  1830. {t("Issue")}
  1831. </Button>
  1832. );
  1833. }
  1834. // 正常 lot:显示 Submit 和 Issue 按钮
  1835. return (
  1836. <Stack direction="row" spacing={1} alignItems="center">
  1837. <Button
  1838. variant="contained"
  1839. onClick={() => {
  1840. const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
  1841. const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
  1842. handlePickQtyChange(lotKey, submitQty);
  1843. handleSubmitPickQtyWithQty(lot, submitQty);
  1844. }}
  1845. disabled={
  1846. lot.lotAvailability === 'expired' ||
  1847. lot.lotAvailability === 'status_unavailable' ||
  1848. lot.lotAvailability === 'rejected' ||
  1849. lot.stockOutLineStatus === 'completed' ||
  1850. lot.stockOutLineStatus === 'pending'
  1851. }
  1852. sx={{ fontSize: '0.75rem', py: 0.5, minHeight: '28px', minWidth: '70px' }}
  1853. >
  1854. {t("Submit")}
  1855. </Button>
  1856. <Button
  1857. variant="outlined"
  1858. size="small"
  1859. onClick={() => handlePickExecutionForm(lot)}
  1860. disabled={
  1861. lot.lotAvailability === 'expired' ||
  1862. lot.lotAvailability === 'status_unavailable' ||
  1863. lot.lotAvailability === 'rejected' ||
  1864. lot.stockOutLineStatus === 'completed' ||
  1865. lot.stockOutLineStatus === 'pending'
  1866. }
  1867. sx={{
  1868. fontSize: '0.7rem',
  1869. py: 0.5,
  1870. minHeight: '28px',
  1871. minWidth: '60px',
  1872. borderColor: 'warning.main',
  1873. color: 'warning.main'
  1874. }}
  1875. title="Report missing or bad items"
  1876. >
  1877. {t("Issue")}
  1878. </Button>
  1879. </Stack>
  1880. );
  1881. })()}
  1882. </Box>
  1883. </TableCell>
  1884. </TableRow>
  1885. );
  1886. })
  1887. )}
  1888. </TableBody>
  1889. </Table>
  1890. </TableContainer>
  1891. <TablePagination
  1892. component="div"
  1893. count={combinedLotData.length}
  1894. page={paginationController.pageNum}
  1895. rowsPerPage={paginationController.pageSize}
  1896. onPageChange={handlePageChange}
  1897. onRowsPerPageChange={handlePageSizeChange}
  1898. rowsPerPageOptions={[10, 25, 50]}
  1899. labelRowsPerPage={t("Rows per page")}
  1900. labelDisplayedRows={({ from, to, count }) =>
  1901. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  1902. }
  1903. />
  1904. </Box>
  1905. </Stack>
  1906. {/* 保留:QR Code Modal */}
  1907. <QrCodeModal
  1908. open={qrModalOpen}
  1909. onClose={() => {
  1910. setQrModalOpen(false);
  1911. setSelectedLotForQr(null);
  1912. stopScan();
  1913. resetScan();
  1914. }}
  1915. lot={selectedLotForQr}
  1916. combinedLotData={combinedLotData}
  1917. onQrCodeSubmit={handleQrCodeSubmitFromModal}
  1918. />
  1919. {/* 保留:Lot Confirmation Modal */}
  1920. {lotConfirmationOpen && expectedLotData && scannedLotData && (
  1921. <LotConfirmationModal
  1922. open={lotConfirmationOpen}
  1923. onClose={() => {
  1924. setLotConfirmationOpen(false);
  1925. setExpectedLotData(null);
  1926. setScannedLotData(null);
  1927. }}
  1928. onConfirm={handleLotConfirmation}
  1929. expectedLot={expectedLotData}
  1930. scannedLot={scannedLotData}
  1931. isLoading={isConfirmingLot}
  1932. />
  1933. )}
  1934. {/* 保留:Good Pick Execution Form Modal */}
  1935. {pickExecutionFormOpen && selectedLotForExecutionForm && (
  1936. <GoodPickExecutionForm
  1937. open={pickExecutionFormOpen}
  1938. onClose={() => {
  1939. setPickExecutionFormOpen(false);
  1940. setSelectedLotForExecutionForm(null);
  1941. }}
  1942. onSubmit={handlePickExecutionFormSubmit}
  1943. selectedLot={selectedLotForExecutionForm}
  1944. selectedPickOrderLine={{
  1945. id: selectedLotForExecutionForm.pickOrderLineId,
  1946. itemId: selectedLotForExecutionForm.itemId,
  1947. itemCode: selectedLotForExecutionForm.itemCode,
  1948. itemName: selectedLotForExecutionForm.itemName,
  1949. pickOrderCode: selectedLotForExecutionForm.pickOrderCode,
  1950. availableQty: selectedLotForExecutionForm.availableQty || 0,
  1951. requiredQty: selectedLotForExecutionForm.requiredQty || 0,
  1952. // uomCode: selectedLotForExecutionForm.uomCode || '',
  1953. uomDesc: selectedLotForExecutionForm.uomDesc || '',
  1954. pickedQty: selectedLotForExecutionForm.actualPickQty || 0,
  1955. uomShortDesc: selectedLotForExecutionForm.uomShortDesc || '',
  1956. suggestedList: []
  1957. }}
  1958. pickOrderId={selectedLotForExecutionForm.pickOrderId}
  1959. pickOrderCreateDate={new Date()}
  1960. />
  1961. )}
  1962. </FormProvider>
  1963. </TestQrCodeProvider>
  1964. );
  1965. };
  1966. export default PickExecution;