Browse Source

update

master
CANCERYS\kw093 1 month ago
parent
commit
a7f10a4f04
5 changed files with 225 additions and 117 deletions
  1. +24
    -1
      src/app/api/jo/actions.ts
  2. +59
    -22
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  3. +137
    -92
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  4. +2
    -1
      src/i18n/zh/pickOrder.json
  5. +3
    -1
      src/i18n/zh/purchaseOrder.json

+ 24
- 1
src/app/api/jo/actions.ts View File

@@ -271,7 +271,30 @@ export interface ProductProcessLineDetailResponse {
byproductQty: number,
byproductUom: string | undefined,
}
export const fetchProductProcessLineDetailByJoid = cache(async (joid: number) => {
export interface ProductProcessLineDetailResponse {
id: number,
productProcessId: number,
bomProcessId: number,
operatorId: number,
equipmentType: string,
operatorName: string,
handlerId: number,
seqNo: number,
name: string,
description: string,
equipment: string,
startTime: string,
endTime: string,
defectQty: number,
defectUom: string,
scrapQty: number,
scrapUom: string,
byproductId: number,
byproductName: string,
byproductQty: number,
byproductUom: string | undefined,
}
export const fetchProductProcessLinesByJoid = cache(async (joid: number) => {
return serverFetchJson<ProductProcessLineDetailResponse>(
`${BASE_API_URL}/product-process/demo/joid/${joid}`,
{


+ 59
- 22
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx View File

@@ -175,29 +175,66 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
}
}, [errors]);

// Update form validation to require either missQty > 0 OR badItemQty > 0
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
const req = selectedLot?.requiredQty || 0;
const ap = formData.actualPickQty || 0;
const miss = formData.missQty || 0;
const bad = formData.badItemQty || 0;
// Update form validation to require either missQty > 0 OR badItemQty > 0
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;

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');
}
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;
};
// 1. 检查 actualPickQty 不能为负数
if (ap < 0) {
newErrors.actualPickQty = t('Qty cannot be negative');
}
// 2. 检查 actualPickQty 不能超过可用数量或需求数量
if (ap > Math.min(remainingAvailableQty, req)) {
newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty');
}
// 3. 检查 missQty 和 badItemQty 不能为负数
if (miss < 0) {
newErrors.missQty = t('Invalid qty');
}
if (bad < 0) {
newErrors.badItemQty = t('Invalid qty');
}
// 4. 🔥 关键验证:总和必须等于 Required Qty(不能多也不能少)
if (total !== req) {
const diff = req - total;
const errorMsg = diff > 0
? t('Total must equal Required Qty. Missing: {{diff}}', { diff })
: t('Total must equal Required Qty. Exceeds by: {{diff}}', { diff: Math.abs(diff) });
newErrors.actualPickQty = errorMsg;
newErrors.missQty = errorMsg;
newErrors.badItemQty = errorMsg;
}
// 5. 🔥 关键验证:如果只有 actualPickQty 有值,而 missQty 和 badItemQty 都为 0,不允许提交
// 这意味着如果 actualPickQty < requiredQty,必须报告问题(missQty 或 badItemQty > 0)
if (ap > 0 && miss === 0 && bad === 0 && ap < req) {
newErrors.missQty = t('If Actual Pick Qty is less than Required Qty, you must report Missing Qty or Bad Item Qty');
newErrors.badItemQty = t('If Actual Pick Qty is less than Required Qty, you must report Missing Qty or Bad Item Qty');
}
// 6. 如果所有值都为 0,不允许提交
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');
}
// 7. 如果 actualPickQty = requiredQty,missQty 和 badItemQty 必须都为 0
if (ap === req && (miss > 0 || bad > 0)) {
newErrors.missQty = t('If Actual Pick Qty equals Required Qty, Missing Qty and Bad Item Qty must be 0');
newErrors.badItemQty = t('If Actual Pick Qty equals Required Qty, Missing Qty and Bad Item Qty must be 0');
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};

const handleSubmit = async () => {
// First validate the form


+ 137
- 92
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx View File

@@ -1984,23 +1984,15 @@ paginatedData.map((lot, index) => {
</TableCell>
<TableCell align="center">
{/* Issue lot 不显示扫描状态 */}
{!isIssueLot && lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Checkbox
checked={true}
disabled={true}
readOnly={true}
size="large"
sx={{
color: 'success.main',
'&.Mui-checked': { color: 'success.main' },
transform: 'scale(1.3)',
}}
/>
</Box>
) : isIssueLot&&lot.stockOutLineStatus?.toLowerCase() == 'partially_completed'||lot.stockOutLineStatus?.toLowerCase() == 'completed' ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{(() => {
const status = lot.stockOutLineStatus?.toLowerCase();
const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected';
const isNoLot = !lot.lotNo;
// rejected lot:显示红色勾选(已扫描但被拒绝)
if (isRejected && !isNoLot) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Checkbox
checked={true}
disabled={true}
@@ -2013,82 +2005,135 @@ paginatedData.map((lot, index) => {
}}
/>
</Box>
) : null}
</TableCell>

<TableCell align="center">
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
{isIssueLot ? (
// Issue lot 只显示 Issue 按钮
<Button
variant="outlined"
size="small"
onClick={() => handlelotnull(lot)}
disabled={
lot.stockOutLineStatus === 'completed'
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
minWidth: '60px',
borderColor: 'warning.main',
color: 'warning.main'
}}
>
{t("Issue")}
</Button>
) : (
// Normal lot 显示两个按钮
<Stack direction="row" spacing={1} alignItems="center">
<Button
variant="contained"
onClick={() => {
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
handlePickQtyChange(lotKey, submitQty);
handleSubmitPickQtyWithQty(lot, submitQty);
}}
disabled={
lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected' ||
lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'pending'
}
sx={{ fontSize: '0.75rem', py: 0.5, minHeight: '28px', minWidth: '70px' }}
>
{t("Submit")}
</Button>
<Button
variant="outlined"
size="small"
onClick={() => handlePickExecutionForm(lot)}
disabled={
lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected' ||
lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'pending'
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
minWidth: '60px',
borderColor: 'warning.main',
color: 'warning.main'
}}
title="Report missing or bad items"
>
{t("Issue")}
</Button>
</Stack>
)}
);
}
// 正常 lot:已扫描(checked/partially_completed/completed)
if (!isNoLot && status !== 'pending' && status !== 'rejected') {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Checkbox
checked={true}
disabled={true}
readOnly={true}
size="large"
sx={{
color: 'success.main',
'&.Mui-checked': { color: 'success.main' },
transform: 'scale(1.3)',
}}
/>
</Box>
</TableCell>
);
}
// noLot 且已完成/部分完成:显示红色勾选
if (isNoLot && (status === 'partially_completed' || status === 'completed')) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Checkbox
checked={true}
disabled={true}
readOnly={true}
size="large"
sx={{
color: 'error.main',
'&.Mui-checked': { color: 'error.main' },
transform: 'scale(1.3)',
}}
/>
</Box>
);
}
return null;
})()}
</TableCell>
<TableCell align="center">
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
{(() => {
const status = lot.stockOutLineStatus?.toLowerCase();
const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected';
const isNoLot = !lot.lotNo;
// rejected lot:不显示任何按钮
if (isRejected && !isNoLot) {
return null;
}
// noLot 情况:只显示 Issue 按钮
if (isNoLot) {
return (
<Button
variant="outlined"
size="small"
onClick={() => handlelotnull(lot)}
disabled={status === 'completed'}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
minWidth: '60px',
borderColor: 'warning.main',
color: 'warning.main'
}}
>
{t("Issue")}
</Button>
);
}
// 正常 lot:显示 Submit 和 Issue 按钮
return (
<Stack direction="row" spacing={1} alignItems="center">
<Button
variant="contained"
onClick={() => {
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
handlePickQtyChange(lotKey, submitQty);
handleSubmitPickQtyWithQty(lot, submitQty);
}}
disabled={
lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected' ||
lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'pending'
}
sx={{ fontSize: '0.75rem', py: 0.5, minHeight: '28px', minWidth: '70px' }}
>
{t("Submit")}
</Button>
<Button
variant="outlined"
size="small"
onClick={() => handlePickExecutionForm(lot)}
disabled={
lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected' ||
lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'pending'
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
minWidth: '60px',
borderColor: 'warning.main',
color: 'warning.main'
}}
title="Report missing or bad items"
>
{t("Issue")}
</Button>
</Stack>
);
})()}
</Box>
</TableCell>
</TableRow>
);
})


+ 2
- 1
src/i18n/zh/pickOrder.json View File

@@ -108,7 +108,8 @@
"submit": "確認提交",
"print": "列印",
"bind": "綁定",

"Total must equal Required Qty. Missing": "總數量必須等於所需數量。缺少:{{diff}}",
"Total must equal Required Qty. Exceeds by": "總數量必須等於所需數量。超出:{{diff}}",


"Pick Order": "提料單",


+ 3
- 1
src/i18n/zh/purchaseOrder.json View File

@@ -166,5 +166,7 @@
"confirm expiry date": "確認到期日",
"Invalid Date": "無效日期",
"Missing QC Template, please contact administrator": "找不到品檢模板,請聯絡管理員",
"submitting": "提交中..."
"submitting": "提交中...",
"Total must equal Required Qty. Missing": "總數量必須等於所需數量。缺少:{{diff}}",
"Total must equal Required Qty. Exceeds by": "總數量必須等於所需數量。超出:{{diff}}"
}

Loading…
Cancel
Save