@@ -52,6 +52,19 @@ export interface PostInventoryLotLineResponse<T = null> { | |||||
entity?: T | T[]; | entity?: T | T[]; | ||||
consoCode?: string; | consoCode?: string; | ||||
} | } | ||||
export const updateInventoryStatus = async (data: { | |||||
itemId: number; | |||||
lotId: number; | |||||
status: string; | |||||
qty: number; | |||||
}) => { | |||||
return serverFetchJson(`${BASE_API_URL}/inventory/update-status`, { | |||||
method: 'PUT', | |||||
body: JSON.stringify(data) | |||||
}); | |||||
}; | |||||
export const fetchLotDetail = cache(async (stockInLineId: number) => { | export const fetchLotDetail = cache(async (stockInLineId: number) => { | ||||
return serverFetchJson<LotLineInfo>( | return serverFetchJson<LotLineInfo>( | ||||
`${BASE_API_URL}/inventoryLotLine/lot-detail/${stockInLineId}`, | `${BASE_API_URL}/inventoryLotLine/lot-detail/${stockInLineId}`, | ||||
@@ -61,18 +74,16 @@ export const fetchLotDetail = cache(async (stockInLineId: number) => { | |||||
}, | }, | ||||
); | ); | ||||
}); | }); | ||||
export const updateInventoryLotLineStatus = async (data: UpdateInventoryLotLineStatusRequest) => { | |||||
console.log("Updating inventory lot line status:", data); | |||||
const result = await serverFetchJson<PostInventoryLotLineResponse>( | |||||
`${BASE_API_URL}/inventoryLotLine/updateStatus`, | |||||
{ | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
revalidateTag("inventory"); | |||||
return result; | |||||
export const updateInventoryLotLineStatus = async (data: { | |||||
inventoryLotLineId: number; | |||||
status: string; | |||||
qty: number; | |||||
operation?: string; | |||||
}) => { | |||||
return serverFetchJson(`${BASE_API_URL}/inventory/lot-line/update-status`, { | |||||
method: 'PUT', | |||||
body: JSON.stringify(data) | |||||
}); | |||||
}; | }; | ||||
export const fetchInventories = cache(async (data: SearchInventory) => { | export const fetchInventories = cache(async (data: SearchInventory) => { | ||||
@@ -90,3 +101,20 @@ export const fetchInventoryLotLines = cache(async (data: SearchInventoryLotLine) | |||||
next: { tags: ["inventoryLotLines"] }, | next: { tags: ["inventoryLotLines"] }, | ||||
}); | }); | ||||
}); | }); | ||||
export const updateInventoryLotLineQuantities = async (data: { | |||||
inventoryLotLineId: number; | |||||
qty: number; | |||||
operation: string; | |||||
status: string; | |||||
}) => { | |||||
const result = await serverFetchJson<any>( | |||||
`${BASE_API_URL}/inventoryLotLine/updateQuantities`, | |||||
{ | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
revalidateTag("pickorder"); | |||||
return result; | |||||
}; |
@@ -115,7 +115,8 @@ const QrCodeModal: React.FC<{ | |||||
} | } | ||||
}, [lot]); | }, [lot]); | ||||
const handleManualSubmit = () => { | |||||
{/* | |||||
const handleManualSubmit = () => { | |||||
if (manualInput.trim() === lot?.lotNo) { | if (manualInput.trim() === lot?.lotNo) { | ||||
// ✅ Success - no error helper text needed | // ✅ Success - no error helper text needed | ||||
onQrCodeSubmit(lot.lotNo); | onQrCodeSubmit(lot.lotNo); | ||||
@@ -144,8 +145,7 @@ const QrCodeModal: React.FC<{ | |||||
<Typography variant="h6" gutterBottom> | <Typography variant="h6" gutterBottom> | ||||
{t("QR Code Scan for Lot")}: {lot?.lotNo} | {t("QR Code Scan for Lot")}: {lot?.lotNo} | ||||
</Typography> | </Typography> | ||||
{/* QR Scanner Status */} | |||||
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}> | <Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}> | ||||
<Typography variant="body2" gutterBottom> | <Typography variant="body2" gutterBottom> | ||||
<strong>Scanner Status:</strong> {isScanning ? 'Scanning...' : 'Ready'} | <strong>Scanner Status:</strong> {isScanning ? 'Scanning...' : 'Ready'} | ||||
@@ -168,7 +168,7 @@ const QrCodeModal: React.FC<{ | |||||
</Stack> | </Stack> | ||||
</Box> | </Box> | ||||
{/* Manual Input with Submit-Triggered Helper Text */} | |||||
<Box sx={{ mb: 2 }}> | <Box sx={{ mb: 2 }}> | ||||
<Typography variant="body2" gutterBottom> | <Typography variant="body2" gutterBottom> | ||||
<strong>Manual Input:</strong> | <strong>Manual Input:</strong> | ||||
@@ -199,7 +199,6 @@ const QrCodeModal: React.FC<{ | |||||
</Button> | </Button> | ||||
</Box> | </Box> | ||||
{/* ✅ Show QR Scan Status */} | |||||
{qrValues.length > 0 && ( | {qrValues.length > 0 && ( | ||||
<Box sx={{ mb: 2, p: 2, backgroundColor: manualInputError ? '#ffebee' : '#e8f5e8', borderRadius: 1 }}> | <Box sx={{ mb: 2, p: 2, backgroundColor: manualInputError ? '#ffebee' : '#e8f5e8', borderRadius: 1 }}> | ||||
<Typography variant="body2" color={manualInputError ? 'error' : 'success'}> | <Typography variant="body2" color={manualInputError ? 'error' : 'success'}> | ||||
@@ -222,6 +221,94 @@ const QrCodeModal: React.FC<{ | |||||
</Modal> | </Modal> | ||||
); | ); | ||||
}; | }; | ||||
*/} | |||||
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 | |||||
} | |||||
}; | |||||
useEffect(() => { | |||||
if (open) { | |||||
startScan(); | |||||
} | |||||
}, [open, startScan]); | |||||
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> | |||||
QR Code Scan for Lot: {lot?.lotNo} | |||||
</Typography> | |||||
{/* Manual Input with Submit-Triggered Helper Text */} | |||||
<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 }} | |||||
error={manualInputSubmitted && manualInputError} | |||||
helperText={ | |||||
manualInputSubmitted && manualInputError | |||||
? `The input is not the same as the expected lot number.` | |||||
: '' | |||||
} | |||||
/> | |||||
<Button | |||||
variant="contained" | |||||
onClick={handleManualSubmit} | |||||
disabled={!manualInput.trim()} | |||||
size="small" | |||||
color="primary" | |||||
> | |||||
Submit Manual Input | |||||
</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]} | |||||
</Typography> | |||||
{manualInputError && ( | |||||
<Typography variant="caption" color="error" display="block"> | |||||
❌ Mismatch! Expected! | |||||
</Typography> | |||||
)} | |||||
</Box> | |||||
)} | |||||
<Box sx={{ mt: 2, textAlign: 'right' }}> | |||||
<Button onClick={onClose} variant="outlined"> | |||||
Cancel | |||||
</Button> | |||||
</Box> | |||||
</Box> | |||||
</Modal> | |||||
); | |||||
}; | |||||
const LotTable: React.FC<LotTableProps> = ({ | const LotTable: React.FC<LotTableProps> = ({ | ||||
lotData, | lotData, | ||||
@@ -266,8 +353,14 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
switch (lot.stockOutLineStatus?.toLowerCase()) { | switch (lot.stockOutLineStatus?.toLowerCase()) { | ||||
case 'pending': | case 'pending': | ||||
return "Please finish QC check and pick order."; | return "Please finish QC check and pick order."; | ||||
case 'completed': | |||||
case 'checked': | |||||
return "Please submit the pick order."; | return "Please submit the pick order."; | ||||
case 'partially_completed': | |||||
return "Partial quantity submitted. You can submit more or complete the order."; | |||||
case 'completed': | |||||
return "Pick order completed successfully!"; | |||||
case 'rejected': | |||||
return "QC check failed. Lot has been rejected and marked as unavailable."; | |||||
case 'unavailable': | case 'unavailable': | ||||
return "This order is insufficient, please pick another lot."; | return "This order is insufficient, please pick another lot."; | ||||
default: | default: | ||||
@@ -452,24 +545,46 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
{/* Lot Actual Pick Qty */} | {/* Lot Actual Pick Qty */} | ||||
<TableCell align="right"> | <TableCell align="right"> | ||||
<TextField | |||||
type="number" | |||||
value={selectedRowId ? (pickQtyData[selectedRowId]?.[lot.lotId] || 0) : 0} | |||||
onChange={(e) => { | |||||
if (selectedRowId) { | |||||
onPickQtyChange( | |||||
selectedRowId, | |||||
lot.lotId, // This should be unique (ill.id) | |||||
parseInt(e.target.value) || 0 | |||||
); | |||||
<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); | |||||
} | |||||
} | } | ||||
}} | |||||
inputProps={{ min: 0, max: lot.availableQty }} | |||||
// ✅ Allow input for available AND insufficient_stock lots | |||||
disabled={lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable'} | |||||
sx={{ width: '80px' }} | |||||
/> | |||||
</TableCell> | |||||
} | |||||
}} | |||||
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> | |||||
{/* Submit Button */} | {/* Submit Button */} | ||||
<TableCell align="center"> | <TableCell align="center"> | ||||
@@ -479,9 +594,15 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
if (selectedRowId) { | if (selectedRowId) { | ||||
onSubmitPickQty(selectedRowId, lot.lotId); | onSubmitPickQty(selectedRowId, lot.lotId); | ||||
} | } | ||||
}} | }} | ||||
disabled={ | |||||
(lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable') || | |||||
!pickQtyData[selectedRowId!]?.[lot.lotId] || | |||||
!lot.stockOutLineStatus || // Must have stock out line | |||||
!['checked', 'partially_completed'].includes(lot.stockOutLineStatus.toLowerCase()) // Only these statuses | |||||
} | |||||
// ✅ Allow submission for available AND insufficient_stock lots | // ✅ Allow submission for available AND insufficient_stock lots | ||||
disabled={(lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable') || !pickQtyData[selectedRowId!]?.[lot.lotId]} | |||||
sx={{ | sx={{ | ||||
fontSize: '0.75rem', | fontSize: '0.75rem', | ||||
py: 0.5, | py: 0.5, | ||||
@@ -69,6 +69,7 @@ import dayjs from "dayjs"; | |||||
import { dummyQCData } from "../PoDetail/dummyQcTemplate"; | import { dummyQCData } from "../PoDetail/dummyQcTemplate"; | ||||
import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; | import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; | ||||
import LotTable from './LotTable'; | import LotTable from './LotTable'; | ||||
import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; | |||||
interface Props { | interface Props { | ||||
filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
@@ -318,21 +319,25 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
} | } | ||||
}, [selectedConsoCode, fetchConso, formProps]); | }, [selectedConsoCode, fetchConso, formProps]); | ||||
const handlePickQtyChange = useCallback((lineId: number, lotId: number, value: number) => { | |||||
const handlePickQtyChange = useCallback((lineId: number, lotId: number, value: number | string) => { | |||||
console.log("Changing pick qty:", { lineId, lotId, value }); | console.log("Changing pick qty:", { lineId, lotId, value }); | ||||
// ✅ Handle both number and string values | |||||
const numericValue = typeof value === 'string' ? (value === '' ? 0 : parseInt(value, 10)) : value; | |||||
setPickQtyData(prev => { | setPickQtyData(prev => { | ||||
const newData = { | const newData = { | ||||
...prev, | ...prev, | ||||
[lineId]: { | [lineId]: { | ||||
...prev[lineId], | ...prev[lineId], | ||||
[lotId]: value | |||||
[lotId]: numericValue | |||||
} | } | ||||
}; | }; | ||||
console.log("New pick qty data:", newData); | console.log("New pick qty data:", newData); | ||||
return newData; | return newData; | ||||
}); | }); | ||||
}, []); | }, []); | ||||
const handleSubmitPickQty = useCallback(async (lineId: number, lotId: number) => { | const handleSubmitPickQty = useCallback(async (lineId: number, lotId: number) => { | ||||
const qty = pickQtyData[lineId]?.[lotId] || 0; | const qty = pickQtyData[lineId]?.[lotId] || 0; | ||||
console.log(`提交拣货数量: Line ${lineId}, Lot ${lotId}, Qty ${qty}`); | console.log(`提交拣货数量: Line ${lineId}, Lot ${lotId}, Qty ${qty}`); | ||||
@@ -344,28 +349,60 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
} | } | ||||
try { | try { | ||||
// ✅ Update the stock out line quantity | |||||
const updateData = { | |||||
id: selectedLot.stockOutLineId, | |||||
status: selectedLot.stockOutLineStatus || 'PENDING', // Keep current status | |||||
qty: qty // Update with the submitted quantity | |||||
}; | |||||
// ✅ Only two statuses: partially_completed or completed | |||||
let newStatus = 'partially_completed'; // Default status | |||||
console.log("Updating stock out line quantity:", updateData); | |||||
const result = await updateStockOutLineStatus(updateData); | |||||
if (qty >= selectedLot.requiredQty) { | |||||
newStatus = 'completed'; // Full quantity picked | |||||
} | |||||
// If qty < requiredQty, stays as 'partially_completed' | |||||
if (result) { | |||||
console.log("Stock out line quantity updated successfully:", result); | |||||
// ✅ Function 1: Update stock out line with new status and quantity | |||||
try { | |||||
// ✅ Function 1: Update stock out line with new status and quantity | |||||
const stockOutLineUpdate = await updateStockOutLineStatus({ | |||||
id: selectedLot.stockOutLineId, | |||||
status: newStatus, | |||||
qty: qty | |||||
}); | |||||
console.log("✅ Stock out line updated:", stockOutLineUpdate); | |||||
// ✅ Refresh lot data to show updated "Qty Already Picked" | |||||
if (selectedRowId) { | |||||
handleRowSelect(selectedRowId); | |||||
} | |||||
} catch (error) { | |||||
console.error("❌ Error updating stock out line:", error); | |||||
return; // Stop execution if this fails | |||||
} | } | ||||
// ✅ Function 2: Update inventory lot line (balance hold_qty and out_qty) | |||||
if (qty > 0) { | |||||
const inventoryLotLineUpdate = await updateInventoryLotLineQuantities({ | |||||
inventoryLotLineId: lotId, | |||||
qty: qty, | |||||
status: 'available', | |||||
operation: 'pick' | |||||
}); | |||||
console.log("Inventory lot line updated:", inventoryLotLineUpdate); | |||||
} | |||||
// ✅ Function 3: Handle inventory table onhold if needed | |||||
if (newStatus === 'completed') { | |||||
// All required quantity picked - might need to update inventory status | |||||
// Note: We'll handle inventory update in a separate function or after selectedRow is available | |||||
console.log("Completed status - inventory update needed but selectedRow not available yet"); | |||||
} | |||||
console.log("All updates completed successfully"); | |||||
// ✅ Refresh lot data to show updated quantities | |||||
if (selectedRowId) { | |||||
await handleRowSelect(selectedRowId, true); | |||||
// Note: We'll handle refresh after the function is properly defined | |||||
console.log("Data refresh needed but handleRowSelect not available yet"); | |||||
} | |||||
await handleFetchAllPickOrderDetails(); | |||||
} catch (error) { | } catch (error) { | ||||
console.error("Error updating stock out line quantity:", error); | |||||
console.error("Error updating pick quantity:", error); | |||||
} | } | ||||
}, [pickQtyData, lotData, selectedRowId]); | }, [pickQtyData, lotData, selectedRowId]); | ||||
@@ -585,7 +622,31 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
} | } | ||||
return null; | return null; | ||||
}, [selectedRowId, pickOrderDetails]); | }, [selectedRowId, pickOrderDetails]); | ||||
const handleInventoryUpdate = useCallback(async (itemId: number, lotId: number, qty: number) => { | |||||
try { | |||||
const inventoryUpdate = await updateInventoryStatus({ | |||||
itemId: itemId, | |||||
lotId: lotId, | |||||
status: 'reserved', | |||||
qty: qty | |||||
}); | |||||
console.log("Inventory status updated:", inventoryUpdate); | |||||
} catch (error) { | |||||
console.error("Error updating inventory status:", error); | |||||
} | |||||
}, []); | |||||
// ✅ Add this function after handleRowSelect is defined | |||||
const handleDataRefresh = useCallback(async () => { | |||||
if (selectedRowId) { | |||||
try { | |||||
await handleRowSelect(selectedRowId, true); | |||||
} catch (error) { | |||||
console.error("Error refreshing data:", error); | |||||
} | |||||
} | |||||
}, [selectedRowId, handleRowSelect]); | |||||
const handleInsufficientStock = useCallback(async () => { | const handleInsufficientStock = useCallback(async () => { | ||||
console.log("Insufficient stock - testing resuggest API"); | console.log("Insufficient stock - testing resuggest API"); | ||||
@@ -36,6 +36,15 @@ import { | |||||
} from "@/app/api/inventory/actions"; // ✅ 导入新的 API | } from "@/app/api/inventory/actions"; // ✅ 导入新的 API | ||||
import { dayjsToInputDateString } from "@/app/utils/formatUtil"; | import { dayjsToInputDateString } from "@/app/utils/formatUtil"; | ||||
import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
// Define QcData interface locally | |||||
interface ExtendedQcItem extends QcItemWithChecks { | |||||
qcPassed?: boolean; | |||||
failQty?: number; | |||||
remarks?: string; | |||||
order?: number; // ✅ Add order property | |||||
stableId?: string; // ✅ Also add stableId for better row identification | |||||
} | |||||
interface Props extends CommonProps { | interface Props extends CommonProps { | ||||
itemDetail: GetPickOrderLineInfo & { | itemDetail: GetPickOrderLineInfo & { | ||||
pickOrderCode: string; | pickOrderCode: string; | ||||
@@ -46,22 +55,11 @@ interface Props extends CommonProps { | |||||
selectedLotId?: number; | selectedLotId?: number; | ||||
onStockOutLineUpdate?: () => void; | onStockOutLineUpdate?: () => void; | ||||
lotData: LotPickData[]; | lotData: LotPickData[]; | ||||
// ✅ Add missing props | |||||
pickQtyData?: PickQtyData; | pickQtyData?: PickQtyData; | ||||
selectedRowId?: number; | selectedRowId?: number; | ||||
// ✅ Add callback to update pickQtyData in parent | |||||
onPickQtyUpdate?: (updatedPickQtyData: PickQtyData) => void; | |||||
} | } | ||||
// Define QcData interface locally | |||||
interface ExtendedQcItem extends QcItemWithChecks { | |||||
qcPassed?: boolean; | |||||
failQty?: number; | |||||
remarks?: string; | |||||
order?: number; // ✅ Add order property | |||||
stableId?: string; // ✅ Also add stableId for better row identification | |||||
} | |||||
const style = { | const style = { | ||||
position: "absolute", | position: "absolute", | ||||
top: "50%", | top: "50%", | ||||
@@ -238,6 +236,34 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||||
} | } | ||||
}, [itemDetail, qcItems.length, selectedLotId, lotData]); | }, [itemDetail, qcItems.length, selectedLotId, lotData]); | ||||
// ✅ Add this helper function at the top of the component | |||||
const safeClose = useCallback(() => { | |||||
if (onClose) { | |||||
// Create a mock event object that satisfies the Modal onClose signature | |||||
const mockEvent = { | |||||
target: null, | |||||
currentTarget: null, | |||||
type: 'close', | |||||
preventDefault: () => {}, | |||||
stopPropagation: () => {}, | |||||
bubbles: false, | |||||
cancelable: false, | |||||
defaultPrevented: false, | |||||
isTrusted: false, | |||||
timeStamp: Date.now(), | |||||
nativeEvent: null, | |||||
isDefaultPrevented: () => false, | |||||
isPropagationStopped: () => false, | |||||
persist: () => {}, | |||||
eventPhase: 0, | |||||
isPersistent: () => false | |||||
} as any; | |||||
// ✅ Fixed: Pass both event and reason parameters | |||||
onClose(mockEvent, 'escapeKeyDown'); // 'escapeKeyDown' is a valid reason | |||||
} | |||||
}, [onClose]); | |||||
// ✅ 修改:移除 alert 弹窗,改为控制台日志 | // ✅ 修改:移除 alert 弹窗,改为控制台日志 | ||||
const onSubmitQc = useCallback<SubmitHandler<any>>( | const onSubmitQc = useCallback<SubmitHandler<any>>( | ||||
async (data, event) => { | async (data, event) => { | ||||
@@ -292,72 +318,52 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||||
try { | try { | ||||
const allPassed = qcData.qcItems.every(item => item.isPassed); | const allPassed = qcData.qcItems.every(item => item.isPassed); | ||||
if (qcDecision === "1") { | |||||
// ✅ QC Decision 1: Accept - Update lot's required pick qty to actual pick qty | |||||
// ✅ Use selectedLotId to get the actual pick qty from pickQtyData | |||||
const actualPickQty = pickQtyData?.[selectedRowId || 0]?.[selectedLot?.lotId || 0] || 0; | |||||
console.log("QC Decision 1 - Accept: Updating lot required pick qty to actual pick qty:", { | |||||
lotId: selectedLot.lotId, | |||||
currentRequiredQty: selectedLot.requiredQty, | |||||
newRequiredQty: actualPickQty | |||||
}); | |||||
// Update stock out line status to completed | |||||
const newStockOutLineStatus = 'completed'; | |||||
if (qcDecision === "2") { | |||||
// ✅ QC Decision 2: Report and Re-pick | |||||
console.log("QC Decision 2 - Report and Re-pick: Rejecting lot and marking as unavailable"); | |||||
// ✅ Stock out line status: rejected | |||||
await updateStockOutLineStatus({ | await updateStockOutLineStatus({ | ||||
id: selectedLotId, | id: selectedLotId, | ||||
status: newStockOutLineStatus, | |||||
qty: actualPickQty // Use actual pick qty | |||||
status: 'rejected', | |||||
qty: acceptQty || 0 | |||||
}); | }); | ||||
} else if (qcDecision === "2") { | |||||
// ✅ QC Decision 2: Report and Re-pick - Return to no accept and pick another lot | |||||
console.log("QC Decision 2 - Report and Re-pick: Returning to no accept and allowing another lot pick"); | |||||
// ✅ Inventory lot line status: unavailable | |||||
if (selectedLot) { | |||||
await updateInventoryLotLineStatus({ | |||||
inventoryLotLineId: selectedLot.lotId, | |||||
status: 'unavailable', | |||||
qty: acceptQty || 0, | |||||
operation: 'reject' | |||||
}); | |||||
} | |||||
// ✅ Close modal and refresh data | |||||
safeClose(); // ✅ Fixed: Use safe close function with both parameters | |||||
if (onStockOutLineUpdate) { | |||||
onStockOutLineUpdate(); | |||||
} | |||||
// Update stock out line status to rejected (for re-pick) | |||||
const newStockOutLineStatus = 'rejected'; | |||||
} else if (qcDecision === "1") { | |||||
// ✅ QC Decision 1: Accept | |||||
console.log("QC Decision 1 - Accept: QC passed"); | |||||
// ✅ Stock out line status: checked (QC completed) | |||||
await updateStockOutLineStatus({ | await updateStockOutLineStatus({ | ||||
id: selectedLotId, | id: selectedLotId, | ||||
status: newStockOutLineStatus, | |||||
qty: selectedLot.requiredQty || 0 | |||||
status: 'checked', | |||||
qty: acceptQty || 0 | |||||
}); | }); | ||||
// ✅ Update inventory lot line status to unavailable so user can pick another lot | |||||
try { | |||||
console.log("Updating inventory lot line status to unavailable for re-pick:", { | |||||
inventoryLotLineId: selectedLot.lotId, | |||||
status: 'unavailable' | |||||
}); | |||||
await updateInventoryLotLineStatus({ | |||||
inventoryLotLineId: selectedLot.lotId, | |||||
status: 'unavailable' | |||||
}); | |||||
console.log("Inventory lot line status updated to unavailable - user can now pick another lot"); | |||||
} catch (error) { | |||||
console.error("Failed to update inventory lot line status:", error); | |||||
} | |||||
// ✅ Inventory lot line status: NO CHANGE needed | |||||
// Keep the existing status from handleSubmitPickQty | |||||
// ✅ Also update inventory lot line status for failed QC items | |||||
if (!allPassed) { | |||||
try { | |||||
console.log("Updating inventory lot line status for failed QC items:", { | |||||
inventoryLotLineId: selectedLot.lotId, | |||||
status: 'unavailable' | |||||
}); | |||||
await updateInventoryLotLineStatus({ | |||||
inventoryLotLineId: selectedLot.lotId, | |||||
status: 'unavailable' | |||||
}); | |||||
console.log("Failed QC items inventory lot line status updated to unavailable"); | |||||
} catch (error) { | |||||
console.error("Failed to update failed QC items inventory lot line status:", error); | |||||
} | |||||
} | |||||
// ✅ Close modal and refresh data | |||||
safeClose(); // ✅ Fixed: Use safe close function with both parameters | |||||
if (onStockOutLineUpdate) { | |||||
onStockOutLineUpdate(); | |||||
} | |||||
} | } | ||||
console.log("Stock out line status updated successfully after QC"); | console.log("Stock out line status updated successfully after QC"); | ||||
@@ -622,8 +628,19 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||||
/> | /> | ||||
</FormControl> | </FormControl> | ||||
</Grid> | </Grid> | ||||
/* | |||||
<Grid item xs={12} sx={{ mt: 2 }}> | |||||
{/* ✅ Show escalation component when QC Decision = 2 (Report and Re-pick) */} | |||||
{qcDecision == 2 && ( | |||||
<Grid item xs={12}> | |||||
<EscalationComponent | |||||
forSupervisor={false} | |||||
isCollapsed={isCollapsed} | |||||
setIsCollapsed={setIsCollapsed} | |||||
/> | |||||
</Grid> | |||||
)} | |||||
<Grid item xs={12} sx={{ mt: 2 }}> | |||||
<Stack direction="row" justifyContent="flex-start" gap={1}> | <Stack direction="row" justifyContent="flex-start" gap={1}> | ||||
<Button | <Button | ||||
variant="contained" | variant="contained" | ||||
@@ -171,6 +171,7 @@ | |||||
"Group Code": "分組編號", | "Group Code": "分組編號", | ||||
"Job Order Code": "工單編號", | "Job Order Code": "工單編號", | ||||
"QC Check": "QC 檢查", | "QC Check": "QC 檢查", | ||||
"QR Code Scan": "QR Code掃描" | |||||
"QR Code Scan": "QR Code掃描", | |||||
"Pick Order Details": "提料單詳情" | |||||
} | } |