| @@ -91,7 +91,9 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({ | |||||
| const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>([]); | const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>([]); | ||||
| const [verifiedQty, setVerifiedQty] = useState<number>(0); | const [verifiedQty, setVerifiedQty] = useState<number>(0); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const missSet = formData.missQty != null; | |||||
| const badItemSet = formData.badItemQty != null; | |||||
| const badPackageSet = (formData as any).badPackageQty != null; | |||||
| const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { | const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { | ||||
| return lot.availableQty || 0; | return lot.availableQty || 0; | ||||
| }, []); | }, []); | ||||
| @@ -162,9 +164,9 @@ useEffect(() => { | |||||
| storeLocation: selectedLot.location, | storeLocation: selectedLot.location, | ||||
| requiredQty: selectedLot.requiredQty, | requiredQty: selectedLot.requiredQty, | ||||
| actualPickQty: initialVerifiedQty, | actualPickQty: initialVerifiedQty, | ||||
| missQty: 0, | |||||
| badItemQty: 0, | |||||
| badPackageQty: 0, // Bad Package Qty (frontend only) | |||||
| missQty: undefined, | |||||
| badItemQty: undefined, | |||||
| badPackageQty: undefined, | |||||
| issueRemark: "", | issueRemark: "", | ||||
| pickerName: "", | pickerName: "", | ||||
| handledBy: undefined, | handledBy: undefined, | ||||
| @@ -195,10 +197,10 @@ useEffect(() => { | |||||
| const newErrors: FormErrors = {}; | const newErrors: FormErrors = {}; | ||||
| const ap = Number(verifiedQty) || 0; | const ap = Number(verifiedQty) || 0; | ||||
| const miss = Number(formData.missQty) || 0; | const miss = Number(formData.missQty) || 0; | ||||
| const badItem = Number(formData.badItemQty) || 0; | |||||
| const badPackage = Number((formData as any).badPackageQty) || 0; | |||||
| const totalBad = badItem + badPackage; | |||||
| const total = ap + miss + totalBad; | |||||
| const badItem = Number(formData.badItemQty) ?? 0; | |||||
| const badPackage = Number((formData as any).badPackageQty) ?? 0; | |||||
| const totalBadQty = badItem + badPackage; | |||||
| const total = ap + miss + totalBadQty; | |||||
| const availableQty = selectedLot?.availableQty || 0; | const availableQty = selectedLot?.availableQty || 0; | ||||
| // 1. Check actualPickQty cannot be negative | // 1. Check actualPickQty cannot be negative | ||||
| @@ -231,7 +233,7 @@ useEffect(() => { | |||||
| } | } | ||||
| // 5. At least one field must have a value | // 5. At least one field must have a value | ||||
| if (ap === 0 && miss === 0 && totalBad === 0) { | |||||
| if (ap === 0 && miss === 0 && totalBadQty === 0) { | |||||
| newErrors.actualPickQty = t("Enter pick qty or issue qty"); | newErrors.actualPickQty = t("Enter pick qty or issue qty"); | ||||
| } | } | ||||
| @@ -288,11 +290,12 @@ useEffect(() => { | |||||
| const submissionData: PickExecutionIssueData = { | const submissionData: PickExecutionIssueData = { | ||||
| ...(formData as PickExecutionIssueData), | ...(formData as PickExecutionIssueData), | ||||
| actualPickQty: verifiedQty, | actualPickQty: verifiedQty, | ||||
| lotId: formData.lotId || selectedLot?.lotId || 0, | |||||
| lotNo: formData.lotNo || selectedLot?.lotNo || '', | |||||
| pickOrderCode: formData.pickOrderCode || selectedPickOrderLine?.pickOrderCode || '', | |||||
| pickerName: session?.user?.name || '', | |||||
| badItemQty: totalBadQty, | |||||
| lotId: formData.lotId ?? selectedLot?.lotId ?? 0, | |||||
| lotNo: formData.lotNo ?? selectedLot?.lotNo ?? '', | |||||
| pickOrderCode: formData.pickOrderCode ?? selectedPickOrderLine?.pickOrderCode ?? '', | |||||
| pickerName: session?.user?.name ?? '', | |||||
| missQty: formData.missQty ?? 0, // 这里:null/undefined → 0 | |||||
| badItemQty: totalBadQty, // totalBadQty 下面用 ?? 0 算 | |||||
| badReason, | badReason, | ||||
| }; | }; | ||||
| @@ -397,7 +400,8 @@ useEffect(() => { | |||||
| pattern: "[0-9]*", | pattern: "[0-9]*", | ||||
| min: 0, | min: 0, | ||||
| }} | }} | ||||
| value={formData.missQty || 0} | |||||
| disabled={badItemSet || badPackageSet} | |||||
| value={formData.missQty || ""} | |||||
| onChange={(e) => { | onChange={(e) => { | ||||
| handleInputChange( | handleInputChange( | ||||
| "missQty", | "missQty", | ||||
| @@ -421,7 +425,7 @@ useEffect(() => { | |||||
| pattern: "[0-9]*", | pattern: "[0-9]*", | ||||
| min: 0, | min: 0, | ||||
| }} | }} | ||||
| value={formData.badItemQty || 0} | |||||
| value={formData.badItemQty || ""} | |||||
| onChange={(e) => { | onChange={(e) => { | ||||
| const newBadItemQty = e.target.value === "" | const newBadItemQty = e.target.value === "" | ||||
| ? undefined | ? undefined | ||||
| @@ -429,6 +433,7 @@ useEffect(() => { | |||||
| handleInputChange('badItemQty', newBadItemQty); | handleInputChange('badItemQty', newBadItemQty); | ||||
| }} | }} | ||||
| error={!!errors.badItemQty} | error={!!errors.badItemQty} | ||||
| disabled={missSet || badPackageSet} | |||||
| helperText={errors.badItemQty} | helperText={errors.badItemQty} | ||||
| variant="outlined" | variant="outlined" | ||||
| /> | /> | ||||
| @@ -444,7 +449,7 @@ useEffect(() => { | |||||
| pattern: "[0-9]*", | pattern: "[0-9]*", | ||||
| min: 0, | min: 0, | ||||
| }} | }} | ||||
| value={(formData as any).badPackageQty || 0} | |||||
| value={(formData as any).badPackageQty || ""} | |||||
| onChange={(e) => { | onChange={(e) => { | ||||
| handleInputChange( | handleInputChange( | ||||
| "badPackageQty", | "badPackageQty", | ||||
| @@ -453,6 +458,7 @@ useEffect(() => { | |||||
| : Math.max(0, Number(e.target.value) || 0) | : Math.max(0, Number(e.target.value) || 0) | ||||
| ); | ); | ||||
| }} | }} | ||||
| disabled={missSet || badItemSet} | |||||
| error={!!errors.badItemQty} | error={!!errors.badItemQty} | ||||
| variant="outlined" | variant="outlined" | ||||
| /> | /> | ||||
| @@ -868,7 +868,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||||
| qty: submitQty, | qty: submitQty, | ||||
| isMissing: false, | isMissing: false, | ||||
| isBad: false, | isBad: false, | ||||
| reason: undefined | |||||
| reason: undefined, | |||||
| userId: currentUserId ?? 0 | |||||
| } | } | ||||
| ); | ); | ||||
| @@ -881,7 +882,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error("Error submitting second scan quantity:", error); | console.error("Error submitting second scan quantity:", error); | ||||
| } | } | ||||
| }, [fetchJobOrderData]); | |||||
| }, [fetchJobOrderData, currentUserId]); | |||||
| const handlePickExecutionForm = useCallback((lot: any) => { | const handlePickExecutionForm = useCallback((lot: any) => { | ||||
| console.log("=== Pick Execution Form ==="); | console.log("=== Pick Execution Form ==="); | ||||
| @@ -1263,55 +1264,24 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||||
| return requiredQty.toLocaleString()+'('+lot.uomShortDesc+')'; | return requiredQty.toLocaleString()+'('+lot.uomShortDesc+')'; | ||||
| })()} | })()} | ||||
| </TableCell> | </TableCell> | ||||
| {/* | |||||
| <TableCell align="center"> | |||||
| {lot.matchStatus?.toLowerCase() === 'scanned' || | |||||
| lot.matchStatus?.toLowerCase() === 'completed' ? ( | |||||
| <Box sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'center', | |||||
| alignItems: 'center', | |||||
| width: '100%', | |||||
| height: '100%' | |||||
| }}> | |||||
| <Checkbox | |||||
| checked={true} | |||||
| disabled={true} | |||||
| readOnly={true} | |||||
| size="large" | |||||
| sx={{ | |||||
| color: 'success.main', | |||||
| '&.Mui-checked': { | |||||
| color: 'success.main', | |||||
| }, | |||||
| transform: 'scale(1.3)', | |||||
| '& .MuiSvgIcon-root': { | |||||
| fontSize: '1.5rem', | |||||
| } | |||||
| }} | |||||
| /> | |||||
| </Box> | |||||
| ) : ( | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t(" ")} | |||||
| </Typography> | |||||
| )} | |||||
| </TableCell> | |||||
| */} | |||||
| <TableCell align="center"> | <TableCell align="center"> | ||||
| <Box sx={{ display: 'flex', justifyContent: 'center' }}> | <Box sx={{ display: 'flex', justifyContent: 'center' }}> | ||||
| <Stack direction="row" spacing={1} alignItems="center"> | <Stack direction="row" spacing={1} alignItems="center"> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| onClick={() => { | |||||
| onClick={async () => { | |||||
| const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; | const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; | ||||
| const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; | const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; | ||||
| handlePickQtyChange(lotKey, submitQty); | handlePickQtyChange(lotKey, submitQty); | ||||
| handleSubmitPickQtyWithQty(lot, submitQty); | |||||
| updateSecondQrScanStatus(lot.pickOrderId, lot.itemId, currentUserId || 0, submitQty); | |||||
| // 先更新 matching 狀態(可選,依你後端流程) | |||||
| await updateSecondQrScanStatus(lot.pickOrderId, lot.itemId, currentUserId || 0, submitQty); | |||||
| // 再提交數量並 await refetch,表格會即時更新提料員 | |||||
| await handleSubmitPickQtyWithQty(lot, submitQty); | |||||
| }} | }} | ||||
| disabled={ | disabled={ | ||||
| //lot.matchStatus !== 'scanned' || | |||||
| lot.matchStatus === 'completed' || | |||||
| lot.matchStatus == 'scanned' || | |||||
| lot.lotAvailability === 'expired' || | lot.lotAvailability === 'expired' || | ||||
| lot.lotAvailability === 'status_unavailable' || | lot.lotAvailability === 'status_unavailable' || | ||||
| lot.lotAvailability === 'rejected' | lot.lotAvailability === 'rejected' | ||||
| @@ -1331,7 +1301,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||||
| size="small" | size="small" | ||||
| onClick={() => handlePickExecutionForm(lot)} | onClick={() => handlePickExecutionForm(lot)} | ||||
| disabled={ | disabled={ | ||||
| lot.matchStatus !== 'scanned' || | |||||
| lot.matchStatus === 'completed' || | |||||
| lot.matchStatus == 'scanned' || | |||||
| lot.lotAvailability === 'expired' || | lot.lotAvailability === 'expired' || | ||||
| lot.lotAvailability === 'status_unavailable' || | lot.lotAvailability === 'status_unavailable' || | ||||
| lot.lotAvailability === 'rejected' | lot.lotAvailability === 'rejected' | ||||
| @@ -80,7 +80,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({ | |||||
| // onNormalPickSubmit, | // onNormalPickSubmit, | ||||
| // selectedRowId, | // selectedRowId, | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation(); | |||||
| const { t } = useTranslation('common'); | |||||
| const [formData, setFormData] = useState<Partial<PickExecutionIssueData>>({}); | const [formData, setFormData] = useState<Partial<PickExecutionIssueData>>({}); | ||||
| const [errors, setErrors] = useState<FormErrors>({}); | const [errors, setErrors] = useState<FormErrors>({}); | ||||
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| @@ -457,5 +457,27 @@ | |||||
| "Delete Success": "刪除成功", | "Delete Success": "刪除成功", | ||||
| "Delete Failed": "刪除失敗", | "Delete Failed": "刪除失敗", | ||||
| "Create Printer": "新增列印機", | "Create Printer": "新增列印機", | ||||
| "Report": "報告" | |||||
| "Report": "報告", | |||||
| "Issue": "問題", | |||||
| "Note:": "注意:", | |||||
| "Required Qty": "需求數量", | |||||
| "Verified Qty": "確認數量", | |||||
| "Max": "最大值", | |||||
| "Min": "最小值", | |||||
| "Max": "最大值", | |||||
| "This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺少的物品或不良物品。", | |||||
| "Pick Execution Issue Form": "提料問題表單", | |||||
| "Missing items": "缺少物品", | |||||
| "Total (Verified + Bad + Missing) must equal Required quantity": "總數必須等於需求數量", | |||||
| "Missing item Qty": "缺少物品數量", | |||||
| "Bad Item Qty": "不良物品數量", | |||||
| "Issue Remark": "問題備註", | |||||
| "At least one issue must be reported": "至少需要報告一個問題", | |||||
| "Qty is required": "數量是必填項", | |||||
| "Verified quantity cannot exceed received quantity": "確認數量不能超過接收數量", | |||||
| "Handled By": "處理者", | |||||
| "submit": "提交", | |||||
| "Received Qty": "接收數量" | |||||
| } | } | ||||
| @@ -557,5 +557,14 @@ | |||||
| "Production Time Remaining": "生產剩餘時間", | "Production Time Remaining": "生產剩餘時間", | ||||
| "Process": "工序", | "Process": "工序", | ||||
| "Start": "開始", | "Start": "開始", | ||||
| "This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺少的物品或不良物品。", | |||||
| "Pick Execution Issue Form": "提料問題表單", | |||||
| "Missing items": "缺少物品", | |||||
| "Total (Verified + Bad + Missing) must equal Required quantity": "總數必須等於需求數量", | |||||
| "Missing item Qty": "缺少物品數量", | |||||
| "Bad Item Qty": "不良物品數量", | |||||
| "Issue Remark": "問題備註", | |||||
| "Handled By": "處理者", | |||||
| "Finish": "完成" | "Finish": "完成" | ||||
| } | } | ||||
| @@ -367,6 +367,7 @@ | |||||
| "View Details": "查看詳情", | "View Details": "查看詳情", | ||||
| "No Item": "沒有貨品", | "No Item": "沒有貨品", | ||||
| "None": "沒有", | "None": "沒有", | ||||
| "This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺少的物品或不良物品。", | |||||
| "Add Selected Items to Created Items": "將已選擇的貨品添加到已建立的貨品中", | "Add Selected Items to Created Items": "將已選擇的貨品添加到已建立的貨品中", | ||||
| "All pick orders created successfully": "所有提料單建立成功", | "All pick orders created successfully": "所有提料單建立成功", | ||||
| "Failed to create group": "建立分組失敗", | "Failed to create group": "建立分組失敗", | ||||