import { format, formatDuration, intervalToDuration } from "date-fns"; import { eq } from "drizzle-orm"; import { success } from "zod/v4"; import { db } from "../../../../../database/dbclient.js"; import { printerData } from "../../../../../database/schema/printers.js"; import { delay } from "../../../../globalUtils/delay.js"; import { runProdApi } from "../../../../globalUtils/runProdApi.js"; import { tryCatch } from "../../../../globalUtils/tryCatch.js"; import { createLog } from "../../../logger/logger.js"; import { query } from "../../../sqlServer/prodSqlServer.js"; import { shiftChange } from "../../../sqlServer/querys/misc/shiftChange.js"; import { labelInfo } from "../../../sqlServer/querys/warehouse/labelInfo.js"; type NewLotData = { runningNumber: number; lotNumber: number; originalAmount: number; level: number; amount: number; type: "lot" | "eom"; }; interface PendingJob { timeoutId: NodeJS.Timeout; runningNumber: string | number; data: any; consumeLot: any; newQty: any; scheduledFor: Date; } export const pendingJobs = new Map(); /** * Move manual material to a new lot. * * The data sent over should be * Running number * Lot number * Orignal Quantity * level of gaylord * amount can be sent over as a precise amount * type what way are we lots */ export const lotMaterialTransfer = async (data: NewLotData) => { // check if we already have this running number scheduled if (pendingJobs.has(data.runningNumber)) { const job = pendingJobs.get(data.runningNumber) as PendingJob; const duration = intervalToDuration({ start: new Date(), end: job.scheduledFor, }); createLog( "error", "materials", "ocp", `${ data.runningNumber } is pending to be transfered already, remaining time ${formatDuration( duration, { format: ["hours", "minutes", "seconds"] }, )}`, ); return { success: false, message: `${ data.runningNumber } is pending to be transfered already, remaining time ${formatDuration( duration, { format: ["hours", "minutes", "seconds"] }, )}`, data: [], }; } // get the shift time const { data: shift, error: shiftError } = (await tryCatch( query(shiftChange, "shift change from material."), )) as any; if (shiftError) { createLog( "error", "materials", "ocp", "There was an error getting the shift times will use fallback times", ); } // shift split const shiftTimeSplit = shift?.data[0]?.shiftChange.split(":"); console.log(parseInt(shiftTimeSplit[0]) - 1); // Current time const now = new Date(); // Target time: today at 06:35 const target = new Date( now.getFullYear(), now.getMonth(), 1, //now.getDate(), shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) : 5, // this will parse the hour to remove teh zero shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 3, 0, 0, ); console.log("target", target.toLocaleString(), "Now", now.toLocaleString()); // to early time const early = new Date( now.getFullYear(), now.getMonth(), 1, //now.getDate(), shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) - 1 : 5, // this will parse the hour to remove teh zero shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 0, 0, 0, ); console.log("early", early.toLocaleString(), "Now", now.toLocaleString()); // next month just to be here const nextMonth = new Date( now.getFullYear(), now.getMonth() + 1, 1, //now.getDate(), shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) - 1 : 5, // this will parse the hour to remove teh zero shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 0, 0, 0, ); console.log( "nextMonth", nextMonth.toLocaleString(), "Now", now.toLocaleString(), ); // console.log(early, target); // if we are to early return early only if we are sending over eom if (data.type === "eom" && (early > now || target < now)) { createLog( "error", "materials", "ocp", `Eom transfers is not allowed right now please try again at ${format( nextMonth, "M/d/yyyy hh:mm", )} `, ); return { success: false, message: `Eom transfers is not allowed right now please try again at ${format( nextMonth, "M/d/yyyy hh:mm", )} `, data: [], }; } let timeoutTrans: number = data.type === "lot" ? 5 : 5; // get the barcode, and layoutID from the running number const { data: label, error: labelError } = (await tryCatch( query( labelInfo.replace("[runningNr]", `${data.runningNumber}`), "Get label info", ), )) as any; console.log(label); if (labelError) { createLog( "error", "materials", "ocp", "There was an error getting the label info", ); return { success: false, message: "There was an error getting the label info", data: labelError, }; } if (label.data.length === 0) { createLog( "error", "materials", "ocp", `${data.runningNumber}: dose not exist or no longer in stock.`, ); return { success: false, message: `${data.runningNumber}: dose not exist or no longer in stock.`, data: [], }; } //console.log(label); if (label.data[0]?.stockStatus === "onStock") { createLog( "error", "materials", "ocp", `${data.runningNumber}: currently in stock and not consumed to a lot.`, ); return { success: false, message: `${data.runningNumber}: currently in stock and not consumed to a lot.`, data: [], }; } // get the pdf24 printer id const { data: printer, error: printerError } = (await tryCatch( db.select().from(printerData).where(eq(printerData.name, "PDF24")), )) as any; if (printerError) { createLog( "error", "materials", "ocp", "There was an error the printer info", ); return { success: false, message: "There was an error the printer info", data: printerError, }; } // calculate the remaining amount bascially it will be orignal number * level sent over // level should be sent in a decimal .25 .5 .75 .95 the 95 will allow basically the what looks to be a full gaylord but we always want to consume something const newQty = data.amount > 0 ? data.amount : (data.originalAmount * data.level).toFixed(0); //console.log(data.amount); // reprint the label and send it to pdf24 const reprintData = { clientId: 999, runningNo: label?.data[0].runnungNumber, printerId: printer[0].humanReadableId, layoutId: label?.data[0].labelLayout, noOfCopies: 0, quantity: newQty, } as any; //console.log(reprintData); const { data: reprint, error: reprintError } = (await tryCatch( runProdApi({ endpoint: "/public/v1.0/ProductionLabelling/ReprintLabel", data: [reprintData], }), )) as any; if (!reprint.success) { createLog( "error", "materials", "ocp", `RN:${data.runningNumber}, Reprinting Error: ${reprint.data.data.message}`, ); return { success: false, message: `RN:${data.runningNumber}, Reprinting Error: ${reprint.data.data.message}`, data: reprint, }; } // return the label back to fm1 lane id 10001 const matReturnData = { barcode: label?.data[0].barcode, laneId: 10001, }; //console.log(matReturnData); // set a delay for when we try to return the label const matReturn = await returnMaterial(matReturnData); if (!matReturn.success) { createLog( "error", "materials", "ocp", `RN:${data.runningNumber}, Return Error ${matReturn.data.data.errors[0].message}`, ); return { success: false, message: `RN:${data.runningNumber}, Return Error ${matReturn.data.data.errors[0].message}`, data: matReturn, }; } // consume to the lot provided. const consumeLot = { productionLot: data.lotNumber, barcode: label?.data[0].barcode, }; const delayTimer = data.type === "lot" ? timeoutTrans * 1000 : target.getTime() - now.getTime(); const transfer = await transferMaterial(delayTimer, data, consumeLot, newQty); if (!transfer.success) { return { success: transfer.success, message: transfer.message, data: transfer.data, }; } const duration = intervalToDuration({ start: now, end: target }); const pretty = formatDuration(duration, { format: ["hours", "minutes", "seconds"], }); if (data.type === "eom") { return { success: true, message: `RN:${data.runningNumber}: qty: ${newQty}, will be transfered to lot: ${data.lotNumber}, in ${pretty} `, data: [], }; } else { return { success: true, message: `RN:${data.runningNumber}: qty: ${newQty}, was transfered to lot: ${data.lotNumber}`, data: [], }; } }; const returnMaterial = async (matReturnData: any) => { console.log("Getting ready to Returning Materials"); await delay(5 * 1000); console.log("doing the return"); const { data: matReturn, error: matReturError } = (await tryCatch( runProdApi({ endpoint: "/public/v1.0/IssueMaterial/ReturnPartiallyConsumedManualMaterial", data: [matReturnData], }), )) as any; if (matReturError) return matReturError; return matReturn; }; const transferMaterial = async ( delay: number, data: any, consumeLot: any, newQty: any, ) => { //console.log(data); console.log("Transferring the material"); if (pendingJobs.has(data.runningNumber)) { createLog( "error", "materials", "ocp", `${data.runningNumber} is pending to be transferred already`, ); return { success: false, message: `${data.runningNumber} is pending to be transfered already`, data: [], }; } const scheduledFor = new Date(Date.now() + delay); // sets the time out based on the type of transfer sent over. const timeoutId = setTimeout(async () => { try { const { data: matConsume, error: matConsumeError } = (await tryCatch( runProdApi({ endpoint: "/public/v1.0/IssueMaterial/ConsumeNonPreparedManualMaterial", data: [consumeLot], }), )) as any; if (!matConsume?.success) { createLog( "error", "materials", "ocp", `RN:${data.runningNumber}, Consume Error ${ matConsume?.data?.data?.errors?.[0]?.message ?? "Unknown" }`, ); return; // still hits finally } createLog( "info", "materials", "ocp", `RN:${data.runningNumber}: qty: ${newQty}, was transferred to lot:${data.lotNumber}`, ); } catch (err) { createLog( "error", "materials", "ocp", `RN:${data.runningNumber}, ${err}`, ); } finally { // Always clear the pending entry, even if error pendingJobs.delete(data.runningNumber); } }, delay); pendingJobs.set(data.runningNumber, { timeoutId, runningNumber: data.runningNumber, data, consumeLot, newQty, scheduledFor, }); // Immediately say we scheduled it return { success: true, message: `Transfer for ${data.runningNumber} scheduled`, data: [], }; }; // setInterval(() => { // console.log(pendingJobs); // }, 5000); // setTimeout(async () => { // lotMaterialTransfer({ // runnungNumber: 603468, // lotNumber: 24897, // originalAmount: 380, // level: 0.95, // }); // }, 5000);