Sfoglia il codice sorgente

update

MergeProblem1
CANCERYS\kw093 1 giorno fa
parent
commit
3501863943
10 ha cambiato i file con 192 aggiunte e 82 eliminazioni
  1. +15
    -10
      src/app/api/jo/actions.ts
  2. +7
    -1
      src/app/api/stockIn/actions.ts
  3. +4
    -3
      src/components/PickOrderSearch/LotTable.tsx
  4. +1
    -1
      src/components/PickOrderSearch/PickExecution.tsx
  5. +1
    -1
      src/components/PickOrderSearch/SearchResultsTable.tsx
  6. +27
    -25
      src/components/ProductionProcess/JobProcessStatus.tsx
  7. +59
    -27
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  8. +68
    -14
      src/components/ProductionProcess/ProductionProcessStepExecution.tsx
  9. +6
    -0
      src/i18n/zh/common.json
  10. +4
    -0
      src/i18n/zh/jo.json

+ 15
- 10
src/app/api/jo/actions.ts Vedi File

@@ -1210,12 +1210,15 @@ export const fetchMaterialPickStatus = cache(async (): Promise<MaterialPickStatu
);
})
export interface ProcessStatusInfo {
processName?: string | null;
equipmentDescription?: string | null;
equipmentDetailName?: string | null;
startTime?: string | null;
endTime?: string | null;
equipmentCode?: string | null;
isRequired: boolean;
}


export interface JobProcessStatusResponse {
jobOrderId: number;
jobOrderCode: string;
@@ -1229,15 +1232,17 @@ export interface JobProcessStatusResponse {
processes: ProcessStatusInfo[];
}

// 添加API调用函数
export const fetchJobProcessStatus = cache(async () => {
return serverFetchJson<JobProcessStatusResponse[]>(
`${BASE_API_URL}/product-process/Demo/JobProcessStatus`,
{
method: "GET",
next: { tags: ["jobProcessStatus"] },
}
);
export const fetchJobProcessStatus = cache(async (date?: string) => {
const params = new URLSearchParams();
if (date) params.set("date", date); // yyyy-MM-dd

const qs = params.toString();
const url = `${BASE_API_URL}/product-process/Demo/JobProcessStatus${qs ? `?${qs}` : ""}`;

return serverFetchJson<JobProcessStatusResponse[]>(url, {
method: "GET",
next: { tags: ["jobProcessStatus"] },
});
});
export const deleteProductProcessLine = async (lineId: number) => {
return serverFetchJson<any>(


+ 7
- 1
src/app/api/stockIn/actions.ts Vedi File

@@ -12,7 +12,7 @@ import { RecordsRes } from "../utils";
import { Uom } from "../settings/uom";
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
// import { BASE_API_URL } from "@/config/api";
import { Result } from "../settings/item";
export interface PostStockInLineResponse<T> {
id: number | null;
name: string;
@@ -242,3 +242,9 @@ export const printQrCodeForSil = cache(async(data: PrintQrCodeForSilRequest) =>
},
)
})
// 添加服务器端 action 用于从客户端组件获取 item 信息
export const fetchItemForPutAway = cache(async (id: number): Promise<Result> => {
return serverFetchJson<Result>(`${BASE_API_URL}/items/details/${id}`, {
next: { tags: ["items"] },
});
});

+ 4
- 3
src/components/PickOrderSearch/LotTable.tsx Vedi File

@@ -28,10 +28,10 @@ import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import
import PickExecutionForm from "./PickExecutionForm";
interface LotPickData {
id: number;
lotId: number;
lotNo: string;
lotId: number ;
lotNo: string ;
expiryDate: string;
location: string;
location: string| null;
stockUnit: string;
inQty: number;
availableQty: number;
@@ -45,6 +45,7 @@ interface LotPickData {
stockOutLineId?: number;
stockOutLineStatus?: string;
stockOutLineQty?: number;
noLot?: boolean;
}

interface PickQtyData {


+ 1
- 1
src/components/PickOrderSearch/PickExecution.tsx Vedi File

@@ -334,7 +334,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
const handlePickQtyChange = useCallback((lineId: number, lotId: number, value: number | string) => {
console.log("Changing pick qty:", { lineId, lotId, value });
const numericValue = typeof value === 'string' ? (value === '' ? 0 : parseInt(value, 10)) : value;
const numericValue = typeof value === 'string' ? (value === '' ? 0 : parseFloat(value) || 0) : value;
setPickQtyData(prev => {
const newData = {


+ 1
- 1
src/components/PickOrderSearch/SearchResultsTable.tsx Vedi File

@@ -74,7 +74,7 @@ const SearchResultsTable: React.FC<SearchResultsTableProps> = ({

const handleQtyChange = useCallback((itemId: number, value: string) => {
// Only allow numbers
if (value === "" || /^\d+$/.test(value)) {
if (value === "" || /^\d*\.?\d+$/.test(value)) {
const numValue = value === "" ? null : Number(value);
onQtyChange(itemId, numValue);
}


+ 27
- 25
src/components/ProductionProcess/JobProcessStatus.tsx Vedi File

@@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import { fetchJobProcessStatus, JobProcessStatusResponse } from '@/app/api/jo/actions';
import { arrayToDayjs } from '@/app/utils/formatUtil';
import { FormControl, Select, MenuItem } from "@mui/material";
const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes

const JobProcessStatus: React.FC = () => {
@@ -29,6 +29,7 @@ const JobProcessStatus: React.FC = () => {
const [loading, setLoading] = useState<boolean>(true);
const refreshCountRef = useRef<number>(0);
const [currentTime, setCurrentTime] = useState(dayjs());
const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD"));

// Update current time every second for countdown
useEffect(() => {
@@ -41,21 +42,8 @@ const JobProcessStatus: React.FC = () => {
const loadData = useCallback(async () => {
setLoading(true);
try {
const result = await fetchJobProcessStatus();
// On second refresh, filter out completed jobs
if (refreshCountRef.current >= 1) {
const filtered = result.filter(item => {
// Check if all required processes are completed
const allCompleted = item.processes
.filter(p => p.isRequired)
.every(p => p.endTime != null);
return !allCompleted;
});
setData(filtered);
} else {
setData(result);
}
const result = await fetchJobProcessStatus(selectedDate);
setData(result);
refreshCountRef.current += 1;
} catch (error) {
console.error('Error fetching job process status:', error);
@@ -63,7 +51,7 @@ const JobProcessStatus: React.FC = () => {
} finally {
setLoading(false);
}
}, []);
}, [selectedDate]);

useEffect(() => {
loadData();
@@ -183,12 +171,22 @@ const JobProcessStatus: React.FC = () => {
return (
<Card sx={{ mb: 2 }}>
<CardContent>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h5" sx={{ fontWeight: 600 }}>
{t("Job Process Status", )}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h5" sx={{ fontWeight: 600 }}>
{t("Job Process Status")}
</Typography>

<FormControl size="small" sx={{ minWidth: 160 }}>
<Select
value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)}
>
<MenuItem value={dayjs().format("YYYY-MM-DD")}>今天</MenuItem>
<MenuItem value={dayjs().subtract(1, "day").format("YYYY-MM-DD")}>昨天</MenuItem>
<MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>前天</MenuItem>
</Select>
</FormControl>
</Box>

<Box sx={{ mt: 2 }}>
{loading ? (
@@ -285,12 +283,16 @@ const JobProcessStatus: React.FC = () => {
</TableCell>
);
}
const label = [
process.processName,
process.equipmentDescription,
process.equipmentDetailName ? `-${process.equipmentDetailName}` : "",
].filter(Boolean).join(" ");
// 如果工序是必需的,显示三行(Start、Finish、Wait Time)
return (
<TableCell key={index} align="center">
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<Typography variant="body2">{process.equipmentCode || '-'}</Typography>
<Typography variant="body2">{label || "-"}</Typography>
<Typography variant="body2">
{formatTime(process.startTime)}
</Typography>


+ 59
- 27
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx Vedi File

@@ -467,17 +467,19 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
align: "left",
headerAlign: "left",
type: "number",
sortable: false, // ✅ 禁用排序
},
{
field: "itemCode",
headerName: t("Item Code"),
headerName: t("Material Code"),
flex: 0.6,
sortable: false, // ✅ 禁用排序
},
{
field: "itemName",
headerName: t("Item Name"),
flex: 1,
sortable: false, // ✅ 禁用排序
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
return `${params.value} (${params.row.reqUom})`;
},
@@ -488,21 +490,35 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
flex: 0.7,
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
const qty = showBaseQty ? params.row.baseReqQty : params.value;
const uom = showBaseQty ? params.row.reqBaseUom : params.row.reqUom;
sortable: false, // ✅ 禁用排序
// ✅ 将切换功能移到 header
renderHeader: () => {
const qty = showBaseQty ? t("Base") : t("Req");
const uom = showBaseQty ? t("Base UOM") : t(" ");
return (
<Box
onClick={toggleBaseQty}
sx={{
cursor: "pointer",
userSelect: "none",
width: "100%",
textAlign: "right",
"&:hover": {
textDecoration: "underline",
},
}}
>
{t("Bom Req. Qty")} ({uom})
</Box>
);
},
// ✅ 移除 cell 中的 onClick,只显示值
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
const qty = showBaseQty ? params.row.baseReqQty : params.value;
const uom = showBaseQty ? params.row.reqBaseUom : params.row.reqUom;
return (
<Box sx={{ textAlign: "right" }}>
{decimalFormatter.format(qty || 0)} ({uom || ""})
</Box>
);
@@ -514,27 +530,39 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
flex: 0.7,
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
const qty = showBaseQty ? params.row.baseReqQty : params.value;
const uom = showBaseQty ? params.row.reqBaseUom : params.row.stockUom;
sortable: false, // ✅ 禁用排序
// ✅ 将切换功能移到 header
renderHeader: () => {
const uom = showBaseQty ? t("Base UOM") : t("Stock UOM");
return (
<Box
onClick={toggleBaseQty}
sx={{
cursor: "pointer",
userSelect: "none",
width: "100%",
textAlign: "right",
"&:hover": {
textDecoration: "underline",
},
}}
>
{t("Stock Req. Qty")} ({uom})
</Box>
);
},
// ✅ 移除 cell 中的 onClick
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
const qty = showBaseQty ? params.row.baseReqQty : params.value;
const uom = showBaseQty ? params.row.reqBaseUom : params.row.stockUom;
return (
<Box sx={{ textAlign: "right" }}>
{decimalFormatter.format(qty || 0)} ({uom || ""})
</Box>
);
},
},

{
field: "stockAvailable",
headerName: t("Stock Available"),
@@ -542,22 +570,35 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
align: "right",
headerAlign: "right",
type: "number",
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
const stockAvailable = getStockAvailable(params.row);
const qty = showBaseQty ? params.row.baseStockQty : (stockAvailable || 0);
const uom = showBaseQty ? params.row.stockBaseUom : params.row.stockUom;
sortable: false, // ✅ 禁用排序
// ✅ 将切换功能移到 header
renderHeader: () => {
const uom = showBaseQty ? t("Base UOM") : t("Stock UOM");
return (
<Box
onClick={toggleBaseQty}
sx={{
cursor: "pointer",
userSelect: "none",
width: "100%",
textAlign: "right",
"&:hover": {
textDecoration: "underline",
},
}}
>
{t("Stock Available")} ({uom})
</Box>
);
},
// ✅ 移除 cell 中的 onClick
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
const stockAvailable = getStockAvailable(params.row);
const qty = showBaseQty ? params.row.baseStockQty : (stockAvailable || 0);
const uom = showBaseQty ? params.row.stockBaseUom : params.row.stockUom;
return (
<Box sx={{ textAlign: "right" }}>
{decimalFormatter.format(qty || 0)} ({uom || ""})
</Box>
);
@@ -570,17 +611,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
align: "right",
headerAlign: "right",
type: "number",
sortable: false, // ✅ 禁用排序
},
/*
{
field: "seqNoRemark",
headerName: t("Seq No Remark"),
flex: 1,
align: "left",
headerAlign: "left",
type: "string",
},
*/
{
field: "stockStatus",
headerName: t("Stock Status"),
@@ -588,8 +620,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
align: "center",
headerAlign: "center",
type: "boolean",
sortable: false, // ✅ 禁用排序
renderCell: (params: GridRenderCellParams<JobOrderLineInfo>) => {
return isStockSufficient(params.row)
? <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" />
: <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />;


+ 68
- 14
src/components/ProductionProcess/ProductionProcessStepExecution.tsx Vedi File

@@ -18,6 +18,8 @@ import {
Card,
CardContent,
Grid,
Select,
MenuItem,
} from "@mui/material";
import { Alert } from "@mui/material";

@@ -106,7 +108,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
if (!lineDetail) return false;
return lineDetail.name === "包裝";
}, [lineDetail])

const uomList = [
"個", "件", "箱", "片", "塊"
];
// ✅ 添加:刷新 line detail 的函数
const handleRefreshLineDetail = useCallback(async () => {
if (lineId) {
@@ -179,7 +183,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
});

if (!lineDetail?.durationInMinutes || !lineDetail?.startTime) {
console.log(" Line duration or start time is not valid", {
console.log(" Line duration or start time is not valid", {
durationInMinutes: lineDetail?.durationInMinutes,
startTime: lineDetail?.startTime,
equipmentId: lineDetail?.equipmentId,
@@ -527,11 +531,11 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
setLastPauseTime(null);
})
.catch(err => {
console.error(" Failed to load line detail after resume", err);
console.error(" Failed to load line detail after resume", err);
});
}
} catch (error) {
console.error(" Error resuming:", error);
console.error(" Error resuming:", error);
alert(t("Failed to resume. Please try again."));
}
};
@@ -791,7 +795,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
/>
</TableCell>
<TableCell>
<TextField
<Select
fullWidth
size="small"
value={outputData.outputFromProcessUom}
@@ -799,7 +803,17 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
...outputData,
outputFromProcessUom: e.target.value
})}
/>
displayEmpty
>
<MenuItem value="">
<em>{t("Select Unit")}</em>
</MenuItem>
{uomList.map((uom) => (
<MenuItem key={uom} value={uom}>
{uom}
</MenuItem>
))}
</Select>
</TableCell>
<TableCell>
<Typography fontSize={15} align="center"> <strong>{t("Description")}</strong></Typography>
@@ -823,7 +837,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
/>
</TableCell>
<TableCell>
<TextField
<Select
fullWidth
size="small"
value={outputData.defectUom}
@@ -831,7 +845,17 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
...outputData,
defectUom: e.target.value
})}
/>
displayEmpty
>
<MenuItem value="">
<em>{t("Select Unit")}</em>
</MenuItem>
{uomList.map((uom) => (
<MenuItem key={uom} value={uom}>
{uom}
</MenuItem>
))}
</Select>
</TableCell>
<TableCell>
<TextField
@@ -861,7 +885,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
/>
</TableCell>
<TableCell>
<TextField
<Select
fullWidth
size="small"
value={outputData.defect2Uom}
@@ -869,7 +893,17 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
...outputData,
defect2Uom: e.target.value
})}
/>
displayEmpty
>
<MenuItem value="">
<em>{t("Select Unit")}</em>
</MenuItem>
{uomList.map((uom) => (
<MenuItem key={uom} value={uom}>
{uom}
</MenuItem>
))}
</Select>
</TableCell>
<TableCell>
<TextField
@@ -899,7 +933,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
/>
</TableCell>
<TableCell>
<TextField
<Select
fullWidth
size="small"
value={outputData.defect3Uom}
@@ -907,7 +941,17 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
...outputData,
defect3Uom: e.target.value
})}
/>
displayEmpty
>
<MenuItem value="">
<em>{t("Select Unit")}</em>
</MenuItem>
{uomList.map((uom) => (
<MenuItem key={uom} value={uom}>
{uom}
</MenuItem>
))}
</Select>
</TableCell>
<TableCell>
<TextField
@@ -937,7 +981,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
/>
</TableCell>
<TableCell>
<TextField
<Select
fullWidth
size="small"
value={outputData.scrapUom}
@@ -945,7 +989,17 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
...outputData,
scrapUom: e.target.value
})}
/>
displayEmpty
>
<MenuItem value="">
<em>{t("Select Unit")}</em>
</MenuItem>
{uomList.map((uom) => (
<MenuItem key={uom} value={uom}>
{uom}
</MenuItem>
))}
</Select>
</TableCell>
</TableRow>
</TableBody>


+ 6
- 0
src/i18n/zh/common.json Vedi File

@@ -207,7 +207,13 @@
"Job Order Match": "工單對料",
"All Pick Order Lots": "所有提料單批號",
"Row per page": "每頁行數",
"Select Unit": "選擇單位",
"No data available": "沒有資料",
"Bom Req. Qty": "需求數(BOM單位)",
"Material Name": "材料清單",
"Material Code": "材料清單",
"Base UOM": "基本單位",
"Stock UOM": "庫存單位",
"jodetail": "工單細節",
"Sign out": "登出",
"By-product": "副產品",


+ 4
- 0
src/i18n/zh/jo.json Vedi File

@@ -25,6 +25,8 @@
"Overall Time Remaining": "總剩餘時間",
"User not found with staffNo:": "用戶不存在",
"Time Remaining": "剩餘時間",
"Base UOM": "基本單位",
"Stock UOM": "庫存單位",
"Over Time": "超時",
"Staff No:": "員工編號:",
"Timer Paused": "計時器已暫停",
@@ -107,6 +109,8 @@
"Lines with sufficient stock: ": "可提料項目數量: ",
"Lines with insufficient stock: ": "未能提料項目數量: ",
"Item Name": "成品/半成品",
"Material Code": "材料編號",
"Select Unit": "選擇單位",
"Job Order Pickexcution": "工單提料",
"Pick Order Detail": "提料單細節",
"Finished Job Order Record": "已完成工單記錄",


Caricamento…
Annulla
Salva