CANCERYS\kw093 пре 2 месеци
родитељ
комит
511f669a0b
4 измењених фајлова са 118 додато и 112 уклоњено
  1. +99
    -102
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  2. +15
    -7
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  3. +1
    -1
      src/i18n/zh/jo.json
  4. +3
    -2
      src/i18n/zh/pickOrder.json

+ 99
- 102
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx Прегледај датотеку

@@ -20,7 +20,7 @@ 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";
import { useRef } from "react";
interface LotPickData {
id: number;
lotId: number;
@@ -81,7 +81,6 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
const [errors, setErrors] = useState<FormErrors>({});
const [loading, setLoading] = useState(false);
const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>([]);

// 计算剩余可用数量
const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => {
const remainingQty = lot.inQty - lot.outQty;
@@ -92,7 +91,18 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
// The actualPickQty in the form should be independent of the database value
return lot.requiredQty || 0;
}, []);
const remaining = selectedLot ? calculateRemainingAvailableQty(selectedLot) : 0;
const req = selectedLot ? calculateRequiredQty(selectedLot) : 0;

const ap = Number(formData.actualPickQty) || 0;
const miss = Number(formData.missQty) || 0;
const bad = Number(formData.badItemQty) || 0;

// Max the user can type
const maxPick = Math.min(remaining, req);
const maxIssueTotal = Math.max(0, req - ap); // remaining room for miss+bad

const clamp0 = (v: any) => Math.max(0, Number(v) || 0);
// 获取处理人员列表
useEffect(() => {
const fetchHandlers = async () => {
@@ -107,55 +117,49 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
fetchHandlers();
}, []);

// 初始化表单数据 - 每次打开时都重新初始化
const initKeyRef = useRef<string | null>(null);

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];
}
};
if (!open || !selectedLot || !selectedPickOrderLine || !pickOrderId) return;

// 计算剩余可用数量
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]);
// Only initialize once per (pickOrderLineId + lotId) while dialog open
const key = `${selectedPickOrderLine.id}-${selectedLot.lotId}`;
if (initKeyRef.current === key) return;

const getSafeDate = (dateValue: any): string => {
if (!dateValue) return new Date().toISOString().split('T')[0];
try {
const d = new Date(dateValue);
return isNaN(d.getTime()) ? new Date().toISOString().split('T')[0] : d.toISOString().split('T')[0];
} catch {
return new Date().toISOString().split('T')[0];
}
};

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,
issueRemark: '',
pickerName: '',
handledBy: undefined,
});

initKeyRef.current = key;
}, [open, selectedPickOrderLine?.id, selectedLot?.lotId, pickOrderId, pickOrderCreateDate]);
// Mutually exclusive inputs: picking vs reporting issues

const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
@@ -168,30 +172,23 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
// ✅ 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');
const req = selectedLot?.requiredQty || 0;
const ap = formData.actualPickQty || 0;
const miss = formData.missQty || 0;
const bad = formData.badItemQty || 0;

if (ap < 0) newErrors.actualPickQty = t('Qty is required');
if (ap > Math.min(remainingAvailableQty, req)) newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty');
if (miss < 0) newErrors.missQty = t('Invalid qty');
if (bad < 0) newErrors.badItemQty = t('Invalid qty');
if (ap + miss + bad > req) {
newErrors.actualPickQty = t('Total exceeds required qty');
newErrors.missQty = t('Total exceeds 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');
if (ap === 0 && miss === 0 && bad === 0) {
newErrors.actualPickQty = t('Enter pick qty or issue qty');
newErrors.missQty = t('Enter pick qty or issue qty');
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
@@ -266,42 +263,42 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
</Grid>

<Grid item xs={12}>
<TextField
fullWidth
label={t('Actual Pick Qty')}
type="number"
value={formData.actualPickQty || 0}
onChange={(e) => handleInputChange('actualPickQty', parseFloat(e.target.value) || 0)}
error={!!errors.actualPickQty}
helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`}
variant="outlined"
/>
<TextField
fullWidth
label={t('Actual Pick Qty')}
type="number"
value={formData.actualPickQty ?? ''}
onChange={(e) => handleInputChange('actualPickQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
error={!!errors.actualPickQty}
helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`}
variant="outlined"
/>
</Grid>

<Grid item xs={12}>
<TextField
fullWidth
label={t('Missing item Qty')}
type="number"
value={formData.missQty || 0}
onChange={(e) => handleInputChange('missQty', parseFloat(e.target.value) || 0)}
error={!!errors.missQty}
// helperText={errors.missQty || t('Enter missing quantity (required if no bad items)')}
variant="outlined"
/>
<TextField
fullWidth
label={t('Missing item Qty')}
type="number"
value={formData.missQty || 0}
onChange={(e) => handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
error={!!errors.missQty}
variant="outlined"
//disabled={(formData.actualPickQty || 0) > 0}
/>
</Grid>

<Grid item xs={12}>
<TextField
fullWidth
label={t('Bad Item Qty')}
type="number"
value={formData.badItemQty || 0}
onChange={(e) => 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"
/>
<TextField
fullWidth
label={t('Bad Item Qty')}
type="number"
value={formData.badItemQty || 0}
onChange={(e) => handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
error={!!errors.badItemQty}
variant="outlined"
//disabled={(formData.actualPickQty || 0) > 0}
/>
</Grid>
{/* ✅ Show issue description and handler fields when bad items > 0 */}


+ 15
- 7
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx Прегледај датотеку

@@ -586,12 +586,20 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
setQrScanError(false);
setQrScanSuccess(false);
setQrScanInput('');
setIsManualScanning(false);
stopScan();
resetScan();
//setIsManualScanning(false);
//stopScan();
//resetScan();
setProcessedQrCodes(new Set());
setLastProcessedQr('');

setQrModalOpen(false);
setPickExecutionFormOpen(false);
if(selectedLotForQr?.stockOutLineId){
const stockOutLineUpdate = await updateStockOutLineStatus({
id: selectedLotForQr.stockOutLineId,
status: 'checked',
qty: 0
});
}
setLotConfirmationOpen(false);
setExpectedLotData(null);
setScannedLotData(null);
@@ -709,9 +717,9 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
setQrScanSuccess(true);
setQrScanError(false);
setQrScanInput(''); // Clear input after successful processing
setIsManualScanning(false);
stopScan();
resetScan();
//setIsManualScanning(false);
// stopScan();
// resetScan();
// ✅ Clear success state after a delay
//setTimeout(() => {


+ 1
- 1
src/i18n/zh/jo.json Прегледај датотеку

@@ -33,7 +33,7 @@
"Pick Order Code": "提料單編號",
"Target Date": "需求日期",
"Lot Required Pick Qty": "批號需求數量",
"Job Order Match": "工單匹配",
"Job Order Match": "工單對料",
"Lot No": "批號",
"Submit Required Pick Qty": "提交需求數量",
"All Pick Order Lots": "所有提料單批號",


+ 3
- 2
src/i18n/zh/pickOrder.json Прегледај датотеку

@@ -278,7 +278,7 @@
"QR code verified.":"QR 碼驗證成功。",
"Order Finished":"訂單完成",
"Submitted Status":"提交狀態",
"Finished Good Record":"成單記錄",
"Finished Good Record":"已完成出倉記錄",
"Delivery No.":"送貨單編號",
"Total":"總數",
"completed DO pick orders":"已完成送貨單提料單",
@@ -290,7 +290,8 @@
"Back to List":"返回列表",
"No completed DO pick orders found":"沒有已完成送貨單提料單",
"Enter the number of cartons: ": "請輸入總箱數",
"Number of cartons": "箱數"
"Number of cartons": "箱數",
"Total exceeds required qty":"總數超出所需數量"




Loading…
Откажи
Сачувај