| @@ -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": "提料單詳情" | |||||
| } | } | ||||