feat(lstv2 move): moved lstv2 into this app to keep them combined and easier to maintain

This commit is contained in:
2025-09-19 22:22:05 -05:00
parent caf2315191
commit e4477402ad
847 changed files with 165801 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
import axios from "axios";
import { prodEndpointCreation } from "../../../../globalUtils/createUrl.js";
import { lstAuth } from "../../../../index.js";
import { createLog } from "../../../logger/logger.js";
import { db } from "../../../../../database/dbclient.js";
import { prodlabels } from "../../../../../database/schema/prodLabels.js";
import { eq, sql } from "drizzle-orm";
export const bookInLabel = async (data: any) => {
// update sscc so we can book in
const SSCC = data.SSCC.slice(2);
// api url
const url = await prodEndpointCreation(
"/public/v1.1/Manufacturing/ProductionControlling/BookIn"
);
// create bookin
const newBookin = {
scannerId: 777,
sscc: SSCC,
};
try {
const res = await axios.post(url, newBookin, {
headers: {
Authorization: `Basic ${lstAuth}`,
accept: "text/plain",
},
});
if (res.status != 200) {
createLog(
"error",
"labeling",
"ocp",
`${
data.printer ? data.printer[0].name : "Manual book in"
}, Error:${res.data}`
);
//printerUpdate(data.printer, 7, "Error while booking in.");
return {
success: false,
message: "There was an error booking in the label.",
data: res.data,
};
}
// update the label.
try {
const booked = await db
.update(prodlabels)
.set({
status: "Booked in",
upd_date: sql`NOW()`,
})
.where(
eq(prodlabels.runningNr, parseInt(data.SSCC.slice(10, -1)))
)
.returning({ runningNr: prodlabels.runningNr });
createLog(
"info",
"labeling",
"ocp",
`${parseInt(data.SSCC.slice(10, -1))} , was just booked in.`
);
return {
success: true,
message: `${parseInt(
data.SSCC.slice(10, -1)
)}, was just booked in`,
data: { SSCC: data.SSCC },
};
} catch (error) {
//console.log(error);
createLog(
"error",
"labeling",
"ocp",
`Error creating new runningNumber in the DB.`
);
return {
success: true,
message: `${parseInt(
data.SSCC.slice(10, -1)
)}, encoutnered an error posting to the db`,
data: error,
};
}
} catch (error: any) {
createLog(
"error",
"labeling",
"ocp",
`${
data.printer ? data.printer[0].name : "Manual book in"
}, "Error: ${error?.response.data}`
);
// console.log(error.response.data);
return {
success: false,
message: "There was an error booking in the label.",
data: error.response.data,
};
}
};

View File

@@ -0,0 +1,150 @@
import { eq, gte, lt, sql } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { printerData } from "../../../../../database/schema/printers.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { prodEndpointCreation } from "../../../../globalUtils/createUrl.js";
import { settings } from "../../../../../database/schema/settings.js";
import { lstAuth } from "../../../../index.js";
import axios from "axios";
import { prodlabels } from "../../../../../database/schema/prodLabels.js";
import { addDays } from "date-fns";
export const createLabel = async (data: any, userPrinted: any) => {
createLog("info", "labeling", "ocp", `Label being created`);
const { data: printer, error: printerError } = await tryCatch(
db
.select()
.from(printerData)
.where(eq(printerData.humanReadableId, data.printerID))
);
const { data: settingsData, error: settingsError } = await tryCatch(
db.select().from(settings)
);
if (printerError) {
return {
success: false,
message: "There was an error getting the printer.",
printerError,
};
}
if (settingsError) {
return {
success: false,
message: "There was an error getting the settings.",
settingsError,
};
}
const url = await prodEndpointCreation(
"/public/v1.0/Warehousing/GenerateAndPrintLabelExtended"
);
const plantToken = settingsData.filter((n) => n.name === "plantToken");
const newLabel = {
scannerId: 99,
lotNr: data.lot,
machineId: data.machineID,
printerId: data.printerID,
//layoutId: cartonCustomers.includes(data.CustomerId.toString()) ? data.cartonLabel : data.palletLabel,
layoutId:
plantToken[0].value === "usksc1"
? data.cartonLabel
: data.palletLabel,
//numberOfCopies: cartonCustomers.includes(data.CustomerId.toString()) ? data.cartonCopies : data.pallerCopies,
numberOfCopies:
plantToken[0].value === "usksc1"
? data.cartonCopies
: data.pallerCopies,
};
// create the label
// create the label with the data we have
try {
const res = await axios.post(url, newLabel, {
headers: {
Authorization: `Basic ${lstAuth}`,
"Content-Type": "application/json",
},
});
// error handling
if (res.data.Result != 0) {
createLog(
"error",
"labeling",
"ocp",
`${data.MachineDescription}, has an error while printing: Error: ${res.data.Message}.`
);
return {
success: false,
mesasge: `${data.MachineDescription}, has an error while printing`,
data: res.data,
};
}
// save the data to lst db (only saved for x time see the db clean up functions)
let newlabel = res.data;
try {
const insertLabel = await db
.insert(prodlabels)
.values({
printerID: parseInt(printer[0]?.humanReadableId!, 10),
runningNr: parseInt(newlabel.SSCC.slice(10, -1)),
printerName: printer[0].name.toLowerCase(),
line: data.MachineLocation,
status: "printed",
add_user: userPrinted || "LST_System",
})
.returning({ runningNr: prodlabels.runningNr });
createLog(
"info",
"labeling",
"ocp",
`${insertLabel[0]?.runningNr} was just inserted into the db.`
);
} catch (error) {
createLog(
"error",
"labeling",
"ocp",
`Error creating new runningNumber in the DB.`
);
}
createLog(
"info",
"labeling",
"ocp",
`New label was created for: ${
data.MachineDescription
}, Running number: ${parseInt(newlabel.SSCC.slice(10, -1))}`
);
const returnData = {
...newlabel,
printer,
};
// check if we can remove labels or not
//deleteLabels();
return { success: true, message: "Label created", data: returnData }; // returning label data to be able to book in if active
} catch (error) {
createLog(
"info",
"labeling",
"ocp",
`There was an error creating the label for, ${printer[0].name}, "Error: ${error}`
);
return {
success: false,
message: "There was an error creating the label",
data: error,
};
}
};
// run the label delete process we want to delate them older than 90 days right now

View File

@@ -0,0 +1,23 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { labelRatio } from "../../../../../database/schema/ratios.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
export const getLabelRatio = async () => {
const { data: labelInfo, error: labelError } = await tryCatch(
db.select().from(labelRatio).where(eq(labelRatio.name, "label"))
);
if (labelError) {
return {
success: false,
message: "There was an error getting the labelratio",
data: [labelError],
};
}
return {
success: true,
message: "Current labelratio.",
data: labelInfo,
};
};

View File

@@ -0,0 +1,33 @@
import { desc, gte, lte, sql } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { prodlabels } from "../../../../../database/schema/prodLabels.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
export const getLabels = async (hours: string) => {
const { data: labelInfo, error: labelError } = await tryCatch(
db
.select()
.from(prodlabels)
.where(
gte(
prodlabels.upd_date,
sql.raw(`NOW() - INTERVAL '${hours} hours'`)
)
)
.orderBy(desc(prodlabels.upd_date))
);
if (labelError) {
return {
success: false,
message: "There was an error getting the labels",
data: [labelError],
};
}
return {
success: true,
message: "Current labels order by upd_Date.",
count: labelInfo.length,
data: labelInfo,
};
};

View File

@@ -0,0 +1,322 @@
import { db } from "../../../../../database/dbclient.js";
import { settings } from "../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { getLots } from "../lots/lots.js";
import { billingCheck } from "../specialProcesses/billingCheck/billingCheck.js";
import { isMainMatStaged } from "../materials/mainMaterial.js";
import { firstLotLabel } from "../specialProcesses/lotChangeLabel/lotCHangeLabel.js";
import { prolinkCheck } from "../lots/prolinkCheck.js";
import { createLabel } from "./createLabel.js";
import { bookInLabel } from "./bookIn.js";
import { delieryInhouse } from "../specialProcesses/inhouse/inhouseDelivery.js";
import { dualPrintingProcess } from "../specialProcesses/dualPrinting/dualPrinting.js";
import { getMac } from "../../utils/getMachineId.js";
interface Printer {
name: string;
humanReadableId: string;
// Add any other expected properties
}
interface Zechetti {
line: string;
printer: number;
printerName: string;
}
export const labelingProcess = async ({
line = null as string | null,
printer = null as Printer | null,
userPrinted = null,
rfidTag = null,
zechette = null as Zechetti | null,
} = {}) => {
/**
* Creates a label once all logic is passed
*/
let filteredLot: any = [];
createLog("debug", "labeling", "ocp", `Starting labeling process`);
const { data: settingData, error: settingError } = await tryCatch(
db.select().from(settings)
);
if (settingError) {
return {
success: false,
message: "There was an error getting the settings.",
settingError,
};
}
// get the plantToken
const plantToken = settingData.filter((n) => n.name === "plantToken");
// get the current lots
const lots = await getLots();
// if we got a line passed over we need to get the machine id from this.
if (line) {
const macId = await getMac(line);
// filter out the lot for the line
filteredLot = lots.data.filter(
(l: any) => l.MachineID === macId[0]?.HumanReadableId
);
if (filteredLot.length === 0) {
createLog(
"error",
"labeling",
"ocp",
`There is not a lot assigned to ${line}.`
);
return {
success: false,
message: `There is not a lot assigned to ${line}.`,
};
}
}
// if we are running the zechettii
if (zechette) {
const macId = await getMac(zechette.line);
// filter out the lot for the line
filteredLot = lots.data.filter(
(l: any) => l.MachineID === macId[0]?.HumanReadableId
);
if (filteredLot.length === 0) {
createLog(
"error",
"labeling",
"ocp",
`There is not a lot assigned to ${line}.`
);
return {
success: false,
message: `There is not a lot assigned to ${line}.`,
};
}
// remap the printer so its the zechetti one
filteredLot = filteredLot.map((p: any) => ({
...p,
printerID: zechette.printer,
PrinterName: zechette.printerName,
}));
}
// if we came from a printer
if (printer) {
// filter the lot based on the printerID
// console.log(printer);
filteredLot = lots.data.filter(
(l: any) => l.printerID === parseInt(printer?.humanReadableId)
);
if (filteredLot.length === 0) {
// console.log(`There is not a lot assigned to ${printer.name}`);
createLog(
"error",
"labeling",
"ocp",
`There is not a lot assigned to ${printer.name}`
);
//printerUpdate(printer, 7, `There is no lot assigned to this printer.`);
return {
success: false,
message: `There is not a lot assigned to ${printer.name}.`,
};
}
}
/**
*
* The checks we do before we can actually print a label will take place meow.
*
*/
// if the plant does not want to have dual printing and we have >2 assigned well return and send error.
let dualPrinting = settingData.filter((d) => d.name === "dualPrinting")[0]
?.value;
if (filteredLot.length > 1 && dualPrinting === "0") {
createLog(
"error",
"labeling",
"ocp",
`${printer?.name}, has more than one lot assigned to it, and dual printing shut off.`
);
return {
success: false,
message: `${printer?.name}, has more than one lot assigned to it, and dual printing shut off.`,
};
}
if (filteredLot.length > 1 && dualPrinting === "1") {
// send over for dual printing processing
createLog(
"info",
"labeling",
"ocp",
`${printer?.name}, being sent over for dual printing processing.`
);
// process what line to print the label for and return the lot info and change filteredLot to returned one.
filteredLot = await dualPrintingProcess(filteredLot);
}
// if there are more than 2 lots it might be an auto labeler, autolabeler will be defined by its id for now only dayton
if (filteredLot?.length > 2 && plantToken[0].value !== "usday1") {
createLog(
"error",
"labeling",
"ocp",
`${printer?.name}, has more than 2 lot assigned to it, and not in ${plantToken[0].value}`
);
return {
success: false,
message: `${printer?.name}, has more than 2 lot assigned to it, and not in ${plantToken[0].value}`,
};
}
// special process for florence
const isBillingTime = await billingCheck();
if (isBillingTime) {
// billing is inside the window we dont want to bill now.
return {
success: false,
message: "Billing time cant print.",
};
}
// check if we actaully have a lot passed over so we do not error out again.
if (filteredLot.length === 0) {
createLog(
"error",
"labeling",
"ocp",
`Error getting lot info from ${
printer ? printer.name : "Missing printer info"
}`
);
return {
success: false,
message: `Error getting lot info from ${
printer ? printer.name : "Missing printer info"
}`,
};
}
// check the material... mm,color (auto and manual combined), pkg
const mmStaged = await isMainMatStaged(filteredLot[0]);
if (!mmStaged.success) {
createLog("error", "labeling", "ocp", mmStaged.message);
return {
success: false,
message: mmStaged.message,
};
}
// comment only but will check for color
createLog(
"info",
"labeling",
"ocp",
`Remaining pallets for: ${filteredLot[0].MachineDescription} is ${filteredLot[0].Remaining}`
);
// do we want to over run
if (filteredLot[0].overPrinting === "no" && filteredLot[0].Remaining <= 0) {
createLog(
"error",
"labeling",
"ocp",
`Over Printing on ${filteredLot[0].MachineDescription} is not allowed please change the lot`
);
// for slc we want to run the first label for henkel
firstLotLabel(filteredLot[0]);
return {
success: false,
message: `Over Printing on ${filteredLot[0].MachineDescription} is not allowed please change the lot`,
};
}
// prolink check by lot
const prolink = await prolinkCheck(filteredLot[0].lot);
if (!prolink) {
//console.error(`Prolink does not match for ${filteredLot[0].MachineDescription}`);
createLog(
"error",
"labeling",
"ocp",
`Prolink does not match for ${filteredLot[0].MachineDescription}`
);
return {
success: false,
message: `Prolink does not match for ${filteredLot[0].MachineDescription}`,
};
}
createLog("info", "labeling", "ocp", `Is prolink good? ${prolink}`);
// create the label
const label = await createLabel(filteredLot[0], userPrinted);
if (!label.success) {
createLog(
"error",
"labeling",
"ocp",
`There was an error creating the label: ${label.message}`
);
return { sucess: false, message: label.message, data: label.data };
}
// send over to be booked in if we can do it.
const bookin = settingData.filter((s) => s.name === "bookin");
let book: any = [];
if (bookin[0].value === "1") {
book = await bookInLabel(label.data);
if (!book.success) {
// createLog(
// "error",
// "labeling",
// "ocp",
// `Error Booking in label: ${book.errors[0].message}`
// );
createLog(
"error",
"labeling",
"ocp",
`There was an error booking in the label: ${JSON.stringify(
book.data
)}`
);
return {
success: false,
message: `Error Booking in label`,
data: book,
};
}
} else {
createLog("info", "labeling", "ocp", "Bookin is turned off.");
// will add later!!! :P
}
// inhouse - if the inhouse funtion is turned on we will deliver to inhouse as long as we did not hit an error state
const inhouseDelivery = settingData.filter(
(s) => s.name === "inhouseDelivery"
);
if (inhouseDelivery[0].value === "1") {
const deliverPallet = await delieryInhouse(book.data);
// console.log(deliverPallet);
}
return {
success: true,
message: "Label fully processed.",
data: label.data,
};
};

View File

@@ -0,0 +1,84 @@
import { eq, sql } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { labelRatio } from "../../../../../database/schema/ratios.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
export const autoLabelCreated = async () => {
const { error } = await tryCatch(
db
.insert(labelRatio)
.values({
name: "label",
autoLabel: 1,
})
.onConflictDoUpdate({
target: labelRatio.name,
set: { autoLabel: sql`${labelRatio.autoLabel} + 1` },
})
);
if (error) {
createLog(
"error",
"labeling",
"ocp",
"There was an error updating auto label ratio"
);
}
};
export const manualLabelCreated = async () => {
const { error } = await tryCatch(
db
.insert(labelRatio)
.values({
name: "label",
manualLabel: 1,
})
.onConflictDoUpdate({
target: labelRatio.name,
set: { manualLabel: sql`${labelRatio.manualLabel} + 1` },
})
);
if (error) {
createLog(
"error",
"labeling",
"ocp",
"There was an error updating manual Label Ratio"
);
}
};
export const resetLabelRatio = async () => {
const { error } = await tryCatch(
db
.update(labelRatio)
.set({
name: sql`'label-' || (SELECT count(*) FROM ${labelRatio})`,
lastReset: sql`NOW()`,
})
.where(eq(labelRatio.name, "label"))
);
if (error) {
console.log(error);
createLog(
"error",
"labeling",
"ocp",
"There was an error resetting Label Ratio"
);
return {
success: false,
message: "There was an issue resetting the label data.",
};
}
return {
success: true,
message: "Label Ratio has been reset.",
};
};

View File

@@ -0,0 +1,53 @@
import { db } from "../../../../../database/dbclient.js";
import { manualPrinting } from "../../../../../database/schema/ocpManualPrint.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { manualTag } from "../../../rfid/controller/tags/manualTag.js";
import { labelingProcess } from "./labelProcess.js";
import { manualLabelCreated } from "./labelRatio.js";
export const manualPrint = async (manualPrint: any) => {
/**
* add the reason we did a manual print.
*/
const manualPrintData = {
line: manualPrint.line,
printReason: manualPrint.printReason,
initials: manualPrint.initials,
additionalComments: manualPrint?.additionalComments,
add_user: "lst",
};
const { data, error } = await tryCatch(
db.insert(manualPrinting).values(manualPrintData).returning({
line: manualPrinting.line,
printReason: manualPrinting.printReason,
initials: manualPrinting.initials,
additionalComments: manualPrinting?.additionalComments,
add_user: manualPrinting.add_user,
})
);
if (error) {
return {
success: false,
message: "There was an error posting the manualPrintData",
data: error,
};
}
let label = await labelingProcess({ line: manualPrint.line });
manualLabelCreated();
if (manualPrint.rfidTag) {
manualTag(
manualPrint.rfidTag,
"wrapper1",
parseInt(label.data.SSCC.slice(10, -1))
);
}
return {
success: true,
message: "Log Entered label will be coming soon.",
data,
};
};

View File

@@ -0,0 +1,39 @@
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { lotQuery } from "../../../sqlServer/querys/ocp/lots.js";
type LotInfo = {
success: boolean;
message: string;
data: any[];
};
export const getLots = async () => {
const { data: l, error: lotError } = await tryCatch(
query(lotQuery, "Alplalabel online lots")
);
const lotInfo = l as LotInfo;
if (lotError) {
console.log("lot error", lotError);
return {
success: false,
message: "There was an error getting the lots",
data: [],
};
}
if (lotInfo.success) {
return {
success: true,
message: "Current active lots that are technically released.",
data: lotInfo.data,
};
} else {
return {
success: true,
message: `Error getting lot info due to: ${lotInfo.message}.`,
data: [],
};
}
};

View File

@@ -0,0 +1,67 @@
import { db } from "../../../../../database/dbclient.js";
import { settings } from "../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { prolinkQuery } from "../../../sqlServer/querys/ocp/prolinkCheck.js";
// check the if the lot matches
export const prolinkCheck = async (lot: any) => {
const { data, error: settingError } = await tryCatch(
db.select().from(settings)
);
if (settingError) {
createLog(
"error",
"ocp",
"ocp",
`There was an error getting the settings.`
);
return;
}
const settingData: any = data;
const plantToken = settingData.filter(
(s: any) => s.name === "plantToken"
)[0].value;
const prolinkCheck = settingData.filter(
(s: any) => s.name === "prolinkCheck"
)[0].value;
// if we want to ignore prolink check it will be disabled and then just return a pass as true
if (prolinkCheck === "0") {
createLog(
"info",
"ocp",
"ocp",
`Prolink Check is disabled skipping check.`
);
return true;
}
// run the query
try {
const p: any = await query(prolinkQuery, "Prolink Checks");
const prolink = p.data;
// filter out the lot
const filterdLot = prolink.filter(
(p: any) => p.AlplaLabelOnline === lot
);
//console.log(filterdLot);
if (filterdLot[0].LotCheck === "Good") {
return true;
} else {
return false;
}
} catch (err) {
createLog(
"error",
"ocp",
"ocp",
`Error from running the Prolink Check query: ${err}`
);
return false;
}
};

View File

@@ -0,0 +1,426 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { printerData } from "../../../../../database/schema/printers.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 { labelInfo } from "../../../sqlServer/querys/warehouse/labelInfo.js";
import { format, formatDuration, intervalToDuration } from "date-fns";
import { shiftChange } from "../../../sqlServer/querys/misc/shiftChange.js";
import { success } from "zod/v4";
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<string | number, PendingJob>();
/**
* 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(shiftTimeSplit);
// 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]) - 1 : 5, // this will parse the hour to remove teh zero
shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) + 3 : 3,
0,
0
);
// 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
);
// 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(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" ? 30 : 10;
// 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;
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);
const { data: matReturn, error: matReturError } = (await tryCatch(
runProdApi({
endpoint:
"/public/v1.0/IssueMaterial/ReturnPartiallyConsumedManualMaterial",
data: [matReturnData],
})
)) as any;
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 delay =
data.type === "lot"
? timeoutTrans * 1000
: target.getTime() - now.getTime();
const transfer = await transferMaterial(delay, 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 transferMaterial = async (
delay: number,
data: any,
consumeLot: any,
newQty: any
) => {
//console.log(data);
if (pendingJobs.has(data.runningNumber)) {
createLog(
"error",
"materials",
"ocp",
`${data.runningNumber} is pending to be transfered 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);

View File

@@ -0,0 +1,235 @@
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { serverSettings } from "../../../server/controller/settings/getSettings.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { machineCheck } from "../../../sqlServer/querys/ocp/machineId.js";
import { mmQuery } from "../../../sqlServer/querys/ocp/mainMaterial.js";
export const isMainMatStaged = async (lot: any) => {
const set = serverSettings.length === 0 ? [] : serverSettings;
// make staged false by deefault and error logged if theres an issue
let isStaged = { message: "Material is staged", success: true };
const { data, error } = (await tryCatch(
query(
machineCheck.replace("where Active = 1 and [Location] = [loc]", ""),
"check machine needs mm"
)
)) as any;
const machine = data.data.filter(
(m: any) => m.HumanReadableId === lot.machineID
);
// we have a check on ksc side to ignore the tetra machine for now as its not updating in 2.0
if (!machine[0].StagingMainMaterialMandatory) {
createLog(
"info",
"mainMaterial",
"ocp",
`The machine dose not require mm to print and book in.`
);
return {
message: "Machine dose not require material to be staged",
success: true,
};
}
// strangly the lot is not always sending over in slc so adding this in for now to see what line is cauing this issue
if (!lot) {
return isStaged;
}
if (typeof lot !== "object" || lot === null || Array.isArray(lot)) {
createLog(
"info",
"mainMaterial",
"ocp",
`The lot sent over is not an object: ${JSON.stringify(lot)}`
);
return isStaged;
}
const updateQuery = mmQuery.replaceAll("[lotNumber]", lot.lot);
try {
const r: any = await query(updateQuery, "Main Material Check");
const res: any = r.data;
// if (res[0].Staged >= 1) {
// isStaged = true;
// }
type CheckConditionArgs = {
results: any[];
filterFn: (n: any) => boolean;
failCondition: (n: any) => boolean;
failMessage: string;
successMessage: string;
lot: { lot: string | number };
};
const checkCondition = ({
results,
filterFn,
failCondition,
failMessage,
successMessage,
lot,
}: CheckConditionArgs): { message: string; success: boolean } => {
const subset = results.filter(filterFn);
if (subset.some(failCondition)) {
const failing = subset.filter(failCondition);
return {
message: `lot: ${lot.lot}, is missing: ${failing
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} ${failMessage}`,
success: false,
};
} else {
return { message: successMessage, success: true };
}
};
createLog("info", "mainMaterial", "ocp", `Maint material query ran.`);
const mainMaterial = res.find((n: any) => n.IsMainMaterial);
if (mainMaterial?.noMMShortage === "noMM") {
return {
message: `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`,
success: false,
};
}
// we need to filter the color stuff and then look for includes instead of a standard name. this way we can capture a everything and not a single type
// for manual consume color if active to check colors
const checkColorSetting = set.filter((n) => n.name === "checkColor");
// 2. Auto color
if (checkColorSetting[0].value === "1") {
// auto check
// 2. Auto color
const autoColor = checkCondition({
results: res,
lot,
filterFn: (n) =>
n.isManual &&
!("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, auto
failCondition: (n) => n.autoConsumeCheck === "autoConsumeNOK", // column = autoConsumeCheck
failMessage: "for autoconsume",
successMessage: "auto color is good",
});
if (!autoColor.success) return autoColor;
console.log(autoColor);
// 3. Manual color
const manualColor = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial &&
n.isManual &&
!("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, manual
failCondition: (n) => n.noManualShortage === "noOK", // column = noManualShortage
failMessage: "for manual material",
successMessage: "manual color is good",
});
if (!manualColor.success) return manualColor;
console.log(manualColor);
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"Color check is not active."
);
}
// // if we want to check the packaging
const checkPKGSetting = set.filter((n) => n.name === "checkPKG");
if (checkPKGSetting[0].value === "1") {
const pkgAuto = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial &&
!n.isManual &&
"noPKGAutoShortage" in n,
failCondition: (n) => n.noPKGAutoShortage === "noAutoPkg",
failMessage: "for pkg",
successMessage: "auto PKG is good",
});
if (!pkgAuto.success) return pkgAuto;
console.log(pkgAuto);
// 5. Packaging manual
const pkgManual = checkCondition({
results: res,
lot,
filterFn: (n) =>
!n.IsMainMaterial &&
n.isManual &&
"noPKGManualShortage" in n,
failCondition: (n) => n.noPKGManualShortage === "noManPkg",
failMessage: "for pkg",
successMessage: "manual PKG is good",
});
if (!pkgManual.success) return pkgManual;
console.log(pkgManual);
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"PKG check is not active."
);
}
// manual pkg
if (checkPKGSetting[0].value === "1") {
const packagingCheck = res.filter(
(n: any) =>
!n.IsMainMaterial &&
n.isManual &&
"noPKGManualShortage" in n
);
if (
packagingCheck.some(
(n: any) => n.noPKGManualShortage === "noManPkg"
)
) {
return (isStaged = {
message: `lot: ${lot.lot}, is missing: ${packagingCheck
.map(
(o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}`
)
.join(",\n ")} for pkg`,
success: false,
});
}
} else {
createLog(
"info",
"mainMaterial",
"ocp",
"PKG check is not active."
);
}
} catch (err) {
createLog(
"error",
"mainMaterial",
"ocp",
`Error from running the Main Material query: ${err}`
);
}
return isStaged;
};

View File

@@ -0,0 +1,19 @@
import { db } from "../../../../../database/dbclient.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { printerData } from "../../../../../database/schema/printers.js";
export const getPrinters = async () => {
const { data: printers, error: printerError } = await tryCatch(
db.select().from(printerData).orderBy(printerData.name)
);
if (printerError) {
return {
success: false,
message: "there was an error getting the printers",
data: printerError,
};
}
return { success: true, message: "Printers", data: printers };
};

View File

@@ -0,0 +1,113 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { settings } from "../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { getPrinters } from "./getPrinters.js";
import { autoLabelingStats, printerStatus } from "./printerStatus.js";
let isPrinterCycling = false;
let actualPrinterCycle: any;
export const printerCycle = async () => {
/**
* We will cycle through the printers to check there states.
*/
if (isPrinterCycling)
return {
success: false,
message: "Printers are already being cycled.",
};
createLog("info", "ocp", "ocp", "Printer cycle has started.");
// get the printers
const { data: s, error: se } = await tryCatch(
db.select().from(settings).where(eq(settings.name, "ocpCycleDelay"))
);
if (se) {
createLog(
"error",
"ocp",
"ocp",
"There was an error getting the ocpCycleDelay."
);
return {
success: false,
message: "Error getting printers.",
};
}
const ocpDelay: any = s;
isPrinterCycling = true;
// start the actual printer cycle
actualPrinterCycle = setInterval(async () => {
const { data, error } = await tryCatch(getPrinters());
if (error) {
createLog(
"error",
"ocp",
"ocp",
"There was an error getting the printers."
);
return {
success: false,
message: "Error getting printers.",
};
}
let printers: any = data.data;
// only keep the assigned ones
printers = printers.filter((p: any) => p.assigned === true);
// for printers we want to ignore there must be a remark stateing to ignore.
printers = printers.filter((p: any) => !p.remark.includes("ignore"));
printers.forEach(async (p: any) => {
/**
* if the last timeprinted would be greater than x well just change the status to idle and extended based on the 2 times.
*
* to get a printer going again label will need to come from the front end as that will just unpause the printer and start the labeling, or the api for manual print
* well need to adjust this to actually print the label then unpause it.
*
* it will be
*
* less than x since time printed run the printer status
* greater than x but less than y change the status to idle, but ping to make sure its online,
* if greater than y change to extended idle but stil also ping to make sure its online.
*/
// ignore pdf printer as we want it here for testing purposes
if (p.name.toLowerCase() === "pdf24") {
return;
}
if (p.name === "Autolabeler") {
//await autoLabelingStats(p);
return;
}
// for all other printers
await printerStatus(p);
});
}, parseInt(ocpDelay[0]?.value) * 1000);
return { success: true, message: "Printer cycle has been started." };
};
export const stopPrinterCycle = async () => {
/**
* We will stop the print cylce this is more an emergancy thing.
*/
if (actualPrinterCycle && !actualPrinterCycle._destroyed) {
createLog("info", "ocp", "ocp", "Printer cycle is being stopped.");
clearInterval(actualPrinterCycle);
isPrinterCycling = false;
return { success: true, message: "Printer cycle has been stopped." };
} else {
createLog("info", "ocp", "ocp", "Printer cycle is already stopped.");
isPrinterCycling = false;
return { success: true, message: "Printer cycle is already Stopped." };
}
};

View File

@@ -0,0 +1,121 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { settings } from "../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { getPrinters } from "./getPrinters.js";
import { autoLabelingStats } from "./printerStatus.js";
let isPrinterCycling = false;
let actualPrinterCycle: any;
export const printerCycleAutoLabelers = async () => {
/**
* Will only check the auto labelers for status updates.
*/
if (isPrinterCycling)
return {
success: false,
message: "Printers are already being cycled.",
};
createLog("info", "ocp", "ocp", "AutoLabeling cycle has started.");
// get the printers
const { data: s, error: se } = await tryCatch(
db.select().from(settings).where(eq(settings.name, "ocpCycleDelay"))
);
if (se) {
createLog(
"error",
"ocp",
"ocp",
"There was an error getting the ocpCycleDelay."
);
return {
success: false,
message: "Error getting printers.",
};
}
const ocpDelay: any = s;
isPrinterCycling = true;
// start the actual printer cycle
actualPrinterCycle = setInterval(async () => {
const { data, error } = await tryCatch(getPrinters());
if (error) {
createLog(
"error",
"ocp",
"ocp",
"There was an error getting the printers."
);
return {
success: false,
message: "Error getting printers.",
};
}
let printers: any = data.data;
// only keep the assigned ones
printers = printers.filter((p: any) => p.assigned === true);
// for printers we want to ignore there must be a remark stateing to ignore.
printers = printers.filter((p: any) => !p.remark.includes("ignore"));
printers.forEach(async (p: any) => {
/**
* if the last timeprinted would be greater than x well just change the status to idle and extended based on the 2 times.
*
* to get a printer going again label will need to come from the front end as that will just unpause the printer and start the labeling, or the api for manual print
* well need to adjust this to actually print the label then unpause it.
*
* it will be
*
* less than x since time printed run the printer status
* greater than x but less than y change the status to idle, but ping to make sure its online,
* if greater than y change to extended idle but stil also ping to make sure its online.
*/
// ignore pdf printer as we want it here for testing purposes
if (p.name.toLowerCase() === "pdf24") {
return;
}
if (p.name === "Autolabeler") {
await autoLabelingStats(p);
return;
}
});
}, parseInt(ocpDelay[0]?.value) * 2 * 1000);
return { success: true, message: "AutoLabeling cycle has been started." };
};
export const stopPrinterCycle = async () => {
/**
* We will stop the print cylce this is more an emergancy thing.
*/
if (actualPrinterCycle && !actualPrinterCycle._destroyed) {
createLog("info", "ocp", "ocp", "AutoLabeling cycle is being stopped.");
clearInterval(actualPrinterCycle);
isPrinterCycling = false;
return {
success: true,
message: "AutoLabeling cycle has been stopped.",
};
} else {
createLog(
"info",
"ocp",
"ocp",
"AutoLabeling cycle is already stopped."
);
isPrinterCycling = false;
return {
success: true,
message: "AutoLabeling cycle is already Stopped.",
};
}
};

View File

@@ -0,0 +1,66 @@
import { eq, sql } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { printerData } from "../../../../../database/schema/printers.js";
import { createLog } from "../../../logger/logger.js";
export const printStatus = [
{ code: 1, text: "Printing" },
{ code: 2, text: "Pending labels" },
{ code: 3, text: "Printing to fast" },
{ code: 4, text: "Creating label" },
{ code: 5, text: "Waiting" },
{ code: 6, text: "Printer Paused" },
{ code: 7, text: "Printer error" },
{
code: 8,
text: "First time printing",
},
{
code: 9,
text: "Offline",
},
];
export const printerUpdate = async (printer: any, status: any) => {
/**
* Updates the status of the printer.
*/
// get current time
let pd = {};
const currentTime = sql`NOW()`;
if (status === 3) {
pd = {
status: status,
statusText: printStatus.filter((c) => c.code === status)[0]?.text,
};
} else if (status === 6) {
pd = {
status: status,
statusText: printStatus.filter((c) => c.code === status)[0]?.text,
};
} else {
pd = {
lastTimePrinted: currentTime,
status: status,
statusText: printStatus.filter((c) => c.code === status)[0]?.text,
};
}
if (printer.humanReadableId) {
try {
await db
.update(printerData)
.set(pd)
.where(
eq(printerData.humanReadableId, printer.humanReadableId)
);
} catch (error) {
createLog("error", "ocp", "ocp", `Error updating printer state`);
}
}
// console.log(printerUpdate.name);
return;
};

View File

@@ -0,0 +1,285 @@
import net from "net";
import { pausePrinter } from "../../utils/pausePrinter.js";
import { addHours, differenceInSeconds } from "date-fns";
import { printerUpdate } from "./printerStatUpdate.js";
import { createLog } from "../../../logger/logger.js";
import { unPausePrinter } from "../../utils/unpausePrinter.js";
import { labelingProcess } from "../labeling/labelProcess.js";
import { timeZoneFix } from "../../../../globalUtils/timeZoneFix.js";
import { autoLabelCreated } from "../labeling/labelRatio.js";
let logLevel: string = process.env.LOG_LEVEL || "info";
let errorCheck = false;
export const printerStatus = async (p: any) => {
/**
* Checks each printer to see what the current state is
*/
createLog("debug", "ocp", "ocp", `Printer cycling`);
const printer = new net.Socket();
return new Promise((resolve) => {
// connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer
printer.write("~HS");
});
// read the data from the printer
printer.on("data", async (data) => {
const res = data.toString();
// turn the data into an array to make it more easy to deal with
const tmp = res.split(",");
//--------------- time stuff-----------------------------------------------------------------
// get last time printed
const lastTime = new Date(p.lastTimePrinted).toISOString();
// console.log(lastTime);
// current time?
/**
*
* add the time zone to the settings db
*/
// const currentTime = addHours(
// new Date(Date.now()),
// -6
// ).toISOString();
const currentTime = timeZoneFix();
let timeBetween = 0;
// if this is our first time printing pause the printer to start the timer, else we just update the time between timer
if (lastTime === undefined) {
printer.end();
printerUpdate(p, 8);
pausePrinter(p);
resolve({ success: true, message: "First Time printing" });
} else {
timeBetween = differenceInSeconds(currentTime, lastTime);
}
// --- end time ---
// --- printer logic ---
createLog(
"debug",
"ocp",
"ocp",
`${p.name}: timeBetween: ${timeBetween}, delay ${parseInt(
p.printDelay
)}, ${currentTime}... ${lastTime}`
);
if (tmp[2] === "0" && tmp[4] !== "000") {
// unpaused and printing labels - reset timer
createLog(
"debug",
"ocp",
"ocp",
`Unpaused and printing labels, time remaing ${differenceInSeconds(
parseInt(p.printDelay),
timeBetween
)}`
);
// update last time printed in the array
printerUpdate(p, 1);
} else if (tmp[2] === "1" && tmp[4] !== "000") {
// was paused or label sent from somewhere else
createLog(
"info",
"ocp",
"ocp",
`${
p.name
} paused to soon, unpausing, remaining time: ${differenceInSeconds(
parseInt(p.printDelay),
timeBetween
)}`
);
// reset the timer for this printer as well other labels shouldnt be sent but if we send them ok
printerUpdate(p, 2);
unPausePrinter(p);
} else if (tmp[2] === "0" && timeBetween < parseInt(p.printDelay)) {
// was unpaused to soon so repause it
createLog(
"debug",
"ocp",
"ocp",
`${p.name} Unpaused before the time allowed, time left ${
differenceInSeconds(parseInt(p.printDelay), timeBetween) //seconds
}`
);
printerUpdate(p, 3);
pausePrinter(p);
} else if (tmp[2] === "0" && timeBetween > parseInt(p.printDelay)) {
// its been long enough we can print a label
createLog(
"debug",
"ocp",
"ocp",
`${p.name} Allowed time passed and printing new label`
);
// update last time printed in the array
printerUpdate(p, 4);
// sending over for labeling.
labelingProcess({ printer: p });
autoLabelCreated();
} else if (tmp[2] === "0") {
// printer was unpaused for the first time or made it here
createLog(
"debug",
"ocp",
"ocp",
`${p.name} Frist time printing`
);
// add the time and printer
printerUpdate(p, 4);
// sending over for labeling.
labelingProcess({ printer: p });
autoLabelCreated();
} else if (tmp[2] === "1") {
// printer is paused and waiting
createLog(
"debug",
"ocp",
"ocp",
`${p.name} paused and waiting`
);
printerUpdate(p, 6);
}
printer.end();
resolve({ success: true, message: "Print cycle completed." });
});
// as a safety destory it if its still there
printer.on("end", () => {
setTimeout(() => {
if (!printer.destroyed) {
createLog(
"info",
"printerState",
"ocp",
`${p.name}: was force closed, during normal cycle counting`
);
printer.destroy();
}
}, 1000);
});
printer.on("error", async (error: any) => {
// just going to say theres an error with the printer
//console.log(error.code);
if (error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog("error", "ocp", "ocp", `${p.name} is offline`);
await printerUpdate(p, 9);
errorCheck = true;
printer.end();
resolve({
success: false,
message: "The printer is offline.",
});
}
if (!error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog(
"error",
"ocp",
"ocp",
`${p.name} encountered an error: ${error}`
);
await printerUpdate(p, 7);
errorCheck = true;
// send log data
// fake line
printer.end();
resolve({
success: false,
message: "There was an error with the printer.",
});
}
});
});
};
export const autoLabelingStats = async (p: any) => {
/**
* Checks autolabeling printers just to see what they are doing.
*/
createLog("debug", "ocp", "ocp", `Printer cycling`);
const printer = new net.Socket();
return new Promise((resolve) => {
// connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer
printer.write("~HS");
});
// read the data from the printer
printer.on("data", async (data) => {
const res = data.toString();
// turn the data into an array to make it more easy to deal with
const tmp = res.split(",");
if (tmp[4] !== "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array
printerUpdate(p, 1);
}
if (tmp[4] === "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array
printerUpdate(p, 5);
}
});
printer.on("error", async (error) => {
// just going to say theres an error with the printer
console.log(error);
if (!errorCheck) {
createLog(
"error",
"ocp",
"ocp",
`${p.name}, encountered an error: ${error}`
);
}
await printerUpdate(p, 7);
errorCheck = true;
// send log data
// fake line
printer.end();
resolve({
success: false,
message: "There was an error with the printer.",
});
});
});
};

View File

@@ -0,0 +1,89 @@
import axios from "axios";
import { printerData } from "../../../../../database/schema/printers.js";
import { sql } from "drizzle-orm";
import { prodEndpointCreation } from "../../../../globalUtils/createUrl.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { lstAuth } from "../../../../index.js";
import { db } from "../../../../../database/dbclient.js";
import { createLog } from "../../../logger/logger.js";
import { assignedPrinters } from "../../utils/checkAssignments.js";
export const updatePrinters = async () => {
const currentTime = new Date(Date.now());
// get the printers from prod
let url = await prodEndpointCreation(
"/public/v1.0/Administration/Printers"
);
const { data: prodPrinters, error: prodError } = await tryCatch(
axios.get(url, {
headers: {
Authorization: `Basic ${lstAuth}`,
"Content-Type": "application/json",
},
})
);
if (prodError || prodPrinters?.data.length > 10000) {
//console.log(prodError);
return {
success: false,
message: "there was an error getting the printers.",
data: prodError,
};
}
// do the printer update into our db
const prodPrinterInfo = prodPrinters?.data;
for (let i = 0; i < prodPrinterInfo.length; i++) {
const printerStuff: any = {
humanReadableId: prodPrinterInfo[i].humanReadableId,
name: prodPrinterInfo[i].name,
ipAddress: prodPrinterInfo[i].ipAddress,
port: prodPrinterInfo[i].port,
remark: prodPrinterInfo[i].remark,
processes: prodPrinterInfo[i].processes,
};
const { data, error } = await tryCatch(
db
.insert(printerData)
.values(printerStuff)
.onConflictDoUpdate({
target: printerData.humanReadableId,
set: {
//humanReadableId: prodPrinterInfo[i].humanReadableId,
name: prodPrinterInfo[i].name,
ipAddress: prodPrinterInfo[i].ipAddress,
port: prodPrinterInfo[i].port,
remark: prodPrinterInfo[i].remark,
processes: prodPrinterInfo[i].processes,
upd_date: sql`NOW()`,
//printDelay: "90", // need to remove in a couple weeks
},
})
);
if (error) {
createLog(
"error",
"lst",
"ocp",
`${
prodPrinterInfo[i].name
} encoutered and error adding/updating ${JSON.stringify(error)}`
);
}
createLog(
"debug",
"lst",
"ocp",
`${prodPrinterInfo[i].name} were just added/updated.`
);
}
await assignedPrinters();
return { success: true, message: "Printers were just added or updated." };
};

View File

@@ -0,0 +1,54 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../../database/dbclient.js";
import { settings } from "../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../logger/logger.js";
const st = 5;
const sm = 55;
const et = 6;
const em = 5;
export const billingCheck = async () => {
const now = new Date();
const startTime = new Date();
const endTime = new Date();
let isOutsideBilling = false;
const { data, error } = await tryCatch(db.select().from(settings));
if (error) {
return { success: false, message: "Error in getting settings." };
}
const plant = data.filter((n) => n.name === "plantToken");
// set everything up
startTime.setHours(st, sm, 0, 0);
endTime.setHours(et, em, 0, 0);
if (plant[0].value === "usflo1") {
if (now >= startTime && now <= endTime) {
createLog(
"warn",
"specialProcess",
"ocp",
`You are inside the billing window no labels will print! please wait`
);
isOutsideBilling = true;
} else {
createLog(
"info",
"specialProcess",
"ocp",
"Out side billing window ok to print"
);
}
} else {
createLog(
"debug",
"specialProcess",
"ocp",
"This plant dose not check for billing"
);
}
return isOutsideBilling;
};

View File

@@ -0,0 +1,147 @@
import { createLog } from "../../../../logger/logger.js";
import { query } from "../../../../sqlServer/prodSqlServer.js";
export const dualPrintingProcess = async (lotInfo: any) => {
// currently we will only allow for 2 printers to be linked only crazy people want more than 2
createLog(
"info",
"ocp",
"ocp",
"Dual printing running logic here to see what to print"
);
// each check
let lastPrinted;
let firstLine;
let secondLine;
// this query checks the last line to print a label
const printedQuery = `SELECT TOP(1) IdProdPlanung,
IdArtikelvarianten,
IdMaschine,
LfdNr,
ArtikelVariantenBez,
Add_User,
Add_Date,
Upd_User,
Upd_Date,
IdProdBereich
FROM AlplaPROD_test1.dbo.T_EtikettenGedruckt (nolock)
where IdMaschine in (${lotInfo[0].machineID},${lotInfo[1].machineID}) order by Add_Date desc`;
const fisrtLineDT = `SELECT top(1) IdMaschine,
case when Endzeit ='1900-01-01 00:00:00.000' and DATEDIFF(MINUTE, Startzeit, GETDATE()) >= 3 Then 1 else 0 end as 'LineCheck'
FROM AlplaPROD_test1.dbo.T_HistoryStillstandsereignis (nolock) where IdMaschine = ${lotInfo[0].machineID} order by Add_Date desc`;
const secondLineDT = `SELECT top(1) IdMaschine,
case when Endzeit ='1900-01-01 00:00:00.000' and DATEDIFF(MINUTE, Startzeit, GETDATE()) >= 3 Then 1 else 0 end as 'LineCheck'
FROM AlplaPROD_test1.dbo.T_HistoryStillstandsereignis (nolock) where IdMaschine = ${lotInfo[1].machineID} order by Add_Date desc`;
try {
// get what was last printed
const result: any = await query(printedQuery, "Last Printed Query");
lastPrinted = result.data[0].IdMaschine;
} catch (err) {
createLog(
"error",
"ocp",
"ocp",
`Error: getting last printed querty: ${err}`
);
}
try {
// get if if running or down
const first: any = await query(fisrtLineDT, "First line DT Check");
firstLine = first.data[0].LineCheck;
} catch (err) {
createLog(
"error",
"ocp",
"ocp",
`Error: with getting ${lotInfo[0].machineID} data query: ${err}`
);
}
try {
const second: any = await query(secondLineDT, "Second line DT Check");
secondLine = second.data[0].LineCheck;
} catch (err) {
createLog(
"error",
"ocp",
"ocp",
`Error: with getting ${lotInfo[1].machineID} data query: ${err}`
);
}
// first check if both are up and running
if (firstLine === 0 && secondLine === 0) {
createLog("info", "ocp", "ocp", "Both lines are up");
// what line last printed
if (lastPrinted === lotInfo[0].machineID) {
// IE 7 then print 8, usslc was the main example for all of this
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[1].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint[0].MachineDescription}`
);
return whatToPrint;
}
if (lastPrinted === lotInfo[1].machineID) {
// IE 8 then print 7, usslc was the main example for all of this
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[0].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint[0].MachineDescription}`
);
return whatToPrint;
}
}
// as we are only allowing 2 lines if one is down the other must be up... still going to conditional it to be sure
createLog(
"info",
"ocp",
"ocp",
"Looking at what line is down and printing the other one!"
);
if (firstLine === 1 && secondLine === 0) {
// print the other line
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[1].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint.MachineDescription}`
);
return whatToPrint;
} else if (firstLine === 0 && secondLine === 1) {
// print the other line
let whatToPrint = lotInfo.filter(
(m: any) => m.machineID === lotInfo[0].machineID
);
// send to label
createLog(
"info",
"ocp",
"ocp",
`Printing label for ${whatToPrint.MachineDescription}`
);
return whatToPrint;
}
};

View File

@@ -0,0 +1,103 @@
import { Controller, Tag } from "st-ethernet-ip";
import { createLog } from "../../../../logger/logger.js";
import { labelerTagRead } from "./plcTags/labelerTag.js";
import { palletSendTag } from "./plcTags/palletSendTag.js";
import { strapperFaults } from "./plcTags/strapperFault.js";
let PLC = new Controller();
let isDycoRunning = false;
// PLC address
let plcAddress = "10.44.5.4";
let isReading = false;
// Initialize the interval variable outside the function
let plcCycle: any;
let plcInterval = 500;
// Create Tag Instances
const labelerTag: any = new Tag("labeler.line_info");
const palletSend = new Tag("Zone_6.Ready_to_Send");
const strapperError = new Tag("Zone_3.Strapper_Faulted");
export const dycoConnect = async () => {
// if we crash or start over reset the timers so we dont get duplicates
clearInterval(plcCycle);
if (isDycoRunning)
return { success: false, message: "Dyco is already connected." };
// Remove all listeners before adding a new one to prevent memory leaks
PLC.removeAllListeners("error");
try {
await PLC.connect(plcAddress, 0).then(async () => {
createLog("info", "dyco", "ocp", `We are connected to the dyco.`);
isDycoRunning = true;
plcCycle = setInterval(async () => {
if (isReading) {
createLog(
"debug",
"dyco",
"ocp",
"Skipping cycle: previous read still in progress."
);
return;
}
isReading = true; // Set flag
try {
await PLC.readTag(labelerTag);
await PLC.readTag(palletSend);
await PLC.readTag(strapperError);
// strapper check
strapperFaults(strapperError);
// send the labeler tag data over
labelerTagRead(labelerTag);
// send the end of line check over.
palletSendTag(palletSend);
} catch (error: any) {
createLog(
"error",
"dyco",
"ocp",
`Error reading PLC tag: ${error.message}`
);
} finally {
isReading = false; // Reset flag
}
}, plcInterval);
});
} catch (error) {
createLog(
"error",
"dyco",
"ocp",
`There was an error in the dyco: ${error}`
);
await PLC.disconnect();
isDycoRunning = false;
}
};
export const closeDyco = async () => {
if (!isDycoRunning)
return { success: false, message: "Dyco is not connected." };
console.log(`Closing the connection`);
try {
await PLC.disconnect();
isDycoRunning = false;
return {
success: true,
message: "Dyco Connection is now closed.",
};
} catch (error) {
console.log(error);
return {
success: false,
message: "There was an error closing the dyco connection.",
};
}
};

View File

@@ -0,0 +1,119 @@
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../../logger/logger.js";
import { readTags } from "../../../../../rfid/controller/readTags.js";
import { labelingProcess } from "../../../labeling/labelProcess.js";
import { autoLabelCreated } from "../../../labeling/labelRatio.js";
import { stapperFaulted, strapperFaults } from "./strapperFault.js";
export let cameraPalletCheck = 20;
export let currentPalletCheck = 0;
let lastProcessedTimestamp = 0;
export const labelerTagRead = async (tagData: any) => {
/**
* Reads the tag data from the Dyco PLC and processes it based on the feedback.
*/
// Convert tag data buffer to a string and extract numeric values
const buffer = Buffer.from(tagData.value);
const numericString = buffer.toString("utf8").replace(/[^0-9#]/g, "");
// Ignore empty or invalid tag data
if (numericString === "#") {
return;
}
const tagTime = new Date(tagData.state.timestamp).getTime();
// get the settings
const { data: settingData, error: settingError } = await tryCatch(
db.select().from(settings)
);
if (settingError) {
createLog(
"error",
"dyco",
"ocp",
"There was an error getting the settings"
);
return;
}
// check the dyco settings
const dycoPrint = settingData.filter((n) => n.name === "dycoPrint");
// Only process if this is a new timestamp within the last 5 seconds
if (tagTime !== lastProcessedTimestamp && Date.now() - tagTime <= 8000) {
lastProcessedTimestamp = tagTime;
/**
* add logic in to see if this is the first time we run this so we return an error to validate we are in sync.
*/
createLog(
"info",
"dyco",
"ocp",
`Current pallet at the wrapper is: ${numericString}`
);
if (dycoPrint[0].value === "1") {
createLog("info", "dyco", "ocp", "Dyco will be printing the label");
// if (!dycoControlCheck) {
// createLog(
// "error",
// "dyco",
// "ocp",
// `Dyco was switch to be the printer guys. please validate the line is in sync, Current line is ${numericString}`
// );
// dycoControlCheck = true;
// return;
// }
// check the stapper error logic.
if (stapperFaulted) {
createLog("error", "dyco", "ocp", `Strapper is faulted.`);
return;
}
// check if we need to manual check due to 20 pallets.
if (currentPalletCheck <= cameraPalletCheck) {
currentPalletCheck++;
labelingProcess({ line: numericString });
createLog(
"info",
"dyco",
"ocp",
`You have printed ${currentPalletCheck} pallets, remaining until ${
cameraPalletCheck - currentPalletCheck
}.`
);
autoLabelCreated();
} else {
currentPalletCheck = 0;
createLog(
"warn",
"dyco",
"ocp",
`You have reached 20 pallets since the last check please validate the labeler is still in sync.`
);
return;
}
}
if (dycoPrint[0].value === "0") {
createLog(
"info",
"dyco",
"ocp",
"Rfid system is contorlling the printing"
);
// trigger the reader so we can get the label from the tag readers.
setTimeout(async () => {
await readTags("wrapper1");
}, 500); // half second delay on this guy
}
}
};
/**
* setting to switch between rfid and dyco labeling
*/

View File

@@ -0,0 +1,59 @@
import { createLog } from "../../../../../logger/logger.js";
import { pickedup } from "../../../../../ocme/controller/pickedup.js";
import { triggerScanner } from "../../../../../ocme/controller/triggerCamera.js";
import { serverSettings } from "../../../../../server/controller/settings/getSettings.js";
let lastProcessedTimestamp = 0;
export const palletSendTag = async (tagData: any) => {
/**
* Reads the tag data from the Dyco PLC and processes it based on the feedback.
* We will only trigger the camera and removal of pending tags
*/
const ocmeActive = serverSettings.filter((n) => n.name === "ocmeService");
const tagTime = new Date(tagData.state.timestamp).getTime();
// Only process if this is a new timestamp within the last 5 seconds
if (
tagTime !== lastProcessedTimestamp &&
Date.now() - tagTime <= 5000 &&
tagData.value &&
ocmeActive[0].value === "1"
) {
lastProcessedTimestamp = tagTime;
//console.log(tagData.state.timestamp);
createLog(
"info",
"dyco",
"ocp",
`Station 6 is ${tagData.value ? "full" : "empty"}`
);
// take the picture
setTimeout(async () => {
const scan: any = await triggerScanner();
if (!scan.success) {
createLog(
"error",
"dyco",
"ocp",
`Scanner failed to take a picture trying one more time in 10 seconds`
);
setTimeout(async () => {
await triggerScanner();
}, 10 * 1000);
}
}, 15 * 1000);
}
if (
tagTime !== lastProcessedTimestamp &&
Date.now() - tagTime <= 5000 &&
!tagData.value &&
ocmeActive[0].value === "1"
) {
await pickedup({ runningNr: 1234, all: true, areaFrom: "wrapper_1" });
}
};

View File

@@ -0,0 +1,86 @@
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../../logger/logger.js";
// strapper related issues
export let strapperActive = false;
export let stapperFaulted = false;
export let strapperFaultCount = 3; // move to db so we can control it outside the app
export const strapperFaults = async (tagData: any) => {
const { data, error } = await tryCatch(db.select().from(settings));
// failed to get settings
if (error) {
return { success: false, message: "Failed to get settings." };
}
const strapperCheckSetting = data.filter((n) => n.name === "strapperCheck");
// strapper error is off
if (strapperCheckSetting[0]?.value === "0") {
return;
}
// strapper error is on
if (strapperCheckSetting[0]?.value === "1") {
// faulted and still has a check or 2 to go
if (stapperFaulted && strapperFaultCount > 0) {
createLog(
"warn",
"dyco",
"ocp",
`There was a strapper error, remaining pallets to check ${strapperFaultCount}.`
);
strapperFaultCount = strapperFaultCount - 1;
return {
success: true,
message: `There was a strapper error, remaining pallets to check ${strapperFaultCount}.`,
};
} else {
// no more checks needed clearing everything
createLog(
"debug",
"dyco",
"ocp",
`Strapper check is active but not faulted, remaining pallets to check ${strapperFaultCount}.`
);
// reset everything
stapperFaulted = false;
strapperFaultCount = 3; // move to db as well
return {
success: false,
message: `Strapper check is active but not faulted, remaining pallets to check ${strapperFaultCount}.`,
};
}
}
// strapper was triggered turning on the counter.
//console.log(`Strapper fault is ${strapperError.value}`);
if (tagData.value && strapperFaultCount > 0) {
// strapper faulted we want to start the trigger to force the check
if (!stapperFaulted) {
createLog(
"error",
"dyco",
"ocp",
`Strapper errored triggering, manual checks will be required for the next ${strapperFaultCount}`
);
stapperFaulted = true;
// change move fault count to db....
strapperFaultCount = 3;
return {
success: true,
message: `Strapper errored triggering, manual checks will be required for the next ${strapperFaultCount}`,
};
}
}
return {
success: false,
message: `Some how we made it here and just going to say we are good. :)`,
};
};

View File

@@ -0,0 +1,91 @@
import axios from "axios";
import { prodEndpointCreation } from "../../../../../globalUtils/createUrl.js";
import { lstAuth } from "../../../../../index.js";
import { createLog } from "../../../../logger/logger.js";
import { db } from "../../../../../../database/dbclient.js";
import { prodlabels } from "../../../../../../database/schema/prodLabels.js";
import { eq, sql } from "drizzle-orm";
export const delieryInhouse = async (data: any) => {
// update sscc so we can book in
const SSCC = data.SSCC.slice(2);
// api url
const url = await prodEndpointCreation(
"/public/v1.0/Warehousing/InhouseDelivery"
);
// create bookin
const newDelivery = {
scannerId: 99,
sscc: SSCC,
};
try {
const res = await axios.post(url, newDelivery, {
headers: {
Authorization: `Basic ${lstAuth}`,
accept: "text/plain",
},
});
if (res.data.Result !== 0) {
createLog(
"error",
"labeling",
"ocp",
`${data.printer?.name}, Error:${res.data.Message}`
);
//printerUpdate(data.printer, 7, "Error while deliverying inhouse.");
return {
success: true,
message: `${data.printer?.name} had an error while trying to deliver.`,
data: res.data,
};
} // label was just delivered
createLog(
"info",
"labeling",
"ocp",
`${parseInt(data.SSCC.slice(10, -1))}, was just delivered`
);
try {
await db
.update(prodlabels)
.set({
status: "Delivered",
upd_date: sql`NOW()`,
})
.where(
eq(prodlabels.runningNr, parseInt(data.SSCC.slice(10, -1)))
);
} catch (error) {
createLog(
"error",
"labeling",
"ocp",
`Error updating ${parseInt(data.SSCC.slice(10, -1))} in the DB.`
);
return {
success: true,
message: `Error updating ${parseInt(
data.SSCC.slice(10, -1)
)} in the DB.`,
data: error,
};
}
} catch (error) {
createLog(
"error",
"labeling",
"ocp",
`${data.printer.name}, "Error: ${error}`
);
return {
success: true,
message: `Error deliverying ${parseInt(data.SSCC.slice(10, -1))}.`,
data: error,
};
}
};

View File

@@ -0,0 +1,72 @@
import { db } from "../../../../../../database/dbclient.js";
import { settings } from "../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../../logger/logger.js";
import { query } from "../../../../sqlServer/prodSqlServer.js";
export const firstLotLabel = async (lot: any) => {
/*
currently this is only requested by slc
when a lot is finished and not set to over run we will trigger this function to insert data into the custom T_firstLotLabel table.
in nice label we look for unique id to run the process and then if the add date is older than 7 days we delete it for tracablility purpose.
we will also only do this for specific customers currently henkel is the requested.
*/
/* work on the server side
new table created in the _cus db
CREATE TABLE T_firstLotLabel
(
ID INT IDENTITY(1,1) PRIMARY KEY,
printerName varchar(max),
add_date datetime
-- Add other columns here as needed
);
the nicelabel trigger to be activated.
*/
const { data: settingData, error: settingError } = await tryCatch(
db.select().from(settings)
);
if (settingError) {
return {
success: false,
message: "There was an error getting the settings.",
settingError,
};
}
// get the plantToken
const plantToken = settingData.filter((n) => n.name === "plantToken");
createLog(
"info",
"ocp",
"ocp",
`Checking if we can create the first label lot.`
);
if (plantToken[0].value === "usslc1") {
const newLabel = `insert into T_firstLotLabel (printerName, add_date) VALUES('${lot.PrinterName}', getdate())`;
// if (lot.CustomerId === 40) {
createLog(
"info",
"ocp",
"ocp",
`Creating first lot label for ${lot.MachineDescription}`
);
await query(newLabel, "newLotLabel");
createLog(
"info",
"ocp",
"ocp",
`Was created for ${lot.MachineDescription}`
);
//}
} else {
return;
}
};

View File

@@ -0,0 +1,177 @@
import { Controller, Tag } from "st-ethernet-ip";
import { labelingProcess } from "../../labeling/labelProcess.js";
import { createLog } from "../../../../logger/logger.js";
let plcAddress = "192.168.193.97"; // zechetti 2
let lastProcessedTimestamp = 0;
let PLC = new Controller() as any;
const labelerTag = new Tag("N7[0]"); // change the car to a or b depending on what zechetti.
//const t = new Tag("CONV_M01_SHTL_UNLD_IN_FROM_PREV_CONV_TRACK_CODE.PAL_ORIGIN_LINE_N") // this is for the new zechette to reach the pallet form
let pollingInterval: any = null;
let heartbeatInterval: any = null;
let reconnecting = false;
let lastTag = 0;
// Track last successful read
let lastHeartbeat: number = Date.now();
export async function zechitti1Connect() {
try {
createLog(
"info",
"zechitti1",
"ocp",
`Connecting to PLC at ${plcAddress}...`
);
await PLC.connect(plcAddress, 0);
createLog("info", "zechitti1", "ocp", "Zechetti 2 connected.");
// Start polling tags
startPolling();
// Start heartbeat
// startHeartbeat();
// Handle disconnects/errors
PLC.on("close", () => {
console.warn("PLC connection closed.");
handleReconnect();
});
PLC.on("error", (err: any) => {
createLog("error", "zechitti1", "ocp", `PLC error: ${err.message}`);
handleReconnect();
});
} catch (err: any) {
createLog(
"error",
"zechitti1",
"ocp",
`Initial connection failed: ${err.message}`
);
handleReconnect();
}
}
function startPolling() {
if (pollingInterval) clearInterval(pollingInterval);
pollingInterval = setInterval(async () => {
try {
await PLC.readTag(labelerTag);
//lastHeartbeat = Date.now();
const tagTime: any = new Date(labelerTag.timestamp);
// so we make sure we are not missing a pallet remove it from the lastTag so we can get this next label correctly
if (
labelerTag.value == 0 &&
Date.now() - lastProcessedTimestamp >= 45000
) {
lastTag = labelerTag.value;
}
// if the tag is not zero and its been longer than 30 seconds and the last tag is not equal to the current tag we can print
if (
labelerTag.value !== 0 &&
lastTag !== labelerTag.value &&
tagTime !== lastProcessedTimestamp &&
Date.now() - lastProcessedTimestamp >= 30000
) {
lastProcessedTimestamp = tagTime;
lastTag = labelerTag.value;
console.log(
`Time since last check: ${
Date.now() - tagTime
}, greater than 30000, ${
Date.now() - lastProcessedTimestamp >= 30000
}, the line to be printed is ${labelerTag.value}`
);
//console.log(labelerTag);
const zechette = {
line: labelerTag.value.toString(),
printer: 22, // this is the id of the zechetti 2 to print we should move this to the db
printerName: "Zechetti1",
};
labelingProcess({ zechette: zechette });
}
} catch (err: any) {
createLog(
"error",
"zechitti1",
"ocp",
`Polling error: ${err.message}`
);
handleReconnect();
}
}, 1000);
}
// function startHeartbeat() {
// if (heartbeatInterval) clearInterval(heartbeatInterval);
// heartbeatInterval = setInterval(() => {
// const diff = Date.now() - lastHeartbeat;
// if (diff > 60000) {
// // 1 minute
// console.warn(`⚠️ Heartbeat timeout: no data for ${diff / 1000}s`);
// handleReconnect();
// }
// }, 10000); // check every 10s
// }
async function handleReconnect() {
if (reconnecting) return;
reconnecting = true;
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
}
let delay = 2000; // start at 2s
let attempts = 0;
const maxAttempts = 10; // or limit by time, e.g. 2 min total
while (!PLC.connected && attempts < maxAttempts) {
attempts++;
createLog(
"info",
"zechitti1",
"ocp",
`Reconnect attempt ${attempts}/${maxAttempts} in ${
delay / 1000
}s...`
);
await new Promise((res) => setTimeout(res, delay));
try {
PLC = new Controller(); // fresh instance
await PLC.connect(plcAddress, 0);
createLog("info", "zechitti1", "ocp", "Reconnected to PLC!");
reconnecting = false;
startPolling();
return;
} catch (err: any) {
createLog(
"error",
"zechitti1",
"ocp",
`Reconnect attempt failed: ${err.message}`
);
delay = Math.min(delay * 2, 30000); // exponential backoff up to 30s
}
}
if (!PLC.connected) {
createLog(
"error",
"zechitti1",
"ocp",
"Max reconnect attempts reached. Stopping retries."
);
reconnecting = false;
// optional: exit process or alert someone here
// process.exit(1);
}
}