| @@ -91,7 +91,9 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({ | |||
| const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>([]); | |||
| const [verifiedQty, setVerifiedQty] = useState<number>(0); | |||
| 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) => { | |||
| return lot.availableQty || 0; | |||
| }, []); | |||
| @@ -162,9 +164,9 @@ useEffect(() => { | |||
| storeLocation: selectedLot.location, | |||
| requiredQty: selectedLot.requiredQty, | |||
| actualPickQty: initialVerifiedQty, | |||
| missQty: 0, | |||
| badItemQty: 0, | |||
| badPackageQty: 0, // Bad Package Qty (frontend only) | |||
| missQty: undefined, | |||
| badItemQty: undefined, | |||
| badPackageQty: undefined, | |||
| issueRemark: "", | |||
| pickerName: "", | |||
| handledBy: undefined, | |||
| @@ -195,10 +197,10 @@ useEffect(() => { | |||
| const newErrors: FormErrors = {}; | |||
| const ap = Number(verifiedQty) || 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; | |||
| // 1. Check actualPickQty cannot be negative | |||
| @@ -231,7 +233,7 @@ useEffect(() => { | |||
| } | |||
| // 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"); | |||
| } | |||
| @@ -288,11 +290,12 @@ useEffect(() => { | |||
| const submissionData: PickExecutionIssueData = { | |||
| ...(formData as PickExecutionIssueData), | |||
| 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, | |||
| }; | |||
| @@ -397,7 +400,8 @@ useEffect(() => { | |||
| pattern: "[0-9]*", | |||
| min: 0, | |||
| }} | |||
| value={formData.missQty || 0} | |||
| disabled={badItemSet || badPackageSet} | |||
| value={formData.missQty || ""} | |||
| onChange={(e) => { | |||
| handleInputChange( | |||
| "missQty", | |||
| @@ -421,7 +425,7 @@ useEffect(() => { | |||
| pattern: "[0-9]*", | |||
| min: 0, | |||
| }} | |||
| value={formData.badItemQty || 0} | |||
| value={formData.badItemQty || ""} | |||
| onChange={(e) => { | |||
| const newBadItemQty = e.target.value === "" | |||
| ? undefined | |||
| @@ -429,6 +433,7 @@ useEffect(() => { | |||
| handleInputChange('badItemQty', newBadItemQty); | |||
| }} | |||
| error={!!errors.badItemQty} | |||
| disabled={missSet || badPackageSet} | |||
| helperText={errors.badItemQty} | |||
| variant="outlined" | |||
| /> | |||
| @@ -444,7 +449,7 @@ useEffect(() => { | |||
| pattern: "[0-9]*", | |||
| min: 0, | |||
| }} | |||
| value={(formData as any).badPackageQty || 0} | |||
| value={(formData as any).badPackageQty || ""} | |||
| onChange={(e) => { | |||
| handleInputChange( | |||
| "badPackageQty", | |||
| @@ -453,6 +458,7 @@ useEffect(() => { | |||
| : Math.max(0, Number(e.target.value) || 0) | |||
| ); | |||
| }} | |||
| disabled={missSet || badItemSet} | |||
| error={!!errors.badItemQty} | |||
| variant="outlined" | |||
| /> | |||
| @@ -868,7 +868,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||
| qty: submitQty, | |||
| isMissing: false, | |||
| isBad: false, | |||
| reason: undefined | |||
| reason: undefined, | |||
| userId: currentUserId ?? 0 | |||
| } | |||
| ); | |||
| @@ -881,7 +882,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||
| } catch (error) { | |||
| console.error("Error submitting second scan quantity:", error); | |||
| } | |||
| }, [fetchJobOrderData]); | |||
| }, [fetchJobOrderData, currentUserId]); | |||
| const handlePickExecutionForm = useCallback((lot: any) => { | |||
| console.log("=== Pick Execution Form ==="); | |||
| @@ -1263,55 +1264,24 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||
| return requiredQty.toLocaleString()+'('+lot.uomShortDesc+')'; | |||
| })()} | |||
| </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"> | |||
| <Box sx={{ display: 'flex', justifyContent: 'center' }}> | |||
| <Stack direction="row" spacing={1} alignItems="center"> | |||
| <Button | |||
| variant="contained" | |||
| onClick={() => { | |||
| onClick={async () => { | |||
| const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; | |||
| const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; | |||
| 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={ | |||
| //lot.matchStatus !== 'scanned' || | |||
| lot.matchStatus === 'completed' || | |||
| lot.matchStatus == 'scanned' || | |||
| lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected' | |||
| @@ -1331,7 +1301,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||
| size="small" | |||
| onClick={() => handlePickExecutionForm(lot)} | |||
| disabled={ | |||
| lot.matchStatus !== 'scanned' || | |||
| lot.matchStatus === 'completed' || | |||
| lot.matchStatus == 'scanned' || | |||
| lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected' | |||
| @@ -80,7 +80,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({ | |||
| // onNormalPickSubmit, | |||
| // selectedRowId, | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| const { t } = useTranslation('common'); | |||
| const [formData, setFormData] = useState<Partial<PickExecutionIssueData>>({}); | |||
| const [errors, setErrors] = useState<FormErrors>({}); | |||
| const [loading, setLoading] = useState(false); | |||
| @@ -457,5 +457,27 @@ | |||
| "Delete Success": "刪除成功", | |||
| "Delete Failed": "刪除失敗", | |||
| "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": "生產剩餘時間", | |||
| "Process": "工序", | |||
| "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": "完成" | |||
| } | |||
| @@ -367,6 +367,7 @@ | |||
| "View Details": "查看詳情", | |||
| "No Item": "沒有貨品", | |||
| "None": "沒有", | |||
| "This form is for reporting issues only. You must report either missing items or bad items.": "此表單僅用於報告問題。您必須報告缺少的物品或不良物品。", | |||
| "Add Selected Items to Created Items": "將已選擇的貨品添加到已建立的貨品中", | |||
| "All pick orders created successfully": "所有提料單建立成功", | |||
| "Failed to create group": "建立分組失敗", | |||