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

GoodPickExecutiondetail.tsx 94 KiB

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