|
- "use client";
- import React, { useCallback, useEffect, useState, useMemo } from "react";
- import {
- Box,
- Button,
- Paper,
- Stack,
- Typography,
- TextField,
- Grid,
- Card,
- CardContent,
- CircularProgress,
- Tabs,
- Tab,
- TabsProps,
- IconButton,
- Dialog,
- DialogTitle,
- DialogContent,
- DialogActions,
- InputAdornment
- } from "@mui/material";
- import ArrowBackIcon from '@mui/icons-material/ArrowBack';
- import { useTranslation } from "react-i18next";
- import { fetchProductProcessesByJobOrderId ,deleteJobOrder, updateProductProcessPriority, updateJoPlanStart,updateJoReqQty,newProductProcessLine,JobOrderLineInfo} from "@/app/api/jo/actions";
- import ProductionProcessDetail from "./ProductionProcessDetail";
- import { BomCombo } from "@/app/api/bom";
- import { fetchBomCombo } from "@/app/api/bom/index";
- import dayjs from "dayjs";
- import { OUTPUT_DATE_FORMAT, integerFormatter, arrayToDateString } from "@/app/utils/formatUtil";
- import StyledDataGrid from "../StyledDataGrid/StyledDataGrid";
- import { GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
- import { decimalFormatter } from "@/app/utils/formatUtil";
- import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
- import DoDisturbAltRoundedIcon from '@mui/icons-material/DoDisturbAltRounded';
- import { fetchInventories } from "@/app/api/inventory/actions";
- import { InventoryResult } from "@/app/api/inventory";
- import { releaseJo, startJo } from "@/app/api/jo/actions";
- import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
- import ProcessSummaryHeader from "./ProcessSummaryHeader";
- import EditIcon from "@mui/icons-material/Edit";
- import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
- import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
- import { dayjsToDateString } from "@/app/utils/formatUtil";
-
-
-
- interface ProductProcessJobOrderDetailProps {
- jobOrderId: number;
- onBack: () => void;
- fromJosave?: boolean;
- }
-
- const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProps> = ({
- jobOrderId,
- onBack,
- fromJosave,
- }) => {
- const { t } = useTranslation();
- const [loading, setLoading] = useState(false);
- const [processData, setProcessData] = useState<any>(null);
- const [jobOrderLines, setJobOrderLines] = useState<JobOrderLineInfo[]>([]);
- const [inventoryData, setInventoryData] = useState<InventoryResult[]>([]);
- const [tabIndex, setTabIndex] = useState(0);
- const [selectedProcessId, setSelectedProcessId] = useState<number | null>(null);
- const [operationPriority, setOperationPriority] = useState<number>(50);
- const [openOperationPriorityDialog, setOpenOperationPriorityDialog] = useState(false);
- const [openPlanStartDialog, setOpenPlanStartDialog] = useState(false);
- const [planStartDate, setPlanStartDate] = useState<dayjs.Dayjs | null>(null);
- const [openReqQtyDialog, setOpenReqQtyDialog] = useState(false);
- const [reqQtyMultiplier, setReqQtyMultiplier] = useState<number>(1);
- const [selectedBomForReqQty, setSelectedBomForReqQty] = useState<BomCombo | null>(null);
- const [bomCombo, setBomCombo] = useState<BomCombo[]>([]);
- const [showBaseQty, setShowBaseQty] = useState<boolean>(false);
-
- const fetchData = useCallback(async () => {
- setLoading(true);
- try {
- const data = await fetchProductProcessesByJobOrderId(jobOrderId);
- if (data && data.length > 0) {
- const firstProcess = data[0];
- setProcessData(firstProcess);
- setJobOrderLines((firstProcess as any).jobOrderLines || []);
- }
- } catch (error) {
- console.error("Error loading data:", error);
- } finally {
- setLoading(false);
- }
- }, [jobOrderId]);
- const toggleBaseQty = useCallback(() => {
- setShowBaseQty(prev => !prev);
- }, []);
-
- // 4. 添加处理函数(约第 166 行后)
- const handleOpenReqQtyDialog = useCallback(async () => {
- if (!processData || !processData.outputQty || !processData.outputQtyUom) {
- alert(t("BOM data not available"));
- return;
- }
-
- const baseOutputQty = processData.bomBaseQty;
- const currentMultiplier = baseOutputQty > 0
- ? Math.round(processData.outputQty / baseOutputQty)
- : 1;
- const bomData = {
- id: processData.bomId || 0,
- value: processData.bomId || 0,
- label: processData.bomDescription || "",
- outputQty: baseOutputQty,
- outputQtyUom: processData.outputQtyUom,
- description: processData.bomDescription || ""
- };
-
- setSelectedBomForReqQty(bomData);
- setReqQtyMultiplier(currentMultiplier);
- setOpenReqQtyDialog(true);
- }, [processData, t]);
-
- const handleCloseReqQtyDialog = useCallback(() => {
- setOpenReqQtyDialog(false);
- setSelectedBomForReqQty(null);
- setReqQtyMultiplier(1);
- }, []);
-
- const handleUpdateReqQty = useCallback(async (jobOrderId: number, newReqQty: number) => {
- try {
- const response = await updateJoReqQty({
- id: jobOrderId,
- reqQty: Math.round(newReqQty)
- });
- if (response) {
- await fetchData();
- }
- } catch (error) {
- console.error("Error updating reqQty:", error);
- alert(t("update failed"));
- }
- }, [fetchData, t]);
-
- const handleConfirmReqQty = useCallback(async () => {
- if (!jobOrderId || !selectedBomForReqQty) return;
- const newReqQty = reqQtyMultiplier * selectedBomForReqQty.outputQty;
- await handleUpdateReqQty(jobOrderId, newReqQty);
- setOpenReqQtyDialog(false);
- setSelectedBomForReqQty(null);
- setReqQtyMultiplier(1);
- }, [jobOrderId, selectedBomForReqQty, reqQtyMultiplier, handleUpdateReqQty]);
- // 获取库存数据
- useEffect(() => {
- const fetchInventoryData = async () => {
- try {
- const inventoryResponse = await fetchInventories({
- code: "",
- name: "",
- type: "",
- pageNum: 0,
- pageSize: 1000
- });
- setInventoryData(inventoryResponse.records);
- } catch (error) {
- console.error("Error fetching inventory data:", error);
- }
- };
- fetchInventoryData();
- }, []);
-
- useEffect(() => {
- fetchData();
- }, [fetchData]);
- // PickTable 组件内容
- const getStockAvailable = (line: JobOrderLineInfo) => {
- if (line.type?.toLowerCase() === "consumables" || line.type?.toLowerCase() === "nm") {
- return line.stockQty || 0;
- }
- const inventory = inventoryData.find(inv =>
- inv.itemCode === line.itemCode || inv.itemName === line.itemName
- );
- if (inventory) {
- return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
- }
- return line.stockQty || 0;
- };
- const handleOpenPlanStartDialog = useCallback(() => {
- // 将 processData.date 转换为 dayjs 对象
- if (processData?.date) {
- // processData.date 可能是字符串或 Date 对象
- setPlanStartDate(dayjs(processData.date));
- } else {
- setPlanStartDate(dayjs());
- }
- setOpenPlanStartDialog(true);
- }, [processData?.date]);
-
- const handleClosePlanStartDialog = useCallback((_event?: object, _reason?: "backdropClick" | "escapeKeyDown") => {
- setOpenPlanStartDialog(false);
- setPlanStartDate(null);
- }, []);
-
- const handleUpdatePlanStart = useCallback(async (jobOrderId: number, planStart: string) => {
- const response = await updateJoPlanStart({ id: jobOrderId, planStart });
- if (response) {
- await fetchData();
- }
- }, [fetchData]);
-
- const handleConfirmPlanStart = useCallback(async () => {
- if (!jobOrderId || !planStartDate) return;
-
- // 将日期转换为后端需要的格式 (YYYY-MM-DDTHH:mm:ss)
- const dateString = `${dayjsToDateString(planStartDate, "input")}T00:00:00`;
- await handleUpdatePlanStart(jobOrderId, dateString);
- setOpenPlanStartDialog(false);
- setPlanStartDate(null);
- }, [jobOrderId, planStartDate, handleUpdatePlanStart]);
- const handleUpdateOperationPriority = useCallback(async (productProcessId: number, productionPriority: number) => {
- const response = await updateProductProcessPriority(productProcessId, productionPriority)
- if (response) {
- await fetchData();
- }
- }, [jobOrderId]);
- const handleOpenPriorityDialog = () => {
- setOperationPriority(processData?.productionPriority ?? 50);
- setOpenOperationPriorityDialog(true);
- };
-
- const handleClosePriorityDialog = (_event?: object, _reason?: "backdropClick" | "escapeKeyDown") => {
- setOpenOperationPriorityDialog(false);
- };
- const handleConfirmPriority = async () => {
- if (!processData?.id) return;
- await handleUpdateOperationPriority(processData.id, Number(operationPriority));
- setOpenOperationPriorityDialog(false);
- };
- const isStockSufficient = (line: JobOrderLineInfo) => {
- if (line.type?.toLowerCase() === "consumables") {
- return false;
- }
- const stockAvailable = getStockAvailable(line);
- if (stockAvailable === null) {
- return false;
- }
- return stockAvailable >= line.reqQty;
- };
- const stockCounts = useMemo(() => {
- // 过滤掉 consumables 类型的 lines
- const nonConsumablesLines = jobOrderLines.filter(
- line => line.type?.toLowerCase() !== "consumables" && line.type?.toLowerCase() !== "cmb" && line.type?.toLowerCase() !== "nm"
- );
- const total = nonConsumablesLines.length;
- const sufficient = nonConsumablesLines.filter(isStockSufficient).length;
- return {
- total,
- sufficient,
- insufficient: total - sufficient,
- };
- }, [jobOrderLines, inventoryData]);
- const status = processData?.status?.toLowerCase?.() ?? "";
- const handleDeleteJobOrder = useCallback(async ( jobOrderId: number) => {
- const response = await deleteJobOrder(jobOrderId)
- if (response) {
- //setProcessData(response.entity);
- //await fetchData();
- onBack();
- }
- }, [jobOrderId]);
- const handleRelease = useCallback(async ( jobOrderId: number) => {
- // TODO: 替换为实际的 release 调用
- console.log("Release clicked for jobOrderId:", jobOrderId);
- const response = await releaseJo({ id: jobOrderId })
- if (response) {
- //setProcessData(response.entity);
- await fetchData();
- }
- }, [jobOrderId]);
- const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
- (_e, newValue) => {
- setTabIndex(newValue);
- },
- [],
- );
-
- // 如果选择了 process detail,显示 detail 页面
- if (selectedProcessId !== null) {
- return (
- <ProductionProcessDetail
- jobOrderId={selectedProcessId}
- onBack={() => {
- setSelectedProcessId(null);
- fetchData(); // 刷新数据
- }}
- />
- );
- }
-
- if (loading) {
- return (
- <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
- <CircularProgress/>
- </Box>
- );
- }
-
- if (!processData) {
- return (
- <Box>
- <Button variant="outlined" onClick={onBack} startIcon={<ArrowBackIcon />}>
- {t("Back")}
- </Button>
- <Typography sx={{ mt: 2 }}>{t("No data found")}</Typography>
- </Box>
- );
- }
-
-
- // InfoCard 组件内容
- const InfoCardContent = () => (
- <Card sx={{ display: "block", mt: 2 }}>
- <CardContent component={Stack} spacing={4}>
- <Box>
- <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
- <Grid item xs={6}>
- <TextField
- label={t("Job Order Code")}
- fullWidth
- disabled={true}
- value={processData?.jobOrderCode || ""}
- />
- </Grid>
-
- <Grid item xs={6}>
- <TextField
- label={t("Item Code")}
- fullWidth
- disabled={true}
- value={processData?.itemCode+"-"+processData?.itemName || ""}
- />
- </Grid>
- <Grid item xs={6}>
- <TextField
- label={t("Job Type")}
- fullWidth
- disabled={true}
- value={t(processData?.jobType) || t("N/A")}
- //value={t("N/A")}
- />
- </Grid>
- <Grid item xs={6}>
- <TextField
- label={t("Req. Qty")}
- fullWidth
- disabled={true}
- value={processData?.outputQty + "(" + processData?.outputQtyUom + ")" || ""}
- InputProps={{
- endAdornment: (processData?.jobOrderStatus === "planning" ? (
- <InputAdornment position="end">
- <IconButton size="small" onClick={handleOpenReqQtyDialog}>
- <EditIcon fontSize="small" />
- </IconButton>
- </InputAdornment>
- ) : null),
- }}
- />
- </Grid>
-
- <Grid item xs={6}>
- <TextField
- value={processData?.date ? dayjs(processData.date).format(OUTPUT_DATE_FORMAT) : ""}
- label={t("Target Production Date")}
- fullWidth
- disabled={true}
- InputProps={{
- endAdornment: (processData?.jobOrderStatus === "planning" ? (
- <InputAdornment position="end">
- <IconButton size="small" onClick={handleOpenPlanStartDialog}>
- <EditIcon fontSize="small" />
- </IconButton>
- </InputAdornment>
- ) : null),
- }}
- />
- </Grid>
- <Grid item xs={6}>
- <TextField
- label={t("Production Priority")}
- fullWidth
- disabled={true}
- value={processData?.productionPriority ?? "50"}
- InputProps={{
- endAdornment: (
- <InputAdornment position="end">
- <IconButton size="small" onClick={handleOpenPriorityDialog}>
- <EditIcon fontSize="small" />
- </IconButton>
- </InputAdornment>
- ),
- }}
- />
- </Grid>
- <Grid item xs={6}>
- <TextField
- label={t("Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity")}
- fullWidth
- disabled={true}
- value={`${processData?.isDark == null || processData?.isDark === "" ? t("N/A") : processData.isDark} | ${processData?.isDense == null || processData?.isDense === "" || processData?.isDense === 0 ? t("N/A") : processData.isDense} | ${processData?.isFloat == null || processData?.isFloat === "" ? t("N/A") : processData.isFloat} | ${processData?.scrapRate == -1 || processData?.scrapRate === "" ? t("N/A") : processData.scrapRate} | ${processData?.allergicSubstance == null || processData?.allergicSubstance === "" ? t("N/A") :t (processData.allergicSubstance)} | ${processData?.timeSequence == null || processData?.timeSequence === "" ? t("N/A") : processData.timeSequence} | ${processData?.complexity == null || processData?.complexity === "" ? t("N/A") : processData.complexity}`}
- />
- </Grid>
-
- </Grid>
- </Box>
- </CardContent>
- </Card>
- );
-
- const productionProcessesLineRemarkTableColumns: GridColDef[] = [
- {
- field: "seqNo",
- headerName: t("Seq"),
- flex: 0.2,
- align: "left",
- headerAlign: "left",
- type: "number",
- renderCell: (params) => {
- return <Typography sx={{ fontWeight: 500 }}>{params.value}</Typography>;
- },
- },
- {
- field: "description",
- headerName: t("Remark"),
- flex: 1,
- align: "left",
- headerAlign: "left",
- renderCell: (params) => {
- return(
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography sx={{ fontWeight: 500 }}> </Typography>
- <Typography sx={{ fontWeight: 500 }}>{params.value || ""}</Typography>
-
- <Typography sx={{ fontWeight: 500 }}> </Typography>
- </Box>
- )
-
- },
- },
-
- ];
- const productionProcessesLineRemarkTableRows =
- processData?.productProcessLines?.map((line: any) => ({
- id: line.seqNo,
- seqNo: line.seqNo,
-
- description: line.description ?? "",
-
-
-
- })) ?? [];
-
-
-
-
- const pickTableColumns: GridColDef[] = [
- {
- field: "id",
- headerName: t("id"),
- flex: 0.2,
- align: "left",
- headerAlign: "left",
- type: "number",
- sortable: false, // ✅ 禁用排序
- },
- {
- field: "itemCode",
- 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})`;
- },
- },
- {
- field: "reqQty",
- headerName: t("Bom Req. Qty"),
- flex: 0.7,
- align: "right",
- headerAlign: "right",
- 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>
- );
- },
- },
- {
- field: "stockReqQty",
- headerName: t("Stock Req. Qty"),
- flex: 0.7,
- align: "right",
- headerAlign: "right",
- 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"),
- flex: 0.7,
- align: "right",
- headerAlign: "right",
- type: "number",
- 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>
- );
- },
- },
- {
- field: "bomProcessSeqNo",
- headerName: t("Seq No"),
- flex: 0.5,
- align: "right",
- headerAlign: "right",
- type: "number",
- sortable: false, // ✅ 禁用排序
- },
- {
- field: "stockStatus",
- headerName: t("Stock Status"),
- flex: 0.5,
- 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" />;
- },
- },
- ];
-
- const pickTableRows = jobOrderLines.map((line, index) => ({
- ...line,
- //id: line.id || index,
- id: index + 1,
- }));
-
- const PickTableContent = () => (
- <Box sx={{ mt: 2 }}>
- <ProcessSummaryHeader processData={processData} />
- <Card sx={{ mb: 2 }}>
- <CardContent>
- <Stack
- direction="row"
- alignItems="center"
- justifyContent="space-between"
- spacing={2}
- >
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
- {t("Total lines: ")}<strong>{stockCounts.total}</strong>
- </Typography>
-
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
- {t("Lines with sufficient stock: ")}<strong style={{ color: "green" }}>{stockCounts.sufficient}</strong>
- </Typography>
-
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
- {t("Lines with insufficient stock: ")}<strong style={{ color: "red" }}>{stockCounts.insufficient}</strong>
- </Typography>
- {fromJosave && (
- <Button
- variant="contained"
- color="error"
- onClick={() => handleDeleteJobOrder(jobOrderId)}
- disabled={processData?.jobOrderStatus !== "planning"}
- >
- {t("Delete Job Order")}
- </Button>
- )}
- {fromJosave && (
- <Button
- variant="contained"
- color="primary"
- onClick={() => handleRelease(jobOrderId)}
- //disabled={stockCounts.insufficient > 0 || processData?.jobOrderStatus !== "planning"}
- disabled={processData?.jobOrderStatus !== "planning"}
- >
- {t("Release")}
- </Button>
- )}
-
- </Stack>
- </CardContent>
- </Card>
-
- <StyledDataGrid
- sx={{ "--DataGrid-overlayHeight": "100px" }}
- disableColumnMenu
- rows={pickTableRows}
- columns={pickTableColumns}
- getRowHeight={() => "auto"}
- />
- </Box>
- );
- const ProductionProcessesLineRemarkTableContent = () => (
- <Box sx={{ mt: 2 }}>
- <ProcessSummaryHeader processData={processData} />
- <StyledDataGrid
- sx={{
- "--DataGrid-overlayHeight": "100px",
- // ✅ Match ProductionProcessDetail font size (default body2 = 0.875rem)
- "& .MuiDataGrid-cell": {
- fontSize: "0.875rem", // ✅ Match default body2 size
- fontWeight: 500,
- },
- "& .MuiDataGrid-columnHeader": {
- fontSize: "0.875rem", // ✅ Match header size
- fontWeight: 600,
- },
- // ✅ Ensure empty columns are visible
- "& .MuiDataGrid-columnHeaders": {
- display: "flex",
- },
- "& .MuiDataGrid-row": {
- display: "flex",
- },
- }}
- disableColumnMenu
- rows={productionProcessesLineRemarkTableRows ?? []}
- columns={productionProcessesLineRemarkTableColumns}
- getRowHeight={() => 'auto'}
- hideFooter={false} // ✅ Ensure footer is visible
- />
- </Box>
- );
-
-
- return (
- <Box>
- {/* 返回按钮 */}
- <Box sx={{ mb: 2 }}>
- <Button variant="outlined" onClick={onBack} startIcon={<ArrowBackIcon />}>
- {t("Back to List")}
- </Button>
- </Box>
-
- {/* 标签页 */}
- <Box sx={{ borderBottom: '1px solid #e0e0e0' }}>
- <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
- <Tab label={t("Job Order Info")} />
- <Tab label={t("BoM Material")} />
-
- <Tab label={t("Production Process")} />
-
-
- <Tab label={t("Production Process Line Remark")} />
- {/* {!fromJosave && (
- <Tab label={t("Matching Stock")} />
- )} */}
- </Tabs>
- </Box>
-
- {/* 标签页内容 */}
- <Box sx={{ p: 2 }}>
- {tabIndex === 0 && <InfoCardContent />}
- {tabIndex === 1 && <PickTableContent />}
-
- {tabIndex === 2 && (
- <ProductionProcessDetail
- jobOrderId={jobOrderId}
- onBack={() => {
- // 切换回第一个标签页,或者什么都不做
- setTabIndex(0);
-
- }}
- fromJosave={fromJosave}
- />
- )}
- {tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />}
-
- {/* {tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />} */}
-
- <Dialog
- open={openOperationPriorityDialog}
- onClose={handleClosePriorityDialog}
- fullWidth
- maxWidth="xs"
- >
- <DialogTitle>{t("Update Production Priority")}</DialogTitle>
- <DialogContent>
- <TextField
- autoFocus
- margin="dense"
- label={t("Production Priority")}
- type="number"
- fullWidth
- value={operationPriority}
- onChange={(e) => setOperationPriority(Number(e.target.value))}
- />
- </DialogContent>
- <DialogActions>
- <Button onClick={handleClosePriorityDialog}>{t("Cancel")}</Button>
- <Button variant="contained" onClick={handleConfirmPriority}>{t("Save")}</Button>
- </DialogActions>
- </Dialog>
- <Dialog
- open={openPlanStartDialog}
- onClose={handleClosePlanStartDialog}
- fullWidth
- maxWidth="xs"
- >
- <DialogTitle>{t("Update Target Production Date")}</DialogTitle>
- <DialogContent>
- <LocalizationProvider dateAdapter={AdapterDayjs}>
- <DatePicker
- label={t("Target Production Date")}
- value={planStartDate}
- onChange={(newValue) => setPlanStartDate(newValue)}
- slotProps={{
- textField: {
- fullWidth: true,
- margin: "dense",
- autoFocus: true,
- }
- }}
- />
- </LocalizationProvider>
- </DialogContent>
- <DialogActions>
- <Button onClick={handleClosePlanStartDialog}>{t("Cancel")}</Button>
- <Button
- variant="contained"
- onClick={handleConfirmPlanStart}
- disabled={!planStartDate}
- >
- {t("Save")}
- </Button>
- </DialogActions>
- </Dialog>
- <Dialog
- open={openReqQtyDialog}
- onClose={handleCloseReqQtyDialog}
- fullWidth
- maxWidth="sm"
- >
- <DialogTitle>{t("Update Required Quantity")}</DialogTitle>
- <DialogContent>
- <Stack spacing={2} sx={{ mt: 1 }}>
- <Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
- <TextField
- label={t("Base Qty")}
- fullWidth
- type="number"
- variant="outlined"
- value={selectedBomForReqQty?.outputQty || 0}
- disabled
- InputProps={{
- endAdornment: selectedBomForReqQty?.outputQtyUom ? (
- <InputAdornment position="end">
- <Typography variant="body2" sx={{ color: "text.secondary" }}>
- {selectedBomForReqQty.outputQtyUom}
- </Typography>
- </InputAdornment>
- ) : null
- }}
- sx={{ flex: 1 }}
- />
- <Typography variant="body1" sx={{ color: "text.secondary" }}>
- ×
- </Typography>
- <TextField
- label={t("Batch Count")}
- fullWidth
- type="number"
- variant="outlined"
- value={reqQtyMultiplier}
- onChange={(e) => {
- const val = e.target.value === "" ? 1 : Math.max(1, Math.floor(Number(e.target.value)));
- setReqQtyMultiplier(val);
- }}
- inputProps={{
- min: 1,
- step: 1
- }}
- sx={{ flex: 1 }}
- />
- <Typography variant="body1" sx={{ color: "text.secondary" }}>
- =
- </Typography>
- <TextField
- label={t("Req. Qty")}
- fullWidth
- variant="outlined"
- type="number"
- value={selectedBomForReqQty ? (reqQtyMultiplier * selectedBomForReqQty.outputQty) : ""}
- disabled
- InputProps={{
- endAdornment: selectedBomForReqQty?.outputQtyUom ? (
- <InputAdornment position="end">
- <Typography variant="body2" sx={{ color: "text.secondary" }}>
- {selectedBomForReqQty.outputQtyUom}
- </Typography>
- </InputAdornment>
- ) : null
- }}
- sx={{ flex: 1 }}
- />
- </Box>
- </Stack>
- </DialogContent>
- <DialogActions>
- <Button onClick={handleCloseReqQtyDialog}>{t("Cancel")}</Button>
- <Button
- variant="contained"
- onClick={handleConfirmReqQty}
- disabled={!selectedBomForReqQty || reqQtyMultiplier < 1}
- >
- {t("Save")}
- </Button>
- </DialogActions>
- </Dialog>
-
- </Box>
- </Box>
-
- );
-
- };
-
- export default ProductionProcessJobOrderDetail;
|