浏览代码

update

MergeProblem1
CANCERYS\kw093 1周前
父节点
当前提交
d04e2eeadc
共有 3 个文件被更改,包括 194 次插入126 次删除
  1. +186
    -117
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  2. +4
    -7
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  3. +4
    -2
      src/i18n/zh/pickOrder.json

+ 186
- 117
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx 查看文件

@@ -1,4 +1,3 @@
// FPSMS-frontend/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
"use client";

import {
@@ -16,16 +15,18 @@ import {
TextField,
Typography,
} from "@mui/material";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import { GetPickOrderLineInfo, PickExecutionIssueData } from "@/app/api/pickOrder/actions";
import {
GetPickOrderLineInfo,
PickExecutionIssueData,
} from "@/app/api/pickOrder/actions";
import { fetchEscalationCombo } from "@/app/api/user/actions";
import { useRef } from "react";
import dayjs from 'dayjs';
import dayjs from "dayjs";
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";

interface LotPickData {
id: number;
id: number;
lotId: number;
lotNo: string;
expiryDate: string;
@@ -39,7 +40,12 @@ interface LotPickData {
requiredQty: number;
actualPickQty: number;
lotStatus: string;
lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected';
lotAvailability:
| "available"
| "insufficient_stock"
| "expired"
| "status_unavailable"
| "rejected";
stockOutLineId?: number;
stockOutLineStatus?: string;
stockOutLineQty?: number;
@@ -77,12 +83,14 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
const [formData, setFormData] = useState<Partial<PickExecutionIssueData>>({});
const [errors, setErrors] = useState<FormErrors>({});
const [loading, setLoading] = useState(false);
const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>([]);
const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>(
[]
);

const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => {
return lot.availableQty || 0;
}, []);
const calculateRequiredQty = useCallback((lot: LotPickData) => {
return lot.requiredQty || 0;
}, []);
@@ -96,7 +104,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
console.error("Error fetching handlers:", error);
}
};
fetchHandlers();
}, []);

@@ -136,92 +144,119 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
requiredQty: selectedLot.requiredQty,
actualPickQty: selectedLot.actualPickQty || 0,
missQty: 0,
badItemQty: 0,
issueRemark: '',
pickerName: '',
badItemQty: 0, // Bad Item Qty
badPackageQty: 0, // Bad Package Qty (frontend only)
issueRemark: "",
pickerName: "",
handledBy: undefined,
reason: '',
badReason: '',
reason: "",
badReason: "",
});

initKeyRef.current = key;
}, [open, selectedPickOrderLine?.id, selectedLot?.lotId, pickOrderId, pickOrderCreateDate]);
}, [
open,
selectedPickOrderLine?.id,
selectedLot?.lotId,
pickOrderId,
pickOrderCreateDate,
]);

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]);
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]
);

// Updated validation logic
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
const req = selectedLot?.requiredQty || 0;
const ap = Number(formData.actualPickQty) || 0;
const miss = Number(formData.missQty) || 0;
const bad = Number(formData.badItemQty) || 0;
const total = ap + miss + bad;
const badItem = Number(formData.badItemQty) || 0;
const badPackage = Number((formData as any).badPackageQty) || 0;
const totalBad = badItem + badPackage;
const total = ap + miss + totalBad;
const availableQty = selectedLot?.availableQty || 0;

// 1. Check actualPickQty cannot be negative
if (ap < 0) {
newErrors.actualPickQty = t('Qty cannot be negative');
newErrors.actualPickQty = t("Qty cannot be negative");
}
// 2. Check actualPickQty cannot exceed available quantity
if (ap > availableQty) {
newErrors.actualPickQty = t('Actual pick qty cannot exceed available qty');
newErrors.actualPickQty = t("Actual pick qty cannot exceed available qty");
}
// 3. Check missQty and badItemQty cannot be negative
// 3. Check missQty and both bad qtys cannot be negative
if (miss < 0) {
newErrors.missQty = t('Invalid qty');
newErrors.missQty = t("Invalid qty");
}
if (bad < 0) {
newErrors.badItemQty = t('Invalid qty');
if (badItem < 0 || badPackage < 0) {
newErrors.badItemQty = t("Invalid qty");
}
// 4. NEW: Total (actualPickQty + missQty + badItemQty) cannot exceed lot available qty
// 4. Total (actualPickQty + missQty + badItemQty + badPackageQty) cannot exceed lot available qty
if (total > availableQty) {
const errorMsg = t('Total qty (actual pick + miss + bad) cannot exceed available qty: {available}', { available: availableQty });
const errorMsg = t(
"Total qty (actual pick + miss + bad) cannot exceed available qty: {available}",
{ available: availableQty }
);
newErrors.actualPickQty = errorMsg;
newErrors.missQty = errorMsg;
newErrors.badItemQty = errorMsg;
}
// 5. If badItemQty > 0, badReason is required
if (bad > 0 && !formData.badReason) {
newErrors.badReason = t('Bad reason is required when bad item qty > 0');
newErrors.badItemQty = t('Bad reason is required');
}
// 6. At least one field must have a value
if (ap === 0 && miss === 0 && bad === 0) {
newErrors.actualPickQty = t('Enter pick qty or issue qty');

// 5. At least one field must have a value
if (ap === 0 && miss === 0 && totalBad === 0) {
newErrors.actualPickQty = t("Enter pick qty or issue qty");
}

setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};

const handleSubmit = async () => {
if (!validateForm()) {
console.error('Form validation failed:', errors);
console.error("Form validation failed:", errors);
return;
}
if (!formData.pickOrderId) {
console.error('Missing pickOrderId');
console.error("Missing pickOrderId");
return;
}

const badItem = Number(formData.badItemQty) || 0;
const badPackage = Number((formData as any).badPackageQty) || 0;
const totalBadQty = badItem + badPackage;

let badReason: string | undefined;
if (totalBadQty > 0) {
// assumption: only one of them is > 0
badReason = badPackage > 0 ? "package_problem" : "quantity_problem";
}

const submitData: PickExecutionIssueData = {
...(formData as PickExecutionIssueData),
badItemQty: totalBadQty,
badReason,
};

setLoading(true);
try {
await onSubmit(formData as PickExecutionIssueData);
await onSubmit(submitData);
} catch (error: any) {
console.error('Error submitting pick execution issue:', error);
alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : ''));
console.error("Error submitting pick execution issue:", error);
alert(
t("Failed to submit issue. Please try again.") +
(error.message ? `: ${error.message}` : "")
);
} finally {
setLoading(false);
}
@@ -239,11 +274,13 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({

const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot);
const requiredQty = calculateRequiredQty(selectedLot);
return (
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle>
{t('Pick Execution Issue Form')}
{t("Pick Execution Issue Form") + " - "+selectedPickOrderLine.itemCode+ " "+ selectedPickOrderLine.itemName}
<br />
{selectedLot.lotNo}
</DialogTitle>
<DialogContent>
<Box sx={{ mt: 2 }}>
@@ -251,17 +288,17 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
<Grid item xs={6}>
<TextField
fullWidth
label={t('Required Qty')}
value={selectedLot?.requiredQty || 0}
label={t("Required Qty")}
value={requiredQty}
disabled
variant="outlined"
/>
</Grid>
<Grid item xs={6}>
<TextField
fullWidth
label={t('Remaining Available Qty')}
label={t("Remaining Available Qty")}
value={remainingAvailableQty}
disabled
variant="outlined"
@@ -269,31 +306,44 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
</Grid>

<Grid item xs={12}>
<TextField
fullWidth
label={t('Actual Pick Qty')}
type="number"
inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', min: 0 }}
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')}: ${remainingAvailableQty}`}
variant="outlined"
/>
</Grid>
<TextField
fullWidth
label={t("Actual Pick Qty")}
type="number"
inputProps={{
inputMode: "numeric",
pattern: "[0-9]*",
min: 0,
}}
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")}: ${remainingAvailableQty}`
}
variant="outlined"
/>
</Grid>

<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>{t('Reason')}</InputLabel>
<InputLabel>{t("Reason")}</InputLabel>
<Select
value={formData.reason || ''}
onChange={(e) => handleInputChange('reason', e.target.value)}
label={t('Reason')}
value={formData.reason || ""}
onChange={(e) => handleInputChange("reason", e.target.value)}
label={t("Reason")}
>
<MenuItem value="">{t('Select Reason')}</MenuItem>
<MenuItem value="miss">{t('Edit')}</MenuItem>
<MenuItem value="bad">{t('Just Complete')}</MenuItem>

<MenuItem value="">{t("Select Reason")}</MenuItem>
<MenuItem value="miss">{t("Edit")}</MenuItem>
<MenuItem value="bad">{t("Just Complete")}</MenuItem>
</Select>
</FormControl>
</Grid>
@@ -301,11 +351,22 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
<Grid item xs={12}>
<TextField
fullWidth
label={t('Missing item Qty')}
label={t("Missing item Qty")}
type="number"
inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', min: 0 }}
inputProps={{
inputMode: "numeric",
pattern: "[0-9]*",
min: 0,
}}
value={formData.missQty || 0}
onChange={(e) => handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
onChange={(e) =>
handleInputChange(
"missQty",
e.target.value === ""
? undefined
: Math.max(0, Number(e.target.value) || 0)
)
}
error={!!errors.missQty}
variant="outlined"
/>
@@ -314,53 +375,61 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
<Grid item xs={12}>
<TextField
fullWidth
label={t('Bad Item Qty')}
label={t("Bad Item Qty")}
type="number"
inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', min: 0 }}
inputProps={{
inputMode: "numeric",
pattern: "[0-9]*",
min: 0,
}}
value={formData.badItemQty || 0}
onChange={(e) => handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))}
onChange={(e) =>
handleInputChange(
"badItemQty",
e.target.value === ""
? undefined
: Math.max(0, Number(e.target.value) || 0)
)
}
error={!!errors.badItemQty}
//helperText={t("Quantity Problem")}
variant="outlined"
/>
</Grid>

{/* Show bad reason dropdown when badItemQty > 0 */}
{(formData.badItemQty && formData.badItemQty > 0) ? (
<Grid item xs={12}>
<FormControl fullWidth error={!!errors.badReason}>
<InputLabel>{t('Bad Reason')}</InputLabel>
<Select
value={formData.badReason || ''}
onChange={(e) => handleInputChange('badReason', e.target.value)}
label={t('Bad Reason')}
>
<MenuItem value="">{t('Select Bad Reason')}</MenuItem>
<MenuItem value="quantity_problem">{t('Quantity Problem')}</MenuItem>
<MenuItem value="package_problem">{t('Package Problem')}</MenuItem>
</Select>
{errors.badReason && (
<Typography variant="caption" color="error" sx={{ mt: 0.5, ml: 1.75 }}>
{errors.badReason}
</Typography>
)}
</FormControl>
</Grid>
) : null}
<Grid item xs={12}>
<TextField
fullWidth
label={t("Bad Package Qty")}
type="number"
inputProps={{
inputMode: "numeric",
pattern: "[0-9]*",
min: 0,
}}
value={(formData as any).badPackageQty || 0}
onChange={(e) =>
handleInputChange(
"badPackageQty",
e.target.value === ""
? undefined
: Math.max(0, Number(e.target.value) || 0)
)
}
error={!!errors.badItemQty}
//helperText={t("Package Problem")}
variant="outlined"
/>
</Grid>
</Grid>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} disabled={loading}>
{t('Cancel')}
{t("Cancel")}
</Button>
<Button
onClick={handleSubmit}
variant="contained"
disabled={loading}
>
{loading ? t('submitting') : t('submit')}
<Button onClick={handleSubmit} variant="contained" disabled={loading}>
{loading ? t("submitting") : t("submit")}
</Button>
</DialogActions>
</Dialog>


+ 4
- 7
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx 查看文件

@@ -937,18 +937,15 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
setIsConfirmingLot(true);
try {
const newLotNo = scannedLotData?.lotNo;
if (!newLotNo) {
console.error("No lot number for scanned lot");
alert(t("Cannot find lot number for scanned lot. Please verify the lot number is correct."));
setIsConfirmingLot(false);
return;
}
const newStockInLineId = scannedLotData?.stockInLineId;
await confirmLotSubstitution({
pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId,
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId,
newInventoryLotNo: newLotNo
newInventoryLotNo: "",
newStockInLineId: newStockInLineId
});
setQrScanError(false);


+ 4
- 2
src/i18n/zh/pickOrder.json 查看文件

@@ -257,9 +257,11 @@
"Pick Execution Issue Form":"提料問題表單",
"This form is for reporting issues only. You must report either missing items or bad items.":"此表單僅用於報告問題。您必須報告缺少的貨品或不良貨品。",
"Bad item Qty":"不良貨品數量",
"Missing item Qty":"缺少貨品數量",
"Missing item Qty":"貨品遺失數量",
"Missing Item Qty":"貨品遺失數量",
"Bad Item Qty":"不良貨品數量",
"Missing Item Qty":"缺少貨品數量",
"Bad Package Qty":"不良包裝數量",
"Actual Pick Qty":"實際提料數量",
"Required Qty":"所需數量",
"Issue Remark":"問題描述",


正在加载...
取消
保存