"use server"; import { cache } from 'react'; import { Pageable, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { JobOrder, JoStatus, Machine, Operator } from "."; import { BASE_API_URL } from "@/config/api"; import { revalidateTag } from "next/cache"; import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; export interface SaveJo { bomId: number; planStart: string; planEnd: string; reqQty: number; type: string; //jobType?: string; jobTypeId?: number; } export interface SaveJoResponse { id: number; } export interface SearchJoResultRequest extends Pageable { code: string; itemName?: string; planStart?: string; planStartTo?: string; jobTypeName?: string; } export interface productProcessLineQtyRequest { productProcessLineId: number; outputFromProcessQty: number; outputFromProcessUom: string; defectQty: number; defectUom: string; scrapQty: number; scrapUom: string; } export interface SearchJoResultResponse { records: JobOrder[]; total: number; } // DEPRECIATED export interface SearchJoResult { id: number; code: string; itemCode: string; name: string; reqQty: number; uom: string; status: JoStatus; } export interface UpdateJoRequest { id: number; status: string; } // For Jo Button Actions export interface CommonActionJoRequest { id: number; } export interface CommonActionJoResponse { id: number; entity: { status: JoStatus } } // For Jo Process export interface IsOperatorExistResponse { id: number | null; name: string; code: string; type?: string; message: string | null; errorPosition: string | keyof T; entity: T; } export interface isCorrectMachineUsedResponse { id: number | null; name: string; code: string; type?: string; message: string | null; errorPosition: string | keyof T; entity: T; } export interface JobOrderDetail { id: number; code: string; name: string; reqQty: number; uom: string; pickLines: any[]; jobTypeName: string; status: string; } export interface UnassignedJobOrderPickOrder { pickOrderId: number; pickOrderCode: string; pickOrderConsoCode: string; pickOrderTargetDate: string; pickOrderStatus: string; jobOrderId: number; jobOrderCode: string; jobOrderName: string; reqQty: number; uom: string; planStart: string; planEnd: string; } export interface AssignJobOrderResponse { id: number | null; code: string | null; name: string | null; type: string | null; message: string | null; errorPosition: string | null; } export interface PrintPickRecordRequest{ pickOrderId: number; printerId: number; printQty: number; } export interface PrintPickRecordResponse{ success: boolean; message?: string } export interface PrintFGStockInLabelRequest { stockInLineId: number; printerId: number; printQty?: number; } export const printFGStockInLabel = cache(async(data: PrintFGStockInLabelRequest) => { const params = new URLSearchParams(); if (data.stockInLineId) { params.append('stockInLineId', data.stockInLineId.toString()); } params.append('printerId', data.printerId.toString()); if (data.printQty !== undefined && data.printQty !== null) { params.append('printQty', data.printQty.toString()); } return serverFetchWithNoContent( `${BASE_API_URL}/jo/print-FGPickRecordLabel?${params.toString()}`, { method: "GET", next: { tags: ["printFGStockInLabel"], }, } ); }); export const recordSecondScanIssue = cache(async ( pickOrderId: number, itemId: number, data: { qty: number; // verified qty (actual pick qty) missQty?: number; // 添加:miss qty badItemQty?: number; // 添加:bad item qty isMissing: boolean; isBad: boolean; reason: string; createdBy: number; type?: string; // type 也应该是可选的 } ) => { return serverFetchJson( `${BASE_API_URL}/jo/second-scan-issue/${pickOrderId}/${itemId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), next: { tags: ["jo-second-scan"] }, }, ); }); export interface ProductProcessResponse { id: number; productProcessCode: string; status: string; startTime?: string; endTime?: string; date: string; bomId?: number; jobOrderId?: number; } export interface ProductProcessLineResponse { id: number, bomprocessId: number, operatorId: number, operatorName: string, equipmentId: number, handlerId: number, seqNo: number, name: string, description: string, equipment_name: string, equipmentDetailCode: 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 { id: number; productProcessCode: string; status: string; startTime?: string; endTime?: string; date: string; bomId?: number; jobOrderId?: number; jobOrderCode: string; jobOrderStatus: string; jobType: string; isDark: string; isDense: number; isFloat: string; timeSequence: number; complexity: number; scrapRate: number; allergicSubstance: string; itemId: number; itemCode: string; itemName: string; outputQty: number; outputQtyUom: string; productionPriority: number; jobOrderLines: JobOrderLineInfo[]; productProcessLines: ProductProcessLineResponse[]; } export interface UpdateProductProcessLineQtyRequest { productProcessLineId: number; outputFromProcessQty: number; outputFromProcessUom: string; byproductName: string; byproductQty: number; byproductUom: string; defectQty: number; defectUom: string; defect2Qty: number; defect2Uom: string; defect3Qty: number; defect3Uom: string; defectDescription: string; defectDescription2: string; defectDescription3: string; scrapQty: number; scrapUom: string; } export interface UpdateProductProcessLineQtyResponse { id: number; outputFromProcessQty: number; outputFromProcessUom: string; defectQty: number; defectUom: string; defect2Qty: number; defect2Uom: string; defect3Qty: number; defect3Uom: string; defectDescription: string; defectDescription2: string; defectDescription3: 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; matchStatus: string; bomId?: number; assignedTo: number; pickOrderId: number; itemName: string; requiredQty: number; jobOrderId: number; stockInLineId: 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 { productProcessLineId: number; //operatorId?: number; //equipmentId?: number; equipmentTypeSubTypeEquipmentNo?: string; staffNo?: string; } export interface ProductProcessLineDetailResponse { id: number, productProcessId: number, bomProcessId: number, operatorId: number, equipmentType: string, 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, equipment: string, startTime: string, endTime: string, defectQty: number, defectUom: string, scrapQty: number, scrapUom: string, byproductId: number, byproductName: string, byproductQty: number, byproductUom: string | undefined, totalStockQty: number, insufficientStockQty: number, sufficientStockQty: number, productionPriority: number, productProcessLines: ProductProcessLineInfoResponse[], jobOrderLineInfo: JobOrderLineInfo[], } export interface JobOrderProcessLineDetailResponse { id: number; productProcessId: number; bomProcessId: number; operatorId: number; equipmentType: string | null; operatorName: string; handlerId: number; seqNo: number; durationInMinutes: 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; defectDescription: string; defectQty2: number; defectUom2: string; defectDescription2: string; defectQty3: number; defectUom3: string; defectDescription3: string; scrapQty: number; scrapUom: string; byproductId: number; byproductName: string; byproductQty: number; byproductUom: string; productProcessIssueId: number; productProcessIssueStatus: string; } export interface JobOrderLineInfo { id: number, jobOrderId: number, jobOrderCode: string, itemId: number, itemCode: string, itemName: string, reqQty: number, stockQty: number, uom: string, shortUom: string, availableStatus: string, bomProcessId: number, bomProcessSeqNo: number, } export interface ProductProcessLineInfoResponse { id: number, bomprocessId: number, operatorId: number, operatorName: string, equipmentId: number, handlerId: number, seqNo: number, name: string, description: string, equipment_name: string, equipmentDetailCode: string, status: string, byproductId: number, byproductName: string, byproductQty: number, byproductUom: string, scrapQty: number, defectQty: number, defectUom: string, durationInMinutes: number, prepTimeInMinutes: number, postProdTimeInMinutes: number, outputFromProcessQty: number, outputFromProcessUom: string, startTime: string, endTime: string } export interface AllJoPickOrderResponse { id: number; pickOrderId: number | null; pickOrderCode: string | null; jobOrderId: number | null; jobOrderCode: string | null; jobOrderTypeId: number | null; jobOrderType: string | null; itemId: number; itemName: string; reqQty: number; uomId: number; uomName: string; jobOrderStatus: string; finishedPickOLineCount: number; } export interface UpdateJoPickOrderHandledByRequest { pickOrderId: number; itemId: number; userId: number; } export interface JobTypeResponse { id: number; name: string; } export interface SaveProductProcessIssueTimeRequest { productProcessLineId: number; reason: string; } export interface JobOrderLotsHierarchicalResponse { pickOrder: PickOrderInfoResponse; pickOrderLines: PickOrderLineWithLotsResponse[]; } export interface PickOrderInfoResponse { id: number | null; code: string | null; consoCode: string | null; targetDate: string | null; type: string | null; status: string | null; assignTo: number | null; jobOrder: JobOrderBasicInfoResponse; } export interface JobOrderBasicInfoResponse { id: number; code: string; name: string; } export interface PickOrderLineWithLotsResponse { id: number; itemId: number | null; itemCode: string | null; itemName: string | null; requiredQty: number | null; uomCode: string | null; uomDesc: string | null; status: string | null; lots: LotDetailResponse[]; } export interface LotDetailResponse { lotId: number | null; lotNo: string | null; expiryDate: string | null; location: string | null; availableQty: number | null; requiredQty: number | null; actualPickQty: number | null; processingStatus: string | null; lotAvailability: string | null; pickOrderId: number | null; pickOrderCode: string | null; pickOrderConsoCode: string | null; pickOrderLineId: number | null; stockOutLineId: number | null; suggestedPickLotId: number | null; stockOutLineQty: number | null; stockOutLineStatus: string | null; routerIndex: number | null; routerArea: string | null; routerRoute: string | null; uomShortDesc: string | null; matchStatus?: string | null; matchBy?: number | null; matchQty?: number | null; } export const saveProductProcessIssueTime = cache(async (request: SaveProductProcessIssueTimeRequest) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/issue`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), } ); }); export const saveProductProcessResumeTime = cache(async (productProcessIssueId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/resume/${productProcessIssueId}`, { method: "POST", } ); }); export const deleteJobOrder=cache(async (jobOrderId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/demo/deleteJobOrder/${jobOrderId}`, { method: "POST", } ); }); export const fetchAllJobTypes = cache(async () => { return serverFetchJson( `${BASE_API_URL}/jo/jobTypes`, { method: "GET", } ); }); export const updateJoPickOrderHandledBy = cache(async (request: UpdateJoPickOrderHandledByRequest) => { return serverFetchJson( `${BASE_API_URL}/jo/update-jo-pick-order-handled-by`, { method: "POST", body: JSON.stringify(request), headers: { "Content-Type": "application/json" }, }, ); }); export const fetchJobOrderLotsHierarchicalByPickOrderId = cache(async (pickOrderId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/all-lots-hierarchical-by-pick-order/${pickOrderId}`, { method: "GET", next: { tags: ["jo-hierarchical"] }, }, ); }); export const fetchAllJoPickOrders = cache(async () => { return serverFetchJson( `${BASE_API_URL}/jo/AllJoPickOrder`, { method: "GET", } ); }); export const fetchProductProcessLineDetail = cache(async (lineId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/detail/${lineId}`, { method: "GET", } ); }); export const updateProductProcessLineQty = cache(async (request: UpdateProductProcessLineQtyRequest) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/update/qty/${request.productProcessLineId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), } ); }); export const updateProductProcessLineQrscan = cache(async (request: ProductProcessLineQrscanUpadteRequest) => { const requestBody: any = { productProcessLineId: request.productProcessLineId, //operatorId: request.operatorId, //equipmentId: request.equipmentId, equipmentTypeSubTypeEquipmentNo: request.equipmentTypeSubTypeEquipmentNo, staffNo: request.staffNo, }; if (request.equipmentTypeSubTypeEquipmentNo !== undefined) { requestBody["EquipmentType-SubType-EquipmentNo"] = request.equipmentTypeSubTypeEquipmentNo; } return serverFetchJson( `${BASE_API_URL}/product-process/Demo/update`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody), } ); }); 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) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/start/${lineId}`, { method: "POST", headers: { "Content-Type": "application/json" }, } ); }; export const completeProductProcessLine = async (lineId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/complete/${lineId}`, { method: "POST", headers: { "Content-Type": "application/json" }, } ); }; // 查询所有 production processes export const fetchProductProcesses = cache(async () => { return serverFetchJson<{ content: ProductProcessResponse[] }>( `${BASE_API_URL}/product-process`, { method: "GET", next: { tags: ["productProcess"] }, } ); }); // 根据 ID 查询 export const fetchProductProcessById = cache(async (id: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/${id}`, { method: "GET", next: { tags: ["productProcess"] }, } ); }); export const updateProductProcessPriority = cache(async (productProcessId: number, productionPriority: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/Process/update/priority/${productProcessId}/${productionPriority}`, { method: "POST", } ); }); // 根据 Job Order ID 查询 export const fetchProductProcessesByJobOrderId = cache(async (jobOrderId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/demo/joid/${jobOrderId}`, { method: "GET", next: { tags: ["productProcess"] }, } ); }); // 获取 process 的所有 lines export const fetchProductProcessLines = cache(async (processId: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/${processId}/lines`, { method: "GET", next: { tags: ["productProcessLines"] }, } ); }); // 创建 production process export const createProductProcess = async (data: { bomId: number; jobOrderId?: number; date?: string; }) => { return serverFetchJson<{ id: number; productProcessCode: string; linesCreated: number }>( `${BASE_API_URL}/product-process`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), } ); }; // 更新 line 产出数据 export const updateLineOutput = async (lineId: number, data: { outputQty?: number; outputUom?: string; defectQty?: number; defectUom?: string; scrapQty?: number; scrapUom?: string; byproductName?: string; byproductQty?: number; byproductUom?: string; }) => { return serverFetchJson( `${BASE_API_URL}/product-process/lines/${lineId}/output`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), } ); }; export const updateSecondQrScanStatus = cache(async (pickOrderId: number, itemId: number, userId: number, qty: number) => { return serverFetchJson( `${BASE_API_URL}/jo/update-match-status`, { method: "POST", body: JSON.stringify({ pickOrderId, itemId, userId, qty }), headers: { 'Content-Type': 'application/json', }, next: { tags: ["update-match-status"] }, }, ); }); export const submitSecondScanQuantity = cache(async ( pickOrderId: number, itemId: number, data: { qty: number; isMissing?: boolean; isBad?: boolean; reason?: string } ) => { return serverFetchJson( `${BASE_API_URL}/jo/second-scan-submit/${pickOrderId}/${itemId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), next: { tags: ["jo-second-scan"] }, }, ); }); // 获取未分配的 Job Order pick orders export const fetchUnassignedJobOrderPickOrders = cache(async () => { return serverFetchJson( `${BASE_API_URL}/jo/unassigned-job-order-pick-orders`, { method: "GET", next: { tags: ["jo-unassigned"] }, }, ); }); // 分配 Job Order pick order 给用户 export const assignJobOrderPickOrder = async (pickOrderId: number, userId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/assign-job-order-pick-order/${pickOrderId}/${userId}`, { method: "POST", headers: { "Content-Type": "application/json" }, } ); }; export const unAssignJobOrderPickOrder = async (pickOrderId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/unassign-job-order-pick-order/${pickOrderId}`, { method: "POST", headers: { "Content-Type": "application/json" }, } ); }; // 获取 Job Order 分层数据 export const fetchJobOrderLotsHierarchical = cache(async (userId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/all-lots-hierarchical/${userId}`, { method: "GET", next: { tags: ["jo-hierarchical"] }, }, ); }); export const fetchCompletedJobOrderPickOrders = cache(async (userId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/completed-job-order-pick-orders/${userId}`, { method: "GET", next: { tags: ["jo-completed"] }, }, ); }); // 获取已完成的 Job Order pick orders export const fetchCompletedJobOrderPickOrdersrecords = cache(async (userId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/completed-job-order-pick-orders-only/${userId}`, { method: "GET", next: { tags: ["jo-completed"] }, }, ); }); // 获取已完成的 Job Order pick order records export const fetchCompletedJobOrderPickOrderRecords = cache(async (userId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/completed-job-order-pick-order-records/${userId}`, { method: "GET", next: { tags: ["jo-records"] }, }, ); }); export const fetchJobOrderDetailByCode = cache(async (code: string) => { return serverFetchJson( `${BASE_API_URL}/jo/detailByCode/${code}`, { method: "GET", next: { tags: ["jo"] }, }, ); }); export const isOperatorExist = async (username: string) => { const isExist = await serverFetchJson>( `${BASE_API_URL}/jop/isOperatorExist`, { method: "POST", body: JSON.stringify({ username }), headers: { "Content-Type": "application/json" }, }, ); revalidateTag("po"); return isExist; }; export const isCorrectMachineUsed = async (machineCode: string) => { const isExist = await serverFetchJson>( `${BASE_API_URL}/jop/isCorrectMachineUsed`, { method: "POST", body: JSON.stringify({ machineCode }), headers: { "Content-Type": "application/json" }, }, ); revalidateTag("po"); return isExist; }; export const fetchJos = cache(async (data?: SearchJoResultRequest) => { const queryStr = convertObjToURLSearchParams(data) console.log("queryStr", queryStr) const fullUrl = `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`; console.log("fetchJos full URL:", fullUrl); console.log("fetchJos BASE_API_URL:", BASE_API_URL); const response = await serverFetchJson( `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`, { method: "GET", headers: { "Content-Type": "application/json" }, next: { tags: ["jos"] } } ) console.log("fetchJos response:", response) return response }) export const updateJo = cache(async (data: UpdateJoRequest) => { return serverFetchJson(`${BASE_API_URL}/jo/update`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }) }) export const releaseJo = cache(async (data: CommonActionJoRequest) => { const response = serverFetchJson(`${BASE_API_URL}/jo/release`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }) // Invalidate the cache after releasing revalidateTag("jo"); return response; }) export const startJo = cache(async (data: CommonActionJoRequest) => { const response = serverFetchJson(`${BASE_API_URL}/jo/start`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }) // Invalidate the cache after starting revalidateTag("jo"); return response; }) export const manualCreateJo = cache(async (data: SaveJo) => { return serverFetchJson(`${BASE_API_URL}/jo/manualCreate`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" } }) }) export const fetchCompletedJobOrderPickOrdersWithCompletedSecondScan = cache(async (userId: number) => { return serverFetchJson(`${BASE_API_URL}/jo/completed-job-order-pick-orders-with-completed-second-scan/${userId}`, { method: "GET", headers: { "Content-Type": "application/json" } }) }) export const fetchCompletedJobOrderPickOrderLotDetails = cache(async (pickOrderId: number) => { return serverFetchJson(`${BASE_API_URL}/jo/completed-job-order-pick-order-lot-details/${pickOrderId}`, { method: "GET", headers: { "Content-Type": "application/json" } }) }) export const fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick = cache(async (pickOrderId: number) => { return serverFetchJson(`${BASE_API_URL}/jo/completed-job-order-pick-order-lot-details-completed-pick/${pickOrderId}`, { method: "GET", headers: { "Content-Type": "application/json" } }) }) export async function PrintPickRecord(request: PrintPickRecordRequest){ const params = new URLSearchParams(); params.append('pickOrderId', request.pickOrderId.toString()) params.append('printerId', request.printerId.toString()) if (request.printQty !== null && request.printQty !== undefined) { params.append('printQty', request.printQty.toString()); } //const response = await serverFetchWithNoContent(`${BASE_API_URL}/jo/print-PickRecord?${params.toString()}`,{ const response = await serverFetchWithNoContent(`${BASE_API_URL}/jo/print-PickRecord?${params.toString()}`,{ method: "GET" }); return { success: true, message: "Print job sent successfully (Pick Record)" } as PrintPickRecordResponse; }