CANCERYS\kw093 pirms 2 mēnešiem
vecāks
revīzija
771f74c4c6
4 mainītis faili ar 235 papildinājumiem un 43 dzēšanām
  1. +2
    -2
      src/components/JoSave/PickTable.tsx
  2. +217
    -39
      src/components/Jodetail/JobPickExecution.tsx
  3. +1
    -1
      src/components/Jodetail/LotConfirmationModal.tsx
  4. +15
    -1
      src/i18n/zh/jo.json

+ 2
- 2
src/components/JoSave/PickTable.tsx Parādīt failu

@@ -181,7 +181,7 @@ const PickTable: React.FC<Props> = ({
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => {
const uomShortDesc = getUomShortDesc(params.row);
return `${decimalFormatter.format(params.value)} ${params.row.shortUom}`;
return `${decimalFormatter.format(params.value)} ${uomShortDesc}`;
},
},
{
@@ -193,7 +193,7 @@ const PickTable: React.FC<Props> = ({
type: "number",
renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => {
const uomShortDesc = getUomShortDesc(params.row);
return `${decimalFormatter.format(params.value)} ${params.row.shortUom}`;
return `${decimalFormatter.format(params.value)} ${uomShortDesc}`;
},
},
{


+ 217
- 39
src/components/Jodetail/JobPickExecution.tsx Parādīt failu

@@ -32,7 +32,8 @@ import {
AutoAssignReleaseResponse,
checkPickOrderCompletion,
PickOrderCompletionResponse,
checkAndCompletePickOrderByConsoCode
checkAndCompletePickOrderByConsoCode,
confirmLotSubstitution
} from "@/app/api/pickOrder/actions";
// ✅ 修改:使用 Job Order API
import {
@@ -47,7 +48,7 @@ import {
} from "react-hook-form";
import SearchBox, { Criterion } from "../SearchBox";
import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
import { updateInventoryLotLineQuantities, analyzeQrCode, fetchLotDetail } from "@/app/api/inventory/actions";
import QrCodeIcon from '@mui/icons-material/QrCode';
import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
import { useSession } from "next-auth/react";
@@ -55,7 +56,7 @@ import { SessionWithTokens } from "@/config/authConfig";
import { fetchStockInLineInfo } from "@/app/api/po/actions";
import GoodPickExecutionForm from "./JobPickExecutionForm";
import FGPickOrderCard from "./FGPickOrderCard";
import LotConfirmationModal from "./LotConfirmationModal";
interface Props {
filterArgs: Record<string, any>;
}
@@ -332,7 +333,10 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false);
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false);
const [expectedLotData, setExpectedLotData] = useState<any>(null);
const [scannedLotData, setScannedLotData] = useState<any>(null);
const [isConfirmingLot, setIsConfirmingLot] = useState(false);
const [qrScanInput, setQrScanInput] = useState<string>('');
const [qrScanError, setQrScanError] = useState<boolean>(false);
const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
@@ -733,6 +737,182 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}, 1000);
}
}, [combinedLotData, fetchJobOrderData]);
const handleLotMismatch = useCallback((expectedLot: any, scannedLot: any) => {
console.log("Lot mismatch detected:", { expectedLot, scannedLot });
setExpectedLotData(expectedLot);
setScannedLotData(scannedLot);
setLotConfirmationOpen(true);
}, []);

// ✅ Add handleLotConfirmation function
const handleLotConfirmation = useCallback(async () => {
if (!expectedLotData || !scannedLotData || !selectedLotForQr) return;
setIsConfirmingLot(true);
try {
let newLotLineId = scannedLotData?.inventoryLotLineId;
if (!newLotLineId && scannedLotData?.stockInLineId) {
const ld = await fetchLotDetail(scannedLotData.stockInLineId);
newLotLineId = ld.inventoryLotLineId;
}
if (!newLotLineId) {
console.error("No inventory lot line id for scanned lot");
return;
}
await confirmLotSubstitution({
pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId,
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId,
newInventoryLotLineId: newLotLineId
});
setQrScanError(false);
setQrScanSuccess(false);
setQrScanInput('');
setProcessedQrCodes(new Set());
setLastProcessedQr('');
if(selectedLotForQr?.stockOutLineId){
await updateStockOutLineStatus({
id: selectedLotForQr.stockOutLineId,
status: 'checked',
qty: 0
});
}
setLotConfirmationOpen(false);
setExpectedLotData(null);
setScannedLotData(null);
setSelectedLotForQr(null);
await fetchJobOrderData();
console.log("✅ Lot substitution confirmed and data refreshed");
} catch (error) {
console.error("Error confirming lot substitution:", error);
} finally {
setIsConfirmingLot(false);
}
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchJobOrderData]);

const processOutsideQrCode = useCallback(async (latestQr: string) => {
let qrData: any = null;
try {
qrData = JSON.parse(latestQr);
} catch {
console.log("QR is not JSON format");
// ✅ Handle non-JSON QR codes as direct lot numbers
const directLotNo = latestQr.replace(/[{}]/g, '');
if (directLotNo) {
console.log(`Processing direct lot number: ${directLotNo}`);
await handleQrCodeSubmit(directLotNo);
}
return;
}

try {
// Only use the new API when we have JSON with stockInLineId + itemId
if (!(qrData?.stockInLineId && qrData?.itemId)) {
console.log("QR JSON missing required fields (itemId, stockInLineId).");
setQrScanError(true);
setQrScanSuccess(false);
return;
}
// ✅ First, fetch stock in line info to get the lot number
let stockInLineInfo: any;
try {
stockInLineInfo = await fetchStockInLineInfo(qrData.stockInLineId);
console.log("Stock in line info:", stockInLineInfo);
} catch (error) {
console.error("Error fetching stock in line info:", error);
setQrScanError(true);
setQrScanSuccess(false);
return;
}

// Call new analyze-qr-code API
const analysis = await analyzeQrCode({
itemId: qrData.itemId,
stockInLineId: qrData.stockInLineId
});
if (!analysis) {
console.error("analyzeQrCode returned no data");
setQrScanError(true);
setQrScanSuccess(false);
return;
}
const {
itemId: analyzedItemId,
itemCode: analyzedItemCode,
itemName: analyzedItemName,
scanned,
} = analysis || {};
// 1) Find all lots for the same item from current expected list
const sameItemLotsInExpected = combinedLotData.filter(l =>
(l.itemId && analyzedItemId && l.itemId === analyzedItemId) ||
(l.itemCode && analyzedItemCode && l.itemCode === analyzedItemCode)
);
if (!sameItemLotsInExpected || sameItemLotsInExpected.length === 0) {
// Case 3: No item code match
console.error("No item match in expected lots for scanned code");
setQrScanError(true);
setQrScanSuccess(false);
return;
}
// Find the ACTIVE suggested lot (not rejected lots)
const activeSuggestedLots = sameItemLotsInExpected.filter(lot =>
lot.lotAvailability !== 'rejected' &&
lot.stockOutLineStatus !== 'rejected' &&
lot.stockOutLineStatus !== 'completed'
);
if (activeSuggestedLots.length === 0) {
console.warn("All lots for this item are rejected or completed");
setQrScanError(true);
setQrScanSuccess(false);
return;
}
// Use the first active suggested lot as the "expected" lot
const expectedLot = activeSuggestedLots[0];
// 2) Check if the scanned lot matches exactly
if (scanned?.lotNo === expectedLot.lotNo) {
// Case 1: Exact match - process normally
console.log(`✅ Exact lot match: ${scanned.lotNo}`);
await handleQrCodeSubmit(scanned.lotNo);
return;
}
// Case 2: Same item, different lot - show confirmation modal
console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`);
setSelectedLotForQr(expectedLot);
handleLotMismatch(
{
lotNo: expectedLot.lotNo,
itemCode: analyzedItemCode || expectedLot.itemCode,
itemName: analyzedItemName || expectedLot.itemName
},
{
lotNo: scanned?.lotNo || '',
itemCode: analyzedItemCode || expectedLot.itemCode,
itemName: analyzedItemName || expectedLot.itemName,
inventoryLotLineId: scanned?.inventoryLotLineId,
stockInLineId: qrData.stockInLineId
}
);
} catch (error) {
console.error("Error during analyzeQrCode flow:", error);
setQrScanError(true);
setQrScanSuccess(false);
return;
}
}, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]);

const handleManualInputSubmit = useCallback(() => {
if (qrScanInput.trim() !== '') {
@@ -782,43 +962,27 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}
}, [selectedLotForQr, fetchJobOrderData]);

// ✅ Outside QR scanning - process QR codes from outside the page automatically
useEffect(() => {
if (qrValues.length > 0 && combinedLotData.length > 0) {
const latestQr = qrValues[qrValues.length - 1];
// Extract lot number from QR code
let lotNo = '';
try {
const qrData = JSON.parse(latestQr);
if (qrData.stockInLineId && qrData.itemId) {
// For JSON QR codes, we need to fetch the lot number
fetchStockInLineInfo(qrData.stockInLineId)
.then((stockInLineInfo) => {
console.log("Outside QR scan - Stock in line info:", stockInLineInfo);
const extractedLotNo = stockInLineInfo.lotNo;
if (extractedLotNo) {
console.log(`Outside QR scan detected (JSON): ${extractedLotNo}`);
handleQrCodeSubmit(extractedLotNo);
}
})
.catch((error) => {
console.error("Outside QR scan - Error fetching stock in line info:", error);
});
return; // Exit early for JSON QR codes
}
} catch (error) {
// Not JSON format, treat as direct lot number
lotNo = latestQr.replace(/[{}]/g, '');
}
if (qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
return;
}
const latestQr = qrValues[qrValues.length - 1];
if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) {
console.log("QR code already processed, skipping...");
return;
}
if (latestQr && latestQr !== lastProcessedQr) {
console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`);
setLastProcessedQr(latestQr);
setProcessedQrCodes(prev => new Set(prev).add(latestQr));
// For direct lot number QR codes
if (lotNo) {
console.log(`Outside QR scan detected (direct): ${lotNo}`);
handleQrCodeSubmit(lotNo);
}
processOutsideQrCode(latestQr);
}
}, [qrValues, combinedLotData, handleQrCodeSubmit]);
}, [qrValues, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]);

const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => {
if (value === '' || value === null || value === undefined) {
@@ -1487,7 +1651,21 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
combinedLotData={combinedLotData}
onQrCodeSubmit={handleQrCodeSubmitFromModal}
/>

{/* ✅ Add Lot Confirmation Modal */}
{lotConfirmationOpen && expectedLotData && scannedLotData && (
<LotConfirmationModal
open={lotConfirmationOpen}
onClose={() => {
setLotConfirmationOpen(false);
setExpectedLotData(null);
setScannedLotData(null);
}}
onConfirm={handleLotConfirmation}
expectedLot={expectedLotData}
scannedLot={scannedLotData}
isLoading={isConfirmingLot}
/>
)}
{/* ✅ Pick Execution Form Modal */}
{pickExecutionFormOpen && selectedLotForExecutionForm && (
<GoodPickExecutionForm


+ 1
- 1
src/components/Jodetail/LotConfirmationModal.tsx Parādīt failu

@@ -39,7 +39,7 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
scannedLot,
isLoading = false,
}) => {
const { t } = useTranslation("jo");
const { t } = useTranslation("pickOrder");

return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>


+ 15
- 1
src/i18n/zh/jo.json Parādīt failu

@@ -40,6 +40,7 @@
"Route": "路線",
"Qty": "數量",
"Unit": "單位",
"Issue": "問題",
"Location": "位置",
"Scan Result": "掃碼結果",
"Expiry Date": "有效期",
@@ -88,6 +89,19 @@
"qty is required": "數量是必需的",
"qty is not allowed to be greater than remaining available qty": "數量不能大於剩餘可用數量",
"qty is not allowed to be greater than required qty": "數量不能大於需求數量",
"qty is not allowed to be greater than picked qty": "數量不能大於已提料數量"
"qty is not allowed to be greater than picked qty": "數量不能大於已提料數量",
"QR code verified.": "QR碼驗證成功。",
"QR code does not match any item in current orders.": "QR碼不匹配當前工單的物料。",
"This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺失的物料或不良的物料。",
"Pick Execution Issue Form": "提料執行問題表單",
"Verified Qty": "驗證數量",
"Missing item Qty": "缺失的物料數量",
"Bad Item Qty": "不良的物料數量",
"submit": "提交",
"Issue Remark": "問題描述",
"Received Qty": "接收數量",
"Create": "創建",
"Confirm Lot Substitution": "確認批號替換"


}

Notiek ielāde…
Atcelt
Saglabāt