diff --git a/check-translations.js b/check-translations.js index fdd0cc9..c9b7138 100644 --- a/check-translations.js +++ b/check-translations.js @@ -11,7 +11,7 @@ function checkMissingTranslations(sourceFile, jsonFile) { // 读取翻译 JSON 文件 const translations = JSON.parse(fs.readFileSync(jsonFile, 'utf-8')); - // ✅ 只匹配 t('...') 和 t("...") 和 t(`...`),不包含模板变量 + // 只匹配 t('...') 和 t("...") 和 t(`...`),不包含模板变量 const tRegex = /\bt\(["`']([^"`'${}]+)["`']\)/g; const matches = [...sourceCode.matchAll(tRegex)]; @@ -86,7 +86,7 @@ if (args.length === 0) { console.log(' node check-translations.js src/components/Jodetail/JodetailSearch.tsx src/i18n/zh/jo.json'); console.log(' node check-translations.js --dir src/components/Jodetail src/i18n/zh/jo.json'); console.log('\n注意:'); - console.log(' ✅ 只检查 t("key") 调用'); + console.log(' 只检查 t("key") 调用'); console.log(' ❌ 忽略 alert(), console.log() 等普通字符串'); console.log(' ❌ 忽略模板字符串中的 ${} 变量部分'); process.exit(0); @@ -103,7 +103,7 @@ if (args[0] === '--dir') { const { results, totalMissing } = checkDirectory(directory, jsonFile); if (Object.keys(results).length === 0) { - console.log('✅ 太棒了!没有发现缺失的翻译键!'); + console.log(' 太棒了!没有发现缺失的翻译键!'); } else { console.log(`⚠️ 发现 ${Object.keys(results).length} 个文件有缺失的翻译键\n`); @@ -153,7 +153,7 @@ if (args[0] === '--dir') { }); console.log('─'.repeat(60)); } else { - console.log('\n✅ 太棒了!所有使用的翻译键都已定义!'); + console.log('\n 太棒了!所有使用的翻译键都已定义!'); } if (result.unusedKeys.length > 0 && result.unusedKeys.length <= 20) { diff --git a/src/app/(main)/production/page.tsx b/src/app/(main)/production/page.tsx index f08ea76..24e9e30 100644 --- a/src/app/(main)/production/page.tsx +++ b/src/app/(main)/production/page.tsx @@ -1,4 +1,4 @@ -import ProductionProcess from "../../../components/ProductionProcess"; +import ProductionProcessPage from "../../../components/ProductionProcess/ProductionProcessPage"; import { getServerI18n } from "../../../i18n"; import Add from "@mui/icons-material/Add"; @@ -15,7 +15,6 @@ export const metadata: Metadata = { const production: React.FC = async () => { const { t } = await getServerI18n("claims"); - // preloadClaims(); return ( <> @@ -26,22 +25,22 @@ const production: React.FC = async () => { rowGap={2} > - {t("Production")} + {t("Production Process")} - + {t("Create Process")} + */} - {/* }> */} - - {/* */} + {/* Use new component */} ); }; + export default production; diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 353c799..f1cb0f3 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -129,13 +129,13 @@ export const recordSecondScanIssue = cache(async ( itemId: number, data: { qty: number; // verified qty (actual pick qty) - missQty?: number; // ✅ 添加:miss qty - badItemQty?: number; // ✅ 添加:bad item qty + missQty?: number; // 添加:miss qty + badItemQty?: number; // 添加:bad item qty isMissing: boolean; isBad: boolean; reason: string; createdBy: number; - type?: string; // ✅ type 也应该是可选的 + type?: string; // type 也应该是可选的 } ) => { @@ -188,6 +188,136 @@ export interface ProductProcessWithLinesResponse { date: string; lines: ProductProcessLineResponse[]; } +export interface UpdateProductProcessLineQtyRequest { + productProcessLineId: number; + outputFromProcessQty: number; + outputFromProcessUom: string; + defectQty: number; + defectUom: string; + scrapQty: number; + scrapUom: string; +} +export interface UpdateProductProcessLineQtyResponse { + id: number; + outputFromProcessQty: number; + outputFromProcessUom: string; + defectQty: number; + defectUom: string; + scrapQty: number; + scrapUom: string; + byproductName: string; + byproductQty: number; + byproductUom: string; +} +export interface AllProductProcessResponse { + id: number; + productProcessCode: string; + status: string; + startTime?: string; + endTime?: string; + date: string; + bomId?: number; +} +export interface AllJoborderProductProcessInfoResponse { + id: number; + productProcessCode: string; + status: string; + startTime?: string; + endTime?: string; + date: string; + bomId?: number; + itemName: string; + jobOrderId: number; + jobOrderCode: string; + productProcessLineCount: number; + FinishedProductProcessLineCount: number; + lines: ProductProcessInfoResponse[]; +} +export interface ProductProcessInfoResponse { + id: number; + operatorId?: number; + operatorName?: string; + equipmentId?: number; + equipmentName?: string; + startTime?: string; + endTime?: string; + status: string; +} +export interface ProductProcessLineQrscanUpadteRequest { + lineId: number; + operatorId?: number; + equipmentId?: 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 fetchProductProcessLineDetailByJoid = 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( + `${BASE_API_URL}/product-process/Demo/ProcessLine/detail/${lineId}`, + { + method: "GET", + } + ); +}); +export const updateProductProcessLineQrscan = cache(async (request: ProductProcessLineQrscanUpadteRequest) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/Demo/update`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + } + ); +}); +export const fetchAllJoborderProductProcessInfo = cache(async () => { + return serverFetchJson( + `${BASE_API_URL}/product-process/Demo/Process/all`, + { + method: "GET", + next: { tags: ["productProcess"] }, + } + ); +}); +export const updateProductProcessLineQty = async (request: UpdateProductProcessLineQtyRequest) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/lines/${request.productProcessLineId}/update/qty`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + } + ); +}; export const startProductProcessLine = async (lineId: number, userId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/lines/${lineId}/start?userId=${userId}`, diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index f8b0272..62bf102 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -95,12 +95,12 @@ export interface GetPickOrderInfoResponse { export interface GetPickOrderInfo { id: number; code: string; - consoCode: string | null; // ✅ 添加 consoCode 属性 - targetDate: string | number[]; // ✅ Support both formats + consoCode: string | null; // 添加 consoCode 属性 + targetDate: string | number[]; // Support both formats type: string; status: string; assignTo: number; - groupName: string; // ✅ Add this field + groupName: string; // Add this field pickOrderLines: GetPickOrderLineInfo[]; } @@ -256,16 +256,16 @@ export interface stockReponse{ noLot: boolean; } export interface FGPickOrderResponse { - // ✅ 新增:支持多个 pick orders + // 新增:支持多个 pick orders doPickOrderId: number; pickOrderIds?: number[]; - pickOrderCodes?: string[]; // ✅ 改为数组 + pickOrderCodes?: string[]; // 改为数组 deliveryOrderIds?: number[]; - deliveryNos?: string[]; // ✅ 改为数组 + deliveryNos?: string[]; // 改为数组 numberOfPickOrders?: number; - lineCountsPerPickOrder?: number[];// ✅ 新增:pick order 数量 + lineCountsPerPickOrder?: number[];// 新增:pick order 数量 - // ✅ 保留原有字段用于向后兼容(显示第一个 pick order) + // 保留原有字段用于向后兼容(显示第一个 pick order) pickOrderId: number; pickOrderCode: string; pickOrderConsoCode: string; @@ -332,17 +332,17 @@ export interface UpdateDoPickOrderHideStatusRequest { } export interface CompletedDoPickOrderResponse { id: number; - doPickOrderRecordId: number; // ✅ 新增 + doPickOrderRecordId: number; // 新增 pickOrderId: number; - pickOrderIds: number[]; // ✅ 新增:所有 pick order IDs + pickOrderIds: number[]; // 新增:所有 pick order IDs pickOrderCode: string; - pickOrderCodes: string; // ✅ 新增:所有 pick order codes (逗号分隔) + pickOrderCodes: string; // 新增:所有 pick order codes (逗号分隔) pickOrderConsoCode: string; pickOrderStatus: string; deliveryOrderId: number; - deliveryOrderIds: number[]; // ✅ 新增:所有 delivery order IDs + deliveryOrderIds: number[]; // 新增:所有 delivery order IDs deliveryNo: string; - deliveryNos: string; // ✅ 新增:所有 delivery order codes (逗号分隔) + deliveryNos: string; // 新增:所有 delivery order codes (逗号分隔) deliveryDate: string; shopId: number; shopCode: string; @@ -352,13 +352,13 @@ export interface CompletedDoPickOrderResponse { shopPoNo: string; numberOfCartons: number; truckLanceCode: string; - DepartureTime: string; // ✅ 新增 + DepartureTime: string; // 新增 storeId: string; completedDate: string; fgPickOrders: FGPickOrderResponse[]; } -// ✅ 新增:搜索参数接口 +// 新增:搜索参数接口 export interface CompletedDoPickOrderSearchParams { pickOrderCode?: string; shopName?: string; @@ -491,7 +491,8 @@ export async function assignByLane( userId: number, storeId: string, truckLanceCode: string, - truckDepartureTime?: string + truckDepartureTime?: string, + requiredDate?: string ): Promise { const response = await serverFetchJson( `${BASE_API_URL}/doPickOrder/assign-by-lane`, @@ -505,12 +506,13 @@ export async function assignByLane( storeId, truckLanceCode, truckDepartureTime, + requiredDate, }), } ); return response; } -// ✅ 新增:获取已完成的 DO Pick Orders API +// 新增:获取已完成的 DO Pick Orders API export const fetchCompletedDoPickOrders = async ( userId: number, searchParams?: CompletedDoPickOrderSearchParams @@ -919,7 +921,7 @@ export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): P } ); - console.log("✅ Fetched hierarchical lot details:", data); + console.log(" Fetched hierarchical lot details:", data); return data; } catch (error) { console.error("❌ Error fetching hierarchical lot details:", error); @@ -947,7 +949,7 @@ export const fetchLotDetailsByDoPickOrderRecordId = async (doPickOrderRecordId: } ); - console.log("✅ Fetched hierarchical lot details:", data); + console.log(" Fetched hierarchical lot details:", data); return data; } catch (error) { console.error("❌ Error fetching lot details:", error); @@ -962,7 +964,7 @@ export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Pro try { console.log("🔍 Fetching all pick order line lot details for userId:", userId); - // ✅ Use the non-auto-assign endpoint + // Use the non-auto-assign endpoint const data = await serverFetchJson( `${BASE_API_URL}/pickOrder/all-lots-with-details-no-auto-assign/${userId}`, { @@ -971,7 +973,7 @@ export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Pro } ); - console.log("✅ Fetched lot details:", data); + console.log(" Fetched lot details:", data); return data; } catch (error) { console.error("❌ Error fetching lot details:", error); @@ -987,7 +989,7 @@ export const fetchAllPickOrderDetails = cache(async (userId?: number) => { }; } - // ✅ Use the correct endpoint with userId in the path + // Use the correct endpoint with userId in the path const url = `${BASE_API_URL}/pickOrder/detail-optimized/${userId}`; return serverFetchJson( diff --git a/src/components/DoDetail/DoDetail.tsx b/src/components/DoDetail/DoDetail.tsx index 9f3bd0b..49d8940 100644 --- a/src/components/DoDetail/DoDetail.tsx +++ b/src/components/DoDetail/DoDetail.tsx @@ -13,7 +13,7 @@ import { releaseDo, assignPickOrderByStore, releaseAssignedPickOrderByStore } fr import DoInfoCard from "./DoInfoCard"; import DoLineTable from "./DoLineTable"; import { useSession } from "next-auth/react"; -import { SessionWithTokens } from "@/config/authConfig"; // ✅ Import the correct session type +import { SessionWithTokens } from "@/config/authConfig"; // Import the correct session type type Props = { id?: number; @@ -30,9 +30,9 @@ const DoDetail: React.FC = ({ const [serverError, setServerError] = useState(""); const [successMessage, setSuccessMessage] = useState(""); const [isAssigning, setIsAssigning] = useState(false); - const { data: session } = useSession() as { data: SessionWithTokens | null }; // ✅ Use correct session type + const { data: session } = useSession() as { data: SessionWithTokens | null }; // Use correct session type - const currentUserId = session?.id ? parseInt(session.id) : undefined; // ✅ Get user ID from session.id + const currentUserId = session?.id ? parseInt(session.id) : undefined; // Get user ID from session.id console.log("🔍 DoSearch - session:", session); console.log("🔍 DoSearch - currentUserId:", currentUserId); const formProps = useForm({ @@ -50,7 +50,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId); setSuccessMessage("") if (id) { - // ✅ Get current user ID from session + // Get current user ID from session const currentUserId = session?.id ? parseInt(session.id) : undefined; if (!currentUserId) { @@ -60,7 +60,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId); const response = await releaseDo({ id: id, - userId: currentUserId // ✅ Pass user ID from session + userId: currentUserId // Pass user ID from session }) if (response) { @@ -74,16 +74,16 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId); } finally { setIsUploading(false) } - }, [id, formProps, t, setIsUploading, session]) // ✅ Add session to dependencies + }, [id, formProps, t, setIsUploading, session]) // Add session to dependencies - // ✅ UPDATE STORE-BASED ASSIGNMENT HANDLERS + // UPDATE STORE-BASED ASSIGNMENT HANDLERS const handleAssignByStore = useCallback(async (storeId: string) => { try { setIsAssigning(true) setServerError("") setSuccessMessage("") - // ✅ Get current user ID from session + // Get current user ID from session const currentUserId = session?.id ? parseInt(session.id) : undefined; if (!currentUserId) { @@ -107,7 +107,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId); } finally { setIsAssigning(false) } - }, [t, session]) // ✅ Add session to dependencies + }, [t, session]) // Add session to dependencies const handleReleaseByStore = useCallback(async (storeId: string) => { try { @@ -115,7 +115,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId); setServerError("") setSuccessMessage("") - // ✅ Get current user ID from session + // Get current user ID from session const currentUserId = session?.id ? parseInt(session.id) : undefined; if (!currentUserId) { @@ -139,7 +139,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId); } finally { setIsAssigning(false) } - }, [t, session]) // ✅ Add session to dependencies + }, [t, session]) // Add session to dependencies const onSubmit = useCallback>(async (data, event) => { console.log(data) @@ -182,7 +182,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId); )} - {/* ✅ ADD STORE-BASED ASSIGNMENT BUTTONS */} + {/* ADD STORE-BASED ASSIGNMENT BUTTONS */} { formProps.watch("status")?.toLowerCase() === "released" && ( diff --git a/src/components/FinishedGoodSearch/CombinedLotTable.tsx b/src/components/FinishedGoodSearch/CombinedLotTable.tsx index f594d94..459c935 100644 --- a/src/components/FinishedGoodSearch/CombinedLotTable.tsx +++ b/src/components/FinishedGoodSearch/CombinedLotTable.tsx @@ -34,7 +34,7 @@ interface CombinedLotTableProps { onPageSizeChange: (event: React.ChangeEvent) => void; } -// ✅ Simple helper function to check if item is completed +// Simple helper function to check if item is completed const isItemCompleted = (lot: any) => { const actualPickQty = Number(lot.actualPickQty) || 0; const requiredQty = Number(lot.requiredQty) || 0; @@ -60,7 +60,7 @@ const CombinedLotTable: React.FC = ({ }) => { const { t } = useTranslation("pickOrder"); - // ✅ Paginated data + // Paginated data const paginatedLotData = useMemo(() => { const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; @@ -113,7 +113,7 @@ const CombinedLotTable: React.FC = ({ const isCompleted = isItemCompleted(lot); const isRejected = isItemRejected(lot); - // ✅ Green text color for completed items + // Green text color for completed items const textColor = isCompleted ? 'success.main' : isRejected ? 'error.main' : 'inherit'; return ( diff --git a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx index 3ee7c1a..be5964b 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx @@ -47,7 +47,8 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => const handleAssignByLane = useCallback(async ( storeId: string, truckDepartureTime: string, - truckLanceCode: string + truckLanceCode: string, + requiredDate: string ) => { if (!currentUserId) { console.error("Missing user id in session"); @@ -56,10 +57,10 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => setIsAssigning(true); try { - const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, requiredDate); if (res.code === "SUCCESS") { - console.log("✅ Successfully assigned pick order from lane", truckLanceCode); + console.log(" Successfully assigned pick order from lane", truckLanceCode); window.dispatchEvent(new CustomEvent('pickOrderAssigned')); loadSummaries(); // 刷新按钮状态 onPickOrderAssigned?.(); @@ -231,7 +232,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode)} + onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} sx={{ flex: 1, fontSize: '1.1rem', @@ -344,7 +345,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode)} + onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} sx={{ flex: 1, fontSize: '1.1rem', diff --git a/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx b/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx index 3d88dce..1afad92 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx @@ -27,22 +27,22 @@ const FGPickOrderInfoCard: React.FC = ({ fgOrder, doPickOrderDetail }) => diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx index 1961041..4669828 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx @@ -56,22 +56,31 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => loadSummaries(); }, [loadSummaries]); - const handleAssignByLane = useCallback(async ( - storeId: string, - truckDepartureTime: string, - truckLanceCode: string - ) => { - if (!currentUserId) { - console.error("Missing user id in session"); - return; - } - - setIsAssigning(true); - try { - const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); + const handleAssignByLane = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string, + requiredDate: string + + ) => { + if (!currentUserId) { + console.error("Missing user id in session"); + return; + } + let dateParam: string | undefined; + if (requiredDate === "today") { + dateParam = dayjs().format('YYYY-MM-DD'); + } else if (requiredDate === "tomorrow") { + dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD'); + } else if (requiredDate === "dayAfterTomorrow") { + dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD'); + } + setIsAssigning(true); + try { + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam); if (res.code === "SUCCESS") { - console.log("✅ Successfully assigned pick order from lane", truckLanceCode); + console.log(" Successfully assigned pick order from lane", truckLanceCode); window.dispatchEvent(new CustomEvent('pickOrderAssigned')); loadSummaries(); // 刷新按钮状态 onPickOrderAssigned?.(); @@ -236,7 +245,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode)} + onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} sx={{ flex: 1, fontSize: '1.1rem', @@ -336,7 +345,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode)} + onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} sx={{ flex: 1, fontSize: '1.1rem', diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 8c3ec12..ff54a88 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -247,7 +247,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const handleCompletionStatusChange = (event: CustomEvent) => { const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail; - // ✅ 修复:根据标签页和事件来源决定是否更新打印按钮状态 + // 修复:根据标签页和事件来源决定是否更新打印按钮状态 if (eventTabIndex === undefined || eventTabIndex === tabIndex) { setPrintButtonsEnabled(allLotsCompleted); console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted); @@ -259,9 +259,9 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { return () => { window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); }; - }, [tabIndex]); // ✅ 添加 tabIndex 依赖 + }, [tabIndex]); // 添加 tabIndex 依赖 - // ✅ 新增:处理标签页切换时的打印按钮状态重置 + // 新增:处理标签页切换时的打印按钮状态重置 useEffect(() => { // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态 if (tabIndex === 2) { @@ -286,7 +286,7 @@ const handleAssignByLane = useCallback(async ( const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); if (res.code === "SUCCESS") { - console.log("✅ Successfully assigned pick order from lane", truckLanceCode); + console.log(" Successfully assigned pick order from lane", truckLanceCode); window.dispatchEvent(new CustomEvent('pickOrderAssigned')); loadSummaries(); // 刷新按钮状态 } else if (res.code === "USER_BUSY") { @@ -322,7 +322,7 @@ const handleAssignByLane = useCallback(async ( setIsAssigning(false); } }, [currentUserId, t, loadSummaries]); - // ✅ Manual assignment handler - uses the action function + // Manual assignment handler - uses the action function */ const handleTabChange = useCallback>( (_e, newValue) => { @@ -607,7 +607,7 @@ const handleAssignByLane = useCallback(async ( - {/* Tabs section - ✅ Move the click handler here */} + {/* Tabs section - Move the click handler here */} diff --git a/src/components/FinishedGoodSearch/GoodPickExecution.tsx b/src/components/FinishedGoodSearch/GoodPickExecution.tsx index 4f2ba06..ca39390 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecution.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecution.tsx @@ -26,7 +26,7 @@ import { updateStockOutLineStatus, createStockOutLine, recordPickExecutionIssue, - fetchFGPickOrdersByUserId, // ✅ Add this import + fetchFGPickOrdersByUserId, // Add this import FGPickOrderResponse, autoAssignAndReleasePickOrder, AutoAssignReleaseResponse, @@ -59,13 +59,13 @@ interface Props { onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; } -// ✅ QR Code Modal Component (from LotTable) +// QR Code Modal Component (from LotTable) const QrCodeModal: React.FC<{ open: boolean; onClose: () => void; lot: any | null; onQrCodeSubmit: (lotNo: string) => void; - combinedLotData: any[]; // ✅ Add this prop + combinedLotData: any[]; // Add this prop }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => { const { t } = useTranslation("pickOrder"); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); @@ -105,7 +105,7 @@ const QrCodeModal: React.FC<{ setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); if (stockInLineInfo.lotNo === lot.lotNo) { - console.log(`✅ QR Code verified for lot: ${lot.lotNo}`); + console.log(` QR Code verified for lot: ${lot.lotNo}`); setQrScanSuccess(true); onQrCodeSubmit(lot.lotNo); onClose(); @@ -297,7 +297,7 @@ const QrCodeModal: React.FC<{ {qrScanSuccess && ( - ✅ {t("Verified successfully!")} + {t("Verified successfully!")} )} @@ -348,11 +348,11 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); const formProps = useForm(); const errors = formProps.formState.errors; - // ✅ Add QR modal states + // Add QR modal states const [qrModalOpen, setQrModalOpen] = useState(false); const [selectedLotForQr, setSelectedLotForQr] = useState(null); - // ✅ Add GoodPickExecutionForm states + // Add GoodPickExecutionForm states const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState(null); const [fgPickOrders, setFgPickOrders] = useState([]); @@ -389,14 +389,14 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [currentUserId, selectedPickOrderId]); - // ✅ 简化:移除复杂的 useEffect 依赖 + // 简化:移除复杂的 useEffect 依赖 useEffect(() => { if (currentUserId) { fetchFgPickOrdersData(); } }, [currentUserId, fetchFgPickOrdersData]); - // ✅ Handle QR code button click + // Handle QR code button click const handleQrCodeClick = (pickOrderId: number) => { console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); // TODO: Implement QR code functionality @@ -424,22 +424,22 @@ const fetchFgPickOrdersData = useCallback(async () => { return; } - // ✅ Use the non-auto-assign endpoint - this only fetches existing data + // Use the non-auto-assign endpoint - this only fetches existing data const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); - console.log("✅ All combined lot details:", allLotDetails); + console.log(" All combined lot details:", allLotDetails); setCombinedLotData(allLotDetails); setOriginalCombinedData(allLotDetails); - // ✅ 计算完成状态并发送事件 + // 计算完成状态并发送事件 const allCompleted = allLotDetails.length > 0 && allLotDetails.every(lot => lot.processingStatus === 'completed' ); - // ✅ 发送完成状态事件,包含标签页信息 + // 发送完成状态事件,包含标签页信息 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: allCompleted, - tabIndex: 0 // ✅ 明确指定这是来自标签页 0 的事件 + tabIndex: 0 // 明确指定这是来自标签页 0 的事件 } })); @@ -448,7 +448,7 @@ const fetchFgPickOrdersData = useCallback(async () => { setCombinedLotData([]); setOriginalCombinedData([]); - // ✅ 如果加载失败,禁用打印按钮 + // 如果加载失败,禁用打印按钮 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, @@ -460,18 +460,18 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [currentUserId, combinedLotData]); - // ✅ Only fetch existing data when session is ready, no auto-assignment + // Only fetch existing data when session is ready, no auto-assignment useEffect(() => { if (session && currentUserId && !initializationRef.current) { - console.log("✅ Session loaded, initializing pick order..."); + console.log(" Session loaded, initializing pick order..."); initializationRef.current = true; - // ✅ Only fetch existing data, no auto-assignment + // Only fetch existing data, no auto-assignment fetchAllCombinedLotData(); } }, [session, currentUserId, fetchAllCombinedLotData]); - // ✅ Add event listener for manual assignment + // Add event listener for manual assignment useEffect(() => { const handlePickOrderAssigned = () => { console.log("🔄 Pick order assigned event received, refreshing data..."); @@ -485,12 +485,12 @@ const fetchFgPickOrdersData = useCallback(async () => { }; }, [fetchAllCombinedLotData]); - // ✅ Handle QR code submission for matched lot (external scanning) - // ✅ Handle QR code submission for matched lot (external scanning) + // Handle QR code submission for matched lot (external scanning) + // Handle QR code submission for matched lot (external scanning) const handleQrCodeSubmit = useCallback(async (lotNo: string) => { - console.log(`✅ Processing QR Code for lot: ${lotNo}`); + console.log(` Processing QR Code for lot: ${lotNo}`); - // ✅ Use current data without refreshing to avoid infinite loop + // Use current data without refreshing to avoid infinite loop const currentLotData = combinedLotData; console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo)); @@ -506,7 +506,7 @@ const fetchFgPickOrdersData = useCallback(async () => { return; } - console.log(`✅ Found ${matchingLots.length} matching lots:`, matchingLots); + console.log(` Found ${matchingLots.length} matching lots:`, matchingLots); setQrScanError(false); try { @@ -518,7 +518,7 @@ const fetchFgPickOrdersData = useCallback(async () => { console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`); if (matchingLot.stockOutLineId) { - console.log(`✅ Stock out line already exists for line ${matchingLot.pickOrderLineId}`); + console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`); existsCount++; } else { const stockOutLineData: CreateStockOutLine = { @@ -533,10 +533,10 @@ const fetchFgPickOrdersData = useCallback(async () => { console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, result); if (result && result.code === "EXISTS") { - console.log(`✅ Stock out line already exists for line ${matchingLot.pickOrderLineId}`); + console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`); existsCount++; } else if (result && result.code === "SUCCESS") { - console.log(`✅ Stock out line created successfully for line ${matchingLot.pickOrderLineId}`); + console.log(` Stock out line created successfully for line ${matchingLot.pickOrderLineId}`); successCount++; } else { console.error(`❌ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, result); @@ -545,16 +545,16 @@ const fetchFgPickOrdersData = useCallback(async () => { } } - // ✅ Always refresh data after processing (success or failure) + // Always refresh data after processing (success or failure) console.log("🔄 Refreshing data after QR code processing..."); await fetchAllCombinedLotData(); if (successCount > 0 || existsCount > 0) { - console.log(`✅ QR Code processing completed: ${successCount} created, ${existsCount} already existed`); + console.log(` QR Code processing completed: ${successCount} created, ${existsCount} already existed`); setQrScanSuccess(true); setQrScanInput(''); // Clear input after successful processing - // ✅ Clear success state after a delay + // Clear success state after a delay setTimeout(() => { setQrScanSuccess(false); }, 2000); @@ -563,7 +563,7 @@ const fetchFgPickOrdersData = useCallback(async () => { setQrScanError(true); setQrScanSuccess(false); - // ✅ Clear error state after a delay + // Clear error state after a delay setTimeout(() => { setQrScanError(false); }, 3000); @@ -573,10 +573,10 @@ const fetchFgPickOrdersData = useCallback(async () => { setQrScanError(true); setQrScanSuccess(false); - // ✅ Still refresh data even on error + // Still refresh data even on error await fetchAllCombinedLotData(); - // ✅ Clear error state after a delay + // Clear error state after a delay setTimeout(() => { setQrScanError(false); }, 3000); @@ -589,17 +589,17 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [qrScanInput, handleQrCodeSubmit]); - // ✅ Handle QR code submission from modal (internal scanning) + // Handle QR code submission from modal (internal scanning) const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { - console.log(`✅ QR Code verified for lot: ${lotNo}`); + console.log(` QR Code verified for lot: ${lotNo}`); const requiredQty = selectedLotForQr.requiredQty; const lotId = selectedLotForQr.lotId; // Create stock out line const stockOutLineData: CreateStockOutLine = { - consoCode: selectedLotForQr.pickOrderConsoCode, // ✅ Use pickOrderConsoCode instead of pickOrderCode + consoCode: selectedLotForQr.pickOrderConsoCode, // Use pickOrderConsoCode instead of pickOrderCode pickOrderLineId: selectedLotForQr.pickOrderLineId, inventoryLotLineId: selectedLotForQr.lotId, qty: 0.0 @@ -620,7 +620,7 @@ const fetchFgPickOrdersData = useCallback(async () => { ...prev, [lotKey]: requiredQty })); - console.log(`✅ Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); + console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); }, 500); // Refresh data @@ -631,7 +631,7 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [selectedLotForQr, fetchAllCombinedLotData]); - // ✅ Outside QR scanning - process QR codes from outside the page automatically + // Outside QR scanning - process QR codes from outside the page automatically useEffect(() => { if (qrValues.length > 0 && combinedLotData.length > 0) { const latestQr = qrValues[qrValues.length - 1]; @@ -707,7 +707,7 @@ const fetchFgPickOrdersData = useCallback(async () => { if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) { console.log("Found completed pick orders, auto-assigning next..."); - // ✅ 移除前端的自动分配逻辑,因为后端已经处理了 + // 移除前端的自动分配逻辑,因为后端已经处理了 // await handleAutoAssignAndRelease(); // 删除这个函数 } } catch (error) { @@ -715,7 +715,7 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [currentUserId]); - // ✅ Handle submit pick quantity + // Handle submit pick quantity const handleSubmitPickQty = useCallback(async (lot: any) => { const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; const newQty = pickQtyData[lotKey] || 0; @@ -759,14 +759,14 @@ const fetchFgPickOrdersData = useCallback(async () => { }); } - // ✅ FIXED: Use the proper API function instead of direct fetch + // FIXED: Use the proper API function instead of direct fetch if (newStatus === 'completed' && lot.pickOrderConsoCode) { - console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); + console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); try { - // ✅ Use the imported API function instead of direct fetch + // Use the imported API function instead of direct fetch const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); - console.log(`✅ Pick order completion check result:`, completionResponse); + console.log(` Pick order completion check result:`, completionResponse); if (completionResponse.code === "SUCCESS") { console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); @@ -792,7 +792,7 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]); - // ✅ Handle reject lot + // Handle reject lot const handleRejectLot = useCallback(async (lot: any) => { if (!lot.stockOutLineId) { console.error("No stock out line found for this lot"); @@ -818,7 +818,7 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [fetchAllCombinedLotData, checkAndAutoAssignNext]); - // ✅ Handle pick execution form + // Handle pick execution form const handlePickExecutionForm = useCallback((lot: any) => { console.log("=== Pick Execution Form ==="); console.log("Lot data:", lot); @@ -847,7 +847,7 @@ const fetchFgPickOrdersData = useCallback(async () => { console.log("Pick execution issue recorded:", result); if (result && result.code === "SUCCESS") { - console.log("✅ Pick execution issue recorded successfully"); + console.log(" Pick execution issue recorded successfully"); } else { console.error("❌ Failed to record pick execution issue:", result); } @@ -861,7 +861,7 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [fetchAllCombinedLotData]); - // ✅ Calculate remaining required quantity + // Calculate remaining required quantity const calculateRemainingRequiredQty = useCallback((lot: any) => { const requiredQty = lot.requiredQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0; @@ -942,7 +942,7 @@ const fetchFgPickOrdersData = useCallback(async () => { // Pagination data with sorting by routerIndex const paginatedData = useMemo(() => { - // ✅ Sort by routerIndex first, then by other criteria + // Sort by routerIndex first, then by other criteria const sortedData = [...combinedLotData].sort((a, b) => { const aIndex = a.routerIndex || 0; const bIndex = b.routerIndex || 0; @@ -970,14 +970,14 @@ const fetchFgPickOrdersData = useCallback(async () => { return ( - {/* ✅ 修复:改进条件渲染逻辑 */} + {/* 修复:改进条件渲染逻辑 */} {combinedDataLoading || fgPickOrdersLoading ? ( - // ✅ 数据加载中,显示加载指示器 + // 数据加载中,显示加载指示器 ) : fgPickOrders.length === 0 ? ( - // ✅ 没有活动订单,显示楼层选择面板 + // 没有活动订单,显示楼层选择面板 { if (currentUserId) { @@ -987,7 +987,7 @@ return ( }} /> ) : ( - // ✅ 有活动订单,显示 FG 订单信息 + // 有活动订单,显示 FG 订单信息 {fgPickOrders.map((fgOrder) => ( diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index ab9f480..8a9a76b 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -53,7 +53,7 @@ interface PickExecutionFormProps { selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null; pickOrderId?: number; pickOrderCreateDate: any; - // ✅ Remove these props since we're not handling normal cases + // Remove these props since we're not handling normal cases // onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise; // selectedRowId?: number | null; } @@ -75,7 +75,7 @@ const PickExecutionForm: React.FC = ({ selectedPickOrderLine, pickOrderId, pickOrderCreateDate, - // ✅ Remove these props + // Remove these props // onNormalPickSubmit, // selectedRowId, }) => { @@ -86,11 +86,11 @@ const PickExecutionForm: React.FC = ({ const [handlers, setHandlers] = useState>([]); // 计算剩余可用数量 const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { - // ✅ 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty + // 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty return lot.availableQty || 0; }, []); const calculateRequiredQty = useCallback((lot: LotPickData) => { - // ✅ Use the original required quantity, not subtracting actualPickQty + // Use the original required quantity, not subtracting actualPickQty // The actualPickQty in the form should be independent of the database value return lot.requiredQty || 0; }, []); @@ -175,7 +175,7 @@ const PickExecutionForm: React.FC = ({ } }, [errors]); - // ✅ Update form validation to require either missQty > 0 OR badItemQty > 0 + // Update form validation to require either missQty > 0 OR badItemQty > 0 const validateForm = (): boolean => { const newErrors: FormErrors = {}; const req = selectedLot?.requiredQty || 0; @@ -200,10 +200,10 @@ const PickExecutionForm: React.FC = ({ }; const handleSubmit = async () => { - // ✅ 先验证表单 + // First validate the form if (!validateForm()) { console.error('Form validation failed:', errors); - return; // ✅ 阻止提交,显示验证错误 + return; // Prevent submission, show validation errors } if (!formData.pickOrderId) { @@ -214,10 +214,10 @@ const PickExecutionForm: React.FC = ({ setLoading(true); try { await onSubmit(formData as PickExecutionIssueData); - // ✅ 成功时会自动关闭(由 onClose 处理) + // Automatically closed when successful (handled by onClose) } catch (error: any) { console.error('Error submitting pick execution issue:', error); - // ✅ 显示错误消息(可以通过 props 或 state 传递错误消息到父组件) + // Show error message (can be passed to parent component via props or state) // 或者在这里显示 toast/alert alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : '')); } finally { @@ -241,11 +241,11 @@ const PickExecutionForm: React.FC = ({ return ( - {t('Pick Execution Issue Form')} {/* ✅ Always show issue form title */} + {t('Pick Execution Issue Form')} {/* Always show issue form title */} - {/* ✅ Add instruction text */} + {/* Add instruction text */} @@ -255,7 +255,7 @@ const PickExecutionForm: React.FC = ({ - {/* ✅ Keep the existing form fields */} + {/* Keep the existing form fields */} = ({ /> - {/* ✅ Show issue description and handler fields when bad items > 0 */} + {/* Show issue description and handler fields when bad items > 0 */} {(formData.badItemQty && formData.badItemQty > 0) ? ( <> diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx index 3bd7052..db214b6 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx @@ -72,7 +72,7 @@ interface Props { } -// ✅ 新增:Pick Order 数据接口 +// 新增:Pick Order 数据接口 interface PickOrderData { pickOrderId: number; pickOrderCode: string; @@ -89,20 +89,20 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { const currentUserId = session?.id ? parseInt(session.id) : undefined; - // ✅ 新增:已完成 DO Pick Orders 状态 + // 新增:已完成 DO Pick Orders 状态 const [completedDoPickOrders, setCompletedDoPickOrders] = useState([]); const [completedDoPickOrdersLoading, setCompletedDoPickOrdersLoading] = useState(false); - // ✅ 新增:详情视图状态 + // 新增:详情视图状态 const [selectedDoPickOrder, setSelectedDoPickOrder] = useState(null); const [showDetailView, setShowDetailView] = useState(false); const [detailLotData, setDetailLotData] = useState([]); - // ✅ 新增:搜索状态 + // 新增:搜索状态 const [searchQuery, setSearchQuery] = useState>({}); const [filteredDoPickOrders, setFilteredDoPickOrders] = useState([]); - // ✅ 新增:分页状态 + // 新增:分页状态 const [paginationController, setPaginationController] = useState({ pageNum: 0, pageSize: 10, @@ -307,7 +307,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { } }, [t]); - // ✅ 修改:使用新的 API 获取已完成的 DO Pick Orders + // 修改:使用新的 API 获取已完成的 DO Pick Orders const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => { if (!currentUserId) return; @@ -319,7 +319,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { setCompletedDoPickOrders(completedDoPickOrders); setFilteredDoPickOrders(completedDoPickOrders); - console.log("✅ Fetched completed DO pick orders:", completedDoPickOrders); + console.log(" Fetched completed DO pick orders:", completedDoPickOrders); } catch (error) { console.error("❌ Error fetching completed DO pick orders:", error); setCompletedDoPickOrders([]); @@ -329,14 +329,14 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { } }, [currentUserId]); - // ✅ 初始化时获取数据 + // 初始化时获取数据 useEffect(() => { if (currentUserId) { fetchCompletedDoPickOrdersData(); } }, [currentUserId, fetchCompletedDoPickOrdersData]); - // ✅ 修改:搜索功能使用新的 API + // 修改:搜索功能使用新的 API const handleSearch = useCallback((query: Record) => { setSearchQuery({ ...query }); console.log("Search query:", query); @@ -352,13 +352,13 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { fetchCompletedDoPickOrdersData(searchParams); }, [fetchCompletedDoPickOrdersData]); - // ✅ 修复:重命名函数避免重复声明 + // 修复:重命名函数避免重复声明 const handleSearchReset = useCallback(() => { setSearchQuery({}); fetchCompletedDoPickOrdersData(); // 重新获取所有数据 }, [fetchCompletedDoPickOrdersData]); - // ✅ 分页功能 + // 分页功能 const handlePageChange = useCallback((event: unknown, newPage: number) => { setPaginationController(prev => ({ ...prev, @@ -374,14 +374,14 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { }); }, []); - // ✅ 分页数据 + // 分页数据 const paginatedData = useMemo(() => { const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; return filteredDoPickOrders.slice(startIndex, endIndex); }, [filteredDoPickOrders, paginationController]); - // ✅ 搜索条件 + // 搜索条件 const searchCriteria: Criterion[] = [ { label: t("Pick Order Code"), @@ -405,11 +405,11 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { setShowDetailView(true); try { - // ✅ 使用 doPickOrderRecordId 而不是 pickOrderId + // 使用 doPickOrderRecordId 而不是 pickOrderId const hierarchicalData = await fetchLotDetailsByDoPickOrderRecordId(doPickOrder.doPickOrderRecordId); - console.log("✅ Loaded hierarchical lot data:", hierarchicalData); + console.log(" Loaded hierarchical lot data:", hierarchicalData); - // ✅ 转换为平铺格式 + // 转换为平铺格式 const flatLotData: any[] = []; if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) { @@ -465,7 +465,7 @@ if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) { } }); } else if (lineStockouts.length > 0) { - // ✅ lots 为空但有 stockouts(如「雞絲碗仔翅」),也要显示 + // lots 为空但有 stockouts(如「雞絲碗仔翅」),也要显示 lineStockouts.forEach((so: any) => { flatLotData.push({ pickOrderCode: po.pickOrderCode, @@ -488,7 +488,7 @@ if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) { setDetailLotData(flatLotData); - // ✅ 计算完成状态 + // 计算完成状态 const allCompleted = flatLotData.length > 0 && flatLotData.every(lot => lot.processingStatus === 'completed' ); @@ -500,7 +500,7 @@ setDetailLotData(flatLotData); } })); - } catch (error) { // ✅ 添加 catch 块 + } catch (error) { // 添加 catch 块 console.error("❌ Error loading detail lot data:", error); setDetailLotData([]); @@ -514,13 +514,13 @@ setDetailLotData(flatLotData); }, []); - // ✅ 返回列表视图 + // 返回列表视图 const handleBackToList = useCallback(() => { setShowDetailView(false); setSelectedDoPickOrder(null); setDetailLotData([]); - // ✅ 返回列表时禁用打印按钮 + // 返回列表时禁用打印按钮 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, @@ -530,8 +530,8 @@ setDetailLotData(flatLotData); }, []); - // ✅ 如果显示详情视图,渲染类似 GoodPickExecution 的表格 - // ✅ 如果显示详情视图,渲染层级结构 + // 如果显示详情视图,渲染类似 GoodPickExecution 的表格 + // 如果显示详情视图,渲染层级结构 if (showDetailView && selectedDoPickOrder) { return ( @@ -567,7 +567,7 @@ if (showDetailView && selectedDoPickOrder) { - {/* ✅ 添加:多个 Pick Orders 信息(如果有) */} + {/* 添加:多个 Pick Orders 信息(如果有) */} {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && ( @@ -586,7 +586,7 @@ if (showDetailView && selectedDoPickOrder) { )} - {/* ✅ 数据检查 */} + {/* 数据检查 */} {detailLotData.length === 0 ? ( @@ -594,16 +594,16 @@ if (showDetailView && selectedDoPickOrder) { ) : ( - /* ✅ 按 Pick Order 分组显示 */ + /* 按 Pick Order 分组显示 */ - {/* ✅ 按 pickOrderCode 分组 */} + {/* 按 pickOrderCode 分组 */} {Object.entries( detailLotData.reduce((acc: any, lot: any) => { const key = lot.pickOrderCode || 'Unknown'; if (!acc[key]) { acc[key] = { lots: [], - deliveryOrderCode: lot.deliveryOrderCode || 'N/A' // ✅ 保存对应的 deliveryOrderCode + deliveryOrderCode: lot.deliveryOrderCode || 'N/A' // 保存对应的 deliveryOrderCode }; } acc[key].lots.push(lot); @@ -615,7 +615,7 @@ if (showDetailView && selectedDoPickOrder) { {t("Pick Order")}: {pickOrderCode} ({data.lots.length} {t("items")}) {" | "} - {t("Delivery Order")}: {data.deliveryOrderCode} {/* ✅ 使用保存的 deliveryOrderCode */} + {t("Delivery Order")}: {data.deliveryOrderCode} {/* 使用保存的 deliveryOrderCode */} @@ -665,7 +665,7 @@ if (showDetailView && selectedDoPickOrder) { ); } - // ✅ 默认列表视图 + // 默认列表视图 return ( diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index aa7f54f..e610e52 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -32,7 +32,7 @@ import { createStockOutLine, updateStockOutLine, recordPickExecutionIssue, - fetchFGPickOrders, // ✅ Add this import + fetchFGPickOrders, // Add this import FGPickOrderResponse, stockReponse, PickExecutionIssueData, @@ -42,8 +42,8 @@ import { checkAndCompletePickOrderByConsoCode, updateSuggestedLotLineId, confirmLotSubstitution, - fetchDoPickOrderDetail, // ✅ 必须添加 - DoPickOrderDetail, // ✅ 必须添加 + fetchDoPickOrderDetail, // 必须添加 + DoPickOrderDetail, // 必须添加 fetchFGPickOrdersByUserId } from "@/app/api/pickOrder/actions"; @@ -70,13 +70,13 @@ interface Props { filterArgs: Record; } -// ✅ QR Code Modal Component (from LotTable) +// QR Code Modal Component (from LotTable) const QrCodeModal: React.FC<{ open: boolean; onClose: () => void; lot: any | null; onQrCodeSubmit: (lotNo: string) => void; - combinedLotData: any[]; // ✅ Add this prop + combinedLotData: any[]; // Add this prop }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => { const { t } = useTranslation("pickOrder"); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); @@ -116,7 +116,7 @@ const QrCodeModal: React.FC<{ setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); if (stockInLineInfo.lotNo === lot.lotNo) { - console.log(`✅ QR Code verified for lot: ${lot.lotNo}`); + console.log(` QR Code verified for lot: ${lot.lotNo}`); setQrScanSuccess(true); onQrCodeSubmit(lot.lotNo); onClose(); @@ -308,7 +308,7 @@ const QrCodeModal: React.FC<{ {qrScanSuccess && ( - ✅ {t("Verified successfully!")} + {t("Verified successfully!")} )} @@ -359,20 +359,20 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); const formProps = useForm(); const errors = formProps.formState.errors; - // ✅ Add QR modal states + // Add QR modal states const [qrModalOpen, setQrModalOpen] = useState(false); const [selectedLotForQr, setSelectedLotForQr] = useState(null); const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false); const [expectedLotData, setExpectedLotData] = useState(null); const [scannedLotData, setScannedLotData] = useState(null); const [isConfirmingLot, setIsConfirmingLot] = useState(false); - // ✅ Add GoodPickExecutionForm states + // Add GoodPickExecutionForm states const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState(null); const [fgPickOrders, setFgPickOrders] = useState([]); const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); - // ✅ Add these missing state variables after line 352 + // Add these missing state variables after line 352 const [isManualScanning, setIsManualScanning] = useState(false); const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [lastProcessedQr, setLastProcessedQr] = useState(''); @@ -381,7 +381,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); - // ✅ Handle QR code button click + // Handle QR code button click const handleQrCodeClick = (pickOrderId: number) => { console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); // TODO: Implement QR code functionality @@ -435,11 +435,11 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO return; } - // ✅ 获取新结构的层级数据 + // 获取新结构的层级数据 const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); - console.log("✅ Hierarchical data (new structure):", hierarchicalData); + console.log(" Hierarchical data (new structure):", hierarchicalData); - // ✅ 检查数据结构 + // 检查数据结构 if (!hierarchicalData.fgInfo || !hierarchicalData.pickOrders || hierarchicalData.pickOrders.length === 0) { console.warn("⚠️ No FG info or pick orders found"); setCombinedLotData([]); @@ -448,10 +448,10 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO return; } - // ✅ 使用合并后的 pick order 对象(现在只有一个对象,包含所有数据) + // 使用合并后的 pick order 对象(现在只有一个对象,包含所有数据) const mergedPickOrder = hierarchicalData.pickOrders[0]; - // ✅ 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片) + // 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片) // 修改第 478-509 行的 fgOrder 构建逻辑: const fgOrder: FGPickOrderResponse = { @@ -464,7 +464,7 @@ const fgOrder: FGPickOrderResponse = { DepartureTime: hierarchicalData.fgInfo.departureTime, shopAddress: "", pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", - // ✅ 兼容字段 + // 兼容字段 pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, pickOrderConsoCode: mergedPickOrder.consoCode || "", pickOrderTargetDate: mergedPickOrder.targetDate || "", @@ -477,16 +477,16 @@ const fgOrder: FGPickOrderResponse = { numberOfCartons: mergedPickOrder.pickOrderLines?.length || 0, qrCodeData: hierarchicalData.fgInfo.doPickOrderId, - // ✅ 新增:多个 pick orders 信息 - 保持数组格式,不要 join + // 新增:多个 pick orders 信息 - 保持数组格式,不要 join numberOfPickOrders: mergedPickOrder.pickOrderIds?.length || 0, pickOrderIds: mergedPickOrder.pickOrderIds || [], pickOrderCodes: Array.isArray(mergedPickOrder.pickOrderCodes) ? mergedPickOrder.pickOrderCodes - : [], // ✅ 改:保持数组 + : [], // 改:保持数组 deliveryOrderIds: mergedPickOrder.doOrderIds || [], deliveryNos: Array.isArray(mergedPickOrder.deliveryOrderCodes) ? mergedPickOrder.deliveryOrderCodes - : [], // ✅ 改:保持数组 + : [], // 改:保持数组 lineCountsPerPickOrder: Array.isArray(mergedPickOrder.lineCountsPerPickOrder) ? mergedPickOrder.lineCountsPerPickOrder : [] @@ -499,38 +499,38 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); // ❌ 移除:不需要 doPickOrderDetail 和 switcher 逻辑 // if (hierarchicalData.pickOrders.length > 1) { ... } - // ✅ 直接使用合并后的 pickOrderLines + // 直接使用合并后的 pickOrderLines console.log("🎯 Displaying merged pick order lines"); - // ✅ 将层级数据转换为平铺格式(用于表格显示) + // 将层级数据转换为平铺格式(用于表格显示) const flatLotData: any[] = []; mergedPickOrder.pickOrderLines.forEach((line: any) => { if (line.lots && line.lots.length > 0) { - // ✅ 修复:先对 lots 按 lotId 去重并合并 requiredQty + // 修复:先对 lots 按 lotId 去重并合并 requiredQty const lotMap = new Map(); line.lots.forEach((lot: any) => { const lotId = lot.id; if (lotMap.has(lotId)) { - // ✅ 如果已存在,合并 requiredQty + // 如果已存在,合并 requiredQty const existingLot = lotMap.get(lotId); existingLot.requiredQty = (existingLot.requiredQty || 0) + (lot.requiredQty || 0); - // ✅ 保留其他字段(使用第一个遇到的 lot 的字段) + // 保留其他字段(使用第一个遇到的 lot 的字段) } else { - // ✅ 首次遇到,添加到 map + // 首次遇到,添加到 map lotMap.set(lotId, { ...lot }); } }); - // ✅ 遍历去重后的 lots + // 遍历去重后的 lots lotMap.forEach((lot: any) => { flatLotData.push({ - // ✅ 使用合并后的数据 + // 使用合并后的数据 pickOrderConsoCode: mergedPickOrder.consoCode, pickOrderTargetDate: mergedPickOrder.targetDate, pickOrderStatus: mergedPickOrder.status, - pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // ✅ 使用第一个 pickOrderId + pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", pickOrderLineId: line.id, pickOrderLineRequiredQty: line.requiredQty, @@ -548,7 +548,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); location: lot.location, stockUnit: lot.stockUnit, availableQty: lot.availableQty, - requiredQty: lot.requiredQty, // ✅ 使用合并后的 requiredQty + requiredQty: lot.requiredQty, // 使用合并后的 requiredQty actualPickQty: lot.actualPickQty, inQty: lot.inQty, outQty: lot.outQty, @@ -569,16 +569,16 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); }); }); } else { - // ✅ 没有 lots 的情况(null stock)- 从 stockouts 数组中获取 id + // 没有 lots 的情况(null stock)- 从 stockouts 数组中获取 id const firstStockout = line.stockouts && line.stockouts.length > 0 ? line.stockouts[0] : null; flatLotData.push({ - pickOrderConsoCode: mergedPickOrder.consoCodes?.[0] || "", // ✅ 修复:consoCodes 是数组 + pickOrderConsoCode: mergedPickOrder.consoCodes?.[0] || "", // 修复:consoCodes 是数组 pickOrderTargetDate: mergedPickOrder.targetDate, pickOrderStatus: mergedPickOrder.status, - pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // ✅ 使用第一个 pickOrderId + pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", pickOrderLineId: line.id, pickOrderLineRequiredQty: line.requiredQty, @@ -590,7 +590,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); uomDesc: line.item.uomDesc, uomShortDesc: line.item.uomShortDesc, - // ✅ Null stock 字段 - 从 stockouts 数组中获取 + // Null stock 字段 - 从 stockouts 数组中获取 lotId: firstStockout?.lotId || null, lotNo: firstStockout?.lotNo || null, expiryDate: null, @@ -606,7 +606,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); lotAvailability: 'insufficient_stock', processingStatus: firstStockout?.status || 'pending', suggestedPickLotId: null, - stockOutLineId: firstStockout?.id || null, // ✅ 使用 stockouts 数组中的 id + stockOutLineId: firstStockout?.id || null, // 使用 stockouts 数组中的 id stockOutLineStatus: firstStockout?.status || null, stockOutLineQty: firstStockout?.qty || 0, @@ -619,7 +619,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } }); - console.log("✅ Transformed flat lot data:", flatLotData); + console.log(" Transformed flat lot data:", flatLotData); console.log("🔍 Total items (including null stock):", flatLotData.length); setCombinedLotData(flatLotData); @@ -635,25 +635,25 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); setCombinedDataLoading(false); } }, [currentUserId, checkAllLotsCompleted]); // ❌ 移除 selectedPickOrderId 依赖 - // ✅ Add effect to check completion when lot data changes + // Add effect to check completion when lot data changes useEffect(() => { if (combinedLotData.length > 0) { checkAllLotsCompleted(combinedLotData); } }, [combinedLotData, checkAllLotsCompleted]); - // ✅ Add function to expose completion status to parent + // Add function to expose completion status to parent const getCompletionStatus = useCallback(() => { return allLotsCompleted; }, [allLotsCompleted]); - // ✅ Expose completion status to parent component + // Expose completion status to parent component useEffect(() => { // Dispatch custom event with completion status const event = new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted, - tabIndex: 1 // ✅ 明确指定这是来自标签页 1 的事件 + tabIndex: 1 // 明确指定这是来自标签页 1 的事件 } }); window.dispatchEvent(event); @@ -708,22 +708,22 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } }, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData]); const handleQrCodeSubmit = useCallback(async (lotNo: string) => { - console.log(`✅ Processing QR Code for lot: ${lotNo}`); + console.log(` Processing QR Code for lot: ${lotNo}`); - // ✅ 检查 lotNo 是否为 null 或 undefined(包括字符串 "null") + // 检查 lotNo 是否为 null 或 undefined(包括字符串 "null") if (!lotNo || lotNo === 'null' || lotNo.trim() === '') { console.error("❌ Invalid lotNo: null, undefined, or empty"); return; } - // ✅ Use current data without refreshing to avoid infinite loop + // Use current data without refreshing to avoid infinite loop const currentLotData = combinedLotData; console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo)); - // ✅ 修复:在比较前确保 lotNo 不为 null + // 修复:在比较前确保 lotNo 不为 null const lotNoLower = lotNo.toLowerCase(); const matchingLots = currentLotData.filter(lot => { - if (!lot.lotNo) return false; // ✅ 跳过 null lotNo + if (!lot.lotNo) return false; // 跳过 null lotNo return lot.lotNo === lotNo || lot.lotNo.toLowerCase() === lotNoLower; }); @@ -736,7 +736,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } - console.log(`✅ Found ${matchingLots.length} matching lots:`, matchingLots); + console.log(` Found ${matchingLots.length} matching lots:`, matchingLots); setQrScanError(false); try { @@ -811,20 +811,20 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } } - // ✅ FIXED: Set refresh flag before refreshing data + // FIXED: Set refresh flag before refreshing data setIsRefreshingData(true); console.log("🔄 Refreshing data after QR code processing..."); await fetchAllCombinedLotData(); if (successCount > 0) { - console.log(`✅ QR Code processing completed: ${successCount} updated/created`); + console.log(` QR Code processing completed: ${successCount} updated/created`); setQrScanSuccess(true); setQrScanError(false); setQrScanInput(''); // Clear input after successful processing //setIsManualScanning(false); // stopScan(); // resetScan(); - // ✅ Clear success state after a delay + // Clear success state after a delay //setTimeout(() => { //setQrScanSuccess(false); @@ -834,7 +834,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); setQrScanError(true); setQrScanSuccess(false); - // ✅ Clear error state after a delay + // Clear error state after a delay // setTimeout(() => { // setQrScanError(false); //}, 3000); @@ -844,16 +844,16 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); setQrScanError(true); setQrScanSuccess(false); - // ✅ Still refresh data even on error + // Still refresh data even on error setIsRefreshingData(true); await fetchAllCombinedLotData(); - // ✅ Clear error state after a delay + // Clear error state after a delay setTimeout(() => { setQrScanError(false); }, 3000); } finally { - // ✅ Clear refresh flag after a short delay + // Clear refresh flag after a short delay setTimeout(() => { setIsRefreshingData(false); }, 1000); @@ -914,7 +914,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } - // ✅ FIXED: Find the ACTIVE suggested lot (not rejected lots) + // FIXED: Find the ACTIVE suggested lot (not rejected lots) const activeSuggestedLots = sameItemLotsInExpected.filter(lot => lot.lotAvailability !== 'rejected' && lot.stockOutLineStatus !== 'rejected' && @@ -942,7 +942,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } // Case 2: Item matches but lot number differs -> open confirmation modal - // ✅ FIXED: Use the first ACTIVE suggested lot, not just any lot + // FIXED: Use the first ACTIVE suggested lot, not just any lot const expectedLot = activeSuggestedLots[0]; if (!expectedLot) { console.error("Could not determine expected lot for confirmation"); @@ -951,7 +951,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } - // ✅ Check if the expected lot is already the scanned lot (after substitution) + // Check if the expected lot is already the scanned lot (after substitution) if (expectedLot.lotNo === scanned?.lotNo) { console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`); handleQrCodeSubmit(scanned.lotNo); @@ -981,8 +981,8 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return; } }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); - // ✅ Update the outside QR scanning effect to use enhanced processing -// ✅ Update the outside QR scanning effect to use enhanced processing + // Update the outside QR scanning effect to use enhanced processing +// Update the outside QR scanning effect to use enhanced processing useEffect(() => { if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { return; @@ -1003,18 +1003,18 @@ useEffect(() => { processOutsideQrCode(latestQr); } }, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]); - // ✅ Only fetch existing data when session is ready, no auto-assignment + // Only fetch existing data when session is ready, no auto-assignment useEffect(() => { if (session && currentUserId && !initializationRef.current) { - console.log("✅ Session loaded, initializing pick order..."); + console.log(" Session loaded, initializing pick order..."); initializationRef.current = true; - // ✅ Only fetch existing data, no auto-assignment + // Only fetch existing data, no auto-assignment fetchAllCombinedLotData(); } }, [session, currentUserId, fetchAllCombinedLotData]); - // ✅ Add event listener for manual assignment + // Add event listener for manual assignment useEffect(() => { const handlePickOrderAssigned = () => { console.log("🔄 Pick order assigned event received, refreshing data..."); @@ -1036,10 +1036,10 @@ useEffect(() => { } }, [qrScanInput, handleQrCodeSubmit]); - // ✅ Handle QR code submission from modal (internal scanning) + // Handle QR code submission from modal (internal scanning) const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { - console.log(`✅ QR Code verified for lot: ${lotNo}`); + console.log(` QR Code verified for lot: ${lotNo}`); const requiredQty = selectedLotForQr.requiredQty; const lotId = selectedLotForQr.lotId; @@ -1068,7 +1068,7 @@ useEffect(() => { ...prev, [lotKey]: requiredQty })); - console.log(`✅ Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); + console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); }, 500); // Refresh data @@ -1117,7 +1117,7 @@ useEffect(() => { if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) { console.log("Found completed pick orders, auto-assigning next..."); - // ✅ 移除前端的自动分配逻辑,因为后端已经处理了 + // 移除前端的自动分配逻辑,因为后端已经处理了 // await handleAutoAssignAndRelease(); // 删除这个函数 } } catch (error) { @@ -1125,7 +1125,7 @@ useEffect(() => { } }, [currentUserId]); - // ✅ Handle submit pick quantity + // Handle submit pick quantity const handleSubmitPickQty = useCallback(async (lot: any) => { const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; const newQty = pickQtyData[lotKey] || 0; @@ -1136,11 +1136,11 @@ useEffect(() => { } try { - // ✅ FIXED: Calculate cumulative quantity correctly + // FIXED: Calculate cumulative quantity correctly const currentActualPickQty = lot.actualPickQty || 0; const cumulativeQty = currentActualPickQty + newQty; - // ✅ FIXED: Determine status based on cumulative quantity vs required quantity + // FIXED: Determine status based on cumulative quantity vs required quantity let newStatus = 'partially_completed'; if (cumulativeQty >= lot.requiredQty) { @@ -1163,7 +1163,7 @@ useEffect(() => { await updateStockOutLineStatus({ id: lot.stockOutLineId, status: newStatus, - qty: cumulativeQty // ✅ Use cumulative quantity + qty: cumulativeQty // Use cumulative quantity }); if (newQty > 0) { @@ -1175,13 +1175,13 @@ useEffect(() => { }); } - // ✅ Check if pick order is completed when lot status becomes 'completed' + // Check if pick order is completed when lot status becomes 'completed' if (newStatus === 'completed' && lot.pickOrderConsoCode) { - console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); + console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); try { const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); - console.log(`✅ Pick order completion check result:`, completionResponse); + console.log(` Pick order completion check result:`, completionResponse); if (completionResponse.code === "SUCCESS") { console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); @@ -1207,7 +1207,7 @@ useEffect(() => { } }, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]); - // ✅ Handle reject lot + // Handle reject lot const handleRejectLot = useCallback(async (lot: any) => { if (!lot.stockOutLineId) { console.error("No stock out line found for this lot"); @@ -1233,7 +1233,7 @@ useEffect(() => { } }, [fetchAllCombinedLotData, checkAndAutoAssignNext]); - // ✅ Handle pick execution form + // Handle pick execution form const handlePickExecutionForm = useCallback((lot: any) => { console.log("=== Pick Execution Form ==="); console.log("Lot data:", lot); @@ -1264,7 +1264,7 @@ useEffect(() => { console.log("Pick execution issue recorded:", result); if (result && result.code === "SUCCESS") { - console.log("✅ Pick execution issue recorded successfully"); + console.log(" Pick execution issue recorded successfully"); } else { console.error("❌ Failed to record pick execution issue:", result); } @@ -1285,7 +1285,7 @@ useEffect(() => { } }, [fetchAllCombinedLotData]); - // ✅ Calculate remaining required quantity + // Calculate remaining required quantity const calculateRemainingRequiredQty = useCallback((lot: any) => { const requiredQty = lot.requiredQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0; @@ -1369,7 +1369,7 @@ useEffect(() => { const paginatedData = useMemo(() => { const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; - return combinedLotData.slice(startIndex, endIndex); // ✅ No sorting needed + return combinedLotData.slice(startIndex, endIndex); // No sorting needed }, [combinedLotData, paginationController]); const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => { if (!lot.stockOutLineId) { @@ -1378,11 +1378,11 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe } try { - // ✅ FIXED: Calculate cumulative quantity correctly + // FIXED: Calculate cumulative quantity correctly const currentActualPickQty = lot.actualPickQty || 0; const cumulativeQty = currentActualPickQty + submitQty; - // ✅ FIXED: Determine status based on cumulative quantity vs required quantity + // FIXED: Determine status based on cumulative quantity vs required quantity let newStatus = 'partially_completed'; if (cumulativeQty >= lot.requiredQty) { @@ -1405,7 +1405,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe await updateStockOutLineStatus({ id: lot.stockOutLineId, status: newStatus, - qty: cumulativeQty // ✅ Use cumulative quantity + qty: cumulativeQty // Use cumulative quantity }); if (submitQty > 0) { @@ -1417,13 +1417,13 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe }); } - // ✅ Check if pick order is completed when lot status becomes 'completed' + // Check if pick order is completed when lot status becomes 'completed' if (newStatus === 'completed' && lot.pickOrderConsoCode) { - console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); + console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); try { const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); - console.log(`✅ Pick order completion check result:`, completionResponse); + console.log(` Pick order completion check result:`, completionResponse); if (completionResponse.code === "SUCCESS") { console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); @@ -1450,7 +1450,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe }, [fetchAllCombinedLotData, checkAndAutoAssignNext]); - // ✅ Add these functions after line 395 + // Add these functions after line 395 const handleStartScan = useCallback(() => { console.log(" Starting manual QR scan..."); setIsManualScanning(true); @@ -1468,7 +1468,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe console.log("🔍 Switching to pick order:", pickOrderId); setSelectedPickOrderId(pickOrderId); - // ✅ 强制刷新数据,确保显示正确的 pick order 数据 + // 强制刷新数据,确保显示正确的 pick order 数据 await fetchAllCombinedLotData(currentUserId, pickOrderId); } catch (error) { console.error("Error switching pick order:", error); @@ -1487,7 +1487,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe }, [stopScan, resetScan]); // ... existing code around line 1469 ... const handlelotnull = useCallback(async (lot: any) => { - // ✅ 优先使用 stockouts 中的 id,如果没有则使用 stockOutLineId + // 优先使用 stockouts 中的 id,如果没有则使用 stockOutLineId const stockOutLineId = lot.stockOutLineId; if (!stockOutLineId) { @@ -1496,14 +1496,14 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe } try { - // ✅ Step 1: Update stock out line status + // Step 1: Update stock out line status await updateStockOutLineStatus({ id: stockOutLineId, status: 'completed', qty: 0 }); - // ✅ Step 2: Create pick execution issue for no-lot case + // Step 2: Create pick execution issue for no-lot case // Get pick order ID from fgPickOrders or use 0 if not available const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0; const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || ''; @@ -1512,18 +1512,18 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe type: "Do", // Delivery Order type pickOrderId: pickOrderId, pickOrderCode: pickOrderCode, - pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // ✅ Use dayjs format + pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format pickExecutionDate: dayjs().format('YYYY-MM-DD'), pickOrderLineId: lot.pickOrderLineId, itemId: lot.itemId, itemCode: lot.itemCode || '', itemDescription: lot.itemName || '', - lotId: null, // ✅ No lot available - lotNo: null, // ✅ No lot number + lotId: null, // No lot available + lotNo: null, // No lot number storeLocation: lot.location || '', requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, - actualPickQty: 0, // ✅ No items picked (no lot available) - missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, // ✅ All quantity is missing + actualPickQty: 0, // No items picked (no lot available) + missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, // All quantity is missing badItemQty: 0, issueRemark: `No lot available for this item. Handled via handlelotnull.`, pickerName: session?.user?.name || '', @@ -1531,15 +1531,15 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe }; const result = await recordPickExecutionIssue(issueData); - console.log("✅ Pick execution issue created for no-lot item:", result); + console.log(" Pick execution issue created for no-lot item:", result); if (result && result.code === "SUCCESS") { - console.log("✅ No-lot item handled and issue recorded successfully"); + console.log(" No-lot item handled and issue recorded successfully"); } else { console.error("❌ Failed to record pick execution issue:", result); } - // ✅ Step 3: Refresh data + // Step 3: Refresh data await fetchAllCombinedLotData(); } catch (error) { console.error("❌ Error in handlelotnull:", error); @@ -1548,14 +1548,14 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe // ... existing code ... const handleSubmitAllScanned = useCallback(async () => { const scannedLots = combinedLotData.filter(lot => { - // ✅ 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete + // 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete if (lot.noLot === true) { return lot.stockOutLineStatus === 'checked' || lot.stockOutLineStatus === 'pending' || lot.stockOutLineStatus === 'partially_completed' || lot.stockOutLineStatus === 'PARTIALLY_COMPLETE'; } - // ✅ 正常情况:只包含 checked 状态 + // 正常情况:只包含 checked 状态 return lot.stockOutLineStatus === 'checked'; }); @@ -1568,35 +1568,35 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); try { - // ✅ Submit all items in parallel using Promise.all + // Submit all items in parallel using Promise.all const submitPromises = scannedLots.map(async (lot) => { - // ✅ 检查是否是 noLot 情况 + // 检查是否是 noLot 情况 if (lot.noLot === true) { - // ✅ 使用 handlelotnull 处理无 lot 的情况 + // 使用 handlelotnull 处理无 lot 的情况 console.log(`Submitting no-lot item: ${lot.itemName || lot.itemCode}`); await updateStockOutLineStatus({ id: lot.stockOutLineId, status: 'completed', qty: 0 }); - console.log(`✅ No-lot item completed: ${lot.itemName || lot.itemCode}`); + console.log(` No-lot item completed: ${lot.itemName || lot.itemCode}`); const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0; const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || ''; const issueData: PickExecutionIssueData = { type: "Do", // Delivery Order type pickOrderId: pickOrderId, pickOrderCode: pickOrderCode, - pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // ✅ Use dayjs format + pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format pickExecutionDate: dayjs().format('YYYY-MM-DD'), pickOrderLineId: lot.pickOrderLineId, itemId: lot.itemId, itemCode: lot.itemCode || '', itemDescription: lot.itemName || '', - lotId: null, // ✅ No lot available - lotNo: null, // ✅ No lot number + lotId: null, // No lot available + lotNo: null, // No lot number storeLocation: lot.location || '', requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, - actualPickQty: 0, // ✅ No items picked (no lot available) + actualPickQty: 0, // No items picked (no lot available) missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, badItemQty: 0, issueRemark: `No lot available for this item. Handled via handlelotnull.`, @@ -1607,7 +1607,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe return { success: true, lotNo: lot.lotNo || 'No Lot', isNoLot: true }; } - // ✅ 正常情况:有 lot 的处理逻辑 + // 正常情况:有 lot 的处理逻辑 const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; const currentActualPickQty = lot.actualPickQty || 0; const cumulativeQty = currentActualPickQty + submitQty; @@ -1644,13 +1644,13 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe return { success: true, lotNo: lot.lotNo }; }); - // ✅ Wait for all submissions to complete + // Wait for all submissions to complete const results = await Promise.all(submitPromises); const successCount = results.filter(r => r.success).length; - console.log(`✅ Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); + console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); - // ✅ Refresh data once after all submissions + // Refresh data once after all submissions await fetchAllCombinedLotData(); if (successCount > 0) { @@ -1669,11 +1669,11 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe } }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]); - // ✅ Calculate scanned items count - // ✅ Calculate scanned items count (should match handleSubmitAllScanned filter logic) + // Calculate scanned items count + // Calculate scanned items count (should match handleSubmitAllScanned filter logic) const scannedItemsCount = useMemo(() => { const filtered = combinedLotData.filter(lot => { - // ✅ 如果是 noLot 情况,只要状态不是 completed 或 rejected,就包含 + // 如果是 noLot 情况,只要状态不是 completed 或 rejected,就包含 if (lot.noLot === true) { const status = lot.stockOutLineStatus?.toLowerCase(); const include = status !== 'completed' && status !== 'rejected'; @@ -1682,11 +1682,11 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe } return include; } - // ✅ 正常情况:只包含 checked 状态 + // 正常情况:只包含 checked 状态 return lot.stockOutLineStatus === 'checked'; }); - // ✅ 添加调试日志 + // 添加调试日志 const noLotCount = filtered.filter(l => l.noLot === true).length; const normalCount = filtered.filter(l => l.noLot !== true).length; console.log(`📊 scannedItemsCount calculation: total=${filtered.length}, noLot=${noLotCount}, normal=${normalCount}`); @@ -1699,7 +1699,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe return filtered.length; }, [combinedLotData]); - // ✅ ADD THIS: Auto-stop scan when no data available + // ADD THIS: Auto-stop scan when no data available useEffect(() => { if (isManualScanning && combinedLotData.length === 0) { console.log("⏹️ No data available, auto-stopping QR scan..."); @@ -1707,7 +1707,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe } }, [combinedLotData.length, isManualScanning, handleStopScan]); - // ✅ Cleanup effect + // Cleanup effect useEffect(() => { return () => { // Cleanup when component unmounts (e.g., when switching tabs) @@ -1754,7 +1754,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe - {/* ✅ 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} + {/* 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} @@ -1784,7 +1784,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe )} - {/* ✅ 保留:Submit All Scanned Button */} + {/* 保留:Submit All Scanned Button */} ) : ( - // ✅ Normal lot 显示两个按钮 + // Normal lot 显示两个按钮 )} - {/* ✅ ADD THIS: Submit All Scanned Button */} + {/* ADD THIS: Submit All Scanned Button */} )} - {/* ✅ ADD THIS: Submit All Scanned Button */} + {/* ADD THIS: Submit All Scanned Button */} + + + {/* ========== 第一部分:基本信息 ========== */} + + + {t("Production Process Information")} + + + + {t("Process Code")}: {processData?.productProcessCode} + + + {t("Status")}:{" "} + + + + {t("Date")}: {dayjs(processData?.date).format(OUTPUT_DATE_FORMAT)} + + + {t("Total Steps")}: {lines.length} + + + + + {/* ========== 第二部分:Process Lines ========== */} + + + {t("Production Process Steps")} + + + {!isExecutingLine ? ( + /* ========== 步骤列表视图 ========== */ + + + + + {t("Seq")} + {t("Step Name")} + {t("Description")} + {t("Equipment Type")} + {t("Status")} + {t("Action")} + + + + {lines.map((line) => ( + + {line.seqNo} + + {line.name} + + {line.description} + {line.equipmentType} + + {line.endTime ? ( + + ) : line.startTime ? ( + + ) : ( + + )} + + + + + + ))} + +
+
+ ) : ( + /* ========== 步骤执行视图 ========== */ + + {/* 当前步骤信息 */} + + + + {t("Executing")}: {selectedLine?.name} (Seq: {selectedLine?.seqNo}) + + + {selectedLine?.description} + + + {t("Equipment")}: {selectedLine?.equipmentType} + + + + + + {/* 合并的扫描器 */} + + + {t("Scan Operator & Equipment")} + + + + {/* 操作员扫描 */} + + + {scannedOperators.length > 0 + ? `${t("Operator")}: ${scannedOperators[0].name || scannedOperators[0].username}` + : t("Please scan operator code") + } + + + + {/* 设备扫描 */} + + + {scannedMachines.length > 0 + ? `${t("Equipment")}: ${scannedMachines[0].name || scannedMachines[0].code}` + : t("Please scan equipment code") + } + + + + {/* 单个扫描按钮 */} + + + + + {/* ========== 产出输入表单 ========== */} + {scannedOperators.length > 0 && scannedMachines.length > 0 && ( + + + {t("Production Output Data Entry")} + + + + + + {t("Type")} + {t("Quantity")} + {t("Unit of Measure")} + + + + {/* 步骤收成 */} + + + {t("Output from Process")} + + + setOutputData(prev => ({ ...prev, outputFromProcessQty: e.target.value }))} + /> + + + setOutputData(prev => ({ ...prev, outputFromProcessUom: e.target.value }))} + placeholder="KG, L, PCS..." + /> + + + + {/* 副产品 */} + + + + {t("By-product")} + setOutputData(prev => ({ ...prev, byproductName: e.target.value }))} + placeholder={t("By-product name")} + sx={{ mt: 1 }} + /> + + + + setOutputData(prev => ({ ...prev, byproductQty: e.target.value }))} + /> + + + setOutputData(prev => ({ ...prev, byproductUom: e.target.value }))} + placeholder="KG, L, PCS..." + /> + + + + {/* 次品 */} + + + {t("Defect")} + + + setOutputData(prev => ({ ...prev, defectQty: e.target.value }))} + /> + + + setOutputData(prev => ({ ...prev, defectUom: e.target.value }))} + placeholder="KG, L, PCS..." + /> + + + + {/* 废品 */} + + + {t("Scrap")} + + + setOutputData(prev => ({ ...prev, scrapQty: e.target.value }))} + /> + + + setOutputData(prev => ({ ...prev, scrapUom: e.target.value }))} + placeholder="KG, L, PCS..." + /> + + + +
+ + {/* 提交按钮 */} + + + + +
+ )} +
+
+ )} +
+
+ ); +}; + +export default ProductionProcessDetail; \ No newline at end of file diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx new file mode 100644 index 0000000..8180892 --- /dev/null +++ b/src/components/ProductionProcess/ProductionProcessList.tsx @@ -0,0 +1,185 @@ +"use client"; +import React, { useCallback, useEffect, useState } from "react"; +import { + Box, + Button, + Card, + CardContent, + CardActions, + Stack, + Typography, + Chip, + CircularProgress, + TablePagination, + Grid, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useSession } from "next-auth/react"; +import { SessionWithTokens } from "@/config/authConfig"; +import dayjs from "dayjs"; +import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; +import { + fetchAllJoborderProductProcessInfo, + AllJoborderProductProcessInfoResponse, +} from "@/app/api/jo/actions"; + +interface ProductProcessListProps { + onSelectProcess: (processId: number) => void; +} + +const PER_PAGE = 6; + +const ProductProcessList: React.FC = ({ onSelectProcess }) => { + const { t } = useTranslation(); + const { data: session } = useSession() as { data: SessionWithTokens | null }; + + const [loading, setLoading] = useState(false); + const [processes, setProcesses] = useState([]); + const [page, setPage] = useState(0); + + const fetchProcesses = useCallback(async () => { + setLoading(true); + try { + const data = await fetchAllJoborderProductProcessInfo(); + setProcesses(data || []); + setPage(0); + } catch (e) { + console.error(e); + setProcesses([]); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchProcesses(); + }, [fetchProcesses]); + + const startIdx = page * PER_PAGE; + const paged = processes.slice(startIdx, startIdx + PER_PAGE); + + return ( + + {loading ? ( + + + + ) : ( + + + {t("Total processes")}: {processes.length} + + + + {paged.map((process) => { + const status = String(process.status || ""); + const statusLower = status.toLowerCase(); + const statusColor = + statusLower === "completed" + ? "success" + : statusLower === "in_progress" || statusLower === "processing" + ? "primary" + : "default"; + + const finishedCount = + (process as any).finishedProductProcessLineCount ?? + (process as any).FinishedProductProcessLineCount ?? + 0; + + const totalCount = process.productProcessLineCount ?? process.lines?.length ?? 0; + const linesWithStatus = (process.lines || []).filter( + (l) => String(l.status ?? "").trim() !== "" + ); + + const dateDisplay = process.date + ? dayjs(process.date as any).format(OUTPUT_DATE_FORMAT) + : "-"; + const jobOrderCode = + (process as any).jobOrderCode ?? + (process.jobOrderId ? `JO-${process.jobOrderId}` : "N/A"); + const inProgressLines = (process.lines || []) + .filter(l => String(l.status ?? "").trim() !== "") + .filter(l => String(l.status).toLowerCase() === "in_progress"); + + return ( + + + + + + + {process.productProcessCode} + + + {t("Job Order")}: {jobOrderCode} + + + + + + + {statusLower !== "pending" && linesWithStatus.length > 0 && ( + + + {t("Finished lines")}: {finishedCount} / {totalCount} + + + {inProgressLines.length > 0 && ( + + {inProgressLines.map(line => ( + + {t("Operator")}: {line.operatorName || "-"}
+ {t("Equipment")}: {line.equipmentName || "-"} +
+ ))} +
+ )} +
+ )} +
+ + + + + + {t("Lines")}: {totalCount} + + +
+
+ ); + })} +
+ + {processes.length > 0 && ( + setPage(p)} + rowsPerPageOptions={[PER_PAGE]} + /> + )} +
+ )} +
+ ); +}; + +export default ProductProcessList; \ No newline at end of file diff --git a/src/components/ProductionProcess/ProductionProcessPage.tsx b/src/components/ProductionProcess/ProductionProcessPage.tsx new file mode 100644 index 0000000..4343ac4 --- /dev/null +++ b/src/components/ProductionProcess/ProductionProcessPage.tsx @@ -0,0 +1,67 @@ +"use client"; +import React, { useState, useEffect, useCallback } from "react"; +import { useSession } from "next-auth/react"; +import { SessionWithTokens } from "@/config/authConfig"; +import ProductionProcessList from "@/components/ProductionProcess/ProductionProcessList"; +import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail"; +import { + fetchProductProcesses, + fetchProductProcessLines, + ProductProcessLineResponse +} from "@/app/api/jo/actions"; + +const ProductionProcessPage = () => { + 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)} + /> + ); + } + + return ( + setSelectedProcessId(id)} + /> + ); +}; + +export default ProductionProcessPage; \ No newline at end of file