diff --git a/src/app/(main)/production/page.tsx b/src/app/(main)/production/page.tsx
index 24e9e30..9cdec1a 100644
--- a/src/app/(main)/production/page.tsx
+++ b/src/app/(main)/production/page.tsx
@@ -8,6 +8,7 @@ import Typography from "@mui/material/Typography";
import { Metadata } from "next";
import Link from "next/link";
import { Suspense } from "react";
+import { fetchPrinterCombo } from "@/app/api/settings/printer";
export const metadata: Metadata = {
title: "Claims",
@@ -15,7 +16,7 @@ export const metadata: Metadata = {
const production: React.FC = async () => {
const { t } = await getServerI18n("claims");
-
+ const printerCombo = await fetchPrinterCombo();
return (
<>
{
{t("Create Process")}
*/}
- {/* Use new component */}
+ {/* Use new component */}
>
);
};
diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts
index 55ddffa..1070597 100644
--- a/src/app/api/jo/actions.ts
+++ b/src/app/api/jo/actions.ts
@@ -171,20 +171,31 @@ export interface ProductProcessResponse {
}
export interface ProductProcessLineResponse {
- id: number;
- seqNo: number;
- name: string;
- description?: string;
- equipmentType?: string;
- startTime?: string;
- endTime?: string;
- outputFromProcessQty?: number;
- outputFromProcessUom?: string;
- defectQty?: number;
- scrapQty?: number;
- byproductName?: string;
- byproductQty?: number;
- handlerId?: number;
+ id: number,
+ bomprocessId: number,
+ operatorId: number,
+ operatorName: string,
+ equipmentId: number,
+ handlerId: number,
+ seqNo: number,
+ name: string,
+ description: string,
+ equipment_name: string,
+ status: string,
+ byproductId: number,
+ byproductName: string,
+ byproductQty: number,
+ byproductUom: string,
+ scrapQty: number,
+ defectQty: number,
+ defectUom: string,
+ outputFromProcessQty: number,
+ outputFromProcessUom: string,
+ durationInMinutes: number,
+ prepTimeInMinutes: number,
+ postProdTimeInMinutes: number,
+ startTime: string,
+ endTime: string,
}
export interface ProductProcessWithLinesResponse {
@@ -196,7 +207,19 @@ export interface ProductProcessWithLinesResponse {
date: string;
bomId?: number;
jobOrderId?: number;
+ jobOrderCode: string;
+ isDark: string;
+ isDense: number;
+ isFloat: string;
+ itemId: number;
+ itemCode: string;
+ itemName: string;
+ outputQty: number;
+ outputQtyUom: string;
+ productionPriority: number;
+ jobOrderLines: JobOrderLineInfo[];
productProcessLines: ProductProcessLineResponse[];
+
}
export interface UpdateProductProcessLineQtyRequest {
productProcessLineId: number;
@@ -241,6 +264,7 @@ export interface AllJoborderProductProcessInfoResponse {
bomId?: number;
itemName: string;
jobOrderId: number;
+ stockInLineId: number;
jobOrderCode: string;
productProcessLineCount: number;
FinishedProductProcessLineCount: number;
@@ -261,6 +285,7 @@ export interface ProductProcessLineQrscanUpadteRequest {
operatorId?: number;
equipmentId?: number;
}
+
export interface ProductProcessLineDetailResponse {
id: number,
productProcessId: number,
@@ -270,9 +295,17 @@ export interface ProductProcessLineDetailResponse {
operatorName: string,
handlerId: number,
seqNo: number,
+ isDark: string,
+ isDense: number,
+ isFloat: string,
+ outputQtyUom: string,
+ outputQty: number,
+ pickOrderId: number,
+ jobOrderCode: string,
+ jobOrderId: number,
name: string,
description: string,
- equipmentId: number,
+ equipment: string,
startTime: string,
endTime: string,
defectQty: number,
@@ -283,50 +316,83 @@ export interface ProductProcessLineDetailResponse {
byproductName: string,
byproductQty: number,
byproductUom: string | undefined,
+ totalStockQty: number,
+ insufficientStockQty: number,
+ sufficientStockQty: number,
+ productionPriority: number,
+ productProcessLines: ProductProcessLineInfoResponse[],
+ jobOrderLineInfo: JobOrderLineInfo[],
}
-export interface ProductProcessLineDetailResponse {
+export interface JobOrderProcessLineDetailResponse {
+ id: number;
+ productProcessId: number;
+ bomProcessId: number;
+ operatorId: number;
+ equipmentType: string | null;
+ operatorName: string;
+ handlerId: number;
+ seqNo: number;
+ name: string;
+ description: string;
+ equipmentId: number;
+ startTime: string | number[]; // API 返回的是数组格式
+ endTime: string | number[]; // API 返回的是数组格式
+ status: string;
+ outputFromProcessQty: number;
+ outputFromProcessUom: string;
+ defectQty: number;
+ defectUom: string;
+ scrapQty: number;
+ scrapUom: string;
+ byproductId: number;
+ byproductName: string;
+ byproductQty: number;
+ byproductUom: string;
+}
+export interface JobOrderLineInfo {
id: number,
- productProcessId: number,
- bomProcessId: number,
+ jobOrderId: number,
+ jobOrderCode: string,
+ itemId: number,
+ itemCode: string,
+ itemName: string,
+ reqQty: number,
+ stockQty: number,
+ uom: string,
+ shortUom: string,
+ availableStatus: string
+}
+export interface ProductProcessLineInfoResponse {
+ id: number,
+ bomprocessId: number,
operatorId: number,
- equipmentType: string,
operatorName: string,
+ equipmentId: number,
handlerId: number,
seqNo: number,
- isDark: string,
- isDense: number,
- isFloat: string,
- outputQtyUom: string,
- outputQty: number,
- pickOrderId: number,
- jobOrderCode: string,
- jobOrderId: number,
name: string,
description: string,
- equipment: string,
- startTime: string,
- endTime: string,
- defectQty: number,
- defectUom: string,
- scrapQty: number,
- scrapUom: string,
+ equipment_name: string,
+ status: string,
byproductId: number,
byproductName: string,
byproductQty: number,
- byproductUom: string | undefined,
+ byproductUom: string,
+ scrapQty: number,
+ defectQty: number,
+ defectUom: string,
+ durationInMinutes: number,
+ prepTimeInMinutes: number,
+ postProdTimeInMinutes: number,
+ outputFromProcessQty: number,
+ outputFromProcessUom: string,
+ startTime: string,
+ endTime: string
}
-export const fetchProductProcessLinesByJoid = cache(async (joid: number) => {
- return serverFetchJson(
- `${BASE_API_URL}/product-process/demo/joid/${joid}`,
- {
- method: "GET",
- }
- );
-});
-// /product-process/Demo/ProcessLine/detail/{lineId}
+
export const fetchProductProcessLineDetail = cache(async (lineId: number) => {
- return serverFetchJson(
+ return serverFetchJson(
`${BASE_API_URL}/product-process/Demo/ProcessLine/detail/${lineId}`,
{
method: "GET",
diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
index 485e137..e6f2499 100644
--- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
+++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
@@ -189,12 +189,9 @@ const validateForm = (): boolean => {
newErrors.actualPickQty = t('Qty cannot be negative');
}
- // 2. 检查 actualPickQty 不能超过可用数量或需求数量
-<<<<<<< Updated upstream
- if (ap > Math.min(req)) {
-=======
+
+
if (ap > Math.min( req)) {
->>>>>>> Stashed changes
newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty');
}
diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx
index 531f8f0..6e75c71 100644
--- a/src/components/ProductionProcess/ProductionProcessDetail.tsx
+++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx
@@ -34,11 +34,12 @@ import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import {
fetchProductProcessById,
- fetchProductProcessLines,
updateProductProcessLineQrscan,
fetchProductProcessLineDetail,
ProductProcessLineDetailResponse,
+ JobOrderProcessLineDetailResponse,
updateLineOutput,
+ ProductProcessLineInfoResponse,
ProductProcessResponse,
ProductProcessLineResponse,
completeProductProcessLine,
@@ -47,14 +48,6 @@ import {
} from "@/app/api/jo/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";
import ProductionProcessStepExecution from "./ProductionProcessStepExecution";
-// 添加设备数据库(从 MachineScanner.tsx)
-const machineDatabase: Machine[] = [
- { id: 1, name: "CNC Mill #1", code: "CNC001", qrCode: "QR-CNC001" },
- { id: 2, name: "Lathe #2", code: "LAT002", qrCode: "QR-LAT002" },
- { id: 3, name: "Press #3", code: "PRS003", qrCode: "QR-PRS003" },
- { id: 4, name: "Welder #4", code: "WLD004", qrCode: "QR-WLD004" },
- { id: 5, name: "Drill Press #5", code: "DRL005", qrCode: "QR-DRL005" },
-];
interface ProductProcessDetailProps {
@@ -73,7 +66,7 @@ const ProductionProcessDetail: React.FC = ({
// 基本信息
const [processData, setProcessData] = useState(null);
- const [lines, setLines] = useState([]);
+ const [lines, setLines] = useState([]);
const [loading, setLoading] = useState(false);
// 选中的 line 和执行状态
@@ -86,7 +79,7 @@ const ProductionProcessDetail: React.FC = ({
const [scannedOperatorId, setScannedOperatorId] = useState(null);
const [scannedEquipmentId, setScannedEquipmentId] = useState(null);
const [scanningLineId, setScanningLineId] = useState(null);
- const [lineDetailForScan, setLineDetailForScan] = useState(null);
+ const [lineDetailForScan, setLineDetailForScan] = useState(null);
const [showScanDialog, setShowScanDialog] = useState(false);
const autoSubmitTimerRef = useRef(null);
@@ -105,7 +98,11 @@ const ProductionProcessDetail: React.FC = ({
// 处理 QR 码扫描
// 处理 QR 码扫描
-
+ const handleBackFromStep = async () => {
+ await fetchProcessDetail(); // 重新拉取最新的 process/lines
+ setIsExecutingLine(false);
+ setSelectedLineId(null);
+ };
// 获取 process 和 lines 数据
const fetchProcessDetail = useCallback(async () => {
@@ -230,6 +227,7 @@ const ProductionProcessDetail: React.FC = ({
if (!scannedOperatorId) {
console.log("No operatorId, cannot submit");
+ setIsAutoSubmitting(false);
return false; // 没有 operatorId,不能提交
}
@@ -255,8 +253,15 @@ const ProductionProcessDetail: React.FC = ({
// 检查响应中的 message 字段来判断是否成功
// 如果后端返回 message 不为 null,说明验证失败
if (response && response.message) {
+ setIsAutoSubmitting(false);
+ // 清除定时器
+ if (autoSubmitTimerRef.current) {
+ clearTimeout(autoSubmitTimerRef.current);
+ autoSubmitTimerRef.current = null;
+ }
//alert(response.message || t("Validation failed. Please check operator and equipment."));
return false;
+
}
// 验证通过,继续执行后续步骤
console.log("Validation passed, starting line...");
@@ -297,11 +302,17 @@ const ProductionProcessDetail: React.FC = ({
// 开始扫描
const handleStartScan = useCallback((lineId: number) => {
+ if (autoSubmitTimerRef.current) {
+ clearTimeout(autoSubmitTimerRef.current);
+ autoSubmitTimerRef.current = null;
+ }
setScanningLineId(lineId);
setIsManualScanning(true);
setProcessedQrCodes(new Set());
setScannedOperatorId(null);
setScannedEquipmentId(null);
+ setIsAutoSubmitting(false); // 添加:重置自动提交状态
+ setLineDetailForScan(null);
// 获取 line detail 以获取 bomProcessEquipmentId
fetchProductProcessLineDetail(lineId)
.then(setLineDetailForScan)
@@ -311,7 +322,16 @@ const ProductionProcessDetail: React.FC = ({
// 停止扫描
const handleStopScan = useCallback(() => {
+ console.log("🛑 Stopping scan");
+
+ // 清除定时器
+ if (autoSubmitTimerRef.current) {
+ clearTimeout(autoSubmitTimerRef.current);
+ autoSubmitTimerRef.current = null;
+ }
+
setIsManualScanning(false);
+ setIsAutoSubmitting(false); // 添加:重置自动提交状态
stopScan();
resetScan();
}, [stopScan, resetScan]);
@@ -374,11 +394,24 @@ const ProductionProcessDetail: React.FC = ({
}, []);
const handleStartLineWithScan = async (lineId: number) => {
+ console.log("🚀 Starting line with scan for lineId:", lineId);
+
+ // 确保状态完全重置
+ setIsAutoSubmitting(false);
+ setScannedOperatorId(null);
+ setScannedEquipmentId(null);
+ setProcessedQrCodes(new Set());
+
+ // 清除之前的定时器
+ if (autoSubmitTimerRef.current) {
+ clearTimeout(autoSubmitTimerRef.current);
+ autoSubmitTimerRef.current = null;
+ }
+
setScanningLineId(lineId);
setShowScanDialog(true);
handleStartScan(lineId);
};
-
const selectedLine = lines.find(l => l.id === selectedLineId);
if (loading) {
@@ -391,14 +424,14 @@ const ProductionProcessDetail: React.FC = ({
return (
- {/* 返回按钮 */}
-
+ {/*
+
- {/* ========== 第一部分:基本信息 ========== */}
+
{t("Production Process Information")}
@@ -437,7 +470,7 @@ const ProductionProcessDetail: React.FC = ({
-
+*/}
{/* ========== 第二部分:Process Lines ========== */}
@@ -452,8 +485,12 @@ const ProductionProcessDetail: React.FC = ({
{t("Seq")}
{t("Step Name")}
- {t("Description")}
+ {t("Description")}
+ {t("Operator")}
{t("Equipment Type")}
+ {t("Duration")}
+ {t("Prep Time")}
+ {t("Post Prod Time")}
{t("Status")}
{t("Action")}
@@ -462,7 +499,7 @@ const ProductionProcessDetail: React.FC = ({
{lines.map((line) => {
const status = (line as any).status || '';
const statusLower = status.toLowerCase();
- const equipmentName = (line as any).equipment_name || line.equipmentType || "-";
+ const equipmentName = line.equipment_name || "-";
const isCompleted = statusLower === 'completed';
const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress';
@@ -475,7 +512,11 @@ const ProductionProcessDetail: React.FC = ({
{line.name}
{line.description || "-"}
+ {line.operatorName}
{equipmentName}
+ {line.durationInMinutes} {t("Minutes")}
+ {line.prepTimeInMinutes} {t("Minutes")}
+ {line.postProdTimeInMinutes} {t("Minutes")}
{isCompleted ? (
@@ -534,6 +575,7 @@ const ProductionProcessDetail: React.FC = ({
/* ========== 步骤执行视图 ========== */
{
// setIsExecutingLine(false)
// setSelectedLineId(null)
diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
new file mode 100644
index 0000000..b4f2f48
--- /dev/null
+++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
@@ -0,0 +1,411 @@
+"use client";
+import React, { useCallback, useEffect, useState } from "react";
+import {
+ Box,
+ Button,
+ Paper,
+ Stack,
+ Typography,
+ TextField,
+ Grid,
+ Card,
+ CardContent,
+ CircularProgress,
+ Tabs,
+ Tab,
+ TabsProps,
+} from "@mui/material";
+import ArrowBackIcon from '@mui/icons-material/ArrowBack';
+import { useTranslation } from "react-i18next";
+import { fetchProductProcessesByJobOrderId } from "@/app/api/jo/actions";
+import ProductionProcessDetail from "./ProductionProcessDetail";
+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 JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
+interface JobOrderLine {
+ id: number;
+ jobOrderId: number;
+ jobOrderCode: string;
+ itemId: number;
+ itemCode: string;
+ itemName: string;
+ reqQty: number;
+ stockQty: number;
+ uom: string;
+ shortUom: string;
+ availableStatus: string;
+}
+
+interface ProductProcessJobOrderDetailProps {
+ jobOrderId: number;
+ onBack: () => void;
+}
+
+const ProductionProcessJobOrderDetail: React.FC = ({
+ jobOrderId,
+ onBack,
+}) => {
+ const { t } = useTranslation();
+ const [loading, setLoading] = useState(false);
+ const [processData, setProcessData] = useState(null);
+ const [jobOrderLines, setJobOrderLines] = useState([]);
+ const [inventoryData, setInventoryData] = useState([]);
+ const [tabIndex, setTabIndex] = useState(0);
+ const [selectedProcessId, setSelectedProcessId] = useState(null);
+
+ // 获取数据
+ 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]);
+
+ // 获取库存数据
+ 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]);
+
+ const handleTabChange = useCallback>(
+ (_e, newValue) => {
+ setTabIndex(newValue);
+ },
+ [],
+ );
+
+ // 如果选择了 process detail,显示 detail 页面
+ if (selectedProcessId !== null) {
+ return (
+ {
+ setSelectedProcessId(null);
+ fetchData(); // 刷新数据
+ }}
+ />
+ );
+ }
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (!processData) {
+ return (
+
+ }>
+ {t("Back")}
+
+ {t("No data found")}
+
+ );
+ }
+
+ // InfoCard 组件内容
+ const InfoCardContent = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ // PickTable 组件内容
+ const getStockAvailable = (line: JobOrderLine) => {
+ 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 isStockSufficient = (line: JobOrderLine) => {
+ const stockAvailable = getStockAvailable(line);
+ return stockAvailable >= line.reqQty;
+ };
+ const productionProcessesLineRemarkTableColumns: GridColDef[] = [
+ {
+ field: "seqNo",
+ headerName: t("Seq"),
+ flex: 0.2,
+ align: "left",
+ headerAlign: "center",
+ type: "number",
+ },
+ {
+ field: "description",
+ headerName: t("Remark"),
+ flex: 1,
+ align: "left",
+ headerAlign: "center",
+ },
+ ];
+ 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",
+
+ },
+ {
+ field: "itemCode",
+ headerName: t("Item Code"),
+ flex: 0.6,
+ },
+ {
+ field: "itemName",
+ headerName: t("Item Name"),
+ flex: 1,
+ renderCell: (params: GridRenderCellParams) => {
+ return `${params.value} (${params.row.uom})`;
+ },
+ },
+ {
+ field: "reqQty",
+ headerName: t("Req. Qty"),
+ flex: 0.7,
+ align: "right",
+ headerAlign: "right",
+ renderCell: (params: GridRenderCellParams) => {
+ return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`;
+ },
+ },
+ {
+ field: "stockAvailable",
+ headerName: t("Stock Available"),
+ flex: 0.7,
+ align: "right",
+ headerAlign: "right",
+ type: "number",
+
+ renderCell: (params: GridRenderCellParams) => {
+ const stockAvailable = getStockAvailable(params.row);
+ return `${decimalFormatter.format(stockAvailable)} (${params.row.shortUom})`;
+ },
+ },
+ {
+ field: "stockStatus",
+ headerName: t("Stock Status"),
+ flex: 0.5,
+ align: "right",
+ headerAlign: "right",
+ type: "boolean",
+ renderCell: (params: GridRenderCellParams) => {
+ return isStockSufficient(params.row)
+ ?
+ : ;
+ },
+ },
+ ];
+
+ const pickTableRows = jobOrderLines.map((line, index) => ({
+ ...line,
+ id: line.id || index,
+ }));
+
+ const PickTableContent = () => (
+
+ 'auto'}
+ />
+
+ );
+ const ProductionProcessesLineRemarkTableContent = () => (
+
+ 'auto'}
+ />
+
+ );
+
+
+ return (
+
+ {/* 返回按钮 */}
+
+ }>
+ {t("Back to List")}
+
+
+
+ {/* 标签页 */}
+
+
+
+
+
+
+
+
+
+
+ {/* 标签页内容 */}
+
+ {tabIndex === 0 && }
+ {tabIndex === 1 && }
+ {tabIndex === 2 && (
+ {
+ // 切换回第一个标签页,或者什么都不做
+ setTabIndex(0);
+ }}
+ />
+ )}
+ {tabIndex === 3 && }
+ {tabIndex === 4 && }
+
+
+ );
+};
+
+export default ProductionProcessJobOrderDetail;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx
index dd5f134..6c48394 100644
--- a/src/components/ProductionProcess/ProductionProcessList.tsx
+++ b/src/components/ProductionProcess/ProductionProcessList.tsx
@@ -14,6 +14,7 @@ import {
Grid,
} from "@mui/material";
import { useTranslation } from "react-i18next";
+import QcStockInModal from "../Qc/QcStockInModal";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
@@ -22,21 +23,38 @@ import {
fetchAllJoborderProductProcessInfo,
AllJoborderProductProcessInfoResponse,
} from "@/app/api/jo/actions";
-
+import { StockInLineInput } from "@/app/api/stockIn";
+import { PrinterCombo } from "@/app/api/settings/printer";
interface ProductProcessListProps {
onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void;
+ printerCombo: PrinterCombo[];
}
+
const PER_PAGE = 6;
-const ProductProcessList: React.FC = ({ onSelectProcess }) => {
+const ProductProcessList: React.FC = ({ onSelectProcess, printerCombo }) => {
const { t } = useTranslation();
const { data: session } = useSession() as { data: SessionWithTokens | null };
-
+ const sessionToken = session as SessionWithTokens | null;
const [loading, setLoading] = useState(false);
const [processes, setProcesses] = useState([]);
const [page, setPage] = useState(0);
+ const [openModal, setOpenModal] = useState(false);
+ const [modalInfo, setModalInfo] = useState();
+ const handleViewStockIn = useCallback((process: AllJoborderProductProcessInfoResponse) => {
+ if (!process.stockInLineId) {
+ alert(t("Invalid Stock In Line Id"));
+ return;
+ }
+ setModalInfo({
+ id: process.stockInLineId,
+ expiryDate: dayjs().add(1, "month").format(OUTPUT_DATE_FORMAT),
+ // 视需要补 itemId、jobOrderId 等
+ });
+ setOpenModal(true);
+ }, [t]);
const fetchProcesses = useCallback(async () => {
setLoading(true);
try {
@@ -54,7 +72,12 @@ const ProductProcessList: React.FC = ({ onSelectProcess
useEffect(() => {
fetchProcesses();
}, [fetchProcesses]);
-
+ const closeNewModal = useCallback(() => {
+ // const response = updateJo({ id: 1, status: "storing" });
+ setOpenModal(false); // Close the modal first
+ // setTimeout(() => {
+ // }, 300); // Add a delay to avoid immediate re-trigger of useEffect
+}, []);
const startIdx = page * PER_PAGE;
const paged = processes.slice(startIdx, startIdx + PER_PAGE);
@@ -155,6 +178,11 @@ const ProductProcessList: React.FC = ({ onSelectProcess
+ {statusLower === "completed" && (
+
+ )}
{t("Lines")}: {totalCount}
@@ -165,7 +193,13 @@ const ProductProcessList: React.FC = ({ onSelectProcess
);
})}
-
+
{processes.length > 0 && (
= ({ onSelectProcess
)}
+
);
};
diff --git a/src/components/ProductionProcess/ProductionProcessPage.tsx b/src/components/ProductionProcess/ProductionProcessPage.tsx
index 907b1ba..ce9828d 100644
--- a/src/components/ProductionProcess/ProductionProcessPage.tsx
+++ b/src/components/ProductionProcess/ProductionProcessPage.tsx
@@ -4,53 +4,38 @@ import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import ProductionProcessList from "@/components/ProductionProcess/ProductionProcessList";
import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail";
+import ProductionProcessJobOrderDetail from "@/components/ProductionProcess/ProductionProcessJobOrderDetail";
+
import {
fetchProductProcesses,
- fetchProductProcessLines,
+ fetchProductProcessesByJobOrderId,
ProductProcessLineResponse
} from "@/app/api/jo/actions";
+type PrinterCombo = {
+ id: number;
+ value: number;
+ label?: string;
+ code?: string;
+ name?: string;
+ description?: string;
+ ip?: string;
+ port?: number;
+};
+
+interface ProductionProcessPageProps {
+ printerCombo: PrinterCombo[];
+}
-const ProductionProcessPage = () => {
+const ProductionProcessPage: React.FC = ({ printerCombo }) => {
const [selectedProcessId, setSelectedProcessId] = useState(null);
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;
- const checkAndRedirectToDetail = useCallback(async () => {
- if (!currentUserId) return;
-
- try {
- // 获取所有 processes
- const processes = await fetchProductProcesses();
-
- // 获取所有 lines 并检查是否有匹配的
- for (const process of processes.content || []) {
- const lines = await fetchProductProcessLines(process.id);
- const pendingLine = lines.find((line: ProductProcessLineResponse) =>
- line.handlerId === currentUserId &&
- !line.endTime &&
- line.startTime
- );
-
- if (pendingLine) {
- setSelectedProcessId(process.id);
- break;
- }
- }
- } catch (error) {
- console.error("Error checking pending lines:", error);
- }
- }, [currentUserId]);
-
- useEffect(() => {
- if (currentUserId && !selectedProcessId) {
- // 检查是否有当前用户的 pending line
- checkAndRedirectToDetail();
- }
- }, [currentUserId, selectedProcessId, checkAndRedirectToDetail]);
+ // …原有逻辑省略…
if (selectedProcessId !== null) {
return (
- setSelectedProcessId(null)}
/>
@@ -59,12 +44,11 @@ const ProductionProcessPage = () => {
return (
{
+ printerCombo={printerCombo}
+ onSelectProcess={(jobOrderId) => {
const id = jobOrderId ?? null;
if (id !== null) {
setSelectedProcessId(id);
- } else {
-
}
}}
/>
diff --git a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx
index 46dbc5c..92b9a4b 100644
--- a/src/components/ProductionProcess/ProductionProcessStepExecution.tsx
+++ b/src/components/ProductionProcess/ProductionProcessStepExecution.tsx
@@ -21,21 +21,24 @@ import StopIcon from "@mui/icons-material/Stop";
import PauseIcon from "@mui/icons-material/Pause";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import { useTranslation } from "react-i18next";
-import { ProductProcessLineDetailResponse, updateProductProcessLineQty,updateProductProcessLineQrscan,fetchProductProcessLineDetail ,UpdateProductProcessLineQtyRequest} from "@/app/api/jo/actions";
+import { JobOrderProcessLineDetailResponse, updateProductProcessLineQty,updateProductProcessLineQrscan,fetchProductProcessLineDetail ,UpdateProductProcessLineQtyRequest} from "@/app/api/jo/actions";
import { Operator, Machine } from "@/app/api/jo";
import React, { useCallback, useEffect, useState } from "react";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import { fetchNameList, NameList } from "@/app/api/user/actions";
interface ProductionProcessStepExecutionProps {
lineId: number | null
+ onBack: () => void
//onClose: () => void
// onOutputSubmitted: () => Promise
}
const ProductionProcessStepExecution: React.FC = ({
lineId,
+ onBack,
}) => {
const { t } = useTranslation();
- const [lineDetail, setLineDetail] = useState(null);
+ const [lineDetail, setLineDetail] = useState(null);
+ const isCompleted = lineDetail?.status === "Completed";
const [outputData, setOutputData] = useState {
+ if (!lineId) {
+ setLineDetail(null);
+ return;
+ }
+ fetchProductProcessLineDetail(lineId)
+ .then((detail) => {
+ setLineDetail(detail as any);
+ // 初始化 outputData 从 lineDetail
+ setOutputData(prev => ({
+ ...prev,
+ productProcessLineId: detail.id,
+ outputFromProcessQty: (detail as any).outputFromProcessQty || 0, // 取消注释,使用类型断言
+ outputFromProcessUom: (detail as any).outputFromProcessUom || "", // 取消注释,使用类型断言
+ defectQty: detail.defectQty || 0,
+ defectUom: detail.defectUom || "",
+ scrapQty: detail.scrapQty || 0,
+ scrapUom: detail.scrapUom || "",
+ byproductName: detail.byproductName || "",
+ byproductQty: detail.byproductQty || 0,
+ byproductUom: detail.byproductUom || ""
+ }));
+ })
+ .catch(err => {
+ console.error("Failed to load line detail", err);
+ setLineDetail(null);
+ });
+ }, [lineId]);
const handleSubmitOutput = async () => {
if (!lineDetail?.id) return;
@@ -88,27 +120,15 @@ const ProductionProcessStepExecution: React.FC {
- if (!lineId) {
- setLineDetail(null);
- return;
- }
- fetchProductProcessLineDetail(lineId)
+ fetchProductProcessLineDetail(lineDetail.id)
.then((detail) => {
- setLineDetail(detail);
+ setLineDetail(detail as any);
// 初始化 outputData 从 lineDetail
setOutputData(prev => ({
...prev,
productProcessLineId: detail.id,
- //outputFromProcessQty: detail.outputFromProcessQty || 0,
- // outputFromProcessUom: detail.outputFromProcessUom || "",
+ outputFromProcessQty: (detail as any).outputFromProcessQty || 0, // 取消注释,使用类型断言
+ outputFromProcessUom: (detail as any).outputFromProcessUom || "", // 取消注释,使用类型断言
defectQty: detail.defectQty || 0,
defectUom: detail.defectUom || "",
scrapQty: detail.scrapQty || 0,
@@ -122,8 +142,12 @@ const ProductionProcessStepExecution: React.FC {
if (isManualScanning && qrValues.length > 0 && lineDetail?.id) {
@@ -159,245 +183,352 @@ const ProductionProcessStepExecution: React.FC
- {/* 当前步骤信息 */}
-
-
-
-
-
- {t("Executing")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo})
-
-
- {lineDetail?.description}
-
-
- {t("Operator")}: {lineDetail?.operatorName || "-"}
-
-
- {t("Equipment")}: {equipmentName}
-
-
- }
- onClick={handleStop}
- >
- {t("Stop")}
-
- {!isPaused ? (
- }
- onClick={handlePause}
- >
- {t("Pause")}
-
- ) : (
- }
- onClick={handleContinue}
- >
- {t("Continue")}
-
- )}
-
-
-
-
-
-
-
+
+
+
-
-
+ {/* 如果已完成,显示合并的视图 */}
+ {isCompleted ? (
+
+
+
+ {t("Completed Step")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo})
+
+
+ {/**/}
+
+ {/* 步骤信息部分 */}
+
+ {t("Step Information")}
+
+
+
+
+ {t("Description")}: {lineDetail?.description || "-"}
+
+
+
+
+ {t("Operator")}: {lineDetail?.operatorName || "-"}
+
+
+
+
+ {t("Equipment")}: {equipmentName}
+
+
+
+
+ {t("Status")}: {lineDetail?.status || "-"}
+
+
+
+
+ {/**/}
- {/* ========== 产出输入表单 ========== */}
- {bothScanned && (
-
-
-
- {t("Production Output Data Entry")}
+ {/* 产出数据部分 */}
+
+ {t("Production Output Data")}
-
-
-
- {showOutputTable && (
-
-
-
-
- {t("Type")}
- {t("Quantity")}
- {t("Unit")}
-
-
-
- {/* start line output */}
+
+
+
+ {t("Type")}
+ {t("Quantity")}
+ {t("Unit")}
+
+
+
+ {/* Output from Process */}
+
+
+ {t("Output from Process")}
+
+
+ {lineDetail?.outputFromProcessQty || 0}
+
+
+ {lineDetail?.outputFromProcessUom || "-"}
+
+
+
+ {/* By-product */}
+ {lineDetail?.byproductQty && lineDetail.byproductQty > 0 && (
- {t("Output from Process")}
+ {t("By-product")}
+ {lineDetail.byproductName && (
+
+ ({lineDetail.byproductName})
+
+ )}
- setOutputData({
- ...outputData,
- outputFromProcessQty: parseInt(e.target.value) || 0
- })}
- />
+ {lineDetail.byproductQty}
- setOutputData({
- ...outputData,
- outputFromProcessUom: e.target.value
- })}
- //placeholder="KG, L, PCS..."
- />
-
-
- {/* byproduct */}
-
-
-
- {t("By-product")}
- setOutputData({
- ...outputData,
- byproductName: e.target.value
- })}
- placeholder={t("By-product name")}
- sx={{ mt: 1 }}
- />
-
-
-
- setOutputData({
- ...outputData,
- byproductQty: parseInt(e.target.value) || 0
- })}
- />
-
-
- setOutputData({
- ...outputData,
- byproductUom: e.target.value
- })}
- //placeholder="KG, L, PCS..."
- />
+ {lineDetail.byproductUom || "-"}
+ )}
- {/* defect */}
+ {/* Defect */}
+ {lineDetail?.defectQty && lineDetail.defectQty > 0 && (
{t("Defect")}
- setOutputData({
- ...outputData,
- defectQty: parseInt(e.target.value) || 0
- })}
- />
+ {lineDetail.defectQty}
- setOutputData({
- ...outputData,
- defectUom: e.target.value
- })}
- //placeholder="KG, L, PCS..."
- />
+ {lineDetail.defectUom || "-"}
+ )}
- {/* scrap */}
+ {/* Scrap */}
+ {lineDetail?.scrapQty && lineDetail.scrapQty > 0 && (
{t("Scrap")}
- setOutputData({
- ...outputData,
- scrapQty: parseInt(e.target.value) || 0
- })}
- />
+ {lineDetail.scrapQty}
- setOutputData({
- ...outputData,
- scrapUom: e.target.value
- })}
- //placeholder="KG, L, PCS..."
- />
+ {lineDetail.scrapUom || "-"}
-
-
+ )}
+
+
+
+
+ ) : (
+ <>
+ {/* 如果未完成,显示原来的两个部分 */}
+ {/* 当前步骤信息 */}
+
+
+
+
+
+ {t("Executing")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo})
+
+
+ {lineDetail?.description}
+
+
+ {t("Operator")}: {lineDetail?.operatorName || "-"}
+
+
+ {t("Equipment")}: {equipmentName}
+
+
+ }
+ onClick={handleStop}
+ >
+ {t("Stop")}
+
+ {!isPaused ? (
+ }
+ onClick={handlePause}
+ >
+ {t("Pause")}
+
+ ) : (
+ }
+ onClick={handleContinue}
+ >
+ {t("Continue")}
+
+ )}
+
+
+
+
+
+
+ {/* ========== 产出输入表单 ========== */}
+
+
+
+ {t("Production Output Data Entry")}
+
+
+
+
+ {showOutputTable && (
+
+
+
+
+ {t("Type")}
+ {t("Quantity")}
+ {t("Unit")}
+
+
+
+ {/* start line output */}
+
+
+ {t("Output from Process")}
+
+
+ setOutputData({
+ ...outputData,
+ outputFromProcessQty: parseInt(e.target.value) || 0
+ })}
+ />
+
+
+ setOutputData({
+ ...outputData,
+ outputFromProcessUom: e.target.value
+ })}
+ />
+
+
+ {/* byproduct */}
+
+
+
+ {t("By-product")}
+
+
+
+ setOutputData({
+ ...outputData,
+ byproductQty: parseInt(e.target.value) || 0
+ })}
+ />
+
+
+ setOutputData({
+ ...outputData,
+ byproductUom: e.target.value
+ })}
+ />
+
+
- {/* submit button */}
-
-
- }
- onClick={handleSubmitOutput}
- >
- {t("Complete Step")}
-
-
-
- )}
-
+ {/* defect */}
+
+
+ {t("Defect")}
+
+
+ setOutputData({
+ ...outputData,
+ defectQty: parseInt(e.target.value) || 0
+ })}
+ />
+
+
+ setOutputData({
+ ...outputData,
+ defectUom: e.target.value
+ })}
+ />
+
+
+
+ {/* scrap */}
+
+
+ {t("Scrap")}
+
+
+ setOutputData({
+ ...outputData,
+ scrapQty: parseInt(e.target.value) || 0
+ })}
+ />
+
+
+ setOutputData({
+ ...outputData,
+ scrapUom: e.target.value
+ })}
+ />
+
+
+
+
+
+ {/* submit button */}
+
+
+ }
+ onClick={handleSubmitOutput}
+ >
+ {t("Complete Step")}
+
+
+
+ )}
+
+ >
)}
);