| @@ -77,13 +77,21 @@ export const fetchLotDetail = cache(async (stockInLineId: number) => { | |||
| export const updateInventoryLotLineStatus = async (data: { | |||
| inventoryLotLineId: number; | |||
| status: string; | |||
| qty: number; | |||
| operation?: string; | |||
| //qty: number; | |||
| //operation?: string; | |||
| }) => { | |||
| return serverFetchJson(`${BASE_API_URL}/inventory/lot-line/update-status`, { | |||
| method: 'PUT', | |||
| body: JSON.stringify(data) | |||
| // return await serverFetchJson(`${BASE_API_URL}/inventoryLotLine/updateStatus`, { | |||
| // next: { tags: ["inventoryLotLine"] }, | |||
| // method: 'POST', | |||
| // body: JSON.stringify(data) | |||
| // }); | |||
| return await serverFetchJson<PostInventoryLotLineResponse<InventoryLotLineResult>>(`${BASE_API_URL}/inventoryLotLine/updateStatus`, { | |||
| next: { tags: ["inventoryLotLine"] }, | |||
| method: 'POST', | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| // revalidateTag("po"); | |||
| }; | |||
| export const fetchInventories = cache(async (data: SearchInventory) => { | |||
| @@ -22,7 +22,8 @@ import { useTranslation } from "react-i18next"; | |||
| import QrCodeIcon from '@mui/icons-material/QrCode'; | |||
| 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"; | |||
| interface LotPickData { | |||
| id: number; | |||
| lotId: number; | |||
| @@ -63,6 +64,7 @@ interface LotTableProps { | |||
| setShowInputBody: (show: boolean) => void; | |||
| selectedLotForInput: LotPickData | null; | |||
| generateInputBody: () => any; | |||
| onDataRefresh: () => Promise<void>; | |||
| } | |||
| // ✅ QR Code Modal Component | |||
| @@ -222,6 +224,23 @@ const handleManualSubmit = () => { | |||
| ); | |||
| }; | |||
| */} | |||
| 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]); | |||
| const handleManualSubmit = () => { | |||
| if (manualInput.trim() === lot?.lotNo) { | |||
| // ✅ Success - no error helper text needed | |||
| @@ -327,6 +346,7 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| setShowInputBody, | |||
| selectedLotForInput, | |||
| generateInputBody, | |||
| onDataRefresh, | |||
| }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| @@ -347,24 +367,24 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| // ✅ 添加状态消息生成函数 | |||
| const getStatusMessage = useCallback((lot: LotPickData) => { | |||
| if (!lot.stockOutLineId) { | |||
| return "Please finish QR code scan, QC check and pick order."; | |||
| return t("Please finish QR code scan, QC check and pick order."); | |||
| } | |||
| switch (lot.stockOutLineStatus?.toLowerCase()) { | |||
| case 'pending': | |||
| return "Please finish QC check and pick order."; | |||
| return t("Please finish QC check and pick order."); | |||
| case 'checked': | |||
| return "Please submit the pick order."; | |||
| return t("Please submit the pick order."); | |||
| case 'partially_completed': | |||
| return "Partial quantity submitted. You can submit more or complete the order."; | |||
| return t("Partial quantity submitted. Please submit more or complete the order.") ; | |||
| case 'completed': | |||
| return "Pick order completed successfully!"; | |||
| return t("Pick order completed successfully!"); | |||
| case 'rejected': | |||
| return "QC check failed. Lot has been rejected and marked as unavailable."; | |||
| return t("QC check failed. Lot has been rejected and marked as unavailable."); | |||
| case 'unavailable': | |||
| return "This order is insufficient, please pick another lot."; | |||
| return t("This order is insufficient, please pick another lot."); | |||
| default: | |||
| return "Please finish QR code scan, QC check and pick order."; | |||
| return t("Please finish QR code scan, QC check and pick order."); | |||
| } | |||
| }, []); | |||
| @@ -399,21 +419,34 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| }, []); | |||
| // ✅ Handle QR code submission | |||
| const handleQrCodeSubmit = useCallback((lotNo: string) => { | |||
| const handleQrCodeSubmit = useCallback(async (lotNo: string) => { | |||
| if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { | |||
| console.log(`✅ QR Code verified for lot: ${lotNo}`); | |||
| // ✅ Create stock out line | |||
| onCreateStockOutLine(selectedLotForQr.lotId); | |||
| // ✅ Store the required quantity before creating stock out line | |||
| const requiredQty = selectedLotForQr.requiredQty; | |||
| const lotId = selectedLotForQr.lotId; | |||
| // ✅ Show success message | |||
| console.log("Stock out line created successfully!"); | |||
| // ✅ Create stock out line and wait for it to complete | |||
| await onCreateStockOutLine(selectedLotForQr.lotId); | |||
| // ✅ Close modal | |||
| setQrModalOpen(false); | |||
| setSelectedLotForQr(null); | |||
| // ✅ Set pick quantity AFTER stock out line creation and refresh is complete | |||
| if (selectedRowId) { | |||
| // Add a small delay to ensure the data refresh from onCreateStockOutLine is complete | |||
| setTimeout(() => { | |||
| onPickQtyChange(selectedRowId, lotId, requiredQty); | |||
| console.log(`✅ Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); | |||
| }, 500); // 500ms delay to ensure refresh is complete | |||
| } | |||
| // ✅ Show success message | |||
| console.log("Stock out line created successfully!"); | |||
| } | |||
| }, [selectedLotForQr, onCreateStockOutLine]); | |||
| }, [selectedLotForQr, onCreateStockOutLine, selectedRowId, onPickQtyChange]); | |||
| return ( | |||
| <> | |||
| @@ -520,6 +553,7 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| </TableCell> | |||
| {/* QC Check Button */} | |||
| {/* | |||
| <TableCell align="center"> | |||
| <Button | |||
| variant="outlined" | |||
| @@ -541,7 +575,8 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| > | |||
| {t("QC")} | |||
| </Button> | |||
| </TableCell> | |||
| */} | |||
| {/* Lot Actual Pick Qty */} | |||
| <TableCell align="right"> | |||
| @@ -585,7 +620,42 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| placeholder="0" // Show placeholder instead of default value | |||
| /> | |||
| </TableCell> | |||
| <TableCell align="center"> | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| onClick={async () => { | |||
| if (selectedRowId && selectedRow && lot.stockOutLineId) { | |||
| try { | |||
| // ✅ Call updateStockOutLineStatus to reject the stock out line | |||
| await updateStockOutLineStatus({ | |||
| id: lot.stockOutLineId, | |||
| status: 'rejected', | |||
| qty: 0 | |||
| }); | |||
| // ✅ Refresh data after rejection | |||
| if (onDataRefresh) { | |||
| await onDataRefresh(); | |||
| } | |||
| } catch (error) { | |||
| console.error("Error rejecting lot:", error); | |||
| } | |||
| } | |||
| }} | |||
| // ✅ Only enable if stock out line exists | |||
| disabled={!lot.stockOutLineId} | |||
| sx={{ | |||
| fontSize: '0.7rem', | |||
| py: 0.5, | |||
| minHeight: '28px', | |||
| whiteSpace: 'nowrap', | |||
| minWidth: '40px' | |||
| }} | |||
| > | |||
| {t("Reject")} | |||
| </Button> | |||
| </TableCell> | |||
| {/* Submit Button */} | |||
| <TableCell align="center"> | |||
| <Button | |||
| @@ -600,7 +670,7 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| (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 | |||
| !['pending','checked', 'partially_completed'].includes(lot.stockOutLineStatus.toLowerCase()) // Only these statuses | |||
| } | |||
| // ✅ Allow submission for available AND insufficient_stock lots | |||
| sx={{ | |||
| @@ -515,6 +515,13 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| // ✅ Fix lot selection logic | |||
| const handleLotSelection = useCallback((uniqueLotId: string, lotId: number) => { | |||
| console.log("=== DEBUG: Lot Selection ==="); | |||
| console.log("uniqueLotId:", uniqueLotId); | |||
| console.log("lotId (inventory lot line ID):", lotId); | |||
| // Find the selected lot data | |||
| const selectedLot = lotData.find(lot => lot.lotId === lotId); | |||
| console.log("Selected lot data:", selectedLot); | |||
| // If clicking the same lot, unselect it | |||
| if (selectedLotRowId === uniqueLotId) { | |||
| setSelectedLotRowId(null); | |||
| @@ -1022,7 +1029,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| {selectedRow && ( | |||
| <Box> | |||
| <Typography variant="h6" gutterBottom> | |||
| Item lot to be Pick: {selectedRow.pickOrderCode} - {selectedRow.itemName} | |||
| {t("Item lot to be Pick:")} {selectedRow.pickOrderCode} - {selectedRow.itemName} | |||
| </Typography> | |||
| {/* 检查是否有可用的批次数据 */} | |||
| @@ -1039,6 +1046,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| onSubmitPickQty={handleSubmitPickQty} | |||
| onCreateStockOutLine={handleCreateStockOutLine} | |||
| onQcCheck={handleQcCheck} | |||
| onDataRefresh={handleFetchAllPickOrderDetails} | |||
| onLotSelectForInput={handleLotSelectForInput} | |||
| showInputBody={showInputBody} | |||
| setShowInputBody={setShowInputBody} | |||
| @@ -1079,7 +1087,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| onClick={() => handleInsufficientStock()} | |||
| sx={{ whiteSpace: 'nowrap' }} | |||
| > | |||
| {t("Insufficient Stock & Pick Another Lot")} | |||
| {t("Pick Another Lot")} | |||
| </Button> | |||
| </Stack> | |||
| </Box> | |||
| @@ -228,7 +228,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| // 添加处理提料单创建成功的函数 | |||
| const handlePickOrderCreated = useCallback(() => { | |||
| // 切换到 Assign & Release 标签页 (tabIndex = 1) | |||
| setTabIndex(1); | |||
| setTabIndex(2); | |||
| }, []); | |||
| return ( | |||
| @@ -248,6 +248,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| {t("Pick Order")} | |||
| </Typography> | |||
| </Grid> | |||
| {/* | |||
| <Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||
| <Button onClick={openCreateModal}> | |||
| {t("create")} | |||
| @@ -260,6 +261,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| /> | |||
| } | |||
| </Grid> | |||
| */} | |||
| </Grid> | |||
| </Stack> | |||
| </Box> | |||
| @@ -322,21 +322,41 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||
| // ✅ 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({ | |||
| id: selectedLotId, | |||
| status: 'rejected', | |||
| qty: acceptQty || 0 | |||
| }); | |||
| // ✅ Inventory lot line status: unavailable | |||
| if (selectedLot) { | |||
| await updateInventoryLotLineStatus({ | |||
| inventoryLotLineId: selectedLot.lotId, | |||
| status: 'unavailable', | |||
| qty: acceptQty || 0, | |||
| operation: 'reject' | |||
| }); | |||
| try { | |||
| console.log("=== DEBUG: Updating inventory lot line status ==="); | |||
| console.log("Selected lot:", selectedLot); | |||
| console.log("Selected lot ID:", selectedLotId); | |||
| // ✅ FIX: Only send the fields that the backend expects | |||
| const updateData = { | |||
| inventoryLotLineId: selectedLot.lotId, | |||
| status: 'unavailable' | |||
| // ❌ Remove qty and operation - backend doesn't expect these | |||
| }; | |||
| console.log("Update data:", updateData); | |||
| const result = await updateInventoryLotLineStatus(updateData); | |||
| console.log("✅ Inventory lot line status updated successfully:", result); | |||
| } catch (error) { | |||
| console.error("❌ Error updating inventory lot line status:", error); | |||
| console.error("Error details:", { | |||
| selectedLot, | |||
| selectedLotId, | |||
| acceptQty | |||
| }); | |||
| // Show user-friendly error message | |||
| return; // Stop execution if this fails | |||
| } | |||
| } else { | |||
| console.error("❌ Selected lot not found for inventory update"); | |||
| alert("Selected lot not found. Cannot update inventory status."); | |||
| return; | |||
| } | |||
| // ✅ Close modal and refresh data | |||
| @@ -612,16 +632,16 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||
| <FormControlLabel | |||
| value="1" | |||
| control={<Radio />} | |||
| label="接受出庫" | |||
| label={t("Accept Stock Out")} | |||
| /> | |||
| {/* ✅ Combine options 2 & 3 into one */} | |||
| {/* ✅ Combirne options 2 & 3 into one */} | |||
| <FormControlLabel | |||
| value="2" | |||
| control={<Radio />} | |||
| sx={{"& .Mui-checked": {color: "blue"}}} | |||
| label="上報品檢結果並重新揀貨" | |||
| label={t("Report and Pick another lot")} | |||
| /> | |||
| </RadioGroup> | |||
| )} | |||
| @@ -630,15 +650,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||
| </Grid> | |||
| {/* ✅ 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}> | |||
| @@ -1976,8 +1976,8 @@ const CustomSearchResultsTable = () => { | |||
| )} | |||
| {/* Add Submit Button between tables */} | |||
| {/* Search Results with SearchResults component */} | |||
| {/* | |||
| {hasSearchedSecond && secondSearchResults.length > 0 && selectedSecondSearchItemIds.length > 0 && ( | |||
| <Box sx={{ mt: 2, mb: 2 }}> | |||
| <Box sx={{ display: 'flex', justifyContent: 'left', alignItems: 'center', gap: 2 }}> | |||
| @@ -2001,9 +2001,10 @@ const CustomSearchResultsTable = () => { | |||
| </Typography> | |||
| )} | |||
| </Box> | |||
| </Box> | |||
| )} | |||
| */} | |||
| {/* 创建项目区域 - 修改Group列为可选择的 */} | |||
| {createdItems.length > 0 && ( | |||
| @@ -172,6 +172,16 @@ | |||
| "Job Order Code": "工單編號", | |||
| "QC Check": "QC 檢查", | |||
| "QR Code Scan": "QR Code掃描", | |||
| "Pick Order Details": "提料單詳情" | |||
| "Pick Order Details": "提料單詳情", | |||
| "Partial quantity submitted. Please submit more or complete the order.": "已提料部分數量。請提交更多或完成訂單。", | |||
| "Pick order completed successfully!": "提料單完成成功!", | |||
| "QC check failed. Lot has been rejected and marked as unavailable.": "QC 檢查失敗。批號已拒絕並標記為不可用。", | |||
| "This order is insufficient, please pick another lot.": "此訂單不足,請選擇其他批號。", | |||
| "Please finish QR code scan, QC check and pick order.": "請完成 QR 碼掃描、QC 檢查和提料。", | |||
| "No data available": "沒有資料", | |||
| "Please submit the pick order.": "請提交提料單。", | |||
| "Item lot to be Pick:": "批次貨品提料:", | |||
| "Report and Pick another lot": "上報並需重新選擇批號", | |||
| "Accept Stock Out": "接受出庫", | |||
| "Pick Another Lot": "重新選擇批號" | |||
| } | |||