"use client";

import {
  Box,
  Button,
  Stack,
  TextField,
  Typography,
  Alert,
  CircularProgress,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  Checkbox,
  TablePagination,
  Modal,
  Chip,
} from "@mui/material";
import dayjs from 'dayjs';
import { normalizeTargetDateInput } from "@/utils/workbenchTargetDate";
import TestQrCodeProvider from "@/components/QrCodeScannerProvider/TestQrCodeProvider";
import { fetchLotDetail } from "@/app/api/inventory/actions";
import { formatDepartureTime } from "@/app/utils/formatUtil";
import React, { useCallback, useEffect, useState, useRef, useMemo, startTransition } from "react";
import { useTranslation } from "react-i18next";
import { usePathname, useRouter } from "next/navigation";
import {
  updateStockOutLineStatus,
  createStockOutLine,
  updateStockOutLine,
  recordPickExecutionIssue,
  fetchFGPickOrders, //  Add this import
  FGPickOrderResponse, 
  stockReponse,
  PickExecutionIssueData,
  checkPickOrderCompletion,
  fetchAllPickOrderLotsHierarchicalWorkbench,
  PickOrderCompletionResponse,
  updateSuggestedLotLineId,
  updateStockOutLineStatusByQRCodeAndLotNo,
  confirmLotSubstitution,
  fetchDoPickOrderDetail,  //  必须添加
  DoPickOrderDetail,  //  必须添加
  batchScan,
  BatchScanRequest,
  BatchScanLineRequest,
} from "@/app/api/pickOrder/actions";
import { workbenchBatchScanPick, workbenchScanPick } from "@/app/api/doworkbench/actions";
import { workbenchScanPickResponseNeedsFullRefresh } from "@/app/api/doworkbench/workbenchScanPickUtils";

import FGPickOrderInfoCard from "@/components/FinishedGoodSearch/FGPickOrderInfoCard";
//import { fetchItem } from "@/app/api/settings/item";
import { updateInventoryLotLineStatus, analyzeQrCode } from "@/app/api/inventory/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";
import {
  FormProvider,
  useForm,
} from "react-hook-form";
import SearchBox, { Criterion } from "@/components/SearchBox";
import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
import QrCodeIcon from '@mui/icons-material/QrCode';
import { useQrCodeScannerContext } from "@/components/QrCodeScannerProvider/QrCodeScannerProvider";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import { fetchStockInLineInfo } from "@/app/api/po/actions";
import GoodPickExecutionForm from "@/components/FinishedGoodSearch/GoodPickExecutionForm";
import WorkbenchLotLabelPrintModal from "@/components/DoWorkbench/WorkbenchLotLabelPrintModal";
import FGPickOrderCard from "@/components/FinishedGoodSearch/FGPickOrderCard";
import LinearProgressWithLabel from "@/components/common/LinearProgressWithLabel";
import ScanStatusAlert from "@/components/common/ScanStatusAlert";

interface Props {
  filterArgs: Record<string, any>;
  onSwitchToRecordTab?: () => void;
  onRefreshReleasedOrderCount?: () => void;
  /** 階層揀貨資料已無（例如訂單已完成／釋放）時通知外層，以便重新檢查是否仍為「已指派」狀態 */
  onWorkbenchHierarchyEmpty?: () => void;
}

type ProcessedStockOutLinesByItemId = Map<number, Set<number>>;

function isLotRowPending(lot: any): boolean {
  const st = String(lot?.stockOutLineStatus ?? "").toLowerCase();
  return (
    st === "pending" ||
    st === "partially_completed" ||
    st === "partially_complete" ||
    st === ""
  );
}

function isStockOutLineAlreadyProcessed(
  processedByItemId: ProcessedStockOutLinesByItemId,
  itemId: number,
  stockOutLineId: number | null | undefined,
): boolean {
  const solId = Number(stockOutLineId);
  if (!Number.isFinite(solId) || solId <= 0) return false;
  return processedByItemId.get(itemId)?.has(solId) ?? false;
}

function markProcessedStockOutLine(
  prev: ProcessedStockOutLinesByItemId,
  itemId: number,
  stockOutLineId: number | null | undefined,
): ProcessedStockOutLinesByItemId {
  const solId = Number(stockOutLineId);
  if (!Number.isFinite(solId) || solId <= 0) return prev;
  const newMap = new Map(prev);
  if (!newMap.has(itemId)) newMap.set(itemId, new Set());
  newMap.get(itemId)!.add(solId);
  return newMap;
}

function parseWorkbenchQrPayload(
  latestQr: string,
): { itemId: number; stockInLineId: number } | null {
  try {
    const qrData = JSON.parse(latestQr);
    const itemId = Number(qrData?.itemId);
    const stockInLineId = Number(qrData?.stockInLineId);
    if (!Number.isFinite(itemId) || !Number.isFinite(stockInLineId)) return null;
    return { itemId, stockInLineId };
  } catch {
    return null;
  }
}

function hasPendingActiveRowForStockInLine(
  indexes: {
    byStockInLineId: Map<number, any[]>;
    activeLotsByItemId: Map<number, any[]>;
  },
  itemId: number,
  stockInLineId: number,
  processedByItemId: ProcessedStockOutLinesByItemId,
): boolean {
  const rows = indexes.byStockInLineId.get(stockInLineId) ?? [];
  const activeSet = new Set(indexes.activeLotsByItemId.get(itemId) ?? []);
  return rows.some(
    (lot) =>
      lot.itemId === itemId &&
      activeSet.has(lot) &&
      isLotRowPending(lot) &&
      !isStockOutLineAlreadyProcessed(processedByItemId, itemId, lot.stockOutLineId),
  );
}

/** Any pending active SOL for this item (e.g. scan different location, same lot no → auto-switch). */
function hasPendingActiveRowForItem(
  indexes: { activeLotsByItemId: Map<number, any[]> },
  itemId: number,
  processedByItemId: ProcessedStockOutLinesByItemId,
): boolean {
  const activeLots = indexes.activeLotsByItemId.get(itemId) ?? [];
  return activeLots.some(
    (lot) =>
      isLotRowPending(lot) &&
      !isStockOutLineAlreadyProcessed(processedByItemId, itemId, lot.stockOutLineId),
  );
}

function findExactActiveMatchForStockInLine(
  stockInLineLots: any[],
  scannedItemId: number,
  activeSuggestedLots: any[],
  processedByItemId: ProcessedStockOutLinesByItemId,
): any | null {
  const activeSet = new Set(activeSuggestedLots);
  const candidates = stockInLineLots.filter(
    (lot) =>
      lot.itemId === scannedItemId &&
      activeSet.has(lot) &&
      !isStockOutLineAlreadyProcessed(
        processedByItemId,
        scannedItemId,
        lot.stockOutLineId,
      ),
  );
  if (candidates.length === 0) return null;
  return candidates.find((lot) => isLotRowPending(lot)) ?? candidates[0];
}

/** 同物料多行时，优先对「有建议批次号」且未完成的出库行做替换 */
function pickExpectedLotForSubstitution(
  activeSuggestedLots: any[],
  processedByItemId?: ProcessedStockOutLinesByItemId,
): any | null {
  if (!activeSuggestedLots?.length) return null;
  const itemId = activeSuggestedLots[0]?.itemId;
  const processed = processedByItemId ?? new Map();
  const unprocessed = activeSuggestedLots.filter(
    (l) =>
      !itemId ||
      !isStockOutLineAlreadyProcessed(processed, itemId, l.stockOutLineId),
  );
  const pool = unprocessed.length > 0 ? unprocessed : activeSuggestedLots;
  const withLotNo = pool.filter((l) => l.lotNo != null && String(l.lotNo).trim() !== "");
  const searchPool = withLotNo.length > 0 ? withLotNo : pool;
  if (searchPool.length === 1) return searchPool[0];
  const pending = searchPool.find((l) => isLotRowPending(l));
  return pending ?? searchPool[0];
}

const ManualLotConfirmationModal: React.FC<{
  open: boolean;
  onClose: () => void;
  onConfirm: (expectedLotNo: string, scannedLotNo: string) => void;
  expectedLot: {
    lotNo: string;
    itemCode: string;
    itemName: string;
  } | null;
  scannedLot: {
    lotNo: string;
    itemCode: string;
    itemName: string;
  } | null;
  isLoading?: boolean;
}> = ({ open, onClose, onConfirm, expectedLot, scannedLot, isLoading = false }) => {
  const { t } = useTranslation("pickOrder");
  const [expectedLotInput, setExpectedLotInput] = useState<string>('');
  const [scannedLotInput, setScannedLotInput] = useState<string>('');
  const [error, setError] = useState<string>('');

  // 当模态框打开时，预填充输入框
  useEffect(() => {
    if (open) {
      setExpectedLotInput(expectedLot?.lotNo || '');
      setScannedLotInput(scannedLot?.lotNo || '');
      setError('');
    }
  }, [open, expectedLot, scannedLot]);

  const handleConfirm = () => {
    if (!expectedLotInput.trim() || !scannedLotInput.trim()) {
      setError(t("Please enter both expected and scanned lot numbers."));
      return;
    }
    
    if (expectedLotInput.trim() === scannedLotInput.trim()) {
      setError(t("Expected and scanned lot numbers cannot be the same."));
      return;
    }

    onConfirm(expectedLotInput.trim(), scannedLotInput.trim());
  };

  return (
    <Modal open={open} onClose={onClose}>
      <Box sx={{
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        bgcolor: 'background.paper',
        p: 3,
        borderRadius: 2,
        minWidth: 500,
      }}>
        <Typography variant="h6" gutterBottom color="warning.main">
          {t("Manual Lot Confirmation")}
        </Typography>
        
        <Box sx={{ mb: 2 }}>
          <Typography variant="body2" gutterBottom>
            <strong>{t("Expected Lot Number")}:</strong>
          </Typography>
          <TextField
            fullWidth
            size="small"
            value={expectedLotInput}
            onChange={(e) => {
              setExpectedLotInput(e.target.value);
              setError('');
            }}
            placeholder={expectedLot?.lotNo || t("Enter expected lot number")}
            sx={{ mb: 2 }}
            error={!!error && !expectedLotInput.trim()}
          />
        </Box>

        <Box sx={{ mb: 2 }}>
          <Typography variant="body2" gutterBottom>
            <strong>{t("Scanned Lot Number")}:</strong>
          </Typography>
          <TextField
            fullWidth
            size="small"
            value={scannedLotInput}
            onChange={(e) => {
              setScannedLotInput(e.target.value);
              setError('');
            }}
            placeholder={scannedLot?.lotNo || t("Enter scanned lot number")}
            sx={{ mb: 2 }}
            error={!!error && !scannedLotInput.trim()}
          />
        </Box>

        {error && (
          <Box sx={{ mb: 2, p: 1, backgroundColor: '#ffebee', borderRadius: 1 }}>
            <Typography variant="body2" color="error">
              {error}
            </Typography>
          </Box>
        )}

        <Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
          <Button onClick={onClose} variant="outlined" disabled={isLoading}>
            {t("Cancel")}
          </Button>
          <Button 
            onClick={handleConfirm} 
            variant="contained" 
            color="warning"
            disabled={isLoading || !expectedLotInput.trim() || !scannedLotInput.trim()}
          >
            {isLoading ? t("Processing...") : t("Confirm")}
          </Button>
        </Box>
      </Box>
    </Modal>
  );
};

/** 過期批號（未換有效批前）：與 noLot 類似——單筆/批量預設提交量為 0，除非 Issue 改數 */
function isLotAvailabilityExpired(lot: any): boolean {
  return String(lot?.lotAvailability || "").toLowerCase() === "expired";
}

/** inventory_lot_line.status = unavailable（API 可能用 lotAvailability 或 lotStatus） */
function isInventoryLotLineUnavailable(lot: any): boolean {
  if (!lot) return false;
  const solSt = String(lot.stockOutLineStatus || "").toLowerCase();
  if (solSt === "completed" || solSt === "partially_completed") return false;
  if (lot.lotAvailability === "status_unavailable") return true;
  return String(lot.lotStatus || "").toLowerCase() === "unavailable";
}

/** 提貨台「列印標籤」彈窗頂部：依目前表格列判斷可提貨／已用畢／已過期等 */
function isWorkbenchSourceLotExpired(lot: any): boolean {
  if (!lot) return false;
  if (isLotAvailabilityExpired(lot)) return true;
  if (String(lot.lotAvailability || "").toLowerCase() === "expired") return true;
  if (lot.expiryDate) {
    const d = dayjs(lot.expiryDate).startOf("day");
    if (d.isValid() && d.isBefore(dayjs().startOf("day"))) return true;
  }
  return false;
}

function getWorkbenchSourceLotStatusSummary(lot: any): {
  severity: "success" | "warning" | "error";
  text: string;
} {
  if (!lot) {
    return { severity: "warning", text: "無法判斷此批號狀態" };
  }
  if (isWorkbenchSourceLotExpired(lot)) {
    return { severity: "error", text: "此批號狀態：已過期" };
  }
  const solSt = String(lot.stockOutLineStatus || "").toLowerCase();
  if (solSt === "rejected") {
    return { severity: "warning", text: "此出庫行：已拒絕，請改掃其他批號" };
  }
  if (solSt === "completed" || solSt === "partially_completed") {
    return { severity: "warning", text: "此出庫行：已完成，無需再提貨" };
  }
  /**
   * 無批次列：後端仍標 insufficient_stock，語意是「尚無可出庫批號」而非「已用畢」。
   * 與表格紅字「請檢查周圍是否有 QR 碼…」一致。
   */
  const isNoLotRow =
    lot.noLot === true ||
    !lot.lotNo ||
    String(lot.lotNo || "").trim() === "";
  if (isNoLotRow) {
    return {
      severity: "warning",
      text: "尚未綁定批號／無可用庫存列：請掃描週邊入庫或轉倉 QR",
    };
  }
  const av = String(lot.lotAvailability || "").toLowerCase();
  if (av === "insufficient_stock") {
    return { severity: "warning", text: "此批號狀態：已用畢（無剩餘庫存）" };
  }
  const avail = Number(lot.availableQty);
  if (lot.lotNo && Number.isFinite(avail) && avail <= 0) {
    return { severity: "warning", text: "此批號狀態：已用畢（可用量為 0）" };
  }
  if (isInventoryLotLineUnavailable(lot)) {
    return {
      severity: "warning",
      text: "此批號狀態：庫存不可用（未上架或行狀態不可用）",
    };
  }
  return { severity: "success", text: "此批號狀態：可提貨" };
}

type PickOrderT = (key: string, options?: Record<string, unknown>) => string;

function translateWorkbenchRejectMessage(raw: string, t: PickOrderT): string {
  const msg = raw.trim();
  if (!msg) return msg;

  const expiredMatch = msg.match(/^Lot is expired \(expiry=([^)]+)\)\.?$/i);
  if (expiredMatch) {
    return t("Lot is expired (expiry={{expiry}})", {
      expiry: expiredMatch[1],
    });
  }

  return t(msg);
}

/**
 * 顯示後端拒絕原因：優先 workbench scan API 的 message（暫存於 scanRejectBySolId），
 * 其次階層 API 若帶 stockOutLineRejectMessage，最後依 rejected + lotAvailability 推斷（與後端語意對齊）。
 */
function buildLotRejectDisplayMessage(
  lot: any,
  scanRejectBySolId: Record<number, string>,
  t: PickOrderT,
): string | undefined {
  const solId = Number(lot.stockOutLineId) || 0;
  const fromScan = solId > 0 ? scanRejectBySolId[solId]?.trim() : "";
  if (fromScan) return translateWorkbenchRejectMessage(fromScan, t);

  const fromApi =
    typeof lot.stockOutLineRejectMessage === "string"
      ? lot.stockOutLineRejectMessage.trim()
      : "";
  if (fromApi) return translateWorkbenchRejectMessage(fromApi, t);

  const st = String(lot.stockOutLineStatus || "").toLowerCase();
  const av = String(lot.lotAvailability || "").toLowerCase();
  const isRejected = st === "rejected" || av === "rejected";
  if (!isRejected) return undefined;

  if (isLotAvailabilityExpired(lot) || av === "expired") {
    return t("Rejected: lot expired or no longer valid.");
  }
  if (av === "insufficient_stock") {
    return t("Rejected: no remaining quantity / empty lot.");
  }
  if (isInventoryLotLineUnavailable(lot) || av === "status_unavailable") {
    return t("Rejected: lot unavailable or not yet putaway.");
  }
  return t("Pick was rejected. Please scan another lot or check stock.");
}

/** Issue「改數」未寫入 SOL，刷新/換頁後需靠 session 還原，否則 Qty will submit 會回到 req */
const FG_ISSUE_PICKED_KEY = (doPickOrderId: number) =>
  `fpsms-fg-issuePickedQty:${doPickOrderId}`;

function loadIssuePickedMap(doPickOrderId: number): Record<number, number> {
  if (typeof window === "undefined" || !doPickOrderId) return {};
  try {
    const raw = sessionStorage.getItem(FG_ISSUE_PICKED_KEY(doPickOrderId));
    if (!raw) return {};
    const parsed = JSON.parse(raw) as Record<string, number>;
    const out: Record<number, number> = {};
    Object.entries(parsed).forEach(([k, v]) => {
      const n = Number(v);
      if (!Number.isNaN(n)) out[Number(k)] = n;
    });
    return out;
  } catch {
    return {};
  }
}

function saveIssuePickedMap(doPickOrderId: number, map: Record<number, number>) {
  if (typeof window === "undefined" || !doPickOrderId) return;
  try {
    sessionStorage.setItem(FG_ISSUE_PICKED_KEY(doPickOrderId), JSON.stringify(map));
  } catch {
    // quota / private mode
  }
}

const WorkbenchGoodPickExecutionDetail: React.FC<Props> = ({
  filterArgs,
  onSwitchToRecordTab,
  onRefreshReleasedOrderCount,
  onWorkbenchHierarchyEmpty,
}) => {
  const workbenchMode = true;
  const { t } = useTranslation("pickOrder");
  const router = useRouter();
  const pathname = usePathname();
  const { data: session } = useSession() as { data: SessionWithTokens | null };
  const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null);
const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null);
const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
  const currentUserId = session?.id ? parseInt(session.id) : undefined;
  const [allLotsCompleted, setAllLotsCompleted] = useState(false);
  const [combinedLotData, setCombinedLotData] = useState<any[]>([]);
  const [combinedDataLoading, setCombinedDataLoading] = useState(false);
  const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]);
  // issue form 里填的 actualPickQty（用于 batch submit 只提交实际拣到数量，而不是补拣到 required）
  const [issuePickedQtyBySolId, setIssuePickedQtyBySolId] = useState<Record<number, number>>({});
  const applyLocalStockOutLineUpdate = useCallback((
    stockOutLineId: number,
    status: string,
    actualPickQty?: number
  ) => {
    setCombinedLotData(prev => prev.map((lot) => {
      if (Number(lot.stockOutLineId) !== Number(stockOutLineId)) return lot;
      return {
        ...lot,
        stockOutLineStatus: status,
        ...(typeof actualPickQty === "number"
          ? { actualPickQty, stockOutLineQty: actualPickQty }
          : {}),
      };
    }));
  }, []);
  // 防止重复点击（Submit / Just Completed / Issue）
  const [actionBusyBySolId, setActionBusyBySolId] = useState<Record<number, boolean>>({});
  
  const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  
  const [qrScanInput, setQrScanInput] = useState<string>('');
  const [qrScanError, setQrScanError] = useState<boolean>(false);
  const [qrScanErrorMsg, setQrScanErrorMsg] = useState<string>('');
  const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
  /** Workbench scanPick 等非 SUCCESS 時後端 message，按 stockOutLineId 顯示在批號欄 */
  const [scanRejectMessageBySolId, setScanRejectMessageBySolId] = useState<Record<number, string>>({});
  const rememberWorkbenchScanReject = useCallback((stockOutLineId: number, message: string | undefined | null) => {
    const id = Number(stockOutLineId);
    const m = String(message ?? "").trim();
    if (!id || !m) return;
    setScanRejectMessageBySolId((prev) => ({ ...prev, [id]: m }));
  }, []);
  const clearWorkbenchScanReject = useCallback((stockOutLineId: number) => {
    const id = Number(stockOutLineId);
    if (!id) return;
    setScanRejectMessageBySolId((prev) => {
      if (!(id in prev)) return prev;
      const next = { ...prev };
      delete next[id];
      return next;
    });
  }, []);
  const [manualLotConfirmationOpen, setManualLotConfirmationOpen] = useState(false);
  /** 使用者覆寫「可提交數量」：鍵不存在＝未編輯；鍵存在且為 0＝明確要送 0 */
  const [pickQtyData, setPickQtyData] = useState<Record<string, number>>({});
  /** Workbench row: false = qty TextField read-only; Edit toggles true to type qty (replaces opening issue form). */
  const [workbenchSubmitQtyFieldEnabledByLotKey, setWorkbenchSubmitQtyFieldEnabledByLotKey] = useState<
    Record<string, boolean>
  >({});

  const resolveSingleSubmitQty = useCallback(
    (lot: any) => {
      const required = Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0);
      const solId = Number(lot.stockOutLineId) || 0;
      const lotId = lot.lotId;
      const lotKey =
        Number.isFinite(solId) && solId > 0 ? `sol:${solId}` : `${lot.pickOrderLineId}-${lotId}`;

      const issuePicked = solId > 0 ? issuePickedQtyBySolId[solId] : undefined;
      if (issuePicked !== undefined && !Number.isNaN(Number(issuePicked))) {
        return Number(issuePicked);
      }

      const st = String(lot.stockOutLineStatus || "").toLowerCase();
      if (
        st === "completed" ||
        st === "partially_completed" ||
        st === "partially_complete"
      ) {
        return Number(lot.stockOutLineQty ?? lot.actualPickQty ?? 0);
      }

      if (Object.prototype.hasOwnProperty.call(pickQtyData, lotKey)) {
        const fromPick = pickQtyData[lotKey];
        if (!Number.isNaN(Number(fromPick))) {
          return Number(fromPick);
        }
      }
      return required;
    },
    [issuePickedQtyBySolId, pickQtyData],
  );

  const getWorkbenchQtyLotKey = useCallback((lot: any) => {
    const solId = Number(lot?.stockOutLineId) || 0;
    if (Number.isFinite(solId) && solId > 0) return `sol:${solId}`;
    return `${lot?.pickOrderLineId}-${lot?.lotId}`;
  }, []);

  /** Use table row from combinedLotData so pickQtyData key pickOrderLineId-lotId matches the row user edited (expectedLot from QR index may differ lotId). */
  const workbenchScanPickQtyFromLot = useCallback(
    (lot: any) => {
      const solId = Number(lot?.stockOutLineId);
      const sourceLot =
        Number.isFinite(solId) && solId > 0
          ? combinedLotData.find((r) => Number(r.stockOutLineId) === solId) ?? lot
          : lot;
      const lotKey = getWorkbenchQtyLotKey(sourceLot);
      const hasExplicitPickOverride = Object.prototype.hasOwnProperty.call(
        pickQtyData,
        lotKey,
      );
      const n = Number(resolveSingleSubmitQty(sourceLot));
      // Explicit 0 short submit must send qty=0; implicit 0 must omit qty (backend implicit fill path).
      if (hasExplicitPickOverride && Number.isFinite(n) && n === 0) return { qty: 0 } as const;
      if (!Number.isFinite(n) || n <= 0) return {};
      return { qty: n } as const;
    },
    [resolveSingleSubmitQty, combinedLotData, pickQtyData, getWorkbenchQtyLotKey],
  );

  const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});

  const [paginationController, setPaginationController] = useState({
    pageNum: 0,
    pageSize: -1,
  });

  const [usernameList, setUsernameList] = useState<NameList[]>([]);

  const initializationRef = useRef(false);
  const autoAssignRef = useRef(false);
  /** 曾成功載入過 workbench 階層資料；避免「列表仍有單但階層暫空」時對外層重複觸發造成迴圈 */
  const workbenchHierarchicalReadyRef = useRef(false);
  /** 最後一筆 workbench 票號（階層清空或完成後仍可用於導向完成紀錄） */
  const lastWorkbenchTicketNoRef = useRef<string | null>(null);
  const lastWorkbenchTargetDateRef = useRef<string | null>(null);
  /** 同一筆揀貨完成後只導向「完成紀錄」分頁一次 */
  const workbenchFinishNavigateDoneRef = useRef(false);

  const formProps = useForm();
  const errors = formProps.formState.errors;

  //  Add GoodPickExecutionForm states
  const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
  const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
  const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);

  const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false);
  const isExtraTicket = useMemo(() => {
    const ticketNo = String(fgPickOrders?.[0]?.ticketNo ?? "").trim().toUpperCase();
    return ticketNo.startsWith("TI-E-");
  }, [fgPickOrders]);

  const lotFloorPrefixFilter = useMemo(() => {
    const storeId = String(fgPickOrders?.[0]?.storeId ?? "")
      .trim()
      .toUpperCase()
      .replace(/\s/g, "");
    const floorKey = storeId.replace(/\//g, "");
    return floorKey ? `${floorKey}-` : "";
  }, [fgPickOrders]);

  const defaultLabelPrinterName = useMemo(() => {
    const storeId = String(fgPickOrders?.[0]?.storeId ?? "")
      .trim()
      .toUpperCase()
      .replace(/\s/g, "");
    const floorKey = storeId.replace(/\//g, "");
    if (floorKey === "2F") return "Label機 2F A+B";
    if (floorKey === "4F") return "Label機 4F 乾貨 C, D";
    return undefined;
  }, [fgPickOrders]);

  const [workbenchLotLabelModalOpen, setWorkbenchLotLabelModalOpen] =
    useState(false);
  const [workbenchLotLabelInitialPayload, setWorkbenchLotLabelInitialPayload] =
    useState<{ itemId: number; stockInLineId: number } | null>(null);
  const [workbenchLotLabelReminderText, setWorkbenchLotLabelReminderText] =
    useState<string | null>(null);
  const [workbenchLotLabelContextLot, setWorkbenchLotLabelContextLot] =
    useState<any | null>(null);

  useEffect(() => {
    if (!qrScanSuccess || !workbenchLotLabelModalOpen) return;
    setWorkbenchLotLabelModalOpen(false);
    setWorkbenchLotLabelInitialPayload(null);
    setWorkbenchLotLabelReminderText(null);
    setWorkbenchLotLabelContextLot(null);
  }, [qrScanSuccess, workbenchLotLabelModalOpen]);

    //  Add these missing state variables after line 352
    const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
    // Track processed stock-out lines per item (allow same physical lot QR for next SOL)
    const [processedQrCombinations, setProcessedQrCombinations] =
      useState<ProcessedStockOutLinesByItemId>(new Map());
    const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
    const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
    const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false);
    const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false);
    
    // Cache for fetchStockInLineInfo API calls to avoid redundant requests
    const stockInLineInfoCache = useRef<Map<number, { lotNo: string | null; timestamp: number }>>(new Map());
    const CACHE_TTL = 60000; // 60 seconds cache TTL
    const abortControllerRef = useRef<AbortController | null>(null);
    const qrProcessingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
    
    // Use refs for processed QR tracking to avoid useEffect dependency issues and delays
    const processedQrCodesRef = useRef<Set<string>>(new Set());
    const lastProcessedQrRef = useRef<string>('');
    /** Last qrValues.length handled; new pick on same QR requires a new scanner event (length increase). */
    const lastProcessedQrScanCountRef = useRef(0);
    const qrPickInFlightRef = useRef(false);
    
    // Store callbacks in refs to avoid useEffect dependency issues
    const processOutsideQrCodeRef = useRef<
      ((latestQr: string, qrScanCountAtInvoke?: number) => Promise<void>) | null
    >(null);
    const resetScanRef = useRef<(() => void) | null>(null);
    
 
  
  //  Handle QR code button click
  const handleQrCodeClick = (pickOrderId: number) => {
    console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
    // TODO: Implement QR code functionality
  };
  const progress = useMemo(() => {
    // 只给进度条统计可正常拣货的 lot（排除 unavailable / expired）
    const progressLots = combinedLotData.filter(
      (lot) => !isLotAvailabilityExpired(lot) && !isInventoryLotLineUnavailable(lot)
    );
  
    if (progressLots.length === 0) {
      return { completed: 0, total: 0 };
    }
  
    const completedCount = progressLots.filter((lot) => {
      const status = String(lot.stockOutLineStatus || "").toLowerCase();
      return status !== "pending";
    }).length;
  
    return {
      completed: completedCount,
      total: progressLots.length,
    };
  }, [combinedLotData]);

  
  // Cached version of fetchStockInLineInfo to avoid redundant API calls
  const fetchStockInLineInfoCached = useCallback(async (stockInLineId: number): Promise<{ lotNo: string | null }> => {
    const now = Date.now();
    const cached = stockInLineInfoCache.current.get(stockInLineId);
    
    // Return cached value if still valid
    if (cached && (now - cached.timestamp) < CACHE_TTL) {
      console.log(`✅ [CACHE HIT] Using cached stockInLineInfo for ${stockInLineId}`);
      return { lotNo: cached.lotNo };
    }
    
    // Cancel previous request if exists
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
    
    // Create new abort controller for this request
    const abortController = new AbortController();
    abortControllerRef.current = abortController;
    
    try {
      console.log(`  [CACHE MISS] Fetching stockInLineInfo for ${stockInLineId}`);
      const stockInLineInfo = await fetchStockInLineInfo(stockInLineId);
      
      // Store in cache
      stockInLineInfoCache.current.set(stockInLineId, {
        lotNo: stockInLineInfo.lotNo || null,
        timestamp: now
      });
      
      // Limit cache size to prevent memory leaks
      if (stockInLineInfoCache.current.size > 100) {
        const firstKey = stockInLineInfoCache.current.keys().next().value;
        if (firstKey !== undefined) {
          stockInLineInfoCache.current.delete(firstKey);
        }
      }
      
      return { lotNo: stockInLineInfo.lotNo || null };
    } catch (error: any) {
      if (error.name === 'AbortError') {
        console.log(`  [CACHE] Request aborted for ${stockInLineId}`);
        throw error;
      }
      console.error(`❌ [CACHE] Error fetching stockInLineInfo for ${stockInLineId}:`, error);
      throw error;
    }
  }, []);

  const checkAllLotsCompleted = useCallback((lotData: any[]) => {
    if (lotData.length === 0) {
      setAllLotsCompleted(false);
      return false;
    }

    // Filter out rejected lots
    const nonRejectedLots = lotData.filter(lot => 
      lot.lotAvailability !== 'rejected' && 
      lot.stockOutLineStatus !== 'rejected'
    );

    if (nonRejectedLots.length === 0) {
      setAllLotsCompleted(false);
      return false;
    }

    // Check if all non-rejected lots are completed
    const allCompleted = nonRejectedLots.every(lot => 
      lot.stockOutLineStatus === 'completed'
    );

    setAllLotsCompleted(allCompleted);
    return allCompleted;
  }, []);
// 在 fetchAllCombinedLotData 函数中（约 446-684 行）

const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => {
  setCombinedDataLoading(true);
  try {
    const userIdToUse = userId || currentUserId;
    
    console.log("  fetchAllCombinedLotData called with userId:", userIdToUse);
    
    if (!userIdToUse) {
      console.warn("⚠️ No userId available, skipping API call");
      setCombinedLotData([]);
      setOriginalCombinedData([]);
      setAllLotsCompleted(false);
      setIssuePickedQtyBySolId({});
      return;
    }
    
    //  获取新结构的层级数据
    const hierarchicalData = await fetchAllPickOrderLotsHierarchicalWorkbench(userIdToUse);
    console.log(" Hierarchical data (new structure):", hierarchicalData);
    
    //  检查数据结构
    if (!hierarchicalData?.fgInfo || !hierarchicalData.pickOrders?.length) {
      console.warn("⚠️ No FG info or pick orders found");
      const hadWorkbenchData = workbenchHierarchicalReadyRef.current;
      const ticketForRedirect =
        String(hierarchicalData?.fgInfo?.ticketNo ?? "").trim() ||
        lastWorkbenchTicketNoRef.current ||
        "";
      const targetDateForRedirect =
        normalizeTargetDateInput(hierarchicalData?.pickOrders?.[0]?.targetDate) ||
        lastWorkbenchTargetDateRef.current ||
        "";
      setCombinedLotData([]);
      setOriginalCombinedData([]);
      setAllLotsCompleted(false);
      setIssuePickedQtyBySolId({});
      setFgPickOrders([]);
      workbenchHierarchicalReadyRef.current = false;
      if (hadWorkbenchData) {
        onWorkbenchHierarchyEmpty?.();
      }
      if (
        hadWorkbenchData &&
        ticketForRedirect &&
        !workbenchFinishNavigateDoneRef.current
      ) {
        workbenchFinishNavigateDoneRef.current = true;
        const redirectParams = new URLSearchParams();
        redirectParams.set("tab", "2");
        redirectParams.set("ticketNo", ticketForRedirect);
        if (targetDateForRedirect) {
          redirectParams.set("targetDate", targetDateForRedirect);
        }
        router.replace(`${pathname}?${redirectParams.toString()}`, { scroll: false });
      }
      return;
    }
    
    //  使用合并后的 pick order 对象（现在只有一个对象，包含所有数据）
    const mergedPickOrder = hierarchicalData.pickOrders[0];
    
    //  设置 FG info 到 fgPickOrders（用于显示 FG 信息卡片）
   // 修改第 478-509 行的 fgOrder 构建逻辑：

   const fgOrder: FGPickOrderResponse = {
    doPickOrderId: hierarchicalData.fgInfo.doPickOrderId,
    ticketNo: hierarchicalData.fgInfo.ticketNo,
    storeId: hierarchicalData.fgInfo.storeId,
    shopCode: hierarchicalData.fgInfo.shopCode,
    shopName: hierarchicalData.fgInfo.shopName,
    truckLanceCode: hierarchicalData.fgInfo.truckLanceCode,
    DepartureTime: hierarchicalData.fgInfo.departureTime,
    shopAddress: "",
    pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
  
    // 兼容字段（注意 consoCodes 是数组）
    pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0,
    pickOrderConsoCode: Array.isArray(mergedPickOrder.consoCodes)
      ? mergedPickOrder.consoCodes[0] || ""
      : "",
    pickOrderTargetDate: mergedPickOrder.targetDate || "",
    pickOrderStatus: mergedPickOrder.status || "",
    deliveryOrderId: mergedPickOrder.doOrderIds?.[0] || 0,
    deliveryNo: mergedPickOrder.deliveryOrderCodes?.[0] || "",
    deliveryDate: "",
    shopId: 0,
    shopPoNo: "",
    numberOfCartons: mergedPickOrder.pickOrderLines?.length || 0,
    qrCodeData: hierarchicalData.fgInfo.doPickOrderId,
  
    // 多个 pick orders 信息：全部保留为数组
    numberOfPickOrders: mergedPickOrder.pickOrderIds?.length || 0,
    pickOrderIds: mergedPickOrder.pickOrderIds || [],
    pickOrderCodes: Array.isArray(mergedPickOrder.pickOrderCodes)
      ? mergedPickOrder.pickOrderCodes
      : [],
    deliveryOrderIds: mergedPickOrder.doOrderIds || [],
    deliveryNos: Array.isArray(mergedPickOrder.deliveryOrderCodes)
      ? mergedPickOrder.deliveryOrderCodes
      : [],
    lineCountsPerPickOrder: Array.isArray(mergedPickOrder.lineCountsPerPickOrder)
      ? mergedPickOrder.lineCountsPerPickOrder
      : [],
  };
  
  setFgPickOrders([fgOrder]);
  workbenchHierarchicalReadyRef.current = true;
  lastWorkbenchTicketNoRef.current =
    String(fgOrder.ticketNo ?? "").trim() || null;
  lastWorkbenchTargetDateRef.current =
    normalizeTargetDateInput(mergedPickOrder.targetDate);
  workbenchFinishNavigateDoneRef.current = false;
  console.log("  DEBUG fgOrder.lineCountsPerPickOrder:", fgOrder.lineCountsPerPickOrder);
  console.log("  DEBUG fgOrder.pickOrderCodes:", fgOrder.pickOrderCodes);
  console.log("  DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
  
  // 直接使用合并后的 pickOrderLines
  console.log("🎯 Displaying merged pick order lines");
  
  // 将层级数据转换为平铺格式（用于表格显示）
  const flatLotData: any[] = [];

  // 2/F 與後端 store_id 一致時需按 itemOrder；避免 API 未走 2F 分支時畫面仍亂序
  const doFloorKey = String(hierarchicalData.fgInfo.storeId ?? '')
    .trim()
    .toUpperCase()
    .replace(/\//g, '')
    .replace(/\s/g, '');
  const pickOrderLinesForDisplay =
    doFloorKey === '2F'
      ? [...(mergedPickOrder.pickOrderLines || [])].sort((a: any, b: any) => {
          const ao = a.itemOrder != null ? Number(a.itemOrder) : 999999;
          const bo = b.itemOrder != null ? Number(b.itemOrder) : 999999;
          if (ao !== bo) return ao - bo;
          return (Number(a.id) || 0) - (Number(b.id) || 0);
        })
      : mergedPickOrder.pickOrderLines || [];

  pickOrderLinesForDisplay.forEach((line: any) => {
    // 用来记录这一行已经通过 lots 出现过的 lotId / stockOutLineId
    const lotIdSet = new Set<number>();
    const stockOutLineIdSet = new Set<number>();

    // ✅ lots：按 SOL 优先去重（其次 lotId），保持 requiredQty 原值，不在前端累计
    if (line.lots && line.lots.length > 0) {
      const lotMap = new Map<string, any>();
  
      line.lots.forEach((lot: any, lotIdx: number) => {
        const lotId = lot.id;
        const solId = Number(lot?.stockOutLineId);
        const lotKey =
          Number.isFinite(solId) && solId > 0
            ? `sol:${solId}`
            : lotId != null
              ? `lot:${lotId}`
              : `idx:${lotIdx}`;
        if (!lotMap.has(lotKey)) {
          lotMap.set(lotKey, { ...lot });
        }
      });
  
      lotMap.forEach((lot: any) => {
        if (lot.id != null) {
          lotIdSet.add(lot.id);
        }
        const solId = Number(lot?.stockOutLineId);
        if (Number.isFinite(solId) && solId > 0) {
          stockOutLineIdSet.add(solId);
        }
  
        flatLotData.push({
          pickOrderConsoCode: Array.isArray(mergedPickOrder.consoCodes)
            ? mergedPickOrder.consoCodes[0] || ""
            : "",
          pickOrderTargetDate: mergedPickOrder.targetDate,
          pickOrderStatus: mergedPickOrder.status,
          pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0,
          pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
          pickOrderLineId: line.id,
          pickOrderLineRequiredQty: line.requiredQty,
          pickOrderLineStatus: line.status,
  
          itemId: line.item.id,
          itemCode: line.item.code,
          itemName: line.item.name,
          uomDesc: line.item.uomDesc,
          uomShortDesc: line.item.uomShortDesc,
  
          lotId: lot.id,
          lotNo: lot.lotNo,
          expiryDate: lot.expiryDate,
          location: lot.location,
          stockUnit: lot.stockUnit,
          availableQty: lot.availableQty,
          requiredQty: lot.requiredQty,
          actualPickQty: lot.actualPickQty,
          inQty: lot.inQty,
          outQty: lot.outQty,
          holdQty: lot.holdQty,
          lotStatus: lot.lotStatus,
          lotAvailability: lot.lotAvailability,
          processingStatus: lot.processingStatus,
          suggestedPickLotId: lot.suggestedPickLotId,
          stockOutLineId: lot.stockOutLineId,
          stockOutLineStatus: lot.stockOutLineStatus,
          stockOutLineQty: lot.stockOutLineQty,
          stockOutLineRejectMessage: lot.stockOutLineRejectMessage ?? null,
          stockInLineId: lot.stockInLineId,
          routerId: lot.router?.id,
          routerIndex: lot.router?.index,
          routerRoute: lot.router?.route,
          routerArea: lot.router?.area,
          noLot: false,
        });
      });
    }
  
    // ✅ stockouts：只保留“真正无批次 / 未在 lots 出现过”的行
    if (line.stockouts && line.stockouts.length > 0) {
      line.stockouts.forEach((stockout: any) => {
        const hasLot = stockout.lotId != null;
        const hasSolId = stockout.id != null;
        const lotAlreadyInLots =
          hasLot && lotIdSet.has(stockout.lotId as number);
        const solAlreadyInLots =
          hasSolId && stockOutLineIdSet.has(Number(stockout.id));
  
        // 有批次 & 已经通过 lots 渲染过 → 跳过，避免一条变两行
        if (!stockout.noLot && (solAlreadyInLots || lotAlreadyInLots)) {
          return;
        }
  
        const effectiveStockoutRequiredQty =
          stockout?.requiredQty ?? stockout?.suggestedPickLotQty ?? null;
        const fallbackRouteFromLine =
          line?.lots?.[0]?.router?.route ?? line?.lots?.[0]?.location ?? null;

        // 只渲染：
        // - noLot === true 的 Null stock 行
        // - 或者 lotId 在 lots 中不存在的特殊情况
        flatLotData.push({
          pickOrderConsoCode: Array.isArray(mergedPickOrder.consoCodes)
            ? mergedPickOrder.consoCodes[0] || ""
            : "",
          pickOrderTargetDate: mergedPickOrder.targetDate,
          pickOrderStatus: mergedPickOrder.status,
          pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0,
          pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
          pickOrderLineId: line.id,
          pickOrderLineRequiredQty: line.requiredQty,
          pickOrderLineStatus: line.status,
  
          itemId: line.item.id,
          itemCode: line.item.code,
          itemName: line.item.name,
          uomDesc: line.item.uomDesc,
          uomShortDesc: line.item.uomShortDesc,
  
          lotId: stockout.lotId || null,
          lotNo: stockout.lotNo || null,
          expiryDate: null,
          location: stockout.location || null,
          stockUnit: line.item.uomDesc,
          availableQty: stockout.availableQty || 0,
          // Workbench stockout row required qty should come from backend stockout payload
          // (linked by stockOutLineId to suggested_pick_lot), not inferred from line gap.
          requiredQty: effectiveStockoutRequiredQty,
          actualPickQty: stockout.qty || 0,
          inQty: 0,
          outQty: 0,
          holdQty: 0,
          lotStatus: stockout.noLot ? "unavailable" : "available",
          lotAvailability: stockout.noLot ? "insufficient_stock" : "available",
          processingStatus: stockout.status || "pending",
          suggestedPickLotId: stockout.suggestedPickLotId ?? null,
          stockOutLineId: stockout.id || null,
          stockOutLineStatus: stockout.status || null,
          stockOutLineQty: stockout.qty || 0,
          stockOutLineRejectMessage: stockout.rejectMessage ?? stockout.rejectReason ?? null,
  
          routerId: null,
          routerIndex: stockout.noLot ? 999999 : null,
          routerRoute: fallbackRouteFromLine,
          routerArea: fallbackRouteFromLine,
          noLot: !!stockout.noLot,
        });
      });
    }
  });
  
  console.log(" Transformed flat lot data:", flatLotData);
  console.log("  Total items (including null stock):", flatLotData.length);
  
  setCombinedLotData(flatLotData);
  setOriginalCombinedData(flatLotData);
  const doPid = hierarchicalData.fgInfo?.doPickOrderId;
  if (doPid) {
    setIssuePickedQtyBySolId(loadIssuePickedMap(doPid));
  }
  checkAllLotsCompleted(flatLotData);
  } catch (error) {
    console.error("  Error fetching combined lot data:", error);
    setCombinedLotData([]);
    setOriginalCombinedData([]);
    setAllLotsCompleted(false);
    setIssuePickedQtyBySolId({});
  } finally {
    setCombinedDataLoading(false);
  }
}, [
  currentUserId,
  checkAllLotsCompleted,
  onWorkbenchHierarchyEmpty,
  router,
  pathname,
]); //   移除 selectedPickOrderId 依赖

  /** After workbench scan-pick (incl. split → new stock_out_line), reload hierarchical rows. */
  const refreshWorkbenchAfterScanPick = useCallback(async () => {
    setIsRefreshingData(true);
    try {
      await fetchAllCombinedLotData();
    } finally {
      setIsRefreshingData(false);
    }
  }, [fetchAllCombinedLotData]);

  const openWorkbenchLotLabelModalForLot = useCallback(
    (lot: any, reminderText?: string | null) => {
      const itemId = Number(lot?.itemId);
      const stockInLineId = Number(lot?.stockInLineId);
      const solId = Number(lot?.stockOutLineId);
      if (!Number.isFinite(itemId) || itemId <= 0 || !Number.isFinite(solId) || solId <= 0) {
        return;
      }
      setWorkbenchLotLabelContextLot(lot);
      if (Number.isFinite(stockInLineId) && stockInLineId > 0) {
        setWorkbenchLotLabelInitialPayload({ itemId, stockInLineId });
      } else {
        setWorkbenchLotLabelInitialPayload(null);
      }
      setWorkbenchLotLabelReminderText(reminderText ?? null);
      // Clear latched success so the lot-label modal effect cannot instantly re-close on open.
      setQrScanSuccess(false);
      setWorkbenchLotLabelModalOpen(true);
    },
    [],
  );

  const shouldOpenWorkbenchLotLabelModalForFailure = useCallback(
    (code?: string | null, msg?: string | null): boolean => {
      const normalizedCode = String(code || "").toUpperCase();
      if (
        normalizedCode.includes("UNAVAILABLE") ||
        normalizedCode.includes("EXPIRED")
      ) {
        return true;
      }

      const normalizedMsg = String(msg || "").toLowerCase();
      if (!normalizedMsg) return false;
      return (
        normalizedMsg.includes("unavailable") ||
        normalizedMsg.includes("not available") ||
        normalizedMsg.includes("expired") ||
        normalizedMsg.includes("不可用") ||
        normalizedMsg.includes("無法使用") ||
        normalizedMsg.includes("过期")
      );
    },
    [],
  );

  const handleWorkbenchLotLabelScanPick = useCallback(
    async ({
      inventoryLotLineId,
      lotNo,
      qty,
    }: {
      inventoryLotLineId: number;
      lotNo: string;
      qty?: number;
    }) => {
      const lot = workbenchLotLabelContextLot;
      if (!lot?.stockOutLineId) {
        throw new Error(t("Missing stock out line for this row."));
      }
      const qtyPayload = Number.isFinite(Number(qty))
        ? { qty: Number(qty) }
        : workbenchScanPickQtyFromLot(lot);
      const res = await workbenchScanPick({
        stockOutLineId: Number(lot.stockOutLineId),
        lotNo: String(lotNo || "").trim(),
        inventoryLotLineId,
        storeId: fgPickOrders?.[0]?.storeId ?? null,
        userId: currentUserId ?? 1,
        ...qtyPayload,
      });
      if (res.code !== "SUCCESS") {
        const errMsg =
          (res as { message?: string })?.message ||
          t("Workbench scan-pick failed.");
        rememberWorkbenchScanReject(Number(lot.stockOutLineId), errMsg);
        throw new Error(errMsg);
      }
      clearWorkbenchScanReject(Number(lot.stockOutLineId));
      await refreshWorkbenchAfterScanPick();
      setWorkbenchLotLabelModalOpen(false);
      setWorkbenchLotLabelContextLot(null);
      setWorkbenchLotLabelInitialPayload(null);
      setWorkbenchLotLabelReminderText(null);
    },
    [
      workbenchLotLabelContextLot,
      fgPickOrders,
      currentUserId,
      workbenchScanPickQtyFromLot,
      refreshWorkbenchAfterScanPick,
      rememberWorkbenchScanReject,
      clearWorkbenchScanReject,
      t,
    ],
  );

  const workbenchLotLabelStatusBanner = useMemo(() => {
    if (!workbenchLotLabelModalOpen || !workbenchLotLabelContextLot) {
      return {
        text: undefined as string | undefined,
        severity: undefined as "success" | "warning" | "error" | undefined,
      };
    }
    const s = getWorkbenchSourceLotStatusSummary(workbenchLotLabelContextLot);
    return { text: s.text, severity: s.severity };
  }, [workbenchLotLabelModalOpen, workbenchLotLabelContextLot]);

  const workbenchLotLabelSubmitQty = useMemo(() => {
    if (!workbenchLotLabelContextLot) return 0;
    return Number(resolveSingleSubmitQty(workbenchLotLabelContextLot)) || 0;
  }, [workbenchLotLabelContextLot, resolveSingleSubmitQty]);

  const handleWorkbenchLotLabelSubmitQtyChange = useCallback(
    (qty: number) => {
      if (!workbenchLotLabelContextLot) return;
      const lotKey = getWorkbenchQtyLotKey(workbenchLotLabelContextLot);
      setPickQtyData((prev) => ({ ...prev, [lotKey]: qty }));
    },
    [workbenchLotLabelContextLot, getWorkbenchQtyLotKey],
  );

  /**
   * 與「掃描結果」欄綠勾一致：有批號且 SOL 非 pending、非 rejected 時視為已掃／已提貨，停用掃碼提貨。
   */
  const workbenchLotLabelScanPickDisabled = useMemo(() => {
    if (!workbenchLotLabelModalOpen || !workbenchLotLabelContextLot) return true;
    const lot = workbenchLotLabelContextLot;
    const status = String(lot.stockOutLineStatus || "").toLowerCase();
    const isNoLot = !lot.lotNo || String(lot.lotNo).trim() === "";
    if (isNoLot) return false;
    if (status === "pending" || status === "rejected") return false;
    return true;
  }, [workbenchLotLabelModalOpen, workbenchLotLabelContextLot]);

  // Manual lot substitution is part of the legacy "checked then batch submit" flow.
  // Workbench now uses scan-pick (immediate posting) + qty=0 zero-complete; disable this path to avoid writing `checked`.
  const handleManualLotConfirmation = useCallback(async () => {
    alert(t("Workbench does not support manual lot substitution in this flow. Please use scan-pick / Just Completed."));
  }, [t]);
  useEffect(() => {
    if (combinedLotData.length > 0) {
      checkAllLotsCompleted(combinedLotData);
    }
  }, [combinedLotData, checkAllLotsCompleted]);

  //  Add function to expose completion status to parent
  const getCompletionStatus = useCallback(() => {
    return allLotsCompleted;
  }, [allLotsCompleted]);

  //  Expose completion status to parent component
  useEffect(() => {
    // Dispatch custom event with completion status
    const event = new CustomEvent('pickOrderCompletionStatus', {
      detail: { 
        allLotsCompleted,
        tabIndex: 2 //  DO workbench「Finished Good Record (mine)」分頁索引
      }
    });
    window.dispatchEvent(event);
  }, [allLotsCompleted]);

  const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
    console.log(` Processing QR Code for lot: ${lotNo}`);
    
    //  检查 lotNo 是否为 null 或 undefined（包括字符串 "null"）
    if (!lotNo || lotNo === 'null' || lotNo.trim() === '') {
      console.error("  Invalid lotNo: null, undefined, or empty");
      return;
    }
    
    //  Use current data without refreshing to avoid infinite loop
    const currentLotData = combinedLotData;
    console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo));
    
    //  修复：在比较前确保 lotNo 不为 null
    const lotNoLower = lotNo.toLowerCase();
    const matchingLots = currentLotData.filter(lot => {
      if (!lot.lotNo) return false; //  跳过 null lotNo
      return lot.lotNo === lotNo || lot.lotNo.toLowerCase() === lotNoLower;
    });
    
    if (matchingLots.length === 0) {
      console.error(`  Lot not found: ${lotNo}`);
      setQrScanError(true);
      setQrScanSuccess(false);
      const availableLotNos = currentLotData.map(lot => lot.lotNo).join(', ');
      console.log(`  QR Code "${lotNo}" does not match any expected lots. Available lots: ${availableLotNos}`);
      return; 
    }

    const hasExpiredLot = matchingLots.some(
      (lot: any) => String(lot.lotAvailability || '').toLowerCase() === 'expired'
    );
    if (hasExpiredLot) {
      console.warn(`⚠️ [QR PROCESS] Scanned lot ${lotNo} is expired`);
      setQrScanError(true);
      setQrScanSuccess(false);
      return;
    }
    
    // Legacy QR flow marked SOL as `checked` (normal version). Workbench uses scan-pick (immediate posting).
    setQrScanError(true);
    setQrScanSuccess(false);
    setQrScanErrorMsg(
      t("Workbench uses scan-pick. Please use Just Completed / submit via scan-pick instead of checked status."),
    );
  }, [combinedLotData]);
  const handleFastQrScan = useCallback(async (lotNo: string) => {
    const startTime = performance.now();
    console.log(`  [FAST SCAN START] Lot: ${lotNo}`);
    console.log(`  Start time: ${new Date().toISOString()}`);
    
    // 从 combinedLotData 中找到对应的 lot
    const findStartTime = performance.now();
    const matchingLot = combinedLotData.find(lot => 
      lot.lotNo && lot.lotNo === lotNo
    );
    const findTime = performance.now() - findStartTime;
    console.log(`  Find lot time: ${findTime.toFixed(2)}ms`);
  
    if (!matchingLot || !matchingLot.stockOutLineId) {
      const totalTime = performance.now() - startTime;
      console.warn(`⚠️ Fast scan: Lot ${lotNo} not found or no stockOutLineId`);
      console.log(`  Total time: ${totalTime.toFixed(2)}ms`);
      return;
    }
  
    try {
      const apiStartTime = performance.now();
      const res = await workbenchScanPick({
        stockOutLineId: matchingLot.stockOutLineId,
        lotNo,
        ...(typeof matchingLot.stockInLineId === "number" &&
        Number.isFinite(matchingLot.stockInLineId) &&
        matchingLot.stockInLineId > 0
          ? { stockInLineId: matchingLot.stockInLineId }
          : {}),
        ...workbenchScanPickQtyFromLot(matchingLot),
        storeId: fgPickOrders?.[0]?.storeId ?? null,
        userId: currentUserId ?? 1,
      });
      const apiTime = performance.now() - apiStartTime;
      console.log(`  API call time: ${apiTime.toFixed(2)}ms`);

      const ok = res.code === "SUCCESS";
      if (ok) {
        clearWorkbenchScanReject(Number(matchingLot.stockOutLineId));
        const entity = res.entity as any;
        const nextStatus = String(entity?.status ?? "completed").toLowerCase();
        const nextQty = entity?.qty != null ? Number(entity.qty) : undefined;
        const patchRow = (prev: any[]) =>
          prev.map((row) => {
            if (
              row.stockOutLineId === matchingLot.stockOutLineId &&
              row.pickOrderLineId === matchingLot.pickOrderLineId
            ) {
              return {
                ...row,
                stockOutLineStatus: nextStatus,
                stockOutLineQty: nextQty ?? row.stockOutLineQty,
                actualPickQty: nextQty ?? row.actualPickQty,
              };
            }
            return row;
          });
        setCombinedLotData(patchRow);
        setOriginalCombinedData(patchRow);
  
        const totalTime = performance.now() - startTime;
        console.log(`✅ [FAST SCAN END] Lot: ${lotNo}`);
        console.log(`  Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
        console.log(`  End time: ${new Date().toISOString()}`);
      } else {
        const totalTime = performance.now() - startTime;
        console.warn(`⚠️ Fast scan failed for ${lotNo}:`, res.code);
        console.log(`  Total time: ${totalTime.toFixed(2)}ms`);
        if (workbenchMode && matchingLot.stockOutLineId != null) {
          rememberWorkbenchScanReject(
            Number(matchingLot.stockOutLineId),
            (res as { message?: string })?.message,
          );
        }
      }
    } catch (error) {
      const totalTime = performance.now() - startTime;
      console.error(`  Fast scan error for ${lotNo}:`, error);
      console.log(`  Total time: ${totalTime.toFixed(2)}ms`);
    }
  }, [combinedLotData, updateStockOutLineStatusByQRCodeAndLotNo, workbenchMode, currentUserId, clearWorkbenchScanReject, rememberWorkbenchScanReject, refreshWorkbenchAfterScanPick, workbenchScanPickQtyFromLot]);
  // Enhanced lotDataIndexes with cached active lots for better performance
  const lotDataIndexes = useMemo(() => {
    const indexStartTime = performance.now();
    console.log(`  [PERF] lotDataIndexes calculation START, data length: ${combinedLotData.length}`);
    
    const byItemId = new Map<number, any[]>();
    const byItemCode = new Map<string, any[]>();
    const byLotId = new Map<number, any>();
    const byLotNo = new Map<string, any[]>();
    const byStockInLineId = new Map<number, any[]>();
    // Cache active lots separately to avoid filtering on every scan
    const activeLotsByItemId = new Map<number, any[]>();
    const rejectedStatuses = new Set(['rejected']);
    
    // ✅ Use for loop instead of forEach for better performance on tablets
    for (let i = 0; i < combinedLotData.length; i++) {
      const lot = combinedLotData[i];
      const solStatus = String(lot.stockOutLineStatus || "").toLowerCase();
      const lotAvailability = String(lot.lotAvailability || "").toLowerCase();
      const processingStatus = String(lot.processingStatus || "").toLowerCase();
      const isUnavailable = isInventoryLotLineUnavailable(lot);
      const isExpired = isLotAvailabilityExpired(lot);
      const isRejected =
        rejectedStatuses.has(lotAvailability) ||
        rejectedStatuses.has(solStatus) ||
        rejectedStatuses.has(processingStatus);
      const isEnded = solStatus === "checked" || solStatus === "completed";
      const isPartially = solStatus === "partially_completed" || solStatus === "partially_complete";
      const isPending = solStatus === "pending" || solStatus === "";
      const isActive = !isRejected && !isUnavailable && !isExpired && !isEnded && (isPending || isPartially);
      
      if (lot.itemId) {
        if (!byItemId.has(lot.itemId)) {
          byItemId.set(lot.itemId, []);
          activeLotsByItemId.set(lot.itemId, []);
        }
        byItemId.get(lot.itemId)!.push(lot);
        if (isActive) {
          activeLotsByItemId.get(lot.itemId)!.push(lot);
        }
      }
      
      if (lot.itemCode) {
        if (!byItemCode.has(lot.itemCode)) {
          byItemCode.set(lot.itemCode, []);
        }
        byItemCode.get(lot.itemCode)!.push(lot);
      }
      
      if (lot.lotId) {
        byLotId.set(lot.lotId, lot);
      }
      
      if (lot.lotNo) {
        if (!byLotNo.has(lot.lotNo)) {
          byLotNo.set(lot.lotNo, []);
        }
        byLotNo.get(lot.lotNo)!.push(lot);
      }
      
      if (lot.stockInLineId) {
        if (!byStockInLineId.has(lot.stockInLineId)) {
          byStockInLineId.set(lot.stockInLineId, []);
        }
        byStockInLineId.get(lot.stockInLineId)!.push(lot);
      }
    }
    
    const indexTime = performance.now() - indexStartTime;
    if (indexTime > 10) {
      console.log(`  [PERF] lotDataIndexes calculation END: ${indexTime.toFixed(2)}ms (${(indexTime / 1000).toFixed(3)}s)`);
    }
    
    return { byItemId, byItemCode, byLotId, byLotNo, byStockInLineId, activeLotsByItemId };
  }, [combinedLotData.length, combinedLotData]);
  
  // Store resetScan in ref for immediate access (update on every render)
  resetScanRef.current = resetScan;
  
  const processOutsideQrCode = useCallback(async (latestQr: string, qrScanCountAtInvoke?: number) => {
    if (qrPickInFlightRef.current) {
      console.log("  [SKIP] QR pick already in flight");
      return;
    }
    qrPickInFlightRef.current = true;

    const totalStartTime = performance.now();
    console.log(`  [PROCESS OUTSIDE QR START] QR: ${latestQr.substring(0, 50)}...`);
    console.log(`  Start time: ${new Date().toISOString()}`);

    const indexAccessStart = performance.now();
    const indexes = lotDataIndexes;
    const indexAccessTime = performance.now() - indexAccessStart;
    console.log(`  [PERF] Index access time: ${indexAccessTime.toFixed(2)}ms`);

    const recordHandledQrScanCount = (scanCount: number | undefined) => {
      if (scanCount != null && scanCount > lastProcessedQrScanCountRef.current) {
        lastProcessedQrScanCountRef.current = scanCount;
      }
    };

    try {
    // 1) Parse JSON safely (parse once, reuse)
    const parseStartTime = performance.now();
    let qrData: any = null;
    let parseTime = 0;
    try {
      qrData = JSON.parse(latestQr);
      parseTime = performance.now() - parseStartTime;
      console.log(`  [PERF] JSON parse time: ${parseTime.toFixed(2)}ms`);
    } catch {
      console.log("QR content is not JSON; skipping lotNo direct submit to avoid false matches.");
      startTransition(() => {
        setQrScanError(true);
        setQrScanSuccess(false);
      });
      return;
    }
  
    try {
      const validationStartTime = performance.now();
      if (!(qrData?.stockInLineId && qrData?.itemId)) {
        console.log("QR JSON missing required fields (itemId, stockInLineId).");
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
        });
        return;
      }
      const validationTime = performance.now() - validationStartTime;
      console.log(`  [PERF] Validation time: ${validationTime.toFixed(2)}ms`);
  
      const scannedItemId = qrData.itemId;
      const scannedStockInLineId = qrData.stockInLineId;

      const hasPendingOnScannedSil = hasPendingActiveRowForStockInLine(
        indexes,
        scannedItemId,
        scannedStockInLineId,
        processedQrCombinations,
      );
      const hasPendingOnItem = hasPendingActiveRowForItem(
        indexes,
        scannedItemId,
        processedQrCombinations,
      );
      if (!hasPendingOnScannedSil && !hasPendingOnItem) {
        console.log(
          `  [SKIP] No pending stock-out line left for itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId}`,
        );
        return;
      }
      
      // ✅ OPTIMIZATION: Use cached active lots directly (no filtering needed)
      const lookupStartTime = performance.now();
      const activeSuggestedLots = indexes.activeLotsByItemId.get(scannedItemId) || [];
      // ✅ Also get all lots for this item (not just active ones) to allow lot switching even when all lots are rejected
      const allLotsForItem = indexes.byItemId.get(scannedItemId) || [];
      const lookupTime = performance.now() - lookupStartTime;
      console.log(`  [PERF] Index lookup time: ${lookupTime.toFixed(2)}ms, found ${activeSuggestedLots.length} active lots, ${allLotsForItem.length} total lots`);
      
      // ✅ Check if scanned lot is rejected BEFORE checking activeSuggestedLots
      // This allows users to scan other lots even when all suggested lots are rejected
      const stockInLineRows = indexes.byStockInLineId.get(scannedStockInLineId) ?? [];
      const scannedLot =
        findExactActiveMatchForStockInLine(
          stockInLineRows,
          scannedItemId,
          activeSuggestedLots,
          processedQrCombinations,
        ) ??
        allLotsForItem.find((lot: any) => lot.stockInLineId === scannedStockInLineId);
      
      if (scannedLot) {
        const isRejected = 
          scannedLot.stockOutLineStatus?.toLowerCase() === 'rejected' ||
          scannedLot.lotAvailability === 'rejected';
        const isUnavailable = isInventoryLotLineUnavailable(scannedLot);
        
        if (isRejected) {
          console.warn(`⚠️ [QR PROCESS] Scanned lot (stockInLineId: ${scannedStockInLineId}, lotNo: ${scannedLot.lotNo}) is rejected`);
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg(
              `此批次（${scannedLot.lotNo || scannedStockInLineId}）已被拒绝，无法使用。请扫描其他批次。`
            );
          });
          if (scannedLot?.stockOutLineId != null) {
            const nextProcessed = markProcessedStockOutLine(
              processedQrCombinations,
              scannedItemId,
              scannedLot.stockOutLineId,
            );
            setProcessedQrCombinations(nextProcessed);
          }
          return;
        }

        if (isUnavailable) {
          console.warn(`⚠️ [QR PROCESS] Scanned lot (stockInLineId: ${scannedStockInLineId}, lotNo: ${scannedLot.lotNo}) is unavailable; opening lot-label modal`);
          startTransition(() => {
            setQrScanError(false);
            setQrScanSuccess(false);
          });
          openWorkbenchLotLabelModalForLot(
            scannedLot,
            t("This lot is not available, please scan another lot."),
          );
          return;
        }

        const isExpired =
          String(scannedLot.lotAvailability || '').toLowerCase() === 'expired';
        if (isExpired) {
          console.warn(`⚠️ [QR PROCESS] Scanned lot (stockInLineId: ${scannedStockInLineId}, lotNo: ${scannedLot.lotNo}) is expired; opening lot-label modal`);
          startTransition(() => {
            setQrScanError(false);
            setQrScanSuccess(false);
          });
          openWorkbenchLotLabelModalForLot(
            scannedLot,
            `Lot is expired (expiry=${scannedLot.expiryDate || "-"})`,
          );
          return;
        }
      }
      
      // ✅ If no active suggested lots, auto-switch to scanned lot (no modal)
      if (activeSuggestedLots.length === 0) {
        // Check if there are any lots for this item (even if all are rejected)
        if (allLotsForItem.length === 0) {
          console.error("No lots found for this item");
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg("当前订单中没有此物品的批次信息");
          });
          return;
        }
        
        console.log(`⚠️ [QR PROCESS] No active suggested lots, auto-switching to scanned lot.`);
        
        // Find a rejected lot as expected lot (the one that was rejected)
        const rejectedLot = allLotsForItem.find((lot: any) => 
          lot.stockOutLineStatus?.toLowerCase() === 'rejected' ||
          lot.lotAvailability === 'rejected' ||
          isInventoryLotLineUnavailable(lot)
        );
        const expectedLot =
          rejectedLot ||
          pickExpectedLotForSubstitution(
            allLotsForItem.filter(
              (l: any) => l.lotNo != null && String(l.lotNo).trim() !== ""
            )
          ) ||
          allLotsForItem[0];
        
        let scannedLotNo: string | null = scannedLot?.lotNo || null;
        if (!scannedLotNo) {
          try {
            const info = await fetchStockInLineInfoCached(scannedStockInLineId);
            scannedLotNo = info?.lotNo || null;
          } catch (e) {
            console.warn("Failed to fetch lotNo for stockInLineId:", scannedStockInLineId, e);
          }
        }
        if (!scannedLotNo) {
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg(
              t("Cannot resolve lot number from QR. Please rescan or use manual confirmation.")
            );
          });
          return;
        }

        if (!workbenchMode) {
          const substitutionResult = await confirmLotSubstitution({
            pickOrderLineId: expectedLot.pickOrderLineId,
            stockOutLineId: expectedLot.stockOutLineId,
            originalSuggestedPickLotId: expectedLot.suggestedPickLotId,
            newInventoryLotNo: "",
            newStockInLineId: scannedStockInLineId,
          });
          const substitutionCode = (substitutionResult as any)?.code;
          const switchedToUnavailable =
            substitutionCode === "SUCCESS_UNAVAILABLE" || substitutionCode === "BOUND_UNAVAILABLE";
          if (!substitutionResult || (substitutionCode !== "SUCCESS" && !switchedToUnavailable)) {
            const errMsg =
              substitutionResult?.message ||
              t("Lot switch failed; pick line was not updated.");
            startTransition(() => {
              setQrScanError(true);
              setQrScanSuccess(false);
              setQrScanErrorMsg(errMsg);
            });
            if (expectedLot.stockOutLineId != null) {
              rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), errMsg);
            }
            return;
          }
        }

        const res = await workbenchScanPick({
          stockOutLineId: expectedLot.stockOutLineId,
          lotNo: scannedLotNo,
          ...(typeof scannedStockInLineId === "number" &&
          Number.isFinite(scannedStockInLineId) &&
          scannedStockInLineId > 0
            ? { stockInLineId: scannedStockInLineId }
            : {}),
          ...workbenchScanPickQtyFromLot(expectedLot),
          storeId: fgPickOrders?.[0]?.storeId ?? null,
          userId: currentUserId ?? 1,
        });
        const ok = res.code === "SUCCESS";
        if (!ok) {
          const failMsg =
            (res as { message?: string })?.message ||
            t("Workbench scan-pick failed.");
          if (
            shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
            expectedLot
          ) {
            openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
            return;
          }
          if (workbenchMode && expectedLot.stockOutLineId != null) {
            rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), failMsg);
          }
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg(failMsg);
          });
          return;
        }

        clearWorkbenchScanReject(Number(expectedLot.stockOutLineId));
        const entity = res.entity as any;
        const nextStatus = String(entity?.status ?? "completed").toLowerCase();
        const nextQty = entity?.qty != null ? Number(entity.qty) : undefined;

        startTransition(() => {
          setQrScanError(false);
          setQrScanSuccess(true);
          setCombinedLotData((prev) =>
            prev.map((lot) => {
              if (
                lot.stockOutLineId === expectedLot.stockOutLineId &&
                lot.pickOrderLineId === expectedLot.pickOrderLineId
              ) {
                return {
                  ...lot,
                  lotNo: scannedLotNo,
                  stockOutLineStatus: nextStatus,
                  stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                };
              }
              return lot;
            }),
          );
          setOriginalCombinedData((prev) =>
            prev.map((lot) => {
              if (
                lot.stockOutLineId === expectedLot.stockOutLineId &&
                lot.pickOrderLineId === expectedLot.pickOrderLineId
              ) {
                return {
                  ...lot,
                  lotNo: scannedLotNo,
                  stockOutLineStatus: nextStatus,
                  stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                };
              }
              return lot;
            }),
          );
        });

        const nextProcessedNoActive = markProcessedStockOutLine(
          processedQrCombinations,
          scannedItemId,
          expectedLot.stockOutLineId,
        );
        setProcessedQrCombinations(nextProcessedNoActive);
        recordHandledQrScanCount(qrScanCountAtInvoke);

        if (workbenchMode) {
          await refreshWorkbenchAfterScanPick();
        }

        return;
      }
      
      // ✅ OPTIMIZATION: Direct Map lookup for stockInLineId match (O(1))
      const matchStartTime = performance.now();
      const stockInLineLots = indexes.byStockInLineId.get(scannedStockInLineId) || [];
      const exactMatch = findExactActiveMatchForStockInLine(
        stockInLineLots,
        scannedItemId,
        activeSuggestedLots,
        processedQrCombinations,
      );
      const matchTime = performance.now() - matchStartTime;
      console.log(`  [PERF] Find exact match time: ${matchTime.toFixed(2)}ms, found: ${exactMatch ? 'yes' : 'no'}`);
      
      // ✅ Check if scanned lot exists in allLotsForItem but not in activeSuggestedLots
      // This handles the case where Lot A is rejected and user scans Lot B
      // Also handle case where scanned lot is not in allLotsForItem (scannedLot is undefined)
      if (!exactMatch) {
        const expectedLot =
          pickExpectedLotForSubstitution(activeSuggestedLots, processedQrCombinations) ||
          allLotsForItem[0];
        if (expectedLot) {
          const shouldAutoSwitch =
            !scannedLot || (scannedLot.stockInLineId !== expectedLot.stockInLineId);
          if (shouldAutoSwitch) {
            console.log(
              `⚠️ [QR PROCESS] Auto-switching (scanned lot ${scannedLot?.lotNo || 'not in data'} is not in active suggested lots)`,
            );

            let scannedLotNo: string | null = scannedLot?.lotNo || null;
            if (!scannedLotNo) {
              try {
                const info = await fetchStockInLineInfoCached(scannedStockInLineId);
                scannedLotNo = info?.lotNo || null;
              } catch (e) {
                console.warn("Failed to fetch lotNo for stockInLineId:", scannedStockInLineId, e);
              }
            }
            if (!scannedLotNo) {
              startTransition(() => {
                setQrScanError(true);
                setQrScanSuccess(false);
                setQrScanErrorMsg(
                  t("Cannot resolve lot number from QR. Please rescan or use manual confirmation.")
                );
              });
              return;
            }

            if (!workbenchMode) {
              const substitutionResult = await confirmLotSubstitution({
                pickOrderLineId: expectedLot.pickOrderLineId,
                stockOutLineId: expectedLot.stockOutLineId,
                originalSuggestedPickLotId: expectedLot.suggestedPickLotId,
                newInventoryLotNo: "",
                newStockInLineId: scannedStockInLineId,
              });
              const substitutionCode = (substitutionResult as any)?.code;
              const switchedToUnavailable =
                substitutionCode === "SUCCESS_UNAVAILABLE" || substitutionCode === "BOUND_UNAVAILABLE";
              if (!substitutionResult || (substitutionCode !== "SUCCESS" && !switchedToUnavailable)) {
                const errMsg =
                  substitutionResult?.message ||
                  t("Lot switch failed; pick line was not updated.");
                startTransition(() => {
                  setQrScanError(true);
                  setQrScanSuccess(false);
                  setQrScanErrorMsg(errMsg);
                });
                if (expectedLot.stockOutLineId != null) {
                  rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), errMsg);
                }
                return;
              }
            }

            const res = await workbenchScanPick({
              stockOutLineId: expectedLot.stockOutLineId,
              lotNo: scannedLotNo,
              ...(typeof scannedStockInLineId === "number" &&
              Number.isFinite(scannedStockInLineId) &&
              scannedStockInLineId > 0
                ? { stockInLineId: scannedStockInLineId }
                : {}),
              ...workbenchScanPickQtyFromLot(expectedLot),
              storeId: fgPickOrders?.[0]?.storeId ?? null,
              userId: currentUserId ?? 1,
            });
            const ok = res.code === "SUCCESS";
            if (!ok) {
              const failMsg =
                (res as { message?: string })?.message ||
                t("Workbench scan-pick failed.");
              if (
                shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
                expectedLot
              ) {
                openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
                return;
              }
              if (workbenchMode && expectedLot.stockOutLineId != null) {
                rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), failMsg);
              }
              startTransition(() => {
                setQrScanError(true);
                setQrScanSuccess(false);
                setQrScanErrorMsg(failMsg);
              });
              return;
            }

            clearWorkbenchScanReject(Number(expectedLot.stockOutLineId));
            const entity = res.entity as any;
            const nextStatus = String(entity?.status ?? "completed").toLowerCase();
            const nextQty = entity?.qty != null ? Number(entity.qty) : undefined;

            startTransition(() => {
              setQrScanError(false);
              setQrScanSuccess(true);
              setCombinedLotData((prev) =>
                prev.map((lot) => {
                  if (
                    lot.stockOutLineId === expectedLot.stockOutLineId &&
                    lot.pickOrderLineId === expectedLot.pickOrderLineId
                  ) {
                    return {
                      ...lot,
                      lotNo: scannedLotNo,
                      stockOutLineStatus: nextStatus,
                      stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                    };
                  }
                  return lot;
                }),
              );
              setOriginalCombinedData((prev) =>
                prev.map((lot) => {
                  if (
                    lot.stockOutLineId === expectedLot.stockOutLineId &&
                    lot.pickOrderLineId === expectedLot.pickOrderLineId
                  ) {
                    return {
                      ...lot,
                      lotNo: scannedLotNo,
                      stockOutLineStatus: nextStatus,
                      stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                    };
                  }
                  return lot;
                }),
              );
            });

            const nextProcessedAuto = markProcessedStockOutLine(
              processedQrCombinations,
              scannedItemId,
              expectedLot.stockOutLineId,
            );
            setProcessedQrCombinations(nextProcessedAuto);
            recordHandledQrScanCount(qrScanCountAtInvoke);
            if (workbenchMode) {
              await refreshWorkbenchAfterScanPick();
            }
            return;
          }
        }
      }
      
      if (exactMatch) {
        // ✅ Case 1: stockInLineId 匹配 - 直接处理，不需要确认
        console.log(`✅ Exact stockInLineId match found for lot: ${exactMatch.lotNo}`);
        
        if (!exactMatch.stockOutLineId) {
          console.warn("No stockOutLineId on exactMatch, cannot update status by QR.");
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
          });
          return;
        }
  
        try {
          const apiStartTime = performance.now();
          console.log(
            workbenchMode
              ? `  [API CALL START] workbenchScanPick`
              : `  [API CALL START] Calling updateStockOutLineStatusByQRCodeAndLotNo`
          );
          console.log(`  [API CALL] API start time: ${new Date().toISOString()}`);
          const res = await workbenchScanPick({
            stockOutLineId: exactMatch.stockOutLineId,
            lotNo: exactMatch.lotNo,
            ...(typeof scannedStockInLineId === "number" &&
            Number.isFinite(scannedStockInLineId) &&
            scannedStockInLineId > 0
              ? { stockInLineId: scannedStockInLineId }
              : typeof exactMatch.stockInLineId === "number" &&
                  Number.isFinite(exactMatch.stockInLineId) &&
                  exactMatch.stockInLineId > 0
                ? { stockInLineId: exactMatch.stockInLineId }
                : {}),
            ...workbenchScanPickQtyFromLot(exactMatch),
            storeId: fgPickOrders?.[0]?.storeId ?? null,
            userId: currentUserId ?? 1,
          });
          const apiTime = performance.now() - apiStartTime;
          console.log(`  [API CALL END] Total API time: ${apiTime.toFixed(2)}ms (${(apiTime / 1000).toFixed(3)}s)`);
          console.log(`  [API CALL] API end time: ${new Date().toISOString()}`);

          const ok = res.code === "SUCCESS";
          if (ok) {
            clearWorkbenchScanReject(Number(exactMatch.stockOutLineId));
            const entity = res.entity as any;
            const nextStatus = String(entity?.status ?? "completed").toLowerCase();
            const nextQty =
              entity?.qty != null ? Number(entity.qty) : undefined;

            // ✅ Batch state updates using startTransition
            const stateUpdateStartTime = performance.now();
            startTransition(() => {
              setQrScanError(false);
              setQrScanSuccess(true);

              setCombinedLotData(prev => prev.map(lot => {
                if (lot.stockOutLineId === exactMatch.stockOutLineId &&
                    lot.pickOrderLineId === exactMatch.pickOrderLineId) {
                  return {
                    ...lot,
                    stockOutLineStatus: nextStatus,
                    stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                  };
                }
                return lot;
              }));

              setOriginalCombinedData(prev => prev.map(lot => {
                if (lot.stockOutLineId === exactMatch.stockOutLineId &&
                    lot.pickOrderLineId === exactMatch.pickOrderLineId) {
                  return {
                    ...lot,
                    stockOutLineStatus: nextStatus,
                    stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                  };
                }
                return lot;
              }));
            });
            const stateUpdateTime = performance.now() - stateUpdateStartTime;
            console.log(`  [PERF] State update time: ${stateUpdateTime.toFixed(2)}ms`);
            
            // Mark this combination as processed
            const markProcessedStartTime = performance.now();
            const nextProcessedExact = markProcessedStockOutLine(
              processedQrCombinations,
              scannedItemId,
              exactMatch.stockOutLineId,
            );
            setProcessedQrCombinations(nextProcessedExact);
            recordHandledQrScanCount(qrScanCountAtInvoke);
            const markProcessedTime = performance.now() - markProcessedStartTime;
            console.log(`  [PERF] Mark processed time: ${markProcessedTime.toFixed(2)}ms`);

            if (workbenchMode) {
              await refreshWorkbenchAfterScanPick();
            }
  
            const totalTime = performance.now() - totalStartTime;
            console.log(`✅ [PROCESS OUTSIDE QR END] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
            console.log(`  End time: ${new Date().toISOString()}`);
            console.log(`📊 Breakdown: parse=${parseTime.toFixed(2)}ms, validation=${validationTime.toFixed(2)}ms, lookup=${lookupTime.toFixed(2)}ms, match=${matchTime.toFixed(2)}ms, api=${apiTime.toFixed(2)}ms, stateUpdate=${stateUpdateTime.toFixed(2)}ms, markProcessed=${markProcessedTime.toFixed(2)}ms`);
            console.log(
              workbenchMode
                ? "✅ Workbench scan-pick: list refreshed from server"
                : "✅ Status updated locally, no full data refresh needed",
            );
          } else {
            console.warn("Unexpected response code from backend:", res.code);
            const failMsg =
              (res as { message?: string })?.message ||
              t("Workbench scan-pick failed.");
            if (
              shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
              exactMatch
            ) {
              openWorkbenchLotLabelModalForLot(exactMatch, failMsg);
              return;
            }
            if (workbenchMode && exactMatch.stockOutLineId != null) {
              rememberWorkbenchScanReject(Number(exactMatch.stockOutLineId), failMsg);
            }
            startTransition(() => {
              setQrScanError(true);
              setQrScanSuccess(false);
              setQrScanErrorMsg(failMsg);
            });
          }
        } catch (e) {
          const totalTime = performance.now() - totalStartTime;
          console.error(`❌ [PROCESS OUTSIDE QR ERROR] Total time: ${totalTime.toFixed(2)}ms`);
          console.error("Error calling updateStockOutLineStatusByQRCodeAndLotNo:", e);
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
          });
        }
  
        return; // ✅ 直接返回，不需要确认表单
      }
      
      // ✅ Case 2: itemId 匹配但 stockInLineId 不匹配
      // Workbench 策略：不彈窗，直接切換到掃到的批次並提交一次掃描
      const expectedLotStartTime = performance.now();
      const expectedLot = pickExpectedLotForSubstitution(
        activeSuggestedLots,
        processedQrCombinations,
      );
      if (!expectedLot) {
        console.error("Could not determine expected lot for auto-switch");
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
        });
        return;
      }
      const expectedLotTime = performance.now() - expectedLotStartTime;
      console.log(`  [PERF] Get expected lot time: ${expectedLotTime.toFixed(2)}ms`);

      console.log(
        `⚠️ Lot mismatch (auto): Expected stockInLineId=${expectedLot.stockInLineId}, Scanned stockInLineId=${scannedStockInLineId}`,
      );

      // 1) 先把掃到的 stockInLineId 轉成 lotNo（workbenchScanPick 需要 lotNo）
      let scannedLotNo: string | null = null;
      try {
        const info = await fetchStockInLineInfoCached(scannedStockInLineId);
        scannedLotNo = info?.lotNo || null;
      } catch (e) {
        console.warn("Failed to fetch lotNo for stockInLineId:", scannedStockInLineId, e);
      }
      if (!scannedLotNo) {
        const msg = t("Cannot resolve lot number from QR. Please rescan or use manual confirmation.");
        setQrScanError(true);
        setQrScanSuccess(false);
        setQrScanErrorMsg(msg);
        return;
      }

      // 2) 非 workbench：先 confirmLotSubstitution；workbench 僅依 scan-pick 規則與錯誤訊息
      if (!workbenchMode) {
        const substitutionResult = await confirmLotSubstitution({
          pickOrderLineId: expectedLot.pickOrderLineId,
          stockOutLineId: expectedLot.stockOutLineId,
          originalSuggestedPickLotId: expectedLot.suggestedPickLotId,
          newInventoryLotNo: "",
          newStockInLineId: scannedStockInLineId,
        });
        const substitutionCode = (substitutionResult as any)?.code;
        const switchedToUnavailable =
          substitutionCode === "SUCCESS_UNAVAILABLE" || substitutionCode === "BOUND_UNAVAILABLE";
        if (!substitutionResult || (substitutionCode !== "SUCCESS" && !switchedToUnavailable)) {
          const errMsg =
            substitutionResult?.message ||
            t("Lot switch failed; pick line was not updated.");
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(errMsg);
          if (expectedLot.stockOutLineId != null) {
            rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), errMsg);
          }
          return;
        }
      }

      // 3) 提交掃描（workbench：直接 workbenchScanPick）
      try {
        const res = await workbenchScanPick({
          stockOutLineId: expectedLot.stockOutLineId,
          lotNo: scannedLotNo,
          ...(typeof scannedStockInLineId === "number" &&
          Number.isFinite(scannedStockInLineId) &&
          scannedStockInLineId > 0
            ? { stockInLineId: scannedStockInLineId }
            : {}),
          ...workbenchScanPickQtyFromLot(expectedLot),
          storeId: fgPickOrders?.[0]?.storeId ?? null,
          userId: currentUserId ?? 1,
        });

        const ok = res.code === "SUCCESS";

        if (!ok) {
          const failMsg =
            (res as { message?: string })?.message ||
            t("Workbench scan-pick failed.");
          if (
            shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
            expectedLot
          ) {
            openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
            return;
          }
          if (workbenchMode && expectedLot.stockOutLineId != null) {
            rememberWorkbenchScanReject(Number(expectedLot.stockOutLineId), failMsg);
          }
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(failMsg);
          return;
        }

        clearWorkbenchScanReject(Number(expectedLot.stockOutLineId));
        const entity = res.entity as any;
        const nextStatus = String(entity?.status ?? "completed").toLowerCase();
        const nextQty = entity?.qty != null ? Number(entity.qty) : undefined;

        startTransition(() => {
          setQrScanError(false);
          setQrScanSuccess(true);

          setCombinedLotData((prev) =>
            prev.map((lot) => {
              if (
                lot.stockOutLineId === expectedLot.stockOutLineId &&
                lot.pickOrderLineId === expectedLot.pickOrderLineId
              ) {
                return {
                  ...lot,
                  lotNo: scannedLotNo,
                  stockOutLineStatus: nextStatus,
                  stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                };
              }
              return lot;
            }),
          );

          setOriginalCombinedData((prev) =>
            prev.map((lot) => {
              if (
                lot.stockOutLineId === expectedLot.stockOutLineId &&
                lot.pickOrderLineId === expectedLot.pickOrderLineId
              ) {
                return {
                  ...lot,
                  lotNo: scannedLotNo,
                  stockOutLineStatus: nextStatus,
                  stockOutLineQty: nextQty ?? lot.stockOutLineQty,
                };
              }
              return lot;
            }),
          );
        });

        const nextProcessedMismatch = markProcessedStockOutLine(
          processedQrCombinations,
          scannedItemId,
          expectedLot.stockOutLineId,
        );
        setProcessedQrCombinations(nextProcessedMismatch);
        recordHandledQrScanCount(qrScanCountAtInvoke);

        if (workbenchMode) {
          await refreshWorkbenchAfterScanPick();
        }
      } catch (e) {
        console.error("Auto-switch scanPick failed:", e);
        setQrScanError(true);
        setQrScanSuccess(false);
        return;
      }

      const totalTime = performance.now() - totalStartTime;
      console.log(
        `✅ [PROCESS OUTSIDE QR AUTO-SWITCH] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`,
      );
      console.log(`  End time: ${new Date().toISOString()}`);
      console.log(
        `📊 Breakdown: parse=${parseTime.toFixed(2)}ms, validation=${validationTime.toFixed(2)}ms, lookup=${lookupTime.toFixed(2)}ms, match=${matchTime.toFixed(2)}ms, expectedLot=${expectedLotTime.toFixed(2)}ms`,
      );
    } catch (error) {
      const totalTime = performance.now() - totalStartTime;
      console.error(`❌ [PROCESS OUTSIDE QR ERROR] Total time: ${totalTime.toFixed(2)}ms`);
      console.error("Error during QR code processing:", error);
      startTransition(() => {
        setQrScanError(true);
        setQrScanSuccess(false);
      });
      return;
    }
    } finally {
      qrPickInFlightRef.current = false;
    }
  }, [
    lotDataIndexes,
    processedQrCombinations,
    combinedLotData,
    fetchStockInLineInfoCached,
    workbenchMode,
    currentUserId,
    clearWorkbenchScanReject,
    rememberWorkbenchScanReject,
    refreshWorkbenchAfterScanPick,
    workbenchScanPickQtyFromLot,
    openWorkbenchLotLabelModalForLot,
    shouldOpenWorkbenchLotLabelModalForFailure,
    t,
  ]);
  // Store processOutsideQrCode in ref for immediate access (update on every render)
  processOutsideQrCodeRef.current = processOutsideQrCode;
  
  useEffect(() => {
    // Skip if scanner is not active or no data available
    if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
      return;
    }
  
    const qrValuesChangeStartTime = performance.now();
    console.log(`  [QR VALUES EFFECT] Triggered at: ${new Date().toISOString()}`);
    console.log(`  [QR VALUES EFFECT] qrValues.length: ${qrValues.length}`);
    console.log(`  [QR VALUES EFFECT] qrValues:`, qrValues);
  
    const latestQr = qrValues[qrValues.length - 1];
    console.log(`  [QR VALUES EFFECT] Latest QR: ${latestQr}`);
    console.log(`  [QR VALUES EFFECT] Latest QR detected at: ${new Date().toISOString()}`);
    
    // ✅ FIXED: Handle test shortcut {2fitestx,y} or {2fittestx,y} where x=itemId, y=stockInLineId
    // Support both formats: {2fitest (2 t's) and {2fittest (3 t's)
    if ((latestQr.startsWith("{2fitest") || latestQr.startsWith("{2fittest")) && latestQr.endsWith("}")) {
      // Extract content: remove "{2fitest" or "{2fittest" and "}"
      let content = '';
      if (latestQr.startsWith("{2fittest")) {
        content = latestQr.substring(9, latestQr.length - 1); // Remove "{2fittest" and "}"
      } else if (latestQr.startsWith("{2fitest")) {
        content = latestQr.substring(8, latestQr.length - 1); // Remove "{2fitest" and "}"
      }
      
      const parts = content.split(',');
      
      if (parts.length === 2) {
        const itemId = parseInt(parts[0].trim(), 10);
        const stockInLineId = parseInt(parts[1].trim(), 10);
        
        if (!isNaN(itemId) && !isNaN(stockInLineId)) {
          console.log(
            `%c TEST QR: Detected ${latestQr.substring(0, 9)}... - Simulating QR input (itemId=${itemId}, stockInLineId=${stockInLineId})`,
            "color: purple; font-weight: bold"
          );
          
          // ✅ Simulate QR code JSON format
          const simulatedQr = JSON.stringify({
            itemId: itemId,
            stockInLineId: stockInLineId
          });
          
          console.log(`  [TEST QR] Simulated QR content: ${simulatedQr}`);
          console.log(`  [TEST QR] Start time: ${new Date().toISOString()}`);
          const testStartTime = performance.now();
          
          // ✅ Mark as processed FIRST to avoid duplicate processing
          lastProcessedQrRef.current = latestQr;
          processedQrCodesRef.current.add(latestQr);
          if (processedQrCodesRef.current.size > 100) {
            const firstValue = processedQrCodesRef.current.values().next().value;
            if (firstValue !== undefined) {
              processedQrCodesRef.current.delete(firstValue);
            }
          }
          setLastProcessedQr(latestQr);
          setProcessedQrCodes(new Set(processedQrCodesRef.current));
          
          // ✅ Process immediately (bypass QR scanner delay)
          if (processOutsideQrCodeRef.current) {
            processOutsideQrCodeRef.current(simulatedQr, qrValues.length).then(() => {
              const testTime = performance.now() - testStartTime;
              console.log(`  [TEST QR] Total processing time: ${testTime.toFixed(2)}ms (${(testTime / 1000).toFixed(3)}s)`);
              console.log(`  [TEST QR] End time: ${new Date().toISOString()}`);
            }).catch((error) => {
              const testTime = performance.now() - testStartTime;
              console.error(`❌ [TEST QR] Error after ${testTime.toFixed(2)}ms:`, error);
            });
          }
          
          // Reset scan
          if (resetScanRef.current) {
            resetScanRef.current();
          }
          
          const qrValuesChangeTime = performance.now() - qrValuesChangeStartTime;
          console.log(`  [QR VALUES EFFECT] Test QR handling time: ${qrValuesChangeTime.toFixed(2)}ms`);
          return; // ✅ IMPORTANT: Return early to prevent normal processing
        } else {
          console.warn(`  [TEST QR] Invalid itemId or stockInLineId: itemId=${parts[0]}, stockInLineId=${parts[1]}`);
        }
      } else {
        console.warn(`  [TEST QR] Invalid format. Expected {2fitestx,y} or {2fittestx,y}, got: ${latestQr}`);
      }
    }

    // Skip processing if manual confirmation modal is open
    if (manualLotConfirmationOpen) {
      // Check if this is a different QR code than what triggered the modal
      const modalTriggerQr = lastProcessedQrRef.current;
      if (latestQr === modalTriggerQr) {
        console.log(`  [QR PROCESS] Skipping - manual modal open for same QR`);
        return;
      }
      // If it's a different QR, allow processing
      console.log(`  [QR PROCESS] Different QR detected while manual modal open, allowing processing`);
    }
    
    const qrDetectionStartTime = performance.now();
    console.log(`  [QR DETECTION] Latest QR detected: ${latestQr?.substring(0, 50)}...`);
    console.log(`  [QR DETECTION] Detection time: ${new Date().toISOString()}`);
    console.log(`  [QR DETECTION] Time since QR scanner set value: ${(qrDetectionStartTime - qrValuesChangeStartTime).toFixed(2)}ms`);
    
    const scanCount = qrValues.length;
    const qrPayload = parseWorkbenchQrPayload(latestQr);
    const isNewScanEvent = scanCount > lastProcessedQrScanCountRef.current;
    const canRetrySamePhysicalLot =
      qrPayload != null &&
      isNewScanEvent &&
      hasPendingActiveRowForStockInLine(
        lotDataIndexes,
        qrPayload.itemId,
        qrPayload.stockInLineId,
        processedQrCombinations,
      );

    // Skip if already processed; same QR only retries on a new scanner event (qrValues.length increase)
    const checkProcessedStartTime = performance.now();
    if (
      (processedQrCodesRef.current.has(latestQr) || lastProcessedQrRef.current === latestQr) &&
      !canRetrySamePhysicalLot
    ) {
      const checkTime = performance.now() - checkProcessedStartTime;
      console.log(`  [QR PROCESS] Already processed check time: ${checkTime.toFixed(2)}ms`);
      return;
    }
    const checkTime = performance.now() - checkProcessedStartTime;
    console.log(` [QR PROCESS] Not processed check time: ${checkTime.toFixed(2)}ms`);
    
    // Handle special shortcut
    if (latestQr === "{2fic}") {
      console.log("  Detected {2fic} shortcut - opening manual lot confirmation form");
      setManualLotConfirmationOpen(true);
      if (resetScanRef.current) {
        resetScanRef.current();
      }
      lastProcessedQrRef.current = latestQr;
      processedQrCodesRef.current.add(latestQr);
      if (processedQrCodesRef.current.size > 100) {
        const firstValue = processedQrCodesRef.current.values().next().value;
        if (firstValue !== undefined) {
          processedQrCodesRef.current.delete(firstValue);
        }
      }
      setLastProcessedQr(latestQr);
      setProcessedQrCodes(prev => {
        const newSet = new Set(prev);
        newSet.add(latestQr);
        if (newSet.size > 100) {
          const firstValue = newSet.values().next().value;
          if (firstValue !== undefined) {
            newSet.delete(firstValue);
          }
        }
        return newSet;
      });
      return;
    }
    
    // Process new QR code immediately (background mode - no modal)
    if (latestQr && (latestQr !== lastProcessedQrRef.current || isNewScanEvent)) {
      const processingStartTime = performance.now();
      console.log(`  [QR PROCESS] Starting processing at: ${new Date().toISOString()}`);
      console.log(`  [QR PROCESS] Time since detection: ${(processingStartTime - qrDetectionStartTime).toFixed(2)}ms`);
      
      // ✅ Process immediately for better responsiveness
      // Clear any pending debounced processing
      if (qrProcessingTimeoutRef.current) {
        clearTimeout(qrProcessingTimeoutRef.current);
        qrProcessingTimeoutRef.current = null;
      }
      
      // Log immediately (console.log is synchronous)
      console.log(`  [QR PROCESS] Processing new QR code with enhanced validation: ${latestQr}`);
      
      // Update refs immediately (no state update delay) - do this FIRST
      const refUpdateStartTime = performance.now();
      lastProcessedQrRef.current = latestQr;
      processedQrCodesRef.current.add(latestQr);
      if (processedQrCodesRef.current.size > 100) {
        const firstValue = processedQrCodesRef.current.values().next().value;
        if (firstValue !== undefined) {
          processedQrCodesRef.current.delete(firstValue);
        }
      }
      const refUpdateTime = performance.now() - refUpdateStartTime;
      console.log(`  [QR PROCESS] Ref update time: ${refUpdateTime.toFixed(2)}ms`);
      
      // Process immediately in background - no modal/form needed, no delays
      // Use ref to avoid dependency issues
      const processCallStartTime = performance.now();
      if (processOutsideQrCodeRef.current) {
        processOutsideQrCodeRef.current(latestQr, qrValues.length).then(() => {
          const processCallTime = performance.now() - processCallStartTime;
          const totalProcessingTime = performance.now() - processingStartTime;
          console.log(`  [QR PROCESS] processOutsideQrCode call time: ${processCallTime.toFixed(2)}ms`);
          console.log(`  [QR PROCESS] Total processing time: ${totalProcessingTime.toFixed(2)}ms (${(totalProcessingTime / 1000).toFixed(3)}s)`);
        }).catch((error) => {
          const processCallTime = performance.now() - processCallStartTime;
          const totalProcessingTime = performance.now() - processingStartTime;
          console.error(`❌ [QR PROCESS] processOutsideQrCode error after ${processCallTime.toFixed(2)}ms:`, error);
          console.error(`❌ [QR PROCESS] Total processing time before error: ${totalProcessingTime.toFixed(2)}ms`);
        });
      }
      
      // Update state for UI (but don't block on it)
      const stateUpdateStartTime = performance.now();
      setLastProcessedQr(latestQr);
      setProcessedQrCodes(new Set(processedQrCodesRef.current));
      const stateUpdateTime = performance.now() - stateUpdateStartTime;
      console.log(`  [QR PROCESS] State update time: ${stateUpdateTime.toFixed(2)}ms`);
      
      const detectionTime = performance.now() - qrDetectionStartTime;
      const totalEffectTime = performance.now() - qrValuesChangeStartTime;
      console.log(`  [QR DETECTION] Total detection time: ${detectionTime.toFixed(2)}ms`);
      console.log(`  [QR VALUES EFFECT] Total effect time: ${totalEffectTime.toFixed(2)}ms`);
    }
    
    return () => {
      if (qrProcessingTimeoutRef.current) {
        clearTimeout(qrProcessingTimeoutRef.current);
        qrProcessingTimeoutRef.current = null;
      }
    };
  }, [
    qrValues,
    isManualScanning,
    isRefreshingData,
    combinedLotData.length,
    manualLotConfirmationOpen,
    lotDataIndexes,
    processedQrCombinations,
  ]);
const renderCountRef = useRef(0);
const renderStartTimeRef = useRef<number | null>(null);

// Track render performance
useEffect(() => {
  renderCountRef.current++;
  const now = performance.now();
  
  if (renderStartTimeRef.current !== null) {
    const renderTime = now - renderStartTimeRef.current;
    if (renderTime > 100) { // Only log slow renders (>100ms)
      console.log(`  [PERF] Render #${renderCountRef.current} took ${renderTime.toFixed(2)}ms, combinedLotData length: ${combinedLotData.length}`);
    }
    renderStartTimeRef.current = null;
  }
}, [combinedLotData.length]);
  // Auto-start scanner only once on mount
  const scannerInitializedRef = useRef(false);
  
  useEffect(() => {
    if (session && currentUserId && !initializationRef.current) {
      console.log(" Session loaded, initializing pick order...");
      initializationRef.current = true;
      
      //  Only fetch existing data, no auto-assignment
      fetchAllCombinedLotData();
    }
  }, [session, currentUserId, fetchAllCombinedLotData]);
  
  // Separate effect for auto-starting scanner (only once, prevents multiple resets)
  useEffect(() => {
    if (session && currentUserId && !scannerInitializedRef.current) {
      scannerInitializedRef.current = true;
      // ✅ Auto-start scanner on mount for tablet use (background mode - no modal)
      console.log("✅ Auto-starting QR scanner in background mode");
      setIsManualScanning(true);
      startScan();
    }
  }, [session, currentUserId, startScan]);

  //  Add event listener for manual assignment
  useEffect(() => {
    const handlePickOrderAssigned = () => {
      console.log("🔄 Pick order assigned event received, refreshing data...");
      fetchAllCombinedLotData();
    };

    window.addEventListener('pickOrderAssigned', handlePickOrderAssigned);
    
    return () => {
      window.removeEventListener('pickOrderAssigned', handlePickOrderAssigned);
    };
  }, [fetchAllCombinedLotData]);



  const handleManualInputSubmit = useCallback(() => {
    if (qrScanInput.trim() !== '') {
      handleQrCodeSubmit(qrScanInput.trim());
    }
  }, [qrScanInput, handleQrCodeSubmit]);

  //  Handle QR code submission from modal (internal scanning)
  const handleQrCodeSubmitFromModal = useCallback(async () => {
    // Legacy path: marked SOL as `checked` (normal version). Disabled for workbench.
    setQrScanError(true);
    setQrScanSuccess(false);
    setQrScanErrorMsg(t("Workbench uses scan-pick; this QR modal flow is not supported."));
  }, [t]);


  const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => {
    if (value === '' || value === null || value === undefined) {
      setPickQtyData((prev) => {
        if (!Object.prototype.hasOwnProperty.call(prev, lotKey)) return prev;
        const next = { ...prev };
        delete next[lotKey];
        return next;
      });
      return;
    }

    const numericValue = typeof value === "string" ? parseFloat(value) : value;

    if (Number.isNaN(numericValue) || numericValue < 0) {
      setPickQtyData((prev) => {
        if (!Object.prototype.hasOwnProperty.call(prev, lotKey)) return prev;
        const next = { ...prev };
        delete next[lotKey];
        return next;
      });
      return;
    }

    setPickQtyData((prev) => ({
      ...prev,
      [lotKey]: numericValue,
    }));
  }, []);

  const [autoAssignStatus, setAutoAssignStatus] = useState<'idle' | 'checking' | 'assigned' | 'no_orders'>('idle');
  const [autoAssignMessage, setAutoAssignMessage] = useState<string>('');
  const [completionStatus, setCompletionStatus] = useState<PickOrderCompletionResponse | null>(null);

  const checkAndAutoAssignNext = useCallback(async () => {
    if (!currentUserId) return;
    
    try {
      const completionResponse = await checkPickOrderCompletion(currentUserId);
      
      if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) {
        console.log("Found completed pick orders, auto-assigning next...");
        //  移除前端的自动分配逻辑，因为后端已经处理了
        // await handleAutoAssignAndRelease(); // 删除这个函数
      }
    } catch (error) {
      console.error("Error checking pick order completion:", error);
    }
  }, [currentUserId]);

  //  Handle reject lot
  //  Handle pick execution form
  const handlePickExecutionForm = useCallback((lot: any) => {
    console.log("=== Pick Execution Form ===");
    console.log("Lot data:", lot);
    
    if (!lot) {
      console.warn("No lot data provided for pick execution form");
      return;
    }
    
    console.log("Opening pick execution form for lot:", lot.lotNo);
    
    setSelectedLotForExecutionForm(lot);
    setPickExecutionFormOpen(true);
    
    console.log("Pick execution form opened for lot ID:", lot.lotId);
  }, []);

 
  //  Calculate remaining required quantity
  const calculateRemainingRequiredQty = useCallback((lot: any) => {
    const requiredQty = lot.requiredQty || 0;
    const stockOutLineQty = lot.stockOutLineQty || 0;
    return Math.max(0, requiredQty - stockOutLineQty);
  }, []);

  // Search criteria
  const searchCriteria: Criterion<any>[] = [
    {
      label: t("Pick Order Code"),
      paramName: "pickOrderCode",
      type: "text",
    },
      {
        label: t("Item Code"),
        paramName: "itemCode",
        type: "text",
      },
      {
        label: t("Item Name"),
        paramName: "itemName",
        type: "text",
      },
      {
      label: t("Lot No"),
      paramName: "lotNo",
      type: "text",
    },
  ];

  const handleSearch = useCallback((query: Record<string, any>) => {
    setSearchQuery({ ...query });
    console.log("Search query:", query);

    if (!originalCombinedData) return;

    const filtered = originalCombinedData.filter((lot: any) => {
      const pickOrderCodeMatch = !query.pickOrderCode || 
        lot.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase());
      
        const itemCodeMatch = !query.itemCode || 
        lot.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase());
        
        const itemNameMatch = !query.itemName || 
        lot.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase());
      
      const lotNoMatch = !query.lotNo || 
        lot.lotNo?.toLowerCase().includes((query.lotNo || "").toLowerCase());
      
      return pickOrderCodeMatch && itemCodeMatch && itemNameMatch && lotNoMatch;
    });
    
    setCombinedLotData(filtered);
    console.log("Filtered lots count:", filtered.length);
  }, [originalCombinedData]);

  const handleReset = useCallback(() => {
    setSearchQuery({});
    if (originalCombinedData) {
      setCombinedLotData(originalCombinedData);
    }
  }, [originalCombinedData]);

  const handlePageChange = useCallback((event: unknown, newPage: number) => {
    setPaginationController(prev => ({
      ...prev,
      pageNum: newPage,
    }));
  }, []);

  const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
    const newPageSize = parseInt(event.target.value, 10);
    setPaginationController({
      pageNum: 0,
      pageSize: newPageSize === -1 ? -1 : newPageSize,
    });
  }, []);

  // ✅ Workbench list: group same item (within same route), sort completed lots first,
  // and suppress repeated item fields on subsequent rows.
  const paginatedData = useMemo(() => {
    type RowMeta = {
      lot: any;
      isGroupFirst: boolean;
      isGroupLast: boolean;
      groupDisplayIndex: number;
      isMultiLotGroup: boolean;
    };

    const isCompletedStatus = (lot: any) => {
      const st = String(lot?.stockOutLineStatus ?? "").toLowerCase();
      return (
        st === "completed" ||
        st === "partially_completed" ||
        st === "partially_complete"
      );
    };

    const isCheckedStatus = (lot: any) => {
      const st = String(lot?.stockOutLineStatus ?? "").toLowerCase();
      return st === "checked";
    };

    const statusRank = (lot: any) => {
      // Desired order within same item:
      // completed -> checked -> pending -> rejected -> others
      const st = String(lot?.stockOutLineStatus ?? "").toLowerCase();
      if (isCompletedStatus(lot)) return 0;
      if (isCheckedStatus(lot)) return 1;
      if (st === "pending") return 2;
      if (st === "rejected") return 3;
      return 9;
    };

    // Keep stable group ordering by first appearance.
    const groups = new Map<
      string,
      { firstIndex: number; items: { lot: any; originalIndex: number }[] }
    >();
    combinedLotData.forEach((lot: any, originalIndex: number) => {
      const pickOrderLineKey =
        lot?.pickOrderLineId != null ? `pol:${String(lot.pickOrderLineId)}` : "pol:unknown";
      const itemKey =
        lot?.itemId != null
          ? `itemId:${String(lot.itemId)}`
          : `itemCode:${String(lot?.itemCode ?? "").trim()}`;
      // Group by pick order line + item so split lots (different routes) share one display index.
      const key = `${pickOrderLineKey}__${itemKey}`;
      const g = groups.get(key);
      if (!g) {
        groups.set(key, { firstIndex: originalIndex, items: [{ lot, originalIndex }] });
      } else {
        g.items.push({ lot, originalIndex });
      }
    });

    const groupEntries = Array.from(groups.values()).sort(
      (a, b) => a.firstIndex - b.firstIndex,
    );

    const flattened: RowMeta[] = [];
    for (let gi = 0; gi < groupEntries.length; gi += 1) {
      const g = groupEntries[gi];
      // Re-number groups contiguously (avoid gaps after grouping)
      const groupDisplayIndex = gi + 1;
      const sortedWithin = [...g.items].sort((a, b) => {
        const ra = statusRank(a.lot);
        const rb = statusRank(b.lot);
        if (ra !== rb) return ra - rb;
        return a.originalIndex - b.originalIndex; // stable fallback
      });
      sortedWithin.forEach((it, idx) => {
        flattened.push({
          lot: it.lot,
          isGroupFirst: idx === 0,
          isGroupLast: idx === sortedWithin.length - 1,
          groupDisplayIndex,
          isMultiLotGroup: sortedWithin.length > 1,
        });
      });
    }

    if (paginationController.pageSize === -1) return flattened;
    const startIndex =
      paginationController.pageNum * paginationController.pageSize;
    const endIndex = startIndex + paginationController.pageSize;
    return flattened.slice(startIndex, endIndex);
  }, [combinedLotData, paginationController.pageNum, paginationController.pageSize]);
const allItemsReady = useMemo(() => {
  if (combinedLotData.length === 0) return false;

  return combinedLotData.every((lot: any) => {
    const status = lot.stockOutLineStatus?.toLowerCase();
    const isRejected =
      status === 'rejected' || lot.lotAvailability === 'rejected';
    const isCompleted =
      status === 'completed' || status === 'partially_completed' || status === 'partially_complete';
    const isChecked = status === 'checked';
    const isPending = status === 'pending';

    // ✅ FIXED: 无库存（noLot）行：pending 状态也应该被视为 ready（可以提交）
    // ✅ 過期批號（未換批）：與 noLot 相同，視為可收尾
    if (lot.noLot === true || isLotAvailabilityExpired(lot) || isInventoryLotLineUnavailable(lot)) {
      return isChecked || isCompleted || isRejected || isPending;
    }

    // 正常 lot：必须已扫描/提交或者被拒收
    return isChecked || isCompleted || isRejected;
  });
}, [combinedLotData]);
const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number, source: 'justComplete' | 'singleSubmit') => {
  if (!lot.stockOutLineId) {
    console.error("No stock out line found for this lot");
    return;
  }
  const solId = Number(lot.stockOutLineId);
  if (solId > 0 && actionBusyBySolId[solId]) {
    console.warn("Action already in progress for stockOutLineId:", solId);
    return;
  }
  
  try {
    if (solId > 0) setActionBusyBySolId(prev => ({ ...prev, [solId]: true }));
    const targetUnavailable = isInventoryLotLineUnavailable(lot);
    const effectiveSubmitQty = targetUnavailable && submitQty > 0 ? 0 : submitQty;

    const canonicalLotForSol =
      solId > 0
        ? combinedLotData.find((r) => Number(r.stockOutLineId) === solId) ?? lot
        : lot;

    // Workbench「Just Completed」：不掃 QR，直接用列上的 lotNo + stockInLineId 走 scan-pick 完成庫存扣帳
    if (workbenchMode && source === "justComplete") {
      const solIdForOverride = Number(canonicalLotForSol.stockOutLineId) || 0;
      const lotIdForOverride = canonicalLotForSol.lotId;
      const lotKeyForOverride =
        Number.isFinite(solIdForOverride) && solIdForOverride > 0
          ? `sol:${solIdForOverride}`
          : `${canonicalLotForSol.pickOrderLineId}-${lotIdForOverride}`;
      const hasExplicitSubmitOverride = Object.prototype.hasOwnProperty.call(
        pickQtyData,
        lotKeyForOverride,
      );
      const explicitSubmitOverride = hasExplicitSubmitOverride
        ? Number(pickQtyData[lotKeyForOverride])
        : NaN;

      const qtyPayload = workbenchScanPickQtyFromLot(canonicalLotForSol);
      const wbJustQty = qtyPayload.qty;
      const isUnavailableForJustComplete = isInventoryLotLineUnavailable(canonicalLotForSol);
      const canPostScanPick =
        // unavailable lot: Just Completed must always submit qty=0, even without lotNo
        isUnavailableForJustComplete || (
          canonicalLotForSol.lotNo && String(canonicalLotForSol.lotNo).trim() !== "" && (
            // explicit short submit: user typed 0 (must send qty=0 to backend)
            (hasExplicitSubmitOverride &&
              Number.isFinite(explicitSubmitOverride) &&
              explicitSubmitOverride === 0) ||
            // normal pick: positive qty
            (wbJustQty != null && wbJustQty > 0)
          )
        );

      if (canPostScanPick) {
        const qtyToSend = isUnavailableForJustComplete
          ? 0
          : hasExplicitSubmitOverride && explicitSubmitOverride === 0
            ? 0
            : Number(wbJustQty);
        const res = await workbenchScanPick({
          stockOutLineId: Number(canonicalLotForSol.stockOutLineId),
          lotNo: String(canonicalLotForSol.lotNo).trim(),
          ...(typeof canonicalLotForSol.stockInLineId === "number" &&
          Number.isFinite(canonicalLotForSol.stockInLineId) &&
          canonicalLotForSol.stockInLineId > 0
            ? { stockInLineId: canonicalLotForSol.stockInLineId }
            : {}),
          qty: qtyToSend,
          storeId: fgPickOrders?.[0]?.storeId ?? null,
          userId: currentUserId ?? 1,
        });
        const scanOk = res.code === "SUCCESS";
        if (!scanOk) {
          rememberWorkbenchScanReject(
            Number(canonicalLotForSol.stockOutLineId),
            (res as { message?: string })?.message,
          );
          throw new Error(
            (res as { message?: string })?.message || "Workbench scan-pick failed",
          );
        }
        clearWorkbenchScanReject(Number(canonicalLotForSol.stockOutLineId));
        const entity = res.entity as any;
        const nextStatus = String(entity?.status ?? "completed").toLowerCase();
        const nextQty = entity?.qty != null ? Number(entity.qty) : undefined;
        setPickQtyData((prev) => {
          if (!Object.prototype.hasOwnProperty.call(prev, lotKeyForOverride)) return prev;
          const next = { ...prev };
          delete next[lotKeyForOverride];
          return next;
        });
        await refreshWorkbenchAfterScanPick();
        setTimeout(() => {
          checkAndAutoAssignNext();
        }, 1000);
        console.log("Just Completed (workbench): workbenchScanPick posted without QR.");
        return;
      }
      const justCompleteErr = t(
        "Just Completed (workbench): requires a valid lot number and quantity; expired rows must not use this button.",
      );
      if (solId > 0) {
        rememberWorkbenchScanReject(solId, justCompleteErr);
      }
      setQrScanErrorMsg(justCompleteErr);
      throw new Error(justCompleteErr);
    }

    if (effectiveSubmitQty === 0 && source === 'singleSubmit') {
      console.log(`=== SUBMITTING ALL ZEROS CASE ===`);
      console.log(`Lot: ${lot.lotNo}`);
      console.log(`Stock Out Line ID: ${lot.stockOutLineId}`);

      // ✅ Workbench: route qty=0 through scan-pick as well (backend supports zero-complete).
      // Allow empty lotNo for noLot/expired/unavailable rows.
      if (workbenchMode) {
        const res = await workbenchScanPick({
          stockOutLineId: Number(lot.stockOutLineId),
          lotNo: String(lot.lotNo ?? "").trim(),
          ...(typeof lot.stockInLineId === "number" &&
          Number.isFinite(lot.stockInLineId) &&
          lot.stockInLineId > 0
            ? { stockInLineId: lot.stockInLineId }
            : {}),
          qty: 0,
          storeId: fgPickOrders?.[0]?.storeId ?? null,
          userId: currentUserId ?? 1,
        });
        const scanOk = res.code === "SUCCESS";
        if (!scanOk) {
          rememberWorkbenchScanReject(
            Number(lot.stockOutLineId),
            (res as { message?: string })?.message,
          );
          throw new Error(
            (res as { message?: string })?.message || "Workbench scan-pick failed (qty=0)",
          );
        }
        clearWorkbenchScanReject(Number(lot.stockOutLineId));
        await refreshWorkbenchAfterScanPick();
        setTimeout(() => {
          checkAndAutoAssignNext();
        }, 1000);
        return;
      }

      // Legacy non-workbench path used `checked` as an intermediate state.
      // Workbench mode is always true in this page; keep this branch unreachable.
      throw new Error("Unsupported legacy checked flow on workbench page");
    }

    // DO Workbench: inventory posting + SOL/POL rules live in /doPickOrder/workbench/scan-pick
    if (
      workbenchMode &&
      effectiveSubmitQty > 0 &&
      lot.lotNo &&
      String(lot.lotNo).trim() !== "" &&
      !isLotAvailabilityExpired(lot) &&
      !isInventoryLotLineUnavailable(lot)
    ) {
      const res = await workbenchScanPick({
        stockOutLineId: Number(lot.stockOutLineId),
        lotNo: String(lot.lotNo).trim(),
        ...(typeof lot.stockInLineId === "number" &&
        Number.isFinite(lot.stockInLineId) &&
        lot.stockInLineId > 0
          ? { stockInLineId: lot.stockInLineId }
          : {}),
        qty: Number(effectiveSubmitQty),
        storeId: fgPickOrders?.[0]?.storeId ?? null,
        userId: currentUserId ?? 1,
      });
      const scanOk = res.code === "SUCCESS";
      if (!scanOk) {
        rememberWorkbenchScanReject(
          Number(lot.stockOutLineId),
          (res as { message?: string })?.message,
        );
        throw new Error(
          (res as { message?: string })?.message || "Workbench scan-pick failed",
        );
      }
      clearWorkbenchScanReject(Number(lot.stockOutLineId));
      const entity = res.entity as any;
      const nextStatus = String(entity?.status ?? "completed").toLowerCase();
      const nextQty = entity?.qty != null ? Number(entity.qty) : undefined;
      const successLotKey = getWorkbenchQtyLotKey(lot);
      setPickQtyData((prev) => {
        if (!Object.prototype.hasOwnProperty.call(prev, successLotKey)) return prev;
        const next = { ...prev };
        delete next[successLotKey];
        return next;
      });
      await refreshWorkbenchAfterScanPick();
      setTimeout(() => {
        checkAndAutoAssignNext();
      }, 1000);
      return;
    }

    //  FIXED: Calculate cumulative quantity correctly
    const currentActualPickQty = lot.actualPickQty || 0;
    const cumulativeQty = currentActualPickQty + effectiveSubmitQty;
    
    //  FIXED: Determine status based on cumulative quantity vs required quantity
    let newStatus = 'partially_completed';
    
    if (cumulativeQty >= lot.requiredQty) {
      newStatus = 'completed';
    } else if (cumulativeQty > 0) {
      newStatus = 'partially_completed';
    } else {
      // Legacy non-workbench path used `checked` as an intermediate state.
      // Workbench posts immediately via scan-pick.
      newStatus = 'pending';
    }
    
    console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`);
    console.log(`Lot: ${lot.lotNo}`);
    console.log(`Required Qty: ${lot.requiredQty}`);
    console.log(`Current Actual Pick Qty: ${currentActualPickQty}`);
    console.log(`New Submitted Qty: ${effectiveSubmitQty}`);
    console.log(`Cumulative Qty: ${cumulativeQty}`);
    console.log(`New Status: ${newStatus}`);
    console.log(`=====================================`);
    
    if (!workbenchMode) {
      await updateStockOutLineStatus({
        id: lot.stockOutLineId,
        status: newStatus,
        // 后端 updateStatus 的 qty 是“增量 delta”，不能传 cumulativeQty（否则会重复累加导致 out/hold 大幅偏移）
        qty: effectiveSubmitQty
      });
      applyLocalStockOutLineUpdate(Number(lot.stockOutLineId), newStatus, cumulativeQty);
    }
    // 注意：库存过账（hold->out）与 ledger 由后端 updateStatus 内部统一处理；
    // 前端不再额外调用 updateInventoryLotLineQuantities(operation='pick')，避免 double posting。
    
    // Workbench completion is handled in backend scan-pick flow.

    void fetchAllCombinedLotData();
    console.log("Pick quantity submitted successfully!");
    
    setTimeout(() => {
      checkAndAutoAssignNext();
    }, 1000);
    
  } catch (error) {
    console.error("Error submitting pick quantity:", error);
    setQrScanError(true);
    setQrScanSuccess(false);
  } finally {
    if (solId > 0) setActionBusyBySolId(prev => ({ ...prev, [solId]: false }));
  }
}, [
  fetchAllCombinedLotData,
  checkAndAutoAssignNext,
  actionBusyBySolId,
  applyLocalStockOutLineUpdate,
  workbenchMode,
  currentUserId,
  rememberWorkbenchScanReject,
  clearWorkbenchScanReject,
  refreshWorkbenchAfterScanPick,
  combinedLotData,
  workbenchScanPickQtyFromLot,
  t,
]);

const handleSkip = useCallback(async (lot: any) => {
  try {
    console.log("Just Complete clicked (workbench: scan-pick without QR when possible):", lot.lotNo);
    await handleSubmitPickQtyWithQty(lot, 0, 'justComplete');
  } catch (err) {
    console.error("Error in Skip:", err);
  }
}, [handleSubmitPickQtyWithQty]);
const hasPendingBatchSubmit = useMemo(() => {
  return combinedLotData.some((lot) => {
    const status = String(lot.stockOutLineStatus || "").toLowerCase();
    return status === "pending" || status === "partially_completed" || status === "partially_complete";
  });
}, [combinedLotData]);
useEffect(() => {
  if (!hasPendingBatchSubmit) return;
  const handler = (event: BeforeUnloadEvent) => {
    event.preventDefault();
    event.returnValue = "";
  };
  window.addEventListener("beforeunload", handler);
  return () => window.removeEventListener("beforeunload", handler);
}, [hasPendingBatchSubmit]);
const handleStartScan = useCallback(() => {
  const startTime = performance.now();
  console.log(`  [START SCAN] Called at: ${new Date().toISOString()}`);
  console.log(`  [START SCAN] Starting manual QR scan...`);
  
  setIsManualScanning(true);
  const setManualScanningTime = performance.now() - startTime;
  console.log(`  [START SCAN] setManualScanning time: ${setManualScanningTime.toFixed(2)}ms`);
  
  setProcessedQrCodes(new Set());
  setLastProcessedQr('');
  setQrScanError(false);
  setQrScanSuccess(false);
  
  const beforeStartScanTime = performance.now();
  startScan();
  const startScanTime = performance.now() - beforeStartScanTime;
  console.log(`  [START SCAN] startScan() call time: ${startScanTime.toFixed(2)}ms`);
  
  const totalTime = performance.now() - startTime;
  console.log(`  [START SCAN] Total start scan time: ${totalTime.toFixed(2)}ms`);
  console.log(`  [START SCAN] Start scan completed at: ${new Date().toISOString()}`);
}, [startScan]);
  const handlePickOrderSwitch = useCallback(async (pickOrderId: number) => {
    if (pickOrderSwitching) return;
    
    setPickOrderSwitching(true);
    try {
      console.log("  Switching to pick order:", pickOrderId);
      setSelectedPickOrderId(pickOrderId);
      
      //  强制刷新数据，确保显示正确的 pick order 数据
      await fetchAllCombinedLotData(currentUserId, pickOrderId);
    } catch (error) {
      console.error("Error switching pick order:", error);
    } finally {
      setPickOrderSwitching(false);
      }
    }, [pickOrderSwitching, currentUserId, fetchAllCombinedLotData]);
 
  const handleStopScan = useCallback(() => {
    console.log("⏸️ Pausing QR scanner...");
    setIsManualScanning(false);
    setQrScanError(false);
    setQrScanSuccess(false);
    stopScan();
    resetScan();
  }, [stopScan, resetScan]);
  // ... existing code around line 1469 ...
  const handlelotnull = useCallback(async (lot: any) => {
    //  优先使用 stockouts 中的 id，如果没有则使用 stockOutLineId
    const stockOutLineId = lot.stockOutLineId;
    
    if (!stockOutLineId) {
      console.error("  No stockOutLineId found for lot:", lot);
      return;
    }
    const solId = Number(stockOutLineId);
    if (solId > 0 && actionBusyBySolId[solId]) {
      console.warn("Action already in progress for stockOutLineId:", solId);
      return;
    }
    
    try {
      if (solId > 0) setActionBusyBySolId(prev => ({ ...prev, [solId]: true }));
      //  Step 1: Update stock out line status
      await updateStockOutLineStatus({
        id: stockOutLineId,
        status: 'completed',
        qty: 0
      });
      
      //  Step 2: Create pick execution issue for no-lot case
      // Get pick order ID from fgPickOrders or use 0 if not available
      const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0;
      const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || '';
      
      const issueData: PickExecutionIssueData = {
        type: "Do",  // Delivery Order type
        pickOrderId: pickOrderId,
        pickOrderCode: pickOrderCode,
        pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), //  Use dayjs format
        pickExecutionDate: dayjs().format('YYYY-MM-DD'),
        pickOrderLineId: lot.pickOrderLineId,
        itemId: lot.itemId,
        itemCode: lot.itemCode || '',
        itemDescription: lot.itemName || '',
        lotId: null, //  No lot available
        lotNo: null, //  No lot number
        storeLocation: lot.location || '',
        requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
        actualPickQty: 0, //  No items picked (no lot available)
        missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, //  All quantity is missing
        badItemQty: 0,
        issueRemark: `No lot available for this item. Handled via handlelotnull.`,
        pickerName: session?.user?.name || '',
        
      };
      
      const result = await recordPickExecutionIssue(issueData);
      console.log(" Pick execution issue created for no-lot item:", result);
      
      if (result && result.code === "SUCCESS") {
        console.log(" No-lot item handled and issue recorded successfully");
      } else {
        console.error("  Failed to record pick execution issue:", result);
      }
      
      //  Step 3: Refresh data
      await fetchAllCombinedLotData();
    } catch (error) {
      console.error("  Error in handlelotnull:", error);
    } finally {
      if (solId > 0) setActionBusyBySolId(prev => ({ ...prev, [solId]: false }));
    }
  }, [fetchAllCombinedLotData, session, currentUserId, fgPickOrders, actionBusyBySolId]);
  const handleBatchScan = useCallback(async () => {
    const startTime = performance.now();
    console.log(`  [BATCH SCAN START]`);
    console.log(`  Start time: ${new Date().toISOString()}`);
    
    // 获取所有活跃批次（未扫描的）
    const activeLots = combinedLotData.filter(lot => {
      return (
        lot.lotAvailability !== 'rejected' &&
        lot.stockOutLineStatus !== 'rejected' &&
        lot.stockOutLineStatus !== 'completed' &&
        lot.stockOutLineStatus !== 'checked' && // ✅ 只处理未扫描的
        lot.processingStatus !== 'completed' &&
        lot.noLot !== true &&
        lot.lotNo // ✅ 必须有 lotNo
      );
    });
    
    if (activeLots.length === 0) {
      console.log("No active lots to scan");
      return;
    }
    
    console.log(`📦 Batch scanning ${activeLots.length} active lots using batch API...`);
    
    try {
      // ✅ 转换为批量扫描 API 所需的格式
      const lines: BatchScanLineRequest[] = activeLots.map((lot) => ({
        pickOrderLineId: Number(lot.pickOrderLineId),
        inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null,
        pickOrderConsoCode: String(lot.pickOrderConsoCode || ''),
        lotNo: lot.lotNo || null,
        itemId: Number(lot.itemId),
        itemCode: String(lot.itemCode || ''),
        stockOutLineId: lot.stockOutLineId ? Number(lot.stockOutLineId) : null, // ✅ 新增
      }));
      
      const request: BatchScanRequest = {
        userId: currentUserId || 0,
        lines: lines
      };
      
      console.log(`📤 Sending batch scan request with ${lines.length} lines`);
      console.log(`📋 Request data:`, JSON.stringify(request, null, 2));
      
      const scanStartTime = performance.now();
      
      // ✅ 使用新的批量扫描 API（一次性处理所有请求）
      const result = await batchScan(request);
      
      const scanTime = performance.now() - scanStartTime;
      console.log(`  Batch scan API call completed in ${scanTime.toFixed(2)}ms (${(scanTime / 1000).toFixed(3)}s)`);
      console.log(`📥 Batch scan result:`, result);
      
      // ✅ 刷新数据以获取最新的状态
      const refreshStartTime = performance.now();
      await fetchAllCombinedLotData();
      const refreshTime = performance.now() - refreshStartTime;
      console.log(`  Data refresh time: ${refreshTime.toFixed(2)}ms (${(refreshTime / 1000).toFixed(3)}s)`);
      
      const totalTime = performance.now() - startTime;
      console.log(`  [BATCH SCAN END]`);
      console.log(`  Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
      console.log(`  End time: ${new Date().toISOString()}`);
      
      if (result && result.code === "SUCCESS") {
        setQrScanSuccess(true);
        setQrScanError(false);
      } else {
        console.error("❌ Batch scan failed:", result);
        setQrScanError(true);
        setQrScanSuccess(false);
      }
      
    } catch (error) {
      console.error("❌ Error in batch scan:", error);
      setQrScanError(true);
      setQrScanSuccess(false);
    }
  }, [combinedLotData, fetchAllCombinedLotData, currentUserId]);
const handleSubmitAllScanned = useCallback(async () => {
  const startTime = performance.now();
  console.log(`  [BATCH SUBMIT START]`);
  console.log(`  Start time: ${new Date().toISOString()}`);
  
  const scannedLots = combinedLotData.filter(lot => {
    const status = lot.stockOutLineStatus;
    const statusLower = String(status || "").toLowerCase();
    if (statusLower === "completed" || statusLower === "complete") {
      return false;
    }
    // Workbench batch submit is now dedicated to closing noLot / expired / unavailable rows (qty=0 via workbench scan-pick batch).
    if (lot.noLot === true || isLotAvailabilityExpired(lot) || isInventoryLotLineUnavailable(lot)) {
      return true;
    }
    return false;
  });
  
  if (scannedLots.length === 0) {
    console.log("No scanned items to submit");
    return;
  }
  
  setIsSubmittingAll(true);
  console.log(`📦 Submitting ${scannedLots.length} items using workbench batch scan-pick (qty=0)...`);
  
  try {
    const submitStartTime = performance.now();

    const result = await workbenchBatchScanPick({
      lines: scannedLots.map((lot) => ({
        stockOutLineId: Number(lot.stockOutLineId) || 0,
        lotNo: "", // qty=0 path allows empty lotNo (workbench zero-complete)
        qty: 0,
        storeId: fgPickOrders?.[0]?.storeId ?? null,
        userId: currentUserId ?? 1,
      })),
    });
    
    const submitTime = performance.now() - submitStartTime;
    console.log(`  Batch submit API call completed in ${submitTime.toFixed(2)}ms (${(submitTime / 1000).toFixed(3)}s)`);
    console.log(`📥 Batch submit result:`, result);
    
    // Refresh data once after batch submission
    const refreshStartTime = performance.now();
    await fetchAllCombinedLotData();
    const refreshTime = performance.now() - refreshStartTime;
    console.log(`  Data refresh time: ${refreshTime.toFixed(2)}ms (${(refreshTime / 1000).toFixed(3)}s)`);
    
    const totalTime = performance.now() - startTime;
    console.log(`  [BATCH SUBMIT END]`);
    console.log(`  Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`);
    console.log(`  End time: ${new Date().toISOString()}`);
    
    if (result && result.code === "SUCCESS") {
      setQrScanSuccess(true);
      setTimeout(() => {
        setQrScanSuccess(false);
        checkAndAutoAssignNext();
        if (onSwitchToRecordTab) {
          onSwitchToRecordTab();
        }
        if (onRefreshReleasedOrderCount) {
          onRefreshReleasedOrderCount();
        }
      }, 2000);
    } else {
      console.error("Batch submit failed:", result);
      setQrScanError(true);
    }
    
  } catch (error) {
    console.error("Error submitting all scanned items:", error);
    setQrScanError(true);
  } finally {
    setIsSubmittingAll(false);
  }
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, currentUserId, onSwitchToRecordTab, onRefreshReleasedOrderCount, fgPickOrders]);

  //  Calculate scanned items count
    //  Calculate scanned items count (should match handleSubmitAllScanned filter logic)
    const scannedItemsCount = useMemo(() => {
      const filtered = combinedLotData.filter(lot => {
        const status = lot.stockOutLineStatus;
      const statusLower = String(status || "").toLowerCase();
      if (statusLower === "completed" || statusLower === "complete") {
        return false;
      }
        // Keep consistent with handleSubmitAllScanned: batch submit is only for noLot/expired/unavailable.
        if (lot.noLot === true || isLotAvailabilityExpired(lot) || isInventoryLotLineUnavailable(lot)) {
          return true;
        }
        return false;
      });
      
      //  添加调试日志
      const noLotCount = filtered.filter(l => l.noLot === true).length;
      const normalCount = filtered.filter(l => l.noLot !== true).length;
      //console.log(`📊 scannedItemsCount calculation: total=${filtered.length}, noLot=${noLotCount}, normal=${normalCount}`);
      //console.log(`📊 All items breakdown:`, {
        //total: combinedLotData.length,
        //noLot: combinedLotData.filter(l => l.noLot === true).length,
        //normal: combinedLotData.filter(l => l.noLot !== true).length
      //});
      
      return filtered.length;
    }, [combinedLotData]);
/*
  //  ADD THIS: Auto-stop scan when no data available
  useEffect(() => {
    if (isManualScanning && combinedLotData.length === 0) {
      console.log("⏹️ No data available, auto-stopping QR scan...");
      handleStopScan();
    }
  }, [combinedLotData.length, isManualScanning, handleStopScan]);
  */

  //  Cleanup effect
  useEffect(() => {
    return () => {
      // Cleanup when component unmounts (e.g., when switching tabs)
      if (isManualScanning) {
        console.log("🧹 Pick execution component unmounting, stopping QR scanner...");
        stopScan();
        resetScan();
      }
    };
  }, [isManualScanning, stopScan, resetScan]);

  const getStatusMessage = useCallback((lot: any) => {
    switch (lot.stockOutLineStatus?.toLowerCase()) {
      case 'pending':
        return t("Please finish QR code scan and pick order.");
      case 'partially_completed':
        return t("Partial quantity submitted. Please submit more or complete the order.");
      case 'completed':
        return t("Pick order completed successfully!");
      case 'rejected':
        return t("Lot has been rejected and marked as unavailable.");
      case 'unavailable':
        return t("This order is insufficient, please pick another lot.");
      default:
        return t("Please finish QR code scan and pick order.");
    }
  }, [t]);
  return (
    <TestQrCodeProvider
      lotData={combinedLotData}
      onScanLot={handleQrCodeSubmit}
      onBatchScan={handleBatchScan}
      filterActive={(lot) => (
        lot.lotAvailability !== 'rejected' &&
        lot.stockOutLineStatus !== 'rejected' &&
        lot.stockOutLineStatus !== 'completed'
      )}
    >
      <FormProvider {...formProps}>
        <Stack spacing={2}>
        <Box
          sx={{
            // Keep visible while scrolling, but don't cover global top header
            position: 'sticky',
            top: 0,
            zIndex: 5,
            backgroundColor: 'background.paper',
            pt: 1,
            pb: 1,
            px: 2,
            borderBottom: '1px solid',
            borderColor: 'divider',
            boxShadow: 'none',
          }}
        >
          <LinearProgressWithLabel
            completed={progress.completed}
            total={progress.total}
            label={t("Progress")}
          />
          <ScanStatusAlert
            error={qrScanError}
            success={qrScanSuccess}
            errorMessage={t("QR code does not match any item in current orders.")}
            successMessage={t("QR code verified.")}
          />
        </Box>
          {/* DO Header */}

  
        
  
  {/*  保留：Combined Lot Table - 包含所有 QR 扫描功能 */}
  <Box>
    <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, mt: 2 }}>
      <Typography variant="h6" gutterBottom sx={{ mb: 0 }}>
        {t("All Pick Order Lots")}
      </Typography>
      
      <Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
        {/* Scanner status indicator (always visible) */}
        {/*
        <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
          <QrCodeIcon 
            sx={{ 
              color: isManualScanning ? '#4caf50' : '#9e9e9e',
              animation: isManualScanning ? 'pulse 2s infinite' : 'none',
              '@keyframes pulse': {
                '0%, 100%': { opacity: 1 },
                '50%': { opacity: 0.5 }
              }
            }} 
          />
          <Typography variant="body2" sx={{ color: isManualScanning ? '#4caf50' : '#9e9e9e' }}>
            {isManualScanning ? t("Scanner Active") : t("Scanner Inactive")}
          </Typography>
        </Box>
        */}
        
        {/* Pause/Resume button instead of Start/Stop */}
        {isManualScanning ? (
          <Button
            variant="outlined"
            startIcon={<QrCodeIcon />}
            onClick={handleStopScan}
            color={isExtraTicket ? "secondary" : "primary"}
            sx={{ minWidth: '120px' }}
          >
            {t("Stop QR Scan")}
          </Button>
        ) : (
          <Button
            variant="contained"
            startIcon={<QrCodeIcon />}
            onClick={handleStartScan}
            color={isExtraTicket ? "secondary" : "primary"}
            sx={{ minWidth: '120px' }}
          >
            {t("Start QR Scan")}
          </Button>
        )}
        
        {/*  保留：Submit All Scanned Button */}
        <Button
          variant="contained"
          color={isExtraTicket ? "secondary" : "success"}
          onClick={handleSubmitAllScanned}
          disabled={
            scannedItemsCount === 0
            || isSubmittingAll}
          sx={{ minWidth: '160px' }}
        >
          {isSubmittingAll ? (
            <>
              <CircularProgress size={16} sx={{ mr: 1, color: 'white' }} />
              {t("Submitting...")}
            </>
          ) : (
            `${t("Submit All Scanned")} (${scannedItemsCount})`
          )}
        </Button>
      </Box>
    </Box>

  
{fgPickOrders.length > 0 && (
  <Paper
    sx={{
      p: 2,
      mb: 2,
      borderLeft: isExtraTicket ? "6px solid" : "none",
      borderColor: isExtraTicket ? "secondary.main" : "transparent",
      backgroundColor: isExtraTicket ? "#f8f0ff" : "background.paper",
    }}
  >
    <Stack spacing={2}>
      {isExtraTicket && (
        <Alert severity="info" sx={{ mb: 0.5, borderColor: "secondary.main", color: "secondary.dark" }}>
          {t("Etra Ticket Notice")}
        </Alert>
      )}
      {/* 基本信息 */}
      <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap">
        {isExtraTicket && (
          <Chip
            label={t("isExtra order")}
            color="secondary"
            variant="filled"
            sx={{ fontWeight: 700 }}
          />
        )}
        <Typography variant="subtitle1">
          <strong>{t("Shop Name")}:</strong> {fgPickOrders[0].shopName || '-'}
        </Typography>
        <Typography variant="subtitle1">
          <strong>{t("Store ID")}:</strong> {fgPickOrders[0].storeId || '-'}
        </Typography>
        <Typography variant="subtitle1">
          <strong>{t("Ticket No.")}:</strong> {fgPickOrders[0].ticketNo || '-'}{isExtraTicket ? ` (${t("isExtra order")})` : ""}
        </Typography>
        <Typography variant="subtitle1">
          <strong>{t("Departure Time")}:</strong> {formatDepartureTime(fgPickOrders[0].DepartureTime)}
        </Typography>
      </Stack>
      
      {/*  改进：三个字段显示在一起，使用表格式布局 */}
    {/*  改进：三个字段合并显示 */}
{/*  改进：表格式显示每个 pick order */}
<Box sx={{ 
  p: 2, 
  backgroundColor: '#f5f5f5', 
  borderRadius: 1 
}}>
  <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
    {t("Pick Orders Details")}:
  </Typography>
  
  {(() => {
    const pickOrderCodes = fgPickOrders[0].pickOrderCodes as string[] | string | undefined;
    const deliveryNos = fgPickOrders[0].deliveryNos as string[] | string | undefined;
    const lineCounts = fgPickOrders[0].lineCountsPerPickOrder;
    
    const pickOrderCodesArray = Array.isArray(pickOrderCodes) 
      ? pickOrderCodes 
      : (typeof pickOrderCodes === 'string' ? pickOrderCodes.split(', ') : []);
    
    const deliveryNosArray = Array.isArray(deliveryNos) 
      ? deliveryNos 
      : (typeof deliveryNos === 'string' ? deliveryNos.split(', ') : []);
    
    const lineCountsArray = Array.isArray(lineCounts) ? lineCounts : [];
    
    const maxLength = Math.max(
      pickOrderCodesArray.length, 
      deliveryNosArray.length, 
      lineCountsArray.length
    );
    
    if (maxLength === 0) {
      return <Typography variant="body2" color="text.secondary">-</Typography>;
    }
    
    //  使用与外部基本信息相同的样式
    return Array.from({ length: maxLength }, (_, idx) => (
      <Stack 
        key={idx} 
        direction="row" 
        spacing={4} 
        useFlexGap 
        flexWrap="wrap"
        sx={{ mb: idx < maxLength - 1 ? 1 : 0 }}  // 除了最后一行，都添加底部间距
      >
        <Typography variant="subtitle1">
          <strong>{t("Delivery Order")}:</strong> {deliveryNosArray[idx] || '-'}
        </Typography>
        <Typography variant="subtitle1">
          <strong>{t("Pick Order")}:</strong> {pickOrderCodesArray[idx] || '-'}
        </Typography>
        <Typography variant="subtitle1">
          <strong>{t("Finsihed good items")}:</strong> {lineCountsArray[idx] || '-'}<strong>{t("kinds")}</strong>
        </Typography>
      </Stack>
    ));
  })()}
</Box>
    </Stack>
  </Paper>
    )}
    

            <TableContainer component={Paper}>
              <Table>
                <TableHead>
                  <TableRow>
                    <TableCell>{t("Index")}</TableCell>
                    
                    <TableCell>{t("Item Code")}</TableCell>
                    <TableCell>{t("Item Name")}</TableCell>
                    <TableCell>{t("Route")}</TableCell>
                    <TableCell>{t("Suggest Lot No.")}</TableCell>
                    <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
                    <TableCell align="center">{t("Scan Result")}</TableCell>
                    {/*<TableCell align="center">{t("Qty will submit")}</TableCell>*/}
                    <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  {paginatedData.length === 0 ? (
                    <TableRow>
                      <TableCell colSpan={11} align="center">
                        <Typography variant="body2" color="text.secondary">
                          {t("No data available")}
                        </Typography>
                      </TableCell>
                    </TableRow>
                  ) : (
// 在第 1797-1938 行之间，将整个 map 函数修改为：
paginatedData.map((row, index) => {
  const lot = row.lot;
  const solIdForKey = Number(lot.stockOutLineId) || 0;
  const lotKeyForSubmitQty =
    Number.isFinite(solIdForKey) && solIdForKey > 0 ? `sol:${solIdForKey}` : `${lot.pickOrderLineId}-${lot.lotId}`;
  const lockedSubmitQtyDisplay = isInventoryLotLineUnavailable(lot) ? 0 : resolveSingleSubmitQty(lot);
  const hasPickOverride = Object.prototype.hasOwnProperty.call(pickQtyData, lotKeyForSubmitQty);
  const fromPickRow = hasPickOverride ? pickQtyData[lotKeyForSubmitQty] : undefined;
  const workbenchSubmitQtyDisplay =
    hasPickOverride && fromPickRow !== undefined && fromPickRow !== null && !Number.isNaN(Number(fromPickRow))
      ? Number(fromPickRow)
      : lockedSubmitQtyDisplay;

  //  检查是否是 issue lot
  const isIssueLot = lot.stockOutLineStatus === 'rejected' || !lot.lotNo;
  const rejectDisplay = buildLotRejectDisplayMessage(lot, scanRejectMessageBySolId, t);
  const solSt = String(lot.stockOutLineStatus || "").toLowerCase();
  const isSolRejected =
    solSt === "rejected" || String(lot.lotAvailability || "").toLowerCase() === "rejected";
    const isFirstInGroup = row.isGroupFirst;
    const isLastInGroup = row.isGroupLast;
    const shouldOutline = row.isMultiLotGroup;
    const outlineColor = "#008000";
    const groupUsesAltBg = row.groupDisplayIndex % 2 === 0;
    const groupRowBg = groupUsesAltBg ? "neutral.50" : "background.paper";
  return (
    <TableRow 
      key={`${lot.pickOrderLineId}-${lot.stockOutLineId ?? "null"}-${lot.lotId ?? "nolot"}`}
      sx={{
        backgroundColor: groupRowBg,
        "&:nth-of-type(even)": { backgroundColor: groupRowBg },
        "& .MuiTableCell-root": {
          backgroundColor: groupRowBg,
          ...(shouldOutline
            ? {
                borderTop: isFirstInGroup ? `2px solid ${outlineColor}` : "none",
                borderBottom: isLastInGroup ? `2px solid ${outlineColor}` : "none",
           
              }
            : {}),
        },
        "& .MuiTableCell-root:first-of-type": shouldOutline
          ? {
              borderLeft: `2px solid ${outlineColor}`,
              borderTopLeftRadius: isFirstInGroup ? 6 : 0,
              borderBottomLeftRadius: isLastInGroup ? 6 : 0,
            }
          : {},
        "& .MuiTableCell-root:last-of-type": shouldOutline
          ? {
              borderRight: `2px solid ${outlineColor}`,
              borderTopRightRadius: isFirstInGroup ? 6 : 0,
              borderBottomRightRadius: isLastInGroup ? 6 : 0,
            }
          : {},
      }}
    >
      <TableCell>
  <Typography variant="body2" fontWeight="bold">
    {row.isGroupFirst ? row.groupDisplayIndex : ""}
  </Typography>
</TableCell>

      <TableCell>{row.isGroupFirst ? lot.itemCode : ""}</TableCell>
      <TableCell>
        {row.isGroupFirst ? lot.itemName + '(' + lot.stockUnit + ')' : ""}
      </TableCell>
            <TableCell>
        <Typography variant="body2">
          {lot.noLot ? "-" : lot.routerRoute || "-"}
        </Typography>
      </TableCell>
      <TableCell>
      <Stack direction="row" spacing={1} alignItems="flex-start">
      <Box sx={{ flex: 1, minWidth: 0 }}>
      <Typography
        sx={{
          color:
            rejectDisplay || isSolRejected
              ? 'error.main'
              : isInventoryLotLineUnavailable(lot)
                ? 'error.main'
                : lot.lotAvailability === 'expired'
                  ? 'warning.main'
                  : 'inherit',
        }}
      >
        {lot.lotNo ? (
          rejectDisplay ? (
            <>
              {lot.lotNo}
              <Box
                component="span"
                sx={{ display: 'block', mt: 0.25, typography: 'body2', fontWeight: 400 }}
              >
                {rejectDisplay}
              </Box>
            </>
          ) :
      
            lot.lotAvailability === 'expired' ? (
              <>
                {lot.lotNo}{' '}
                {t('is expired. Please check around have available QR code or not.')}
              </>
          ) : isInventoryLotLineUnavailable(lot) ? (
            <>
              {lot.lotNo}{' '}
              {t('is unavable. Please check around have available QR code or not.')}
            </>
          ) : (
            lot.lotNo
          )
        ) : (
          <Box component="span" sx={{ fontSize: "0.85rem", lineHeight: 1.4 }}>
            {rejectDisplay ||
              t(
                "Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.",
              )}
          </Box>
        )}
      </Typography>
    </Box>
      {Number(lot.stockOutLineId) > 0 && Number(lot.itemId) > 0 ? (
        <Button
          variant="outlined"
          size="small"
          onClick={() => openWorkbenchLotLabelModalForLot(lot)}
          sx={{
            flexShrink: 0,
            fontSize: "0.7rem",
            py: 0.25,
            minWidth: "auto",
            px: 1,
            whiteSpace: "nowrap",
          }}
        >
          {t("lot QR code")}
        </Button>
      ) : null}
      </Stack>
      </TableCell>
      <TableCell align="right">
        {(() => {
          if (!row.isGroupFirst) return null;
          const requiredQty = lot.pickOrderLineRequiredQty;
          if (requiredQty == null) return null;
          const requiredQtyNum = Number(requiredQty);
          if (!Number.isFinite(requiredQtyNum)) return String(requiredQty);
          const uom = lot.uomShortDesc ? `(${lot.uomShortDesc})` : "";
          return requiredQtyNum.toLocaleString() + uom;
        })()}
      </TableCell>
      
      <TableCell align="center">
  {(() => {
    const status = lot.stockOutLineStatus?.toLowerCase();
    const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected';
    const isNoLot = !lot.lotNo;
    
    // rejected lot：显示红色勾选（已扫描但被拒绝）
    if (isRejected && !isNoLot) {
      return (
        <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
          <Checkbox
            checked={true}
            disabled={true}
            readOnly={true}
            size="large"
            sx={{
              color: 'error.main',
              '&.Mui-checked': { color: 'error.main' },
              transform: 'scale(1.3)',
            }}
          />
        </Box>
      );
    }

    // 過期批號：與 noLot 同類——視為已掃到/可處理（含 pending），顯示警示色勾選
    if (isLotAvailabilityExpired(lot) && status !== "rejected") {
      return (
        <Box sx={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
          <Checkbox
            checked={true}
            disabled={true}
            readOnly={true}
            size="large"
            sx={{
              color: "warning.main",
              "&.Mui-checked": { color: "warning.main" },
              transform: "scale(1.3)",
            }}
          />
        </Box>
      );
    }

    // 正常 lot：已扫描（checked/partially_completed/completed）
    if (!isNoLot && status !== 'pending' && status !== 'rejected') {
      return (
        <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
          <Checkbox
            checked={true}
            disabled={true}
            readOnly={true}
            size="large"
            sx={{
              color: 'success.main',
              '&.Mui-checked': { color: 'success.main' },
              transform: 'scale(1.3)',
            }}
          />
        </Box>
      );
    }
    
    // noLot 且已完成/部分完成：显示红色勾选
    if (isNoLot && (status === 'partially_completed' || status === 'completed')) {
      return (
        <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
          <Checkbox
            checked={true}
            disabled={true}
            readOnly={true}
            size="large"
            sx={{
              color: 'error.main',
              '&.Mui-checked': { color: 'error.main' },
              transform: 'scale(1.3)',
            }}
          />
        </Box>
      );
    }
    
    return null;
  })()}
</TableCell>
{/*
<TableCell align="center">
  {workbenchSubmitQtyDisplay}
</TableCell>
*/}
<TableCell align="center">
  <Box sx={{ display: 'flex', justifyContent: 'center' }}>
    {(() => {
      const status = lot.stockOutLineStatus?.toLowerCase();
      const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected';
      const isNoLot = !lot.lotNo;
      const isUnavailableRow = isInventoryLotLineUnavailable(lot);
      // ✅ rejected lot：显示提示文本（换行显示）
      if (isRejected && !isNoLot) {
        const rejectHint = buildLotRejectDisplayMessage(lot, scanRejectMessageBySolId, t);
        return (
          <Typography 
            variant="body2" 
            color="error.main"
            sx={{ 
              textAlign: 'center',
              whiteSpace: 'normal',
              wordBreak: 'break-word',
              maxWidth: '200px',
              lineHeight: 1.5
            }}
          >
            {rejectHint || t("This lot is rejected, please scan another lot.")}
          </Typography>
        );
      }
      
      // noLot 且非 unavailable：保留舊行為（Issue）
      if (isNoLot && !isUnavailableRow) {
        return (
          <Button
            variant="outlined"
            size="small"
            onClick={() => handlelotnull(lot)}
            /*
            disabled={
              status === 'completed' ||
              (Number(lot.stockOutLineId) > 0 && actionBusyBySolId[Number(lot.stockOutLineId)] === true)
            }
            */
            disabled={true}
            sx={{ 
              fontSize: '0.7rem',
              py: 0.5,
              minHeight: '28px',
              minWidth: '60px',
              borderColor: 'warning.main',
              color: 'warning.main'
            }}
          >
            {t("Issue")}
          </Button>
        );
      }
      
      // 正常 lot：Submit + 可編輯數量（Edit 解鎖輸入，不再開 issue form）
      {
        const lotKey = lotKeyForSubmitQty;
        const qtyFieldEnabled = workbenchSubmitQtyFieldEnabledByLotKey[lotKey] === true;
        const displayedSubmitQty = workbenchSubmitQtyDisplay;
        const hasPickOverrideRow = Object.prototype.hasOwnProperty.call(pickQtyData, lotKey);
        const textFieldValue = qtyFieldEnabled
          ? hasPickOverrideRow
            ? String(pickQtyData[lotKey])
            : String(displayedSubmitQty)
          : String(displayedSubmitQty);
        const isRowPicked =
          status === "completed" ||
          status === "checked" ||
          status === "partially_completed" ||
          status === "partially_complete";

        return (
        <Stack direction="row" spacing={1} alignItems="center" justifyContent="center">
          {isRowPicked ? (
            <Typography
              variant="body2"
              sx={{
                width: 96,
                textAlign: "center",
                fontSize: "0.75rem",
                fontWeight: 500,
              }}
            >
              {textFieldValue}
            </Typography>
          ) : (
            <TextField
              type="number"
              size="small"
              disabled={!qtyFieldEnabled}
              value={textFieldValue}
              onKeyDown={(e) => {
                if (!qtyFieldEnabled) return;
                if (e.key !== "{") return;
                e.preventDefault();
                setWorkbenchSubmitQtyFieldEnabledByLotKey((prev) => ({
                  ...prev,
                  [lotKey]: false,
                }));
                (e.currentTarget as HTMLInputElement).blur();
              }}
              onChange={(e) => {
                if (!qtyFieldEnabled) return;
                const n = Number(e.target.value);
                if (Number.isFinite(n) && n < 0) return;
                handlePickQtyChange(lotKey, e.target.value);
              }}
              inputProps={{ min: 0, step: 1 }}
              sx={{
                width: 96,
                "& .MuiInputBase-input": { fontSize: "0.75rem", py: 0.5, textAlign: "center" },
              }}
            />
          )}

          {isRowPicked ? (
            <Typography variant="body2" color="success.main" sx={{ fontSize: "0.75rem", fontWeight: 500 }}>
              {t("")}
            </Typography>
          ) : (
            <>
              <Button
                variant="outlined"
                size="small"
                onClick={() => {
                  setWorkbenchSubmitQtyFieldEnabledByLotKey((prev) => ({
                    ...prev,
                    [lotKey]: !(prev[lotKey] === true),
                  }));
                }}
                disabled={
                  Number(lot.stockOutLineId) > 0 &&
                  actionBusyBySolId[Number(lot.stockOutLineId)] === true
                }
                sx={{
                  fontSize: "0.7rem",
                  py: 0.5,
                  minHeight: "28px",
                  minWidth: "60px",
                  borderColor: "warning.main",
                  color: "warning.main",
                }}
                title={qtyFieldEnabled ? t("Lock quantity") : t("Edit quantity")}
              >
                {t("Edit")}
              </Button>
              <Button
                variant="outlined"
                size="small"
                onClick={() => handleSkip(lot)}
                disabled={
                  (Number(lot.stockOutLineId) > 0 &&
                    issuePickedQtyBySolId[Number(lot.stockOutLineId)] !== undefined) ||
                  (Number(lot.stockOutLineId) > 0 &&
                    actionBusyBySolId[Number(lot.stockOutLineId)] === true)
                }
                sx={{ fontSize: "0.7rem", py: 0.5, minHeight: "28px", minWidth: "60px" }}
              >
                {t("Just Completed")}
              </Button>
            </>
          )}
        </Stack>
        );
      }
    })()}
  </Box>
</TableCell>
    </TableRow>
  );
})
                  )}
                </TableBody>
              </Table>
            </TableContainer>
            
            <TablePagination
              component="div"
              count={combinedLotData.length}
              page={paginationController.pageNum}
              rowsPerPage={paginationController.pageSize}
              onPageChange={handlePageChange}
              onRowsPerPageChange={handlePageSizeChange}
              rowsPerPageOptions={[10, 25, 50,-1]}
              labelRowsPerPage={t("Rows per page")}
              labelDisplayedRows={({ from, to, count }) => 
                `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
              }
            />
          </Box>
        </Stack>
  
        {/* QR Code Scanner works in background - no modal needed */}
        <ManualLotConfirmationModal
          open={manualLotConfirmationOpen}
          onClose={() => {
            setManualLotConfirmationOpen(false);
          }}
          onConfirm={handleManualLotConfirmation}
          expectedLot={null}
          scannedLot={null}
          isLoading={false}
        />
        
    

        <WorkbenchLotLabelPrintModal
          open={workbenchLotLabelModalOpen}
          onClose={() => {
            setWorkbenchLotLabelModalOpen(false);
            setWorkbenchLotLabelReminderText(null);
            setWorkbenchLotLabelContextLot(null);
            setWorkbenchLotLabelInitialPayload(null);
          }}
          initialPayload={workbenchLotLabelInitialPayload}
          initialItemId={
            workbenchLotLabelContextLot != null
              ? Number(workbenchLotLabelContextLot.itemId)
              : null
          }
          defaultPrinterName={defaultLabelPrinterName}
          hideScanSection={
            workbenchLotLabelInitialPayload != null ||
            workbenchLotLabelContextLot != null
          }
          reminderText={workbenchLotLabelReminderText ?? undefined}
          statusTitleText={workbenchLotLabelStatusBanner.text}
          statusTitleSeverity={workbenchLotLabelStatusBanner.severity}
          warehouseCodePrefixFilter={lotFloorPrefixFilter}
          triggerLotAvailableQty={
            workbenchLotLabelContextLot != null
              ? Number(workbenchLotLabelContextLot.availableQty)
              : null
          }
          triggerLotUom={
            workbenchLotLabelContextLot != null
              ? String(
                  workbenchLotLabelContextLot.uomShortDesc ??
                    workbenchLotLabelContextLot.stockUnit ??
                    "",
                ).trim() || null
              : null
          }
          disableScanPick={workbenchLotLabelScanPickDisabled}
          onWorkbenchScanPick={handleWorkbenchLotLabelScanPick}
          submitQty={workbenchLotLabelSubmitQty}
          onSubmitQtyChange={handleWorkbenchLotLabelSubmitQtyChange}
        />
      </FormProvider>

  
    
      
    </TestQrCodeProvider>
  );
};

export default WorkbenchGoodPickExecutionDetail;
