// FPSMS-frontend/src/components/PickOrderSearch/PickExecutionForm.tsx "use client"; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, Grid, InputLabel, MenuItem, Select, TextField, Typography, } from "@mui/material"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { GetPickOrderLineInfo, PickExecutionIssueData } from "@/app/api/pickOrder/actions"; import { fetchEscalationCombo } from "@/app/api/user/actions"; interface LotPickData { id: number; lotId: number; lotNo: string; expiryDate: string; location: string; stockUnit: string; inQty: number; outQty: number; holdQty: number; totalPickedByAllPickOrders: number; availableQty: number; requiredQty: number; actualPickQty: number; lotStatus: string; lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; stockOutLineId?: number; stockOutLineStatus?: string; stockOutLineQty?: number; } interface PickExecutionFormProps { open: boolean; onClose: () => void; onSubmit: (data: PickExecutionIssueData) => Promise; selectedLot: LotPickData | null; selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null; pickOrderId?: number; pickOrderCreateDate: any; // ✅ Remove these props since we're not handling normal cases // onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise; // selectedRowId?: number | null; } // 定义错误类型 interface FormErrors { actualPickQty?: string; missQty?: string; badItemQty?: string; issueRemark?: string; handledBy?: string; } const PickExecutionForm: React.FC = ({ open, onClose, onSubmit, selectedLot, selectedPickOrderLine, pickOrderId, pickOrderCreateDate, // ✅ Remove these props // onNormalPickSubmit, // selectedRowId, }) => { const { t } = useTranslation(); const [formData, setFormData] = useState>({}); const [errors, setErrors] = useState({}); const [loading, setLoading] = useState(false); const [handlers, setHandlers] = useState>([]); // 计算剩余可用数量 const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { const remainingQty = lot.inQty - lot.outQty; return Math.max(0, remainingQty); }, []); const calculateRequiredQty = useCallback((lot: LotPickData) => { // ✅ Use the original required quantity, not subtracting actualPickQty // The actualPickQty in the form should be independent of the database value return lot.requiredQty || 0; }, []); // 获取处理人员列表 useEffect(() => { const fetchHandlers = async () => { try { const escalationCombo = await fetchEscalationCombo(); setHandlers(escalationCombo); } catch (error) { console.error("Error fetching handlers:", error); } }; fetchHandlers(); }, []); // 初始化表单数据 - 每次打开时都重新初始化 useEffect(() => { if (open && selectedLot && selectedPickOrderLine && pickOrderId) { const getSafeDate = (dateValue: any): string => { if (!dateValue) return new Date().toISOString().split('T')[0]; try { const date = new Date(dateValue); if (isNaN(date.getTime())) { return new Date().toISOString().split('T')[0]; } return date.toISOString().split('T')[0]; } catch { return new Date().toISOString().split('T')[0]; } }; // 计算剩余可用数量 const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot); const requiredQty = calculateRequiredQty(selectedLot); console.log("=== PickExecutionForm Debug ==="); console.log("selectedLot:", selectedLot); console.log("inQty:", selectedLot.inQty); console.log("outQty:", selectedLot.outQty); console.log("holdQty:", selectedLot.holdQty); console.log("availableQty:", selectedLot.availableQty); console.log("calculated remainingAvailableQty:", remainingAvailableQty); console.log("=== End Debug ==="); setFormData({ pickOrderId: pickOrderId, pickOrderCode: selectedPickOrderLine.pickOrderCode, pickOrderCreateDate: getSafeDate(pickOrderCreateDate), pickExecutionDate: new Date().toISOString().split('T')[0], pickOrderLineId: selectedPickOrderLine.id, itemId: selectedPickOrderLine.itemId, itemCode: selectedPickOrderLine.itemCode, itemDescription: selectedPickOrderLine.itemName, lotId: selectedLot.lotId, lotNo: selectedLot.lotNo, storeLocation: selectedLot.location, requiredQty: selectedLot.requiredQty, actualPickQty: selectedLot.actualPickQty || 0, missQty: 0, badItemQty: 0, // 初始化为 0,用户需要手动输入 issueRemark: '', pickerName: '', handledBy: undefined, }); } }, [open, selectedLot, selectedPickOrderLine, pickOrderId, pickOrderCreateDate, calculateRemainingAvailableQty]); const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => { setFormData(prev => ({ ...prev, [field]: value })); // 清除错误 if (errors[field as keyof FormErrors]) { setErrors(prev => ({ ...prev, [field]: undefined })); } }, [errors]); // ✅ Update form validation to require either missQty > 0 OR badItemQty > 0 const validateForm = (): boolean => { const newErrors: FormErrors = {}; if (formData.actualPickQty === undefined || formData.actualPickQty < 0) { newErrors.actualPickQty = t('Qty is required'); } // ✅ FIXED: Check if actual pick qty exceeds remaining available qty if (formData.actualPickQty && formData.actualPickQty > remainingAvailableQty) { newErrors.actualPickQty = t('Qty is not allowed to be greater than remaining available qty'); } // ✅ FIXED: Check if actual pick qty exceeds required qty (use original required qty) if (formData.actualPickQty && formData.actualPickQty > (selectedLot?.requiredQty || 0)) { newErrors.actualPickQty = t('Qty is not allowed to be greater than required qty'); } // ✅ NEW: Require either missQty > 0 OR badItemQty > 0 (at least one issue must be reported) const hasMissQty = formData.missQty && formData.missQty > 0; const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0; if (!hasMissQty && !hasBadItemQty) { newErrors.missQty = t('At least one issue must be reported'); newErrors.badItemQty = t('At least one issue must be reported'); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async () => { if (!validateForm() || !formData.pickOrderId) { return; } setLoading(true); try { await onSubmit(formData as PickExecutionIssueData); onClose(); } catch (error) { console.error('Error submitting pick execution issue:', error); } finally { setLoading(false); } }; const handleClose = () => { setFormData({}); setErrors({}); onClose(); }; if (!selectedLot || !selectedPickOrderLine) { return null; } const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot); const requiredQty = calculateRequiredQty(selectedLot); return ( {t('Pick Execution Issue Form')} {/* ✅ Always show issue form title */} {/* ✅ Add instruction text */} {t('Note:')} {t('This form is for reporting issues only. You must report either missing items or bad items.')} {/* ✅ Keep the existing form fields */} handleInputChange('actualPickQty', parseFloat(e.target.value) || 0)} error={!!errors.actualPickQty} helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`} variant="outlined" /> handleInputChange('missQty', parseFloat(e.target.value) || 0)} error={!!errors.missQty} // helperText={errors.missQty || t('Enter missing quantity (required if no bad items)')} variant="outlined" /> handleInputChange('badItemQty', parseFloat(e.target.value) || 0)} error={!!errors.badItemQty} // helperText={errors.badItemQty || t('Enter bad item quantity (required if no missing items)')} variant="outlined" /> {/* ✅ Show issue description and handler fields when bad items > 0 */} {(formData.badItemQty && formData.badItemQty > 0) ? ( <> handleInputChange('issueRemark', e.target.value)} error={!!errors.issueRemark} helperText={errors.issueRemark} //placeholder={t('Describe the issue with bad items')} variant="outlined" /> {t('handler')} {errors.handledBy && ( {errors.handledBy} )} ) : (<>)} ); }; export default PickExecutionForm;