Ver código fonte

update

master
CANCERYS\kw093 3 meses atrás
pai
commit
7d20d0eb7a
8 arquivos alterados com 472 adições e 229 exclusões
  1. +1
    -0
      src/app/api/pickOrder/actions.ts
  2. +54
    -6
      src/components/FinishedGoodSearch/AssignAndRelease.tsx
  3. +1
    -1
      src/components/FinishedGoodSearch/LotTable.tsx
  4. +54
    -6
      src/components/PickOrderSearch/AssignAndRelease.tsx
  5. +314
    -206
      src/components/PickOrderSearch/LotTable.tsx
  6. +14
    -2
      src/components/PickOrderSearch/PickExecution.tsx
  7. +1
    -1
      src/components/PickOrderSearch/newcreatitem.tsx
  8. +33
    -7
      src/i18n/zh/pickOrder.json

+ 1
- 0
src/app/api/pickOrder/actions.ts Ver arquivo

@@ -280,6 +280,7 @@ export interface PickOrderLotDetailResponse {
expiryDate: string;
location: string;
stockUnit: string;
inQty: number;
availableQty: number;
requiredQty: number;
actualPickQty: number;


+ 54
- 6
src/components/FinishedGoodSearch/AssignAndRelease.tsx Ver arquivo

@@ -24,6 +24,7 @@ import { useTranslation } from "react-i18next";
import {
newassignPickOrder,
AssignPickOrderInputs,
releaseAssignedPickOrders, // Add this import
} from "@/app/api/pickOrder/actions";
import { fetchNameList, NameList ,fetchNewNameList, NewNameList} from "@/app/api/user/actions";
import { FormProvider, useForm } from "react-hook-form";
@@ -323,7 +324,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
setPagingController(newPagingController);
}, []);

const handleAssignAndRelease = useCallback(async (data: AssignPickOrderInputs) => {
const handleAssignOnly = useCallback(async (data: AssignPickOrderInputs) => {
if (selectedPickOrderIds.length === 0) return;

setIsUploading(true);
@@ -349,6 +350,44 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
}
}, [selectedPickOrderIds, setIsUploading, fetchNewPageItems, pagingController, filterArgs]);

const handleAssignAndReleaseCombined = useCallback(async (data: AssignPickOrderInputs) => {
if (selectedPickOrderIds.length === 0) return;

setIsUploading(true);
try {
// Step 1: Assign the pick orders
const assignRes = await newassignPickOrder({
pickOrderIds: selectedPickOrderIds,
assignTo: data.assignTo,
});

if (assignRes && assignRes.code === "SUCCESS") {
console.log("Assign successful:", assignRes);
// Step 2: Release the assigned pick orders
const releaseRes = await releaseAssignedPickOrders({
pickOrderIds: selectedPickOrderIds,
assignTo: data.assignTo,
});

if (releaseRes && releaseRes.code === "SUCCESS") {
console.log("Assign and Release successful:", releaseRes);
setModalOpen(false);
setSelectedPickOrderIds([]); // 清空选择
fetchNewPageItems(pagingController, filterArgs);
} else {
console.error("Release failed:", releaseRes);
}
} else {
console.error("Assign failed:", assignRes);
}
} catch (error) {
console.error("Error in assign and release:", error);
} finally {
setIsUploading(false);
}
}, [selectedPickOrderIds, setIsUploading, fetchNewPageItems, pagingController, filterArgs]);

const openAssignModal = useCallback(() => {
setModalOpen(true);
formProps.reset();
@@ -464,8 +503,6 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
) : null}
</TableCell>
{/* Pick Order Status - 只在第一个项目显示 */}
<TableCell>
{index === 0 ? upperFirst(item.status) : null}
@@ -541,7 +578,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
</Grid>
<Grid item xs={12}>
<FormProvider {...formProps}>
<form onSubmit={formProps.handleSubmit(handleAssignAndRelease)}>
<form>
<Grid container spacing={2}>
<Grid item xs={12}>
<FormControl fullWidth>
@@ -579,7 +616,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
</Grid>
<Grid item xs={12}>
<Typography variant="body2" color="warning.main">
{t("This action will assign the selected pick orders to picker.")}
{t("Select an action for the assigned pick orders.")}
</Typography>
</Grid>
<Grid item xs={12}>
@@ -587,9 +624,20 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
<Button variant="outlined" onClick={() => setModalOpen(false)}>
{t("Cancel")}
</Button>
<Button type="submit" variant="contained" color="primary">
<Button
variant="contained"
color="primary"
onClick={formProps.handleSubmit(handleAssignOnly)}
>
{t("Assign")}
</Button>
<Button
variant="contained"
color="secondary"
onClick={formProps.handleSubmit(handleAssignAndReleaseCombined)}
>
{t("Assign and Release")}
</Button>
</Box>
</Grid>
</Grid>


+ 1
- 1
src/components/FinishedGoodSearch/LotTable.tsx Ver arquivo

@@ -372,7 +372,7 @@ const LotTable: React.FC<LotTableProps> = ({
switch (lot.stockOutLineStatus?.toLowerCase()) {
case 'pending':
return t("Please finish QC check and pick order.");
return t("Please submit pick order.");
case 'checked':
return t("Please submit the pick order.");
case 'partially_completed':


+ 54
- 6
src/components/PickOrderSearch/AssignAndRelease.tsx Ver arquivo

@@ -24,6 +24,7 @@ import { useTranslation } from "react-i18next";
import {
newassignPickOrder,
AssignPickOrderInputs,
releaseAssignedPickOrders,
} from "@/app/api/pickOrder/actions";
import { fetchNameList, NameList ,fetchNewNameList, NewNameList} from "@/app/api/user/actions";
import { FormProvider, useForm } from "react-hook-form";
@@ -323,7 +324,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
setPagingController(newPagingController);
}, []);

const handleAssignAndRelease = useCallback(async (data: AssignPickOrderInputs) => {
const handleAssignOnly = useCallback(async (data: AssignPickOrderInputs) => {
if (selectedPickOrderIds.length === 0) return;

setIsUploading(true);
@@ -349,6 +350,44 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
}
}, [selectedPickOrderIds, setIsUploading, fetchNewPageItems, pagingController, filterArgs]);

const handleAssignAndReleaseCombined = useCallback(async (data: AssignPickOrderInputs) => {
if (selectedPickOrderIds.length === 0) return;

setIsUploading(true);
try {
// Step 1: Assign the pick orders
const assignRes = await newassignPickOrder({
pickOrderIds: selectedPickOrderIds,
assignTo: data.assignTo,
});

if (assignRes && assignRes.code === "SUCCESS") {
console.log("Assign successful:", assignRes);
// Step 2: Release the assigned pick orders
const releaseRes = await releaseAssignedPickOrders({
pickOrderIds: selectedPickOrderIds,
assignTo: data.assignTo,
});

if (releaseRes && releaseRes.code === "SUCCESS") {
console.log("Assign and Release successful:", releaseRes);
setModalOpen(false);
setSelectedPickOrderIds([]); // 清空选择
fetchNewPageItems(pagingController, filterArgs);
} else {
console.error("Release failed:", releaseRes);
}
} else {
console.error("Assign failed:", assignRes);
}
} catch (error) {
console.error("Error in assign and release:", error);
} finally {
setIsUploading(false);
}
}, [selectedPickOrderIds, setIsUploading, fetchNewPageItems, pagingController, filterArgs]);

const openAssignModal = useCallback(() => {
setModalOpen(true);
formProps.reset();
@@ -464,8 +503,6 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
) : null}
</TableCell>
{/* Pick Order Status - 只在第一个项目显示 */}
<TableCell>
{index === 0 ? upperFirst(item.status) : null}
@@ -541,7 +578,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
</Grid>
<Grid item xs={12}>
<FormProvider {...formProps}>
<form onSubmit={formProps.handleSubmit(handleAssignAndRelease)}>
<form>
<Grid container spacing={2}>
<Grid item xs={12}>
<FormControl fullWidth>
@@ -579,7 +616,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
</Grid>
<Grid item xs={12}>
<Typography variant="body2" color="warning.main">
{t("This action will assign the selected pick orders to picker.")}
{t("Select an action for the assigned pick orders.")}
</Typography>
</Grid>
<Grid item xs={12}>
@@ -587,9 +624,20 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
<Button variant="outlined" onClick={() => setModalOpen(false)}>
{t("Cancel")}
</Button>
<Button type="submit" variant="contained" color="primary">
<Button
variant="contained"
color="primary"
onClick={formProps.handleSubmit(handleAssignOnly)}
>
{t("Assign")}
</Button>
<Button
variant="contained"
color="secondary"
onClick={formProps.handleSubmit(handleAssignAndReleaseCombined)}
>
{t("Assign and Release")}
</Button>
</Box>
</Grid>
</Grid>


+ 314
- 206
src/components/PickOrderSearch/LotTable.tsx Ver arquivo

@@ -24,6 +24,7 @@ import { GetPickOrderLineInfo } from "@/app/api/pickOrder/actions";
import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
import { updateInventoryLotLineStatus } from "@/app/api/inventory/actions";
import { updateStockOutLineStatus } from "@/app/api/pickOrder/actions";
import { fetchStockInLineInfo } from "@/app/api/po/actions"; // ✅ Add this import
interface LotPickData {
id: number;
lotId: number;
@@ -31,6 +32,7 @@ interface LotPickData {
expiryDate: string;
location: string;
stockUnit: string;
inQty: number;
availableQty: number;
requiredQty: number;
actualPickQty: number;
@@ -65,6 +67,7 @@ interface LotTableProps {
selectedLotForInput: LotPickData | null;
generateInputBody: () => any;
onDataRefresh: () => Promise<void>;
onLotDataRefresh: () => Promise<void>;
}

// ✅ QR Code Modal Component
@@ -81,24 +84,114 @@ const QrCodeModal: React.FC<{
// ✅ Add state to track manual input submission
const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false);
const [manualInputError, setManualInputError] = useState<boolean>(false);
const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false);
const [qrScanFailed, setQrScanFailed] = useState<boolean>(false);
const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
// ✅ Add state to track processed QR codes to prevent re-processing
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
// ✅ Add state to store the scanned QR result
const [scannedQrResult, setScannedQrResult] = useState<string>('');

// ✅ Process scanned QR codes
// ✅ Process scanned QR codes with new format
useEffect(() => {
if (qrValues.length > 0 && lot) {
if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) {
const latestQr = qrValues[qrValues.length - 1];
const qrContent = latestQr.replace(/[{}]/g, '');
if (qrContent === lot.lotNo) {
onQrCodeSubmit(lot.lotNo);
onClose();
resetScan();
} else {
// ✅ Set error state for helper text
setManualInputError(true);
setManualInputSubmitted(true);
// ✅ Check if this QR code has already been processed
if (processedQrCodes.has(latestQr)) {
console.log("QR code already processed, skipping...");
return;
}
// ✅ Add to processed set immediately to prevent re-processing
setProcessedQrCodes(prev => new Set(prev).add(latestQr));
try {
// ✅ Parse QR code as JSON
const qrData = JSON.parse(latestQr);
// ✅ Check if it has the expected structure
if (qrData.stockInLineId && qrData.itemId) {
setIsProcessingQr(true);
setQrScanFailed(false);
// ✅ Fetch stock in line info to get lotNo
fetchStockInLineInfo(qrData.stockInLineId)
.then((stockInLineInfo) => {
console.log("Stock in line info:", stockInLineInfo);
// ✅ Store the scanned result for display
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
// ✅ Compare lotNo from API with expected lotNo
if (stockInLineInfo.lotNo === lot.lotNo) {
console.log(`✅ QR Code verified for lot: ${lot.lotNo}`);
setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo);
onClose();
resetScan();
} else {
console.log(`❌ QR Code mismatch. Expected: ${lot.lotNo}, Got: ${stockInLineInfo.lotNo}`);
setQrScanFailed(true);
setManualInputError(true);
setManualInputSubmitted(true);
// ✅ DON'T stop scanning - allow new QR codes to be processed
}
})
.catch((error) => {
console.error("Error fetching stock in line info:", error);
setScannedQrResult('Error fetching data');
setQrScanFailed(true);
setManualInputError(true);
setManualInputSubmitted(true);
// ✅ DON'T stop scanning - allow new QR codes to be processed
})
.finally(() => {
setIsProcessingQr(false);
});
} else {
// ✅ Fallback to old format (direct lotNo comparison)
const qrContent = latestQr.replace(/[{}]/g, '');
// ✅ Store the scanned result for display
setScannedQrResult(qrContent);
if (qrContent === lot.lotNo) {
setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo);
onClose();
resetScan();
} else {
setQrScanFailed(true);
setManualInputError(true);
setManualInputSubmitted(true);
// ✅ DON'T stop scanning - allow new QR codes to be processed
}
}
} catch (error) {
// ✅ If JSON parsing fails, fallback to old format
console.log("QR code is not JSON format, trying direct comparison");
const qrContent = latestQr.replace(/[{}]/g, '');
// ✅ Store the scanned result for display
setScannedQrResult(qrContent);
if (qrContent === lot.lotNo) {
setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo);
onClose();
resetScan();
} else {
setQrScanFailed(true);
setManualInputError(true);
setManualInputSubmitted(true);
// ✅ DON'T stop scanning - allow new QR codes to be processed
}
}
}
}, [qrValues, lot, onQrCodeSubmit, onClose, resetScan]);
}, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes, stopScan]);

// ✅ Clear states when modal opens or lot changes
useEffect(() => {
@@ -106,6 +199,12 @@ const QrCodeModal: React.FC<{
setManualInput('');
setManualInputSubmitted(false);
setManualInputError(false);
setIsProcessingQr(false);
setQrScanFailed(false);
setQrScanSuccess(false);
setScannedQrResult(''); // ✅ Clear scanned result
// ✅ Clear processed QR codes when modal opens
setProcessedQrCodes(new Set());
}
}, [open]);

@@ -114,151 +213,62 @@ const QrCodeModal: React.FC<{
setManualInput('');
setManualInputSubmitted(false);
setManualInputError(false);
setIsProcessingQr(false);
setQrScanFailed(false);
setQrScanSuccess(false);
setScannedQrResult(''); // ✅ Clear scanned result
// ✅ Clear processed QR codes when lot changes
setProcessedQrCodes(new Set());
}
}, [lot]);

{/*
const handleManualSubmit = () => {
if (manualInput.trim() === lot?.lotNo) {
// ✅ Success - no error helper text needed
onQrCodeSubmit(lot.lotNo);
onClose();
setManualInput('');
} else {
// ✅ Show error helper text after submit
setManualInputError(true);
setManualInputSubmitted(true);
// Don't clear input - let user see what they typed
// ✅ Auto-submit manual input when it matches (but only if QR scan hasn't failed)
useEffect(() => {
if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '' && !qrScanFailed && !qrScanSuccess) {
console.log('🔄 Auto-submitting manual input:', manualInput.trim());
const timer = setTimeout(() => {
setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo);
onClose();
setManualInput('');
setManualInputError(false);
setManualInputSubmitted(false);
}, 200);
return () => clearTimeout(timer);
}
};
}, [manualInput, lot, onQrCodeSubmit, onClose, qrScanFailed, qrScanSuccess]);

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: 400,
}}>
<Typography variant="h6" gutterBottom>
{t("QR Code Scan for Lot")}: {lot?.lotNo}
</Typography>
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
<Typography variant="body2" gutterBottom>
<strong>Scanner Status:</strong> {isScanning ? 'Scanning...' : 'Ready'}
</Typography>
<Stack direction="row" spacing={1}>
<Button
variant="contained"
onClick={isScanning ? stopScan : startScan}
size="small"
>
{isScanning ? 'Stop Scan' : 'Start Scan'}
</Button>
<Button
variant="outlined"
onClick={resetScan}
size="small"
>
Reset
</Button>
</Stack>
</Box>

<Box sx={{ mb: 2 }}>
<Typography variant="body2" gutterBottom>
<strong>Manual Input:</strong>
</Typography>
<TextField
fullWidth
size="small"
value={manualInput}
onChange={(e) => setManualInput(e.target.value)}
sx={{ mb: 1 }}
// ✅ Only show error after submit button is clicked
error={manualInputSubmitted && manualInputError}
helperText={
// ✅ Show helper text only after submit with error
manualInputSubmitted && manualInputError
? `The input is not the same as the expected lot number. Expected: ${lot?.lotNo}`
: ''
}
/>
<Button
variant="contained"
onClick={handleManualSubmit}
disabled={!manualInput.trim()}
size="small"
color="primary"
>
Submit Manual Input
</Button>
</Box>

{qrValues.length > 0 && (
<Box sx={{ mb: 2, p: 2, backgroundColor: manualInputError ? '#ffebee' : '#e8f5e8', borderRadius: 1 }}>
<Typography variant="body2" color={manualInputError ? 'error' : 'success'}>
<strong>QR Scan Result:</strong> {qrValues[qrValues.length - 1]}
</Typography>
{manualInputError && (
<Typography variant="caption" color="error" display="block">
❌ Mismatch! Expected: {lot?.lotNo}
</Typography>
)}
</Box>
)}

<Box sx={{ mt: 2, textAlign: 'right' }}>
<Button onClick={onClose} variant="outlined">
Cancel
</Button>
</Box>
</Box>
</Modal>
);
};
*/}
useEffect(() => {
if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '') {
// Auto-submit when manual input matches the expected lot number
console.log('🔄 Auto-submitting manual input:', manualInput.trim());
// Add a small delay to ensure proper execution order
const timer = setTimeout(() => {
onQrCodeSubmit(lot.lotNo);
onClose();
setManualInput('');
setManualInputError(false);
setManualInputSubmitted(false);
}, 200); // 200ms delay
return () => clearTimeout(timer);
}
}, [manualInput, lot, onQrCodeSubmit, onClose]);
// ✅ Add the missing handleManualSubmit function
const handleManualSubmit = () => {
if (manualInput.trim() === lot?.lotNo) {
// ✅ Success - no error helper text needed
setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo);
onClose();
setManualInput('');
} else {
// ✅ Show error helper text after submit
setQrScanFailed(true);
setManualInputError(true);
setManualInputSubmitted(true);
// Don't clear input - let user see what they typed
}
};
useEffect(() => {

// ✅ Add function to restart scanning after manual input error
const handleRestartScan = () => {
setQrScanFailed(false);
setManualInputError(false);
setManualInputSubmitted(false);
setProcessedQrCodes(new Set()); // Clear processed QR codes
startScan(); // Restart scanning
};

useEffect(() => {
if (open) {
startScan();
}
}, [open, startScan]);

return (
<Modal open={open} onClose={onClose}>
<Box sx={{
@@ -272,24 +282,41 @@ useEffect(() => {
minWidth: 400,
}}>
<Typography variant="h6" gutterBottom>
QR Code Scan for Lot: {lot?.lotNo}
{t("QR Code Scan for Lot")}: {lot?.lotNo}
</Typography>
{/* ✅ Show processing status */}
{isProcessingQr && (
<Box sx={{ mb: 2, p: 2, backgroundColor: '#e3f2fd', borderRadius: 1 }}>
<Typography variant="body2" color="primary">
{t("Processing QR code...")}
</Typography>
</Box>
)}
{/* Manual Input with Submit-Triggered Helper Text */}
<Box sx={{ mb: 2 }}>
<Typography variant="body2" gutterBottom>
<strong>Manual Input:</strong>
<strong>{t("Manual Input")}:</strong>
</Typography>
<TextField
fullWidth
size="small"
value={manualInput}
onChange={(e) => setManualInput(e.target.value)}
onChange={(e) => {
setManualInput(e.target.value);
// ✅ Reset error states when user starts typing
if (qrScanFailed || manualInputError) {
setQrScanFailed(false);
setManualInputError(false);
setManualInputSubmitted(false);
}
}}
sx={{ mb: 1 }}
error={manualInputSubmitted && manualInputError}
helperText={
manualInputSubmitted && manualInputError
? `The input is not the same as the expected lot number.`
? `${t("The input is not the same as the expected lot number.")}`
: ''
}
/>
@@ -300,19 +327,25 @@ useEffect(() => {
size="small"
color="primary"
>
Submit Manual Input
{t("Submit")}
</Button>
</Box>

{/* Show QR Scan Status */}
{qrValues.length > 0 && (
<Box sx={{ mb: 2, p: 2, backgroundColor: manualInputError ? '#ffebee' : '#e8f5e8', borderRadius: 1 }}>
<Typography variant="body2" color={manualInputError ? 'error' : 'success'}>
<strong>QR Scan Result:</strong> {qrValues[qrValues.length - 1]}
<Box sx={{
mb: 2,
p: 2,
backgroundColor: qrScanFailed ? '#ffebee' : qrScanSuccess ? '#e8f5e8' : '#f5f5f5',
borderRadius: 1
}}>
<Typography variant="body2" color={qrScanFailed ? 'error' : qrScanSuccess ? 'success' : 'text.secondary'}>
<strong>{t("QR Scan Result:")}</strong> {scannedQrResult}
</Typography>
{manualInputError && (
<Typography variant="caption" color="error" display="block">
❌ Mismatch! Expected!

{qrScanSuccess && (
<Typography variant="caption" color="success" display="block">
✅ {t("Verified successfully!")}
</Typography>
)}
</Box>
@@ -320,7 +353,7 @@ useEffect(() => {

<Box sx={{ mt: 2, textAlign: 'right' }}>
<Button onClick={onClose} variant="outlined">
Cancel
{t("Cancel")}
</Button>
</Box>
</Box>
@@ -347,6 +380,7 @@ const LotTable: React.FC<LotTableProps> = ({
selectedLotForInput,
generateInputBody,
onDataRefresh,
onLotDataRefresh,
}) => {
const { t } = useTranslation("pickOrder");
@@ -367,12 +401,12 @@ const LotTable: React.FC<LotTableProps> = ({
// ✅ 添加状态消息生成函数
const getStatusMessage = useCallback((lot: LotPickData) => {
if (!lot.stockOutLineId) {
return t("Please finish QR code scan, QC check and pick order.");
return t("Please finish QR code scanand pick order.");
}
switch (lot.stockOutLineStatus?.toLowerCase()) {
case 'pending':
return t("Please finish QC check and pick order.");
return t("Please finish pick order.");
case 'checked':
return t("Please submit the pick order.");
case 'partially_completed':
@@ -384,7 +418,7 @@ const LotTable: React.FC<LotTableProps> = ({
case 'unavailable':
return t("This order is insufficient, please pick another lot.");
default:
return t("Please finish QR code scan, QC check and pick order.");
return t("Please finish QR code scan and pick order.");
}
}, []);

@@ -417,7 +451,15 @@ const LotTable: React.FC<LotTableProps> = ({
pageSize: newPageSize,
});
}, []);

const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => {
if (!selectedRowId) return lot.availableQty;
const lactualPickQty = lot.actualPickQty || 0;
const actualPickQty = pickQtyData[selectedRowId]?.[lot.lotId] || 0;
const remainingQty = lot.inQty - actualPickQty - lactualPickQty;
// Ensure it doesn't go below 0
return Math.max(0, remainingQty);
}, [selectedRowId, pickQtyData]);
// ✅ Handle QR code submission
const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
@@ -458,13 +500,19 @@ const LotTable: React.FC<LotTableProps> = ({
<TableCell>{t("Lot#")}</TableCell>
<TableCell>{t("Lot Expiry Date")}</TableCell>
<TableCell>{t("Lot Location")}</TableCell>
<TableCell align="right">{t("Available Lot")}</TableCell>
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
<TableCell>{t("Stock Unit")}</TableCell>
<TableCell align="center">{t("QR Code Scan")}</TableCell>
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
<TableCell align="right">{t("Original Available Qty")}</TableCell>
<TableCell align="center">{t("Lot Actual Pick Qty")}</TableCell>
<TableCell align="right">{t("Reject")}</TableCell>
<TableCell align="center">{t("Submit")}</TableCell>
{/*<TableCell align="right">{t("Available Lot")}</TableCell>*/}
<TableCell align="right">{t("Remaining Available Qty")}</TableCell>
{/*<TableCell align="center">{t("QR Code Scan")}</TableCell>*/}
{/*}
<TableCell align="center">{t("Reject")}</TableCell>
*/}

<TableCell align="center">{t("Action")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
@@ -503,11 +551,102 @@ const LotTable: React.FC<LotTableProps> = ({
</TableCell>
<TableCell>{lot.expiryDate}</TableCell>
<TableCell>{lot.location}</TableCell>
<TableCell align="right">{lot.availableQty.toLocaleString()}</TableCell>
<TableCell align="right">{lot.requiredQty.toLocaleString()}</TableCell>
<TableCell>{lot.stockUnit}</TableCell>
<TableCell align="right">{lot.requiredQty.toLocaleString()}</TableCell>
<TableCell align="right">{lot.inQty.toLocaleString()??'0'}</TableCell>
<TableCell align="center">
{/* Show QR Scan Button if not scanned, otherwise show TextField */}
{!lot.stockOutLineId ? (
<Button
variant="outlined"
size="small"
onClick={() => {
setSelectedLotForQr(lot);
setQrModalOpen(true);
resetScan();
}}
// ✅ Disable when:
// 1. Lot is expired or unavailable
// 2. Not selected (selectedLotRowId doesn't match)
disabled={
(lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable') ||
selectedLotRowId !== `row_${index}`
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '40px', // ✅ Match TextField height
whiteSpace: 'nowrap',
minWidth: '80px', // ✅ Match TextField width
// ✅ Visual feedback
opacity: selectedLotRowId === `row_${index}` ? 1 : 0.5
}}
startIcon={<QrCodeIcon />}
title={
selectedLotRowId !== `row_${index}`
? "Please select this lot first to enable QR scanning"
: "Click to scan QR code"
}
>
{t("Scan")}
</Button>
) : (
<TextField
type="number"
value={selectedRowId ? (pickQtyData[selectedRowId]?.[lot.lotId] || '') : ''}
onChange={(e) => {
if (selectedRowId) {
const inputValue = e.target.value;
// ✅ Fixed: Handle empty string and prevent leading zeros
if (inputValue === '') {
// Allow empty input (user can backspace to clear)
onPickQtyChange(selectedRowId, lot.lotId, 0);
} else {
// Parse the number and prevent leading zeros
const numValue = parseInt(inputValue, 10);
if (!isNaN(numValue)) {
onPickQtyChange(selectedRowId, lot.lotId, numValue);
}
}
}
}}
onBlur={(e) => {
// ✅ Fixed: When input loses focus, ensure we have a valid number
if (selectedRowId) {
const currentValue = pickQtyData[selectedRowId]?.[lot.lotId];
if (currentValue === undefined || currentValue === null) {
// Set to 0 if no value
onPickQtyChange(selectedRowId, lot.lotId, 0);
}
}
}}
inputProps={{
min: 0,
max: lot.availableQty,
step: 1 // Allow only whole numbers
}}
// ✅ Allow input for available AND insufficient_stock lots
disabled={lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable'}
sx={{
width: '80px',
'& .MuiInputBase-root': {
height: '40px', // ✅ Match table cell height
},
'& .MuiInputBase-input': {
height: '40px',
padding: '8px 12px', // ✅ Adjust padding to center text vertically
}
}}
placeholder="0" // Show placeholder instead of default value
/>
)}
</TableCell>
{/*<TableCell align="right">{lot.availableQty.toLocaleString()}</TableCell>*/}
<TableCell align="right">{calculateRemainingAvailableQty(lot).toLocaleString()}</TableCell>
{/* <TableCell>{lot.stockUnit}</TableCell> */}
{/* QR Code Scan Button */}
{/*
<TableCell align="center">
<Box sx={{ textAlign: 'center' }}>
<Button
@@ -547,11 +686,11 @@ const LotTable: React.FC<LotTableProps> = ({
>
{lot.stockOutLineId ? t("Scanned") : t("Scan")}
</Button>
</Box>
</TableCell>
*/}
{/* QC Check Button */}
{/*
<TableCell align="center">
@@ -579,48 +718,9 @@ const LotTable: React.FC<LotTableProps> = ({
{/* Lot Actual Pick Qty */}
<TableCell align="right">
<TextField
type="number"
value={selectedRowId ? (pickQtyData[selectedRowId]?.[lot.lotId] || '') : ''} // ✅ Fixed: Use empty string instead of 0
onChange={(e) => {
if (selectedRowId) {
const inputValue = e.target.value;
// ✅ Fixed: Handle empty string and prevent leading zeros
if (inputValue === '') {
// Allow empty input (user can backspace to clear)
onPickQtyChange(selectedRowId, lot.lotId, 0);
} else {
// Parse the number and prevent leading zeros
const numValue = parseInt(inputValue, 10);
if (!isNaN(numValue)) {
onPickQtyChange(selectedRowId, lot.lotId, numValue);
}
}
}
}}
onBlur={(e) => {
// ✅ Fixed: When input loses focus, ensure we have a valid number
if (selectedRowId) {
const currentValue = pickQtyData[selectedRowId]?.[lot.lotId];
if (currentValue === undefined || currentValue === null) {
// Set to 0 if no value
onPickQtyChange(selectedRowId, lot.lotId, 0);
}
}
}}
inputProps={{
min: 0,
max: lot.availableQty,
step: 1 // Allow only whole numbers
}}
// ✅ Allow input for available AND insufficient_stock lots
disabled={lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable'}
sx={{ width: '80px' }}
placeholder="0" // Show placeholder instead of default value
/>
</TableCell>
<TableCell align="center">
<Stack direction="column" spacing={1} alignItems="center">
<Button
variant="outlined"
size="small"
@@ -638,6 +738,9 @@ const LotTable: React.FC<LotTableProps> = ({
if (onDataRefresh) {
await onDataRefresh();
}
if (onLotDataRefresh) {
await onLotDataRefresh();
}
} catch (error) {
console.error("Error rejecting lot:", error);
}
@@ -655,9 +758,13 @@ const LotTable: React.FC<LotTableProps> = ({
>
{t("Reject")}
</Button>
</Stack>
{/*}
</TableCell>
{/* Submit Button */}
<TableCell align="center">
*/}
<Stack direction="column" spacing={1} alignItems="center">
<Button
variant="contained"
onClick={() => {
@@ -681,6 +788,7 @@ const LotTable: React.FC<LotTableProps> = ({
>
{t("Submit")}
</Button>
</Stack>
</TableCell>
</TableRow>
))
@@ -695,7 +803,7 @@ const LotTable: React.FC<LotTableProps> = ({
{paginatedLotTableData.map((lot, index) => (
<Box key={lot.id} sx={{ mb: 1 }}>
<Typography variant="body2" color="text.secondary">
<strong>Lot {lot.lotNo}:</strong> {getStatusMessage(lot)}
<strong>{t("Lot")} {lot.lotNo}:</strong> {getStatusMessage(lot)}
</Typography>
</Box>
))}


+ 14
- 2
src/components/PickOrderSearch/PickExecution.tsx Ver arquivo

@@ -84,6 +84,7 @@ interface LotPickData {
expiryDate: string;
location: string;
stockUnit: string;
inQty: number;
availableQty: number;
requiredQty: number;
actualPickQty: number;
@@ -562,6 +563,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
expiryDate: lot.expiryDate ? new Date(lot.expiryDate).toLocaleDateString() : 'N/A',
location: lot.location,
stockUnit: lot.stockUnit,
inQty: lot.inQty,
availableQty: lot.availableQty,
requiredQty: lot.requiredQty,
actualPickQty: lot.actualPickQty || 0,
@@ -651,7 +653,15 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
console.error("Error updating inventory status:", error);
}
}, []);
const handleLotDataRefresh = useCallback(async () => {
if (selectedRowId) {
try {
await handleRowSelect(selectedRowId, true); // Preserve lot selection
} catch (error) {
console.error("Error refreshing lot data:", error);
}
}
}, [selectedRowId, handleRowSelect]);
// ✅ Add this function after handleRowSelect is defined
const handleDataRefresh = useCallback(async () => {
if (selectedRowId) {
@@ -1027,7 +1037,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
) : (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
<Typography variant="body2" color="text.secondary">
正在載入數據...
{t("Loading data...")}
</Typography>
</Box>
)}
@@ -1051,10 +1061,12 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
selectedLotId={selectedLotId}
onLotSelection={handleLotSelection}
onPickQtyChange={handlePickQtyChange}
onSubmitPickQty={handleSubmitPickQty}
onCreateStockOutLine={handleCreateStockOutLine}
onQcCheck={handleQcCheck}
onDataRefresh={handleFetchAllPickOrderDetails}
onLotDataRefresh={handleLotDataRefresh}
onLotSelectForInput={handleLotSelectForInput}
showInputBody={showInputBody}
setShowInputBody={setShowInputBody}


+ 1
- 1
src/components/PickOrderSearch/newcreatitem.tsx Ver arquivo

@@ -1916,7 +1916,7 @@ const CustomSearchResultsTable = () => {
textField: {
size: "small",
label: t("Target Date"),
sx: { width: 180 }
sx: { width: 200 }
},
}}
/>


+ 33
- 7
src/i18n/zh/pickOrder.json Ver arquivo

@@ -148,11 +148,11 @@
"Material": "食材",
"Job Order": "工單",
"End Product": "成品",
"Lot Expiry Date": "批號到期日",
"Lot Location": "批號位置",
"Available Lot": "批號可用提料數",
"Lot Required Pick Qty": "批號所需提料數",
"Lot Actual Pick Qty": "批號實際提料數",
"Lot Expiry Date": "到期日",
"Lot Location": "位置",
"Available Lot": "可用提料數",
"Lot Required Pick Qty": "所需數",
"Lot Actual Pick Qty": "此單將提數",
"Lot#": "批號",
"Submit": "提交",
"Created Items": "已建立貨品",
@@ -183,11 +183,37 @@
"Item lot to be Pick:": "批次貨品提料:",
"Report and Pick another lot": "上報並需重新選擇批號",
"Accept Stock Out": "接受出庫",
"Pick Another Lot": "重新選擇批號",
"Pick Another Lot": "欠數,並重新選擇批號",
"Lot No": "批號",
"Expiry Date": "到期日",
"Location": "位置",
"All Pick Order Lots": "所有提料單批次",
"Completed": "已完成",
"Finished Good Order": "成品訂單"
"Finished Good Order": "成品訂單",
"Assign and Release": "分派並放單",
"Original Available Qty": "原可用數",
"Remaining Available Qty": "剩餘",
"Please submit pick order.": "請提交提料單。",
"Please finish QR code scan and pick order.": "請完成 QR 碼掃描和提料。",
"Please finish QR code scanand pick order.": "請完成 QR 碼掃描和提料。",
"First created group": "首次建立分組",
"Latest created group": "最新建立分組",
"Manual Input": "手動輸入",
"QR Code Scan for Lot": " QR 碼掃描批次",
"Processing QR code...": "處理 QR 碼...",
"The input is not the same as the expected lot number.": "輸入的批次號碼與預期的不符。",
"Verified successfully!": "驗證成功!",
"Cancel": "取消",
"Scan": "掃描",
"Scanned": "已掃描",
"Loading data...": "正在載入數據...",
"No available stock for this item": "沒有可用庫存",
"No lot details available for this item": "沒有批次詳情",
"Current stock is insufficient or unavailable": "現時可用庫存不足或不可用",
"Please check inventory status": "請檢查庫存狀態",
"Rows per page": "每頁行數",
"QR Scan Result:": "QR 掃描結果:",
"Action": "操作",
"Please finish pick order.": "請完成提料。",
"Lot": "批號"
}

Carregando…
Cancelar
Salvar