feat(lstv2 move): moved lstv2 into this app to keep them combined and easier to maintain
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { and, desc, eq, gte, inArray, lte, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../database/dbclient.js";
|
||||
import { logs } from "../../../../database/schema/logs.js";
|
||||
import { createLog } from "../../logger/logger.js";
|
||||
import { tryCatch } from "../../../globalUtils/tryCatch.js";
|
||||
import { notifications } from "../../../../database/schema/notifications.js";
|
||||
|
||||
export const getNotifications = async () => {
|
||||
const { data, error } = await tryCatch(db.select().from(notifications));
|
||||
|
||||
if (error) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`Error getting notifications: ${error}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
message: "Error getting notifications.",
|
||||
data: error,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
sucess: true,
|
||||
message: "Current notifications.",
|
||||
data: data,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
import { bow2incoming } from "../../../sqlServer/querys/notifications/bow2henkel.js";
|
||||
|
||||
const notification = async (notifyData: any) => {
|
||||
/**
|
||||
* Pass the entire notification over
|
||||
*/
|
||||
createLog("debug", "reprinting", "notify", `monitoring ${notifyData.name}`);
|
||||
|
||||
// validate if there are any emails.
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.name}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//let labels: Labels[];
|
||||
|
||||
const { data: l, error: labelError } = await tryCatch(
|
||||
query(
|
||||
bow2incoming.replace(
|
||||
"[time]",
|
||||
notifyData.notifiySettings.processTime
|
||||
),
|
||||
"Label Reprints"
|
||||
)
|
||||
);
|
||||
const labels: any = l?.data as any;
|
||||
if (labelError) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`Failed to get the labels: ${labelError}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (labels.length > 0) {
|
||||
//send the email :D
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject: "Alert! New incoming goods has been received",
|
||||
template: "bow2IncomingGoods",
|
||||
context: {
|
||||
items: labels,
|
||||
time: notifyData.notifiySettings.processTime,
|
||||
},
|
||||
};
|
||||
|
||||
const sentEmail = await sendEmail(emailSetup);
|
||||
|
||||
if (!sentEmail.success) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// // update the last time we ran and the prod id
|
||||
// const notifUpdate = {
|
||||
// prodID: labels[0].IdEtikettenHistorie,
|
||||
// lastRan: nowDate(),
|
||||
// };
|
||||
|
||||
// update the last time ran
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...notifyData.notifiySettings,
|
||||
prodID: labels[0].IdEtikettenHistorie,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export default notification;
|
||||
@@ -0,0 +1,95 @@
|
||||
// SELECT count(*) FROM V_EtikettenGedruckt where AnzahlGedruckterKopien > 2 and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108) and Upd_Date > DATEADD(SECOND, -30,getdate()) and VpkVorschriftBez not like '%$%'
|
||||
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { downTimeCheck } from "../../../sqlServer/querys/notifications/downtimecheck.js";
|
||||
|
||||
export interface DownTime {
|
||||
downTimeId?: number;
|
||||
machineAlias?: string;
|
||||
}
|
||||
export default async function reprintLabelMonitor(notifyData: any) {
|
||||
// we will over ride this with users that want to sub to this
|
||||
// a new table will be called subalerts and link to the do a kinda linkn where the user wants it then it dose subId: 1, userID: x, notificationId: y. then in here we look up the userid to get the email :D
|
||||
// this could then leave the emails in the notificaion blank and let users sub to it.
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.name}`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log(data.secondarySetting[0].duration);
|
||||
let dQuery = downTimeCheck
|
||||
.replace("[dtDuration]", notifyData.notifiySettings?.duration)
|
||||
.replace("[daysInPast]", notifyData.notifiySettings?.daysInPast);
|
||||
//console.log(query);
|
||||
let downTime: any = []; //DownTime[];
|
||||
try {
|
||||
const res: any = await query(dQuery, "downTimeCheck");
|
||||
//console.log(labels.length);
|
||||
downTime = res.data;
|
||||
if (
|
||||
downTime.length > 0
|
||||
// && downTime[0]?.downTimeId > notifyData.notifiySettings.prodID
|
||||
) {
|
||||
//send the email :D
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject: `Alert! Downtime recorded greater than ${
|
||||
notifyData.notifiySettings?.duration
|
||||
}min ${
|
||||
downTime.length === 1
|
||||
? `on ${downTime[0].machineAlias}`
|
||||
: ""
|
||||
}`,
|
||||
template: "downTimeCheck",
|
||||
context: {
|
||||
items: downTime,
|
||||
secondarySetting: notifyData.notifiySettings,
|
||||
},
|
||||
};
|
||||
|
||||
const sentEmail = await sendEmail(emailSetup);
|
||||
|
||||
if (!sentEmail.success) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...notifyData.notifiySettings,
|
||||
prodID: downTime[0].downTimeId,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`Error from running the downtimeCheck query: ${err}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
|
||||
const notification = async (notifyData: any) => {
|
||||
/**
|
||||
* Pass the entire notification over
|
||||
*/
|
||||
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
// notifiySettings: {
|
||||
// ...updateSettings,
|
||||
// prodID: labels[0].IdEtikettenHistorie,
|
||||
// },
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
};
|
||||
|
||||
export default notification;
|
||||
@@ -0,0 +1,112 @@
|
||||
import { isBefore } from "date-fns";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { currentInv } from "../../../sqlServer/querys/notifications/fifoIndex/currentInv.js";
|
||||
import { shippedPallets } from "../../../sqlServer/querys/notifications/fifoIndex/shippedPallets.js";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { fifoIndex } from "../../../../../database/schema/fifoIndex.js";
|
||||
|
||||
export default async function fifoIndexCheck() {
|
||||
/**
|
||||
* getting the shipped pallets
|
||||
*/
|
||||
const { data: shipped, error: eShipped } = await tryCatch(
|
||||
query(shippedPallets, "notify shipped pallets")
|
||||
);
|
||||
|
||||
const { data: currentStuff, error: eCurrentInv } = await tryCatch(
|
||||
query(currentInv, "notify shipped pallets")
|
||||
);
|
||||
|
||||
// console.log(shipped?.data[2]);
|
||||
// console.log(currentStuff?.data[2]);
|
||||
|
||||
/**
|
||||
* We want to check if the each shippened pallet is out of fifo
|
||||
*/
|
||||
const check = shipped?.data.map((n: any) => {
|
||||
/**
|
||||
* Returns all data so we know if we are in or out.
|
||||
*/
|
||||
//check if there are pallets older than the current one we are mapped on.
|
||||
const fifoCheck = currentStuff?.data.filter(
|
||||
(i: any) => isBefore(i.prodDate, n.prodDate) && i.av === n.av
|
||||
);
|
||||
//console.log(fifoCheck.length);
|
||||
if (fifoCheck.length > 0) {
|
||||
// console.log("Out of fifo", {
|
||||
// av: n.av,
|
||||
// rn: n.runningNr,
|
||||
// fRn: fifoCheck[0].runningNr,
|
||||
// dates: [fifoCheck[0].prodDate, n.prodDate],
|
||||
// });
|
||||
}
|
||||
|
||||
return {
|
||||
...n,
|
||||
// currentInv: fifoCheck[0],
|
||||
fifoFollowed: fifoCheck.length === 0 ? true : false,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* lets see just the av that is our or in
|
||||
*/
|
||||
|
||||
const avCheck = (check: any) => {
|
||||
/**
|
||||
* This will only return the data based on out of fifo.
|
||||
*/
|
||||
// check how many times each av showed up
|
||||
const avCounts = check.reduce((a: any, c: any) => {
|
||||
if (c.fifoFollowed === false) {
|
||||
const avValue = c.av;
|
||||
a[avValue] = (a[avValue] || 0) + 1;
|
||||
}
|
||||
return a;
|
||||
}, {});
|
||||
|
||||
// transform them back to an avCount Object
|
||||
const result = Object.keys(avCounts).map((av) => ({
|
||||
av: parseInt(av, 10),
|
||||
count: avCounts[av],
|
||||
}));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const outOfFifo: any = avCheck(check);
|
||||
const totalOut = outOfFifo.reduce((sum: any, c: any) => {
|
||||
return sum + c.count;
|
||||
}, 0);
|
||||
|
||||
/**
|
||||
* add the data to the db
|
||||
*/
|
||||
for (let i = 0; i < check.length; i++) {
|
||||
const { data: dbInsert, error: dbE } = await tryCatch(
|
||||
db
|
||||
.insert(fifoIndex)
|
||||
.values({
|
||||
lot: check[i].lot,
|
||||
av: check[i].av,
|
||||
runningNr: check[i].runningNr,
|
||||
prodDate: check[i].prodDate,
|
||||
fifoFollowed: check[i].fifoFollowed,
|
||||
add_Date: check[i].add_Date,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Fifo index data",
|
||||
data: {
|
||||
palletsOut: check,
|
||||
totalShipped: shipped?.data.length,
|
||||
inFifo: shipped?.data.length - totalOut,
|
||||
outOfFifoData: outOfFifo,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
import { palletsRemovedAswaste } from "../../../sqlServer/querys/notifications/palletsRemovedAsWaste.js";
|
||||
import { format } from "date-fns-tz";
|
||||
|
||||
export interface Labels {
|
||||
IdEtikettenHistorie?: number;
|
||||
}
|
||||
const notification = async (notifyData: any) => {
|
||||
/**
|
||||
* Pass the entire notification over
|
||||
*/
|
||||
createLog(
|
||||
"info",
|
||||
"wastebooking",
|
||||
"notify",
|
||||
`monitoring ${notifyData.name}`
|
||||
);
|
||||
|
||||
// validate if there are any emails.
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.name}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: l, error: palletError } = await tryCatch(
|
||||
query(palletsRemovedAswaste, "Removed as waste check")
|
||||
);
|
||||
const pallets: any = l?.data as any;
|
||||
if (palletError) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`Failed to get the labels: ${palletError}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(pallets);
|
||||
|
||||
if (pallets.length > 0) {
|
||||
//send the email :D
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject: `Alert! ${
|
||||
pallets.length > 1 ? "Some pallets were" : "A pallet was "
|
||||
} brought back in`,
|
||||
template: "palletBookedAsWaste",
|
||||
context: {
|
||||
items: pallets.map((i: any) => {
|
||||
return {
|
||||
...i,
|
||||
lastMovingDate: format(i.lastMovingDate, "M/d/yyyy"),
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const sentEmail = await sendEmail(emailSetup);
|
||||
|
||||
if (!sentEmail.success) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// // update the last time we ran and the prod id
|
||||
// const notifUpdate = {
|
||||
// prodID: labels[0].IdEtikettenHistorie,
|
||||
// lastRan: nowDate(),
|
||||
// };
|
||||
|
||||
// update the last time ran
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...notifyData.notifiySettings,
|
||||
prodID: pallets[0].runningnumber,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export default notification;
|
||||
@@ -0,0 +1,137 @@
|
||||
// SELECT count(*) FROM V_EtikettenGedruckt where AnzahlGedruckterKopien > 2 and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108) and Upd_Date > DATEADD(SECOND, -30,getdate()) and VpkVorschriftBez not like '%$%'
|
||||
import { isWeekend } from "date-fns";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
|
||||
export interface PPOO {
|
||||
IdPosition?: number;
|
||||
}
|
||||
export default async function reprintLabelMonitor(notifyData: any) {
|
||||
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.name}`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// parse the secondarySetting back to json to use it.
|
||||
// notifyData = { ...notifyData, secondarySetting: JSON.parse(notifyData.secondarySetting) };
|
||||
|
||||
// as this one goes to managers we want to not send on the weekends
|
||||
|
||||
const weekend = isWeekend(new Date(Date.now()));
|
||||
|
||||
if (weekend && notifyData.notifiySettings.weekend) {
|
||||
createLog(
|
||||
"info",
|
||||
"notify",
|
||||
"notify",
|
||||
`${notifyData.name} will not run on the weekends`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let notifyQuery = `
|
||||
SELECT
|
||||
--[EinlagerungsDatummin] as lastMovingDate,
|
||||
round(VerfuegbareMengeVPKSum,2) as pallets
|
||||
,VerfuegbareMengeSum as total
|
||||
,round([GesperrteMengeVpkSum],2) as held
|
||||
,round([GesperrteMengeSum],2) as heldQty
|
||||
,[IdArtikelVarianten] as av
|
||||
,[IdProdBereich] as pfcID
|
||||
,[ArtikelVariantenBez] as articleDescription
|
||||
,[ArtikelVariantenAlias] as articleDescriptionAlias
|
||||
,[LagerAbteilungKurzBez] as location
|
||||
,[Lfdnr] as runningNumber
|
||||
,[Produktionslos] as lot
|
||||
,[ProduktionsDatumMin] as productionDate
|
||||
,IdPosition
|
||||
FROM [AlplaPROD_test1].[dbo].[V_LagerPositionenBarcodes] (nolock)
|
||||
|
||||
where idlagerabteilung in ([locations]) and [ProduktionsDatumMin] < DATEadd( Hour, -[timeCheck], getdate())
|
||||
|
||||
order by [ProduktionsDatumMin] asc
|
||||
`;
|
||||
|
||||
//update the time check
|
||||
notifyQuery = notifyQuery.replaceAll(
|
||||
"[timeCheck]",
|
||||
notifyData.checkInterval
|
||||
);
|
||||
notifyQuery = notifyQuery.replaceAll(
|
||||
"[locations]",
|
||||
notifyData.notifiySettings.locations
|
||||
);
|
||||
|
||||
let prod: PPOO[];
|
||||
try {
|
||||
const res: any = await query(notifyQuery, "Label Reprints");
|
||||
prod = res.data;
|
||||
//console.log(labels.length);
|
||||
// const now = Date.now()
|
||||
if (prod.length > 0) {
|
||||
//send the email :D
|
||||
|
||||
// update the count with the result
|
||||
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject: `Alert! Pallets in production greater than ${notifyData.checkTime} ${notifyData.timeType}`,
|
||||
template: "productionCheck",
|
||||
context: {
|
||||
items: prod,
|
||||
count: prod.length,
|
||||
checkTime: notifyData.checkInterval,
|
||||
timeCheck: notifyData.timeType,
|
||||
},
|
||||
};
|
||||
|
||||
const sentEmail = await sendEmail(emailSetup);
|
||||
|
||||
if (!sentEmail.success) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let updateSettings = notifyData.notifiySettings;
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...updateSettings,
|
||||
count: prod.length,
|
||||
prodID: prod[0].IdPosition,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
createLog(
|
||||
"error",
|
||||
"sql",
|
||||
"error",
|
||||
`Error from running the Label Reprints query: ${err}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
import { blockQuery } from "../../../sqlServer/querys/notifications/blocking.js";
|
||||
export default async function qualityBlockingMonitor(notifyData: any) {
|
||||
createLog("info", "blocking", "notify", `monitoring ${notifyData.name}`);
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"blocking",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.name}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
message: `There are no emails set for ${notifyData.name}`,
|
||||
};
|
||||
}
|
||||
|
||||
const { data: noti, error: notiError } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
|
||||
if (notiError) {
|
||||
throw Error(`${JSON.stringify(notiError)}`);
|
||||
}
|
||||
|
||||
const notiData: any = noti;
|
||||
const blockingOrders = notiData[0]?.notifiySettings.sentBlockingOrders.map(
|
||||
(l: any) => {
|
||||
return l.blockingOrder;
|
||||
}
|
||||
);
|
||||
|
||||
// console.log(blockingOrders);
|
||||
|
||||
// let blockingQuery = blockQuery.replaceAll(
|
||||
// "[sentBlockingOrders]",
|
||||
// blockingOrders
|
||||
// );
|
||||
|
||||
let blockingQuery = blockQuery.replaceAll(
|
||||
"[lastBlocking]",
|
||||
notiData[0]?.notifiySettings.prodID
|
||||
);
|
||||
|
||||
const { data: b, error: blockingError } = await tryCatch(
|
||||
query(blockingQuery, "Quality Blocking")
|
||||
);
|
||||
const blocking: any = b?.data as any;
|
||||
|
||||
if (blockingError) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Error getting blocking orders",
|
||||
data: blockingError,
|
||||
};
|
||||
}
|
||||
|
||||
if (blocking.length > 0) {
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject:
|
||||
blocking.length > 0
|
||||
? `Alert! New blocking orders.`
|
||||
: blocking[0].subject,
|
||||
template: "qualityBlocking",
|
||||
context: {
|
||||
items: blocking,
|
||||
},
|
||||
};
|
||||
|
||||
const { data: sentEmail, error: sendEmailError } = await tryCatch(
|
||||
sendEmail(emailSetup)
|
||||
);
|
||||
if (sendEmailError) {
|
||||
createLog(
|
||||
"error",
|
||||
"blocking",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
"Failed to send email, will try again on next interval",
|
||||
};
|
||||
}
|
||||
|
||||
const newBlockingOrders = blocking.map((b: any) => {
|
||||
return {
|
||||
blockingOrder: b.HumanReadableId,
|
||||
timeStamp: new Date(Date.now()),
|
||||
};
|
||||
});
|
||||
const uniqueOrders = Array.from(
|
||||
new Set([
|
||||
...notifyData.notifiySettings.sentBlockingOrders,
|
||||
...newBlockingOrders,
|
||||
])
|
||||
);
|
||||
|
||||
console.log(uniqueOrders);
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...notifyData.notifiySettings,
|
||||
prodID: blocking[0].HumanReadableId,
|
||||
sentBlockingOrders: uniqueOrders,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
if (error) {
|
||||
createLog(
|
||||
"error",
|
||||
"blocking",
|
||||
"notify",
|
||||
"Error updating the blocking orders"
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
message: "Error updating the blocking orders",
|
||||
data: error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Blocking query ran successfully",
|
||||
blocking,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
|
||||
export interface Labels {
|
||||
IdEtikettenHistorie?: number;
|
||||
}
|
||||
const notification = async (notifyData: any) => {
|
||||
/**
|
||||
* Pass the entire notification over
|
||||
*/
|
||||
createLog("debug", "reprinting", "notify", `monitoring ${notifyData.name}`);
|
||||
|
||||
// validate if there are any emails.
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.name}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// well set a backup default time here
|
||||
let timeCheck = `DATEADD(SECOND, -30, getdate()) `;
|
||||
|
||||
// set the time of getting the label
|
||||
if (notifyData.timeType === "sec") {
|
||||
timeCheck = `DATEADD(SECOND, -${notifyData.checkInterval}, getdate()) `;
|
||||
} else if (notifyData.timeType === "min") {
|
||||
timeCheck = `DATEADD(MINUTE, -${notifyData.checkInterval}, getdate()) `;
|
||||
}
|
||||
|
||||
let reprintQuery = `
|
||||
SELECT
|
||||
IdEtikettenHistorie,
|
||||
IdArtikelvarianten as av,
|
||||
ArtikelVariantenBez as alias,
|
||||
LfdNr as runningNumber,
|
||||
CONVERT(VARCHAR, CAST(Add_Date AS DATETIME), 100) Add_Date,
|
||||
Add_User,
|
||||
CONVERT(VARCHAR, CAST(Upd_Date AS DATETIME), 100) Upd_Date,
|
||||
Upd_User,
|
||||
EtikettenDruckerBezeichnung as printer,
|
||||
AnzahlGedruckterKopien as totalPrinted
|
||||
FROM Alplaprod_test1.dbo.V_EtikettenGedruckt (nolock)
|
||||
where AnzahlGedruckterKopien > 2
|
||||
and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108)
|
||||
and Upd_Date > DATEADD(SECOND, -30, getdate())
|
||||
and VpkVorschriftBez not like '%$%'
|
||||
`;
|
||||
|
||||
//update the time check
|
||||
|
||||
reprintQuery = reprintQuery.replaceAll(
|
||||
"DATEADD(SECOND, -30, getdate()) ",
|
||||
timeCheck
|
||||
);
|
||||
|
||||
//let labels: Labels[];
|
||||
|
||||
const { data: l, error: labelError } = await tryCatch(
|
||||
query(reprintQuery, "Label Reprints")
|
||||
);
|
||||
const labels: any = l?.data as any;
|
||||
if (labelError) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`Failed to get the labels: ${labelError}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (labels.length > 0) {
|
||||
//send the email :D
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject: "Alert! Label Reprinted",
|
||||
template: "reprintLabels",
|
||||
context: {
|
||||
items: labels,
|
||||
},
|
||||
};
|
||||
|
||||
const sentEmail = await sendEmail(emailSetup);
|
||||
|
||||
if (!sentEmail.success) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// // update the last time we ran and the prod id
|
||||
// const notifUpdate = {
|
||||
// prodID: labels[0].IdEtikettenHistorie,
|
||||
// lastRan: nowDate(),
|
||||
// };
|
||||
|
||||
// update the last time ran
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...notifyData.notifiySettings,
|
||||
prodID: labels[0].IdEtikettenHistorie,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export default notification;
|
||||
@@ -0,0 +1,109 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
import { format } from "date-fns-tz";
|
||||
import { shortageBookings } from "../../../sqlServer/querys/notifications/shortageBookings.js";
|
||||
|
||||
export interface Labels {
|
||||
IdEtikettenHistorie?: number;
|
||||
}
|
||||
const notification = async (notifyData: any) => {
|
||||
/**
|
||||
* Pass the entire notification over
|
||||
*/
|
||||
createLog(
|
||||
"info",
|
||||
"wastebooking",
|
||||
"notify",
|
||||
`monitoring ${notifyData.name}`
|
||||
);
|
||||
|
||||
// validate if there are any emails.
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.name}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log(notifyData);
|
||||
// update the settings so we have everything we need
|
||||
let updatedQuery = shortageBookings
|
||||
.replace("[time]", notifyData?.notifiySettings.time)
|
||||
.replace("[type]", notifyData?.notifiySettings.type)
|
||||
.replace("[avType]", notifyData?.notifiySettings.avType);
|
||||
|
||||
const { data: l, error: shortageError } = await tryCatch(
|
||||
query(updatedQuery, "Removed as waste check")
|
||||
);
|
||||
const pallets: any = l?.data as any;
|
||||
|
||||
//console.log(updatedQuery);
|
||||
//console.log(pallets);
|
||||
if (shortageError) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
`Failed to get the labels: ${shortageError}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pallets.length > 0) {
|
||||
//send the email :D
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject: `Alert! New shortage booking as been completed in the last ${notifyData?.notifiySettings.time} min`,
|
||||
template: "shortageBookings",
|
||||
context: {
|
||||
items: pallets.map((i: any) => {
|
||||
return {
|
||||
...i,
|
||||
bookingDate: format(i.bookingDate, "M/d/yyyy"),
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const sentEmail = await sendEmail(emailSetup);
|
||||
|
||||
if (!sentEmail.success) {
|
||||
createLog(
|
||||
"error",
|
||||
"reprinting",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// // update the last time we ran and the prod id
|
||||
// const notifUpdate = {
|
||||
// prodID: labels[0].IdEtikettenHistorie,
|
||||
// lastRan: nowDate(),
|
||||
// };
|
||||
|
||||
// update the last time ran
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export default notification;
|
||||
@@ -0,0 +1,130 @@
|
||||
// SELECT count(*) FROM V_EtikettenGedruckt where AnzahlGedruckterKopien > 2 and CONVERT(varchar(5), Add_Date,108) not like CONVERT(varchar(5), Upd_Date,108) and Upd_Date > DATEADD(SECOND, -30,getdate()) and VpkVorschriftBez not like '%$%'
|
||||
|
||||
import { isWeekend } from "date-fns";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { sendEmail } from "../sendMail.js";
|
||||
import { query } from "../../../sqlServer/prodSqlServer.js";
|
||||
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
|
||||
import { notifications } from "../../../../../database/schema/notifications.js";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../database/dbclient.js";
|
||||
|
||||
export interface PPOO {
|
||||
IdPosition?: number;
|
||||
}
|
||||
export default async function reprintLabelMonitor(notifyData: any) {
|
||||
createLog("info", "notify", "notify", `monitoring ${notifyData.name}`);
|
||||
if (notifyData.emails === "") {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`There are no emails set for ${notifyData.notificationName}`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// as this one goes to managers we want to not send on the weekends
|
||||
|
||||
const weekend = isWeekend(new Date(Date.now()));
|
||||
|
||||
if (weekend && notifyData.notifiySettings.weekend) {
|
||||
createLog(
|
||||
"info",
|
||||
"notify",
|
||||
"notify",
|
||||
`${notifyData.name} will not run on the weekends`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let noteQuery = `
|
||||
SELECT
|
||||
--[EinlagerungsDatummin] as lastMovingDate,
|
||||
round(VerfuegbareMengeVPKSum,2) as pallets
|
||||
,VerfuegbareMengeSum as total
|
||||
,round([GesperrteMengeVpkSum],2) as held
|
||||
,round([GesperrteMengeSum],2) as heldQty
|
||||
,[IdArtikelVarianten] as av
|
||||
,[IdProdBereich] as pfcID
|
||||
,[ArtikelVariantenBez] as articleDescription
|
||||
,[ArtikelVariantenAlias] as articleDescriptionAlias
|
||||
,[LagerAbteilungKurzBez] as location
|
||||
,[Lfdnr] as runningNumber
|
||||
,[Produktionslos] as lot
|
||||
,[ProduktionsDatumMin] as productionDate
|
||||
,IdPosition
|
||||
FROM [AlplaPROD_test1].[dbo].[V_LagerPositionenBarcodes] (nolock)
|
||||
|
||||
where idlagerabteilung in ([locations]) and [ProduktionsDatumMin] < DATEadd( Hour, -[timeCheck], getdate())
|
||||
|
||||
order by [ProduktionsDatumMin] asc
|
||||
`;
|
||||
|
||||
//update the time check
|
||||
noteQuery = noteQuery
|
||||
.replaceAll("[timeCheck]", notifyData.checkInterval)
|
||||
.replaceAll("[locations]", notifyData.notifiySettings.locations);
|
||||
|
||||
let stage: PPOO[];
|
||||
try {
|
||||
const res = await query(noteQuery, "Staging checks");
|
||||
stage = res?.data as any;
|
||||
//console.log(labels.length);
|
||||
// const now = Date.now()
|
||||
if (stage.length > 0) {
|
||||
//send the email :D
|
||||
|
||||
// update the count with the result
|
||||
|
||||
const emailSetup = {
|
||||
email: notifyData.emails,
|
||||
subject: `Alert! Pallets in staging greater than ${notifyData.checkInterval} ${notifyData.timeType}`,
|
||||
template: "stagingCheck",
|
||||
context: {
|
||||
items: stage,
|
||||
count: stage.length,
|
||||
checkTime: notifyData.checkInterval,
|
||||
timeCheck: notifyData.timeType,
|
||||
},
|
||||
};
|
||||
|
||||
const sentEmail = await sendEmail(emailSetup);
|
||||
|
||||
if (!sentEmail.success) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
"Failed to send email, will try again on next interval"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// update the last time we ran and the prod id
|
||||
let updateSettings = notifyData.notifiySettings;
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...updateSettings,
|
||||
count: stage.length,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, notifyData.name))
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`Error from running the Label Reprints query: ${err}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export const dateCorrection = async (newDate: Date) => {
|
||||
/**
|
||||
* corrects the date format to be what ti is expecting
|
||||
*/
|
||||
|
||||
const newDateFormat = new Date(newDate)
|
||||
.toLocaleString("en-US", {
|
||||
timeZone: "UTC",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: "h23", // Ensures 24-hour format
|
||||
})
|
||||
.replace(",", "");
|
||||
|
||||
return newDateFormat;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
const requestUser = process.env.REQUESTUSER || "";
|
||||
export const headerUpdate = async (data: any, plantToken: any) => {
|
||||
// update the header
|
||||
let webHeader = `
|
||||
<request-id>[requestID]</request-id>
|
||||
<data>
|
||||
<WebImport>
|
||||
<WebImportHeader>
|
||||
<FileName>[requestID].XML</FileName>
|
||||
<Type>SOTransportLoader</Type>
|
||||
<UserName>[requestUser]</UserName>
|
||||
</WebImportHeader>
|
||||
`;
|
||||
|
||||
webHeader = webHeader
|
||||
.replaceAll(
|
||||
"[requestID]",
|
||||
`${data[0].releaseNumber}-${plantToken[0].value}`
|
||||
)
|
||||
.replaceAll("[requestUser]", requestUser);
|
||||
|
||||
return webHeader;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { freightClass } from "../../../../../globalUtils/freightClass.js";
|
||||
|
||||
export const loadItems = async (data: any) => {
|
||||
let itemGroups = "";
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let newItem = `
|
||||
<ItemGroup id="" isShipUnit="false" isHandlingUnit="false" sequence="${
|
||||
i + 1
|
||||
}">
|
||||
<ContainedBy id=""/>
|
||||
<LineItem lineNumber="${i + 1}"/>
|
||||
<Dimensions>
|
||||
<Dimension type="Length" uom="IN">${(
|
||||
data[i].pkgLengh / 25.4
|
||||
).toFixed(2)}</Dimension>
|
||||
<Dimension type="Width" uom="IN">${(
|
||||
data[i].pkgWidth / 25.4
|
||||
).toFixed(2)}</Dimension>
|
||||
<Dimension type="Height" uom="IN">${Math.round(
|
||||
data[i].pkgHeight / 25.4
|
||||
).toFixed(2)}</Dimension>
|
||||
</Dimensions>
|
||||
<Description>${`av ${data[i].article} ${data[i].articleAlias}`}</Description>
|
||||
<FreightClasses>
|
||||
<FreightClass type="">${freightClass(
|
||||
data[i].pkgWeight,
|
||||
data[i].pkgLengh,
|
||||
data[i].pkgWidth,
|
||||
data[i].pkgHeight
|
||||
)}</FreightClass>
|
||||
</FreightClasses>
|
||||
<Commodity/>
|
||||
<NmfcCode/>
|
||||
<HazardousMaterial>false</HazardousMaterial>
|
||||
<HazMatDetail/>
|
||||
<Weights>
|
||||
<Weight type="actual" uom="KG">${
|
||||
data[i].pkgWeight * data[i].Pallets
|
||||
}</Weight>
|
||||
</Weights>
|
||||
<Quantities>
|
||||
<Quantity type="actual" uom="pallet">${
|
||||
data[i].Pallets
|
||||
}</Quantity>
|
||||
</Quantities>
|
||||
</ItemGroup>
|
||||
`;
|
||||
|
||||
itemGroups += newItem;
|
||||
}
|
||||
|
||||
return itemGroups;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import axios from "axios";
|
||||
import querystring from "querystring";
|
||||
import { createLog } from "../../../../logger/logger.js";
|
||||
//tiCreds
|
||||
const userid = process.env.USERID || "";
|
||||
const password = process.env.PASSWORD || "";
|
||||
|
||||
export const postToTi = async (data: string) => {
|
||||
const formBody = querystring.stringify({
|
||||
userid,
|
||||
password,
|
||||
request: data,
|
||||
});
|
||||
axios
|
||||
.post(
|
||||
"https://t-insightws.mercurygate.net/MercuryGate/common/remoteService.jsp",
|
||||
formBody,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
//console.log(response.data)
|
||||
createLog("info", "ti", "notify", "Data was sent over to TI");
|
||||
return {
|
||||
success: true,
|
||||
message: "Data was sent over to TI",
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
createLog("error", "ti", "notify", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Error sending data to TI",
|
||||
data: error,
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { createLog } from "../../../../logger/logger.js";
|
||||
|
||||
export const scacCheck = async (data: any) => {
|
||||
createLog(
|
||||
"info",
|
||||
"ti",
|
||||
"notify",
|
||||
`Checking if ${data[0].addressAlias} has remark: ${data[0].remark}`
|
||||
);
|
||||
|
||||
const priceSheet = `
|
||||
<PriceSheets>
|
||||
<PriceSheet type="Carrier" isSelected="${
|
||||
data[0].remark.includes("SCAC") && // make sure we have the word scac
|
||||
data[0].remark.split(",")[0] && // and its valid as well
|
||||
!data[0].remark.includes("[") // if they funny and put [carrier] just ignore it.
|
||||
? "true"
|
||||
: "false"
|
||||
}">
|
||||
<ContractId/>
|
||||
${
|
||||
data[0].remark.split(",")[0]
|
||||
? `<SCAC>${data[0].remark
|
||||
.split(",")[0]
|
||||
.split(":")[1]
|
||||
?.toUpperCase()
|
||||
.slice(0, 4)}</SCAC>`
|
||||
: `<SCAC/>`
|
||||
}
|
||||
<Mode/>
|
||||
</PriceSheet>
|
||||
</PriceSheets>
|
||||
`;
|
||||
|
||||
return priceSheet;
|
||||
};
|
||||
@@ -0,0 +1,327 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../../../../../../database/dbclient.js";
|
||||
import { serverData } from "../../../../../../database/schema/serverData.js";
|
||||
import { settings } from "../../../../../../database/schema/settings.js";
|
||||
import { tryCatch } from "../../../../../globalUtils/tryCatch.js";
|
||||
import { notifications } from "../../../../../../database/schema/notifications.js";
|
||||
import { getHeaders } from "../../../../sqlServer/querys/notifications/ti/getHeaders.js";
|
||||
import { query } from "../../../../sqlServer/prodSqlServer.js";
|
||||
import { createLog } from "../../../../logger/logger.js";
|
||||
import { getOrderToSend } from "../../../../sqlServer/querys/notifications/ti/getOrderToSend.js";
|
||||
import { xmlPayloadTI } from "./tiXmlPayload.js";
|
||||
import { headerUpdate } from "./headerUpdate.js";
|
||||
import { loadItems } from "./loadItems.js";
|
||||
import { dateCorrection } from "./dateCorrection.js";
|
||||
import { scacCheck } from "./scacCodeCheck.js";
|
||||
import { postToTi } from "./postToTI.js";
|
||||
|
||||
export const tiImport = async () => {
|
||||
// get the plant token
|
||||
let payload = xmlPayloadTI;
|
||||
const { data: plantData, error: plantError } = await tryCatch(
|
||||
db.select().from(settings)
|
||||
);
|
||||
|
||||
if (plantError)
|
||||
return {
|
||||
success: false,
|
||||
message: "Error Getting Plant Data.",
|
||||
data: plantError,
|
||||
};
|
||||
const plantToken = plantData?.filter((n) => n.name === "plantToken");
|
||||
|
||||
const { data: plantInfo, error: plantEr } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(serverData)
|
||||
.where(eq(serverData.plantToken, plantToken[0].value))
|
||||
);
|
||||
|
||||
if (plantEr)
|
||||
return {
|
||||
success: false,
|
||||
message: "Error Getting Plant Data.",
|
||||
data: plantEr,
|
||||
};
|
||||
|
||||
// parsing posting window
|
||||
const plantI = plantInfo!;
|
||||
|
||||
// order notifications
|
||||
const { data: notificationSet, error: notificationSettingsErr } =
|
||||
await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(eq(notifications.name, "tiIntergration"))
|
||||
);
|
||||
if (notificationSettingsErr)
|
||||
return {
|
||||
success: false,
|
||||
message: "Notification missing.",
|
||||
data: notificationSettingsErr,
|
||||
};
|
||||
|
||||
const notiSet: any = notificationSet;
|
||||
const customerAccountNum = plantI[0].customerTiAcc as string; // tiIntergration
|
||||
// get current releaes not in the already sent oders
|
||||
|
||||
const releaseString = notiSet[0].notifiySettings.releases
|
||||
|
||||
.map((num: any) => `'${num.releaseNumber}'`)
|
||||
.join(", ");
|
||||
|
||||
let orders = getHeaders
|
||||
.replaceAll("[from]", notiSet[0]?.notifiySettings.start)
|
||||
.replaceAll("[to]", notiSet[0]?.notifiySettings.end)
|
||||
.replaceAll("[exclude]", releaseString);
|
||||
|
||||
// get the headers pending
|
||||
const { data: h, error: headerError } = await tryCatch(
|
||||
query(orders, "Ti get open headers")
|
||||
);
|
||||
const header: any = h?.data as any;
|
||||
|
||||
if (headerError) {
|
||||
createLog(
|
||||
"error",
|
||||
"ti",
|
||||
"notify",
|
||||
`Error getting headers: ${headerError}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
message: "Error getting headers",
|
||||
data: headerError,
|
||||
};
|
||||
}
|
||||
|
||||
if (header.length === 0) {
|
||||
createLog(
|
||||
"info",
|
||||
"ti",
|
||||
"notify",
|
||||
"There are no pending orders to be sent over to ti."
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
message: "There are no pending orders to be sent over to ti.",
|
||||
};
|
||||
}
|
||||
|
||||
createLog(
|
||||
"info",
|
||||
"tiIntergration",
|
||||
"notify",
|
||||
`There are a total of ${header.length} to send over`
|
||||
);
|
||||
|
||||
/**
|
||||
* Update the query to get only the first header
|
||||
*/
|
||||
|
||||
// update query to have the correct plant token
|
||||
let orderToSend = getOrderToSend
|
||||
|
||||
.replaceAll("test1", plantToken[0].value)
|
||||
.replaceAll("[releaseToProcess]", `'${header[0].releaseNumber}'`)
|
||||
.replaceAll("[from]", notiSet[0].notifiySettings.start)
|
||||
.replaceAll("[to]", notiSet[0].notifiySettings.end);
|
||||
|
||||
// get the headers pending
|
||||
const { data: o, error: ordersError } = await tryCatch(
|
||||
query(orderToSend, "Ti get open headers")
|
||||
);
|
||||
|
||||
const orderData: any = o?.data as any;
|
||||
if (ordersError)
|
||||
return {
|
||||
success: false,
|
||||
message: "Error getting getting orders",
|
||||
data: ordersError,
|
||||
};
|
||||
|
||||
// update the special instructions section
|
||||
const otherSettings = plantI[0]?.otherSettings as {
|
||||
specialInstructions: string;
|
||||
active: boolean;
|
||||
}[];
|
||||
|
||||
const specialInfo = otherSettings[0].specialInstructions.replaceAll(
|
||||
"[header]",
|
||||
orderData[0].Header
|
||||
);
|
||||
|
||||
// add the full amount of pallets sending over
|
||||
let fullPalToSend = orderData.reduce(
|
||||
(acc: any, o: any) => acc + o.Pallets,
|
||||
0
|
||||
);
|
||||
|
||||
//console.log("payload", payload);
|
||||
payload = payload
|
||||
.replaceAll(
|
||||
`[WebImportHeader]`,
|
||||
await headerUpdate(orderData, plantToken)
|
||||
)
|
||||
.replaceAll(`[items]`, await loadItems(orderData))
|
||||
.replaceAll(`[customerAccountNum]`, customerAccountNum)
|
||||
.replaceAll("[fullTotalPal]", fullPalToSend)
|
||||
// add in release info
|
||||
.replaceAll(`[shipNumber]`, orderData[0].releaseNumber)
|
||||
.replaceAll(`[loadNumber]`, orderData[0].releaseNumber);
|
||||
|
||||
// add in the multi release numbers
|
||||
let multiRelease = ``;
|
||||
if (orderData.length > 0) {
|
||||
for (let i = 0; i < orderData.length; i++) {
|
||||
const newRelease = `
|
||||
<ReferenceNumber type="Release Number" isPrimary="false">${orderData[i].releaseNumber}</ReferenceNumber>`;
|
||||
multiRelease += newRelease;
|
||||
}
|
||||
|
||||
payload = payload.replaceAll("[multieReleaseNumber]", multiRelease);
|
||||
} else {
|
||||
payload = payload.replaceAll("[multieReleaseNumber]", "");
|
||||
}
|
||||
|
||||
// add the correct date stuff
|
||||
payload = payload
|
||||
.replaceAll(
|
||||
"[loadingDate]",
|
||||
await dateCorrection(orderData[0].LoadingDate)
|
||||
)
|
||||
|
||||
.replaceAll(
|
||||
"[deliveryDate]",
|
||||
await dateCorrection(orderData[0].DeliveryDate)
|
||||
);
|
||||
|
||||
// shipping ours corrections
|
||||
const formattedDate = orderData[0].LoadingDate.toLocaleDateString("en-US", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
const shippingHours = JSON.parse(plantI[0]?.shippingHours!);
|
||||
|
||||
payload = payload
|
||||
.replaceAll(
|
||||
"[shippingHoursEarly]",
|
||||
`${formattedDate} ${shippingHours[0].early}`
|
||||
)
|
||||
.replaceAll(
|
||||
"[shippingHoursLate]",
|
||||
`${formattedDate} ${shippingHours[0].late}`
|
||||
);
|
||||
|
||||
// special instructions
|
||||
if (otherSettings[0].specialInstructions.length != 0) {
|
||||
payload = payload.replaceAll("[specialInstructions]", specialInfo);
|
||||
} else {
|
||||
payload = payload.replaceAll("[specialInstructions]", "");
|
||||
}
|
||||
|
||||
// shipper info
|
||||
payload = payload
|
||||
.replaceAll("[plantName]", `Alpla ${plantI[0]?.sName!}`)
|
||||
.replaceAll("[plantStreetAddress]", plantI[0]?.streetAddress!)
|
||||
.replaceAll("[plantCity]", plantI[0]?.cityState!.split(",")[0])
|
||||
.replaceAll("[plantState]", plantI[0]?.cityState!.split(",")[1])
|
||||
.replaceAll("[plantZipCode]", plantI[0]?.zipcode!)
|
||||
.replaceAll("[contactNum]", plantI[0]?.contactPhone!)
|
||||
.replaceAll("[contactEmail]", plantI[0]?.contactEmail!)
|
||||
|
||||
// customer info
|
||||
.replaceAll("[customerName]", orderData[0].addressAlias)
|
||||
.replaceAll("[customerStreetAddress]", orderData[0].streetAddress)
|
||||
.replaceAll("[customerCity]", orderData[0].city.split(",")[0])
|
||||
.replaceAll("[customerState]", orderData[0].city.split(",")[1])
|
||||
.replaceAll("[customerZip]", orderData[0].zipCode)
|
||||
.replaceAll("[customerPO]", orderData[0].Header)
|
||||
// .replaceAll(
|
||||
// "[glCoding]",
|
||||
// `52410-${
|
||||
// orderData[0].artileType.toLowerCase() === "preform" ||
|
||||
// orderData[0].artileType.toLowerCase() === "metalCage"
|
||||
// ? 31
|
||||
// : plantI[0].greatPlainsPlantCode
|
||||
// }`
|
||||
// ) // {"52410 - " + (artileType.toLowerCase() === "preform" || artileType.toLowerCase() === "metalCage" ? 31: plantInfo[0].greatPlainsPlantCode)}
|
||||
.replaceAll("[glCoding]", `52410`)
|
||||
.replaceAll(
|
||||
"[pfc]",
|
||||
`${
|
||||
orderData[0].artileType.toLowerCase() === "preform" ||
|
||||
orderData[0].artileType.toLowerCase() === "metalCage"
|
||||
? 40
|
||||
: orderData[0].costCenter
|
||||
}`
|
||||
)
|
||||
.replaceAll(
|
||||
"[locCode]",
|
||||
`${
|
||||
orderData[0].artileType.toLowerCase() === "preform" ||
|
||||
orderData[0].artileType.toLowerCase() === "metalCage"
|
||||
? 31
|
||||
: plantI[0].greatPlainsPlantCode
|
||||
}`
|
||||
)
|
||||
.replaceAll("[priceSheet]", await scacCheck(orderData));
|
||||
//send over to be processed
|
||||
|
||||
//console.log("payload", payload);
|
||||
|
||||
const { data: tiPost, error: tiError } = await tryCatch(postToTi(payload));
|
||||
|
||||
if (tiError) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Error posting to TI",
|
||||
error: tiError,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the db so we dont try to pull the next one
|
||||
*/
|
||||
|
||||
const currentDate = new Date(Date.now());
|
||||
const uniqueOrders = Array.from(
|
||||
new Set([
|
||||
...notiSet[0].notifiySettings.releases,
|
||||
{
|
||||
releaseNumber: header[0].releaseNumber,
|
||||
timeStamp: currentDate,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
// 45 days ago
|
||||
const dateLimit = new Date(
|
||||
currentDate.getTime() - 45 * 24 * 60 * 60 * 1000
|
||||
);
|
||||
|
||||
// filter dates
|
||||
let filteredOrders = uniqueOrders.filter((item) => {
|
||||
const time = new Date(item.timeStamp).getTime();
|
||||
return time >= dateLimit.getTime();
|
||||
});
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({
|
||||
lastRan: sql`NOW()`,
|
||||
notifiySettings: {
|
||||
...notiSet[0].notifiySettings,
|
||||
releases: filteredOrders,
|
||||
},
|
||||
})
|
||||
.where(eq(notifications.name, "tiIntergration"))
|
||||
);
|
||||
|
||||
createLog("info", "ti", "notify", "done with this order");
|
||||
|
||||
return { message: "done with this order" };
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
export let xmlPayloadTI = `
|
||||
<service-request>
|
||||
<service-id>ImportWeb</service-id>
|
||||
[WebImportHeader]
|
||||
<WebImportFile>
|
||||
<MercuryGate>
|
||||
<Header>
|
||||
<SenderID/>
|
||||
<ReceiverID/>
|
||||
<DocTypeID>MasterBillOfLading</DocTypeID>
|
||||
<DocCount>1</DocCount>
|
||||
</Header>
|
||||
<Load action="UpdateOrAdd">
|
||||
<Enterprise name="" customerAcctNum="[customerAccountNum]"/>
|
||||
<AssignedTo/>
|
||||
<ReferenceNumbers>
|
||||
<ReferenceNumber type="Load Number" isPrimary="true">[loadNumber]</ReferenceNumber>
|
||||
</ReferenceNumbers>
|
||||
<Payment>
|
||||
<Method>Prepaid</Method>
|
||||
<BillTo thirdParty="False">
|
||||
<Address Type="BillTo" isResidential="False">
|
||||
<Alias/>
|
||||
<Name>ALPLA</Name>
|
||||
<AddrLine1>CO TRANSPORTATION INSIGHT</AddrLine1>
|
||||
<AddrLine2>PO BOX 23000</AddrLine2>
|
||||
<City>HICKORY</City>
|
||||
<StateProvince>NC</StateProvince>
|
||||
<PostalCode>28603</PostalCode>
|
||||
<CountryCode>USA</CountryCode>
|
||||
<Contacts/>
|
||||
</Address>
|
||||
</BillTo>
|
||||
</Payment>
|
||||
[priceSheet]
|
||||
<!-- Comments here -->
|
||||
<Comments>
|
||||
<Comment type="SpecialInstructions">[specialInstructions]</Comment>
|
||||
</Comments>
|
||||
|
||||
<Plan>
|
||||
<Events count="2">
|
||||
<Event type="Pickup" sequenceNum="1">
|
||||
<Dates>
|
||||
<Date type="earliest">[shippingHoursEarly]</Date>
|
||||
<Date type="latest">[shippingHoursLate]</Date>
|
||||
</Dates>
|
||||
<Address type="" isResidential="" isPrimary="false">
|
||||
<LocationCode/>
|
||||
<Name>[plantName]</Name>
|
||||
<AddrLine1>[plantStreetAddress]</AddrLine1>
|
||||
<AddrLine2/>
|
||||
<City>[plantCity]</City>
|
||||
<StateProvince>[plantState]</StateProvince>
|
||||
<PostalCode>[plantZipCode]</PostalCode>
|
||||
<CountryCode>USA</CountryCode>
|
||||
<Contacts>
|
||||
<Contact type="">
|
||||
<Name/>
|
||||
<ContactMethods>
|
||||
<ContactMethod sequenceNum="1" type="phone">[contactNum]</ContactMethod>
|
||||
<ContactMethod sequenceNum="1" type="email">[contactEmail]</ContactMethod>
|
||||
</ContactMethods>
|
||||
</Contact>
|
||||
</Contacts>
|
||||
</Address>
|
||||
<Shipments>
|
||||
<ReferenceNumbers>
|
||||
<ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber>
|
||||
</ReferenceNumbers>
|
||||
</Shipments>
|
||||
</Event>
|
||||
<Event type="Drop" sequenceNum="2">
|
||||
<Dates>
|
||||
<Date type="earliest">[loadingDate]</Date>
|
||||
<Date type="latest">[deliveryDate]</Date>
|
||||
</Dates>
|
||||
<Address type="" isResidential="" isPrimary="false">
|
||||
<LocationCode/>
|
||||
<Name>[customerName]</Name>
|
||||
<AddrLine1>[customerStreetAddress]</AddrLine1>
|
||||
<AddrLine2/>
|
||||
<City>[customerCity]</City>
|
||||
<StateProvince>[customerState]</StateProvince>
|
||||
<PostalCode>[customerZip]</PostalCode>
|
||||
<CountryCode>USA</CountryCode>
|
||||
<Contacts>
|
||||
<Contact type="">
|
||||
<Name/>
|
||||
<ContactMethods>
|
||||
<ContactMethod sequenceNum="1" type="phone">800-555-1122</ContactMethod>
|
||||
</ContactMethods>
|
||||
</Contact>
|
||||
</Contacts>
|
||||
</Address>
|
||||
<Shipments>
|
||||
<ReferenceNumbers>
|
||||
<ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber>
|
||||
</ReferenceNumbers>
|
||||
</Shipments>
|
||||
</Event>
|
||||
|
||||
</Events>
|
||||
</Plan>
|
||||
<Shipments>
|
||||
<Shipment type="Regular" action="UpdateOrAdd">
|
||||
<Status>Pending</Status>
|
||||
<Enterprise name="" customerAcctNum="[customerAccountNum]"/>
|
||||
<ReferenceNumbers>
|
||||
<ReferenceNumber type="Shipment Number" isPrimary="true">[shipNumber]</ReferenceNumber>
|
||||
<ReferenceNumber type="PO Number" isPrimary="false">[customerPO]</ReferenceNumber>
|
||||
[multieReleaseNumber]
|
||||
<!-- Comments here -->
|
||||
<!-- <ReferenceNumber type="Store Number" isPrimary="false">[glCoding]</ReferenceNumber> -->
|
||||
<ReferenceNumber type="GL Account Code" isPrimary="false">[glCoding]</ReferenceNumber>
|
||||
<ReferenceNumber type="Profit Center" isPrimary="false">[pfc]</ReferenceNumber>
|
||||
<ReferenceNumber type="Location Code" isPrimary="false">[locCode]</ReferenceNumber>
|
||||
</ReferenceNumbers>
|
||||
<Services/>
|
||||
<EquipmentList/>
|
||||
<Dimensions>
|
||||
<Dimension type="RatingCount">[fullTotalPal]</Dimension>
|
||||
</Dimensions>
|
||||
<Dates>
|
||||
<Pickup>
|
||||
<Date type="earliest">[shippingHoursEarly]</Date>
|
||||
<Date type="latest">[shippingHoursLate]</Date>
|
||||
</Pickup>
|
||||
<Drop>
|
||||
<Date type="earliest">[deliveryDate]</Date>
|
||||
<Date type="latest">[deliveryDate]</Date>
|
||||
</Drop>
|
||||
</Dates>
|
||||
[priceSheet]
|
||||
<Shipper>
|
||||
<Address type="" isResidential="" isPrimary="false">
|
||||
<LocationCode/>
|
||||
<Name>[plantName]</Name>
|
||||
<AddrLine1>[plantStreetAddress]</AddrLine1>
|
||||
<AddrLine2/>
|
||||
<City>[plantCity]</City>
|
||||
<StateProvince>[plantState]</StateProvince>
|
||||
<PostalCode>[plantZipCode]</PostalCode>
|
||||
<CountryCode>USA</CountryCode>
|
||||
<Contacts>
|
||||
<Contact type="">
|
||||
<Name/>
|
||||
<ContactMethods>
|
||||
<ContactMethod sequenceNum="1" type="phone">[contactNum]</ContactMethod>
|
||||
</ContactMethods>
|
||||
</Contact>
|
||||
</Contacts>
|
||||
</Address>
|
||||
</Shipper>
|
||||
<Consignee>
|
||||
<Address type="" isResidential="" isPrimary="false">
|
||||
<LocationCode/>
|
||||
<Name>[customerName]</Name>
|
||||
<AddrLine1>[customerStreetAddress]</AddrLine1>
|
||||
<AddrLine2/>
|
||||
<City>[customerCity]</City>
|
||||
<StateProvince>[customerState]</StateProvince>
|
||||
<PostalCode>[customerZip]</PostalCode>
|
||||
<CountryCode>USA</CountryCode>
|
||||
<Contacts />
|
||||
<!-- Location contacts are optional -->
|
||||
<Contacts>
|
||||
<Contact type="">
|
||||
<Name>Alpla</Name>
|
||||
<ContactMethods>
|
||||
<!-- Valid contactMethod types are phone, fax, and email -->
|
||||
<ContactMethod sequenceNum="1" type="phone">[contactNum]</ContactMethod>
|
||||
<ContactMethod sequenceNum="1" type="email">[contactEmail]</ContactMethod>
|
||||
</ContactMethods>
|
||||
</Contact>
|
||||
</Contacts>
|
||||
|
||||
</Address>
|
||||
</Consignee>
|
||||
<ItemGroups>
|
||||
[items]
|
||||
</ItemGroups>
|
||||
<Payment>
|
||||
<Method>Prepaid</Method>
|
||||
<BillTo thirdParty="False">
|
||||
<Address Type="BillTo" isResidential="False">
|
||||
<Alias/>
|
||||
<Name>ALPLA</Name>
|
||||
<AddrLine1>CO TRANSPORTATION INSIGHT</AddrLine1>
|
||||
<AddrLine2>PO BOX 23000</AddrLine2>
|
||||
<City>HICKORY</City>
|
||||
<StateProvince>NC</StateProvince>
|
||||
<PostalCode>28603</PostalCode>
|
||||
<CountryCode>USA</CountryCode>
|
||||
<Contacts/>
|
||||
</Address>
|
||||
</BillTo>
|
||||
</Payment>
|
||||
</Shipment>
|
||||
</Shipments>
|
||||
</Load>
|
||||
</MercuryGate>
|
||||
</WebImportFile>
|
||||
</WebImport>
|
||||
</data>
|
||||
</service-request>
|
||||
`;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { delay } from "../../../../globalUtils/delay.js";
|
||||
import { createLog } from "../../../logger/logger.js";
|
||||
import { tiImport } from "./tiFullFlow/tiImport.js";
|
||||
|
||||
// add a running check so we cant flag it twice
|
||||
export let tiExportRunning = false;
|
||||
|
||||
export const runTiImport = async () => {
|
||||
let finished = false;
|
||||
let test: any;
|
||||
tiExportRunning = true;
|
||||
do {
|
||||
createLog("info", "ti", "notify", "processing new data");
|
||||
// code block to be executed
|
||||
test = await tiImport();
|
||||
createLog(
|
||||
"info",
|
||||
"ti",
|
||||
"notify",
|
||||
`Still more to process? ${test.success ? "No" : "Yes"}`
|
||||
);
|
||||
if (test.success) {
|
||||
finished = true;
|
||||
}
|
||||
|
||||
if (!test.success) {
|
||||
//errors are handled in the tiImport function
|
||||
tiExportRunning = false;
|
||||
}
|
||||
await delay(1000 * 5);
|
||||
} while (!finished);
|
||||
tiExportRunning = false;
|
||||
return { success: true, message: "Finished processing all data." };
|
||||
};
|
||||
|
||||
export default runTiImport;
|
||||
149
lstV2/server/services/notifications/controller/sendMail.ts
Normal file
149
lstV2/server/services/notifications/controller/sendMail.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { tryCatch } from "../../../globalUtils/tryCatch.js";
|
||||
import { db } from "../../../../database/dbclient.js";
|
||||
import { settings } from "../../../../database/schema/settings.js";
|
||||
import nodemailer from "nodemailer";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
|
||||
import type Mail from "nodemailer/lib/mailer/index.js";
|
||||
import type { Address } from "nodemailer/lib/mailer/index.js";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import hbs from "nodemailer-express-handlebars";
|
||||
import { promisify } from "util";
|
||||
import { createLog } from "../../logger/logger.js";
|
||||
import { installed } from "../../../index.js";
|
||||
|
||||
interface HandlebarsMailOptions extends Mail.Options {
|
||||
template: string;
|
||||
context: Record<string, unknown>; // Use a generic object for context
|
||||
}
|
||||
|
||||
interface EmailData {
|
||||
email: string;
|
||||
subject: string;
|
||||
template: string;
|
||||
context: [];
|
||||
}
|
||||
|
||||
export const sendEmail = async (data: any): Promise<any> => {
|
||||
if (!installed) {
|
||||
createLog("error", "notify", "notify", "server not installed.");
|
||||
return;
|
||||
}
|
||||
let transporter: Transporter;
|
||||
let fromEmail: string | Address;
|
||||
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 server = settingData.filter((n) => n.name === "server");
|
||||
|
||||
if (
|
||||
server[0].value === "localhost" &&
|
||||
process.env.EMAIL_USER &&
|
||||
process.env.EMAIL_PASSWORD
|
||||
) {
|
||||
transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD,
|
||||
},
|
||||
//debug: true,
|
||||
});
|
||||
|
||||
// update the from email
|
||||
fromEmail = process.env.EMAIL_USER;
|
||||
} else {
|
||||
// convert to the correct plant token.
|
||||
const plantToken = settingData.filter((s) => s.name === "plantToken");
|
||||
|
||||
let host = `${plantToken[0].value}-smtp.alpla.net`;
|
||||
|
||||
const testServers = ["test1", "test2", "test3"];
|
||||
|
||||
if (testServers.includes(plantToken[0].value)) {
|
||||
host = "USMCD1-smtp.alpla.net";
|
||||
}
|
||||
|
||||
if (plantToken[0].value === "usiow2") {
|
||||
host = "USIOW1-smtp.alpla.net";
|
||||
}
|
||||
|
||||
transporter = nodemailer.createTransport({
|
||||
host: host,
|
||||
port: 25,
|
||||
rejectUnauthorized: false,
|
||||
//secure: false,
|
||||
// auth: {
|
||||
// user: "alplaprod",
|
||||
// pass: "obelix",
|
||||
// },
|
||||
debug: true,
|
||||
} as SMTPTransport.Options);
|
||||
|
||||
// update the from email
|
||||
fromEmail = `noreply@alpla.com`;
|
||||
}
|
||||
|
||||
// creating the handlbar options
|
||||
const viewPath = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"../utils/views/"
|
||||
);
|
||||
|
||||
const handlebarOptions = {
|
||||
viewEngine: {
|
||||
extname: ".hbs",
|
||||
//layoutsDir: path.resolve(viewPath, "layouts"), // Path to layouts directory
|
||||
defaultLayout: "", // Specify the default layout
|
||||
partialsDir: viewPath,
|
||||
},
|
||||
viewPath: viewPath,
|
||||
extName: ".hbs", // File extension for Handlebars templates
|
||||
};
|
||||
|
||||
transporter.use("compile", hbs(handlebarOptions));
|
||||
|
||||
const mailOptions: HandlebarsMailOptions = {
|
||||
from: fromEmail,
|
||||
to: data.email,
|
||||
subject: data.subject,
|
||||
//text: "You will have a reset token here and only have 30min to click the link before it expires.",
|
||||
//html: emailTemplate("BlakesTest", "This is an example with css"),
|
||||
template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs')
|
||||
context: data.context,
|
||||
};
|
||||
|
||||
// now verify and send the email
|
||||
const sendMailPromise = promisify(transporter.sendMail).bind(transporter);
|
||||
|
||||
try {
|
||||
// Send email and await the result
|
||||
const info = await sendMailPromise(mailOptions);
|
||||
createLog(
|
||||
"info",
|
||||
"notification",
|
||||
"system",
|
||||
`Email was sent to: ${data.email}`
|
||||
);
|
||||
return { success: true, message: "Email sent.", data: info };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createLog(
|
||||
"error",
|
||||
"notification",
|
||||
"system",
|
||||
`Error sending Email: ${JSON.stringify(err)}`
|
||||
);
|
||||
return { success: false, message: "Error sending email.", error: err };
|
||||
}
|
||||
};
|
||||
60
lstV2/server/services/notifications/notifyService.ts
Normal file
60
lstV2/server/services/notifications/notifyService.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
|
||||
import sendemail from "./routes/sendMail.js";
|
||||
import { tryCatch } from "../../globalUtils/tryCatch.js";
|
||||
import { db } from "../../../database/dbclient.js";
|
||||
|
||||
import { notifications } from "../../../database/schema/notifications.js";
|
||||
import { createLog } from "../logger/logger.js";
|
||||
import { note, notificationCreate } from "./utils/masterNotifications.js";
|
||||
import { startNotificationMonitor } from "./utils/processNotifications.js";
|
||||
import notifyStats from "./routes/getActiveNotifications.js";
|
||||
import tiTrigger from "./routes/manualTiggerTi.js";
|
||||
import blocking from "./routes/qualityBlocking.js";
|
||||
import notify from "./routes/getNotifications.js";
|
||||
import fifoIndex from "./routes/fifoIndex.js";
|
||||
|
||||
const app = new OpenAPIHono();
|
||||
|
||||
const routes = [
|
||||
sendemail,
|
||||
notifyStats,
|
||||
tiTrigger,
|
||||
blocking,
|
||||
notify,
|
||||
fifoIndex,
|
||||
] as const;
|
||||
|
||||
const appRoutes = routes.forEach((route) => {
|
||||
app.route("/notify", route);
|
||||
});
|
||||
|
||||
app.all("/notify/*", (c) => {
|
||||
return c.json({
|
||||
success: false,
|
||||
message: "you have encounted a notication route that dose not exist.",
|
||||
});
|
||||
});
|
||||
|
||||
// check if the mastNotications is changed compared to the db and add if needed.
|
||||
const { data: notes, error: notesError } = await tryCatch(
|
||||
db.select().from(notifications)
|
||||
);
|
||||
|
||||
if (notesError) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`There was an error getting the notifications: ${JSON.stringify(
|
||||
notesError
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
notificationCreate();
|
||||
startNotificationMonitor();
|
||||
}, 5 * 1000);
|
||||
|
||||
export default app;
|
||||
41
lstV2/server/services/notifications/routes/fifoIndex.ts
Normal file
41
lstV2/server/services/notifications/routes/fifoIndex.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// an external way to creating logs
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||
import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||
import fifoIndexCheck from "../controller/notifications/fifoIndex.js";
|
||||
|
||||
const app = new OpenAPIHono({ strict: false });
|
||||
|
||||
app.openapi(
|
||||
createRoute({
|
||||
tags: ["notify"],
|
||||
summary: "Manually trigger TI intergrations.",
|
||||
method: "get",
|
||||
path: "/fifoindex",
|
||||
//middleware: authMiddleware,
|
||||
responses: responses(),
|
||||
}),
|
||||
async (c) => {
|
||||
/**
|
||||
* get the blocking notification stuff
|
||||
*/
|
||||
apiHit(c, { endpoint: "/fifoindex" });
|
||||
|
||||
/**
|
||||
* getting the shipped pallets
|
||||
*/
|
||||
const checkedData = await fifoIndexCheck();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: "Fifo index results",
|
||||
data: checkedData.data.palletsOut,
|
||||
fifoCheck: {
|
||||
totalShipped: checkedData.data.totalShipped,
|
||||
inFifo: checkedData.data.inFifo,
|
||||
outOfFifoData: checkedData.data.outOfFifoData,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
export default app;
|
||||
@@ -0,0 +1,31 @@
|
||||
// an external way to creating logs
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||
import { getAllJobs } from "../utils/processNotifications.js";
|
||||
import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||
|
||||
const app = new OpenAPIHono({ strict: false });
|
||||
|
||||
app.openapi(
|
||||
createRoute({
|
||||
tags: ["server"],
|
||||
summary: "Returns current active notifications.",
|
||||
method: "get",
|
||||
path: "/activenotifications",
|
||||
//middleware: authMiddleware,
|
||||
responses: responses(),
|
||||
}),
|
||||
async (c) => {
|
||||
apiHit(c, { endpoint: "/activenotifications" });
|
||||
const jobs = getAllJobs();
|
||||
return c.json({
|
||||
success: true,
|
||||
message:
|
||||
jobs.length === 0
|
||||
? "There are no active Notifications Currently."
|
||||
: "Current Active notifications",
|
||||
data: jobs,
|
||||
});
|
||||
}
|
||||
);
|
||||
export default app;
|
||||
@@ -0,0 +1,45 @@
|
||||
// an external way to creating logs
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
//import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||
import { getNotifications } from "../controller/getNotifications.js";
|
||||
import { tryCatch } from "../../../globalUtils/tryCatch.js";
|
||||
import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||
|
||||
const app = new OpenAPIHono({ strict: false });
|
||||
|
||||
app.openapi(
|
||||
createRoute({
|
||||
tags: ["notify"],
|
||||
summary: "Gets notifications.",
|
||||
method: "get",
|
||||
path: "/notifications",
|
||||
// description:
|
||||
// "This might be a temp soltuin during the transtion between versions",
|
||||
responses: responses(),
|
||||
}),
|
||||
async (c: any) => {
|
||||
apiHit(c, { endpoint: "/notifications" });
|
||||
const { data, error } = await tryCatch(getNotifications());
|
||||
|
||||
if (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
message: "There was an error clearing the log.",
|
||||
data: error,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
return c.json(
|
||||
{
|
||||
success: data?.success,
|
||||
message: data?.message,
|
||||
data: data?.data,
|
||||
},
|
||||
200
|
||||
);
|
||||
}
|
||||
);
|
||||
export default app;
|
||||
28
lstV2/server/services/notifications/routes/manualTiggerTi.ts
Normal file
28
lstV2/server/services/notifications/routes/manualTiggerTi.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// an external way to creating logs
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||
|
||||
import runTiImport from "../controller/notifications/tiIntergration.js";
|
||||
import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||
|
||||
const app = new OpenAPIHono({ strict: false });
|
||||
|
||||
app.openapi(
|
||||
createRoute({
|
||||
tags: ["notify"],
|
||||
summary: "Manually trigger TI intergrations.",
|
||||
method: "get",
|
||||
path: "/tiTrigger",
|
||||
//middleware: authMiddleware,
|
||||
responses: responses(),
|
||||
}),
|
||||
async (c) => {
|
||||
apiHit(c, { endpoint: "/tiTrigger" });
|
||||
const tiImport = await runTiImport();
|
||||
return c.json({
|
||||
success: tiImport?.success,
|
||||
message: tiImport?.message,
|
||||
});
|
||||
}
|
||||
);
|
||||
export default app;
|
||||
@@ -0,0 +1,51 @@
|
||||
// an external way to creating logs
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||
|
||||
import qualityBlockingMonitor from "../controller/notifications/qualityBlocking.js";
|
||||
import { tryCatch } from "../../../globalUtils/tryCatch.js";
|
||||
|
||||
import { notifications } from "../../../../database/schema/notifications.js";
|
||||
import { db } from "../../../../database/dbclient.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||
|
||||
const app = new OpenAPIHono({ strict: false });
|
||||
|
||||
app.openapi(
|
||||
createRoute({
|
||||
tags: ["notify"],
|
||||
summary: "Manually trigger TI intergrations.",
|
||||
method: "get",
|
||||
path: "/blockingTrigger",
|
||||
//middleware: authMiddleware,
|
||||
responses: responses(),
|
||||
}),
|
||||
async (c) => {
|
||||
/**
|
||||
* get the blocking notification stuff
|
||||
*/
|
||||
apiHit(c, { endpoint: "/blockingTrigger" });
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(eq(notifications.name, "qualityBlocking"))
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return c.json({
|
||||
success: false,
|
||||
message: "Error Getting Notification Settings.",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
const blocking = await qualityBlockingMonitor(data[0]);
|
||||
|
||||
return c.json({
|
||||
success: blocking?.success,
|
||||
message: blocking?.message,
|
||||
});
|
||||
}
|
||||
);
|
||||
export default app;
|
||||
75
lstV2/server/services/notifications/routes/sendMail.ts
Normal file
75
lstV2/server/services/notifications/routes/sendMail.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// an external way to creating logs
|
||||
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
||||
import { responses } from "../../../globalUtils/routeDefs/responses.js";
|
||||
import { authMiddleware } from "../../auth/middleware/authMiddleware.js";
|
||||
import { sendEmail } from "../controller/sendMail.js";
|
||||
import { tryCatch } from "../../../globalUtils/tryCatch.js";
|
||||
import { apiHit } from "../../../globalUtils/apiHits.js";
|
||||
|
||||
const app = new OpenAPIHono({ strict: false });
|
||||
|
||||
const EmailSchema = z
|
||||
.object({
|
||||
email: z.string().email().openapi({ example: "smith@example.come" }),
|
||||
subject: z.string().openapi({ example: "Welcome to LST" }),
|
||||
template: z.string().openapi({ example: "exampleTemplate" }),
|
||||
context: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
score: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
.openapi({}),
|
||||
})
|
||||
.openapi("User");
|
||||
app.openapi(
|
||||
createRoute({
|
||||
tags: ["server"],
|
||||
summary: "Returns current active lots that are tech released",
|
||||
method: "post",
|
||||
path: "/sendmail",
|
||||
middleware: authMiddleware,
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
"application/json": { schema: EmailSchema },
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: responses(),
|
||||
}),
|
||||
async (c) => {
|
||||
const { data: bodyData, error: bodyError } = await tryCatch(
|
||||
c.req.json()
|
||||
);
|
||||
apiHit(c, { endpoint: "/sendmail", lastBody: bodyData });
|
||||
if (bodyError) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
message: "There was an error sending the email",
|
||||
data: bodyError,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
const { data: emailData, error: emailError } = await tryCatch(
|
||||
sendEmail(bodyData)
|
||||
);
|
||||
|
||||
if (emailError) {
|
||||
return c.json({
|
||||
success: false,
|
||||
message: "There was an error sending the email",
|
||||
data: emailError,
|
||||
});
|
||||
}
|
||||
|
||||
return c.json({
|
||||
success: emailData.success,
|
||||
message: emailData.message,
|
||||
data: emailData.data,
|
||||
});
|
||||
}
|
||||
);
|
||||
export default app;
|
||||
177
lstV2/server/services/notifications/utils/masterNotifications.ts
Normal file
177
lstV2/server/services/notifications/utils/masterNotifications.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { db } from "../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../database/schema/notifications.js";
|
||||
import { createLog } from "../../logger/logger.js";
|
||||
|
||||
export const note: any = [
|
||||
{
|
||||
name: "reprintLabels",
|
||||
description:
|
||||
"Monitors the labels that are printed and returns a value if one falls withing the time frame defined below.",
|
||||
checkInterval: 1,
|
||||
timeType: "min",
|
||||
emails: "",
|
||||
active: false,
|
||||
notifiySettings: { prodID: 1 },
|
||||
},
|
||||
{
|
||||
name: "downTimeCheck",
|
||||
description:
|
||||
"Checks for specific downtimes that are greater than 105 min.",
|
||||
checkInterval: 30,
|
||||
timeType: "min",
|
||||
emails: "",
|
||||
active: false,
|
||||
notifiySettings: { prodID: 1, daysInPast: 5, duration: 105 },
|
||||
},
|
||||
{
|
||||
name: "qualityBlocking",
|
||||
description:
|
||||
"Checks for new blocking orders that have been entered, recommened to get the most recent order in here before activating.",
|
||||
checkInterval: 30,
|
||||
timeType: "min",
|
||||
emails: "",
|
||||
active: false,
|
||||
notifiySettings: {
|
||||
prodID: 1,
|
||||
sentBlockingOrders: [{ timeStamp: "0", blockingOrder: 1 }],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "productionCheck",
|
||||
description: "Checks ppoo",
|
||||
checkInterval: 2,
|
||||
timeType: "hour",
|
||||
emails: "",
|
||||
active: false,
|
||||
notifiySettings: {
|
||||
prodID: 1,
|
||||
count: 0,
|
||||
weekend: false,
|
||||
locations: "0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stagingCheck",
|
||||
description:
|
||||
"Checks staging based on locations, locations need to be seperated by a ,",
|
||||
checkInterval: 2,
|
||||
timeType: "hour",
|
||||
emails: "",
|
||||
active: false,
|
||||
notifiySettings: {
|
||||
prodID: 1,
|
||||
count: 0,
|
||||
weekend: false,
|
||||
locations: "0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tiIntergration",
|
||||
description: "Checks for new releases to be put into ti",
|
||||
checkInterval: 60,
|
||||
timeType: "min",
|
||||
emails: "",
|
||||
active: false,
|
||||
notifiySettings: {
|
||||
prodID: 1,
|
||||
start: 36,
|
||||
end: 36,
|
||||
releases: [{ timeStamp: "0", releaseNumber: 1 }],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exampleNotification",
|
||||
description: "Checks for new releases to be put into ti",
|
||||
checkInterval: 2,
|
||||
timeType: "min",
|
||||
emails: "",
|
||||
active: true,
|
||||
notifiySettings: {
|
||||
prodID: 1,
|
||||
start: 36,
|
||||
end: 36,
|
||||
releases: [1, 2, 3],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fifoIndex",
|
||||
description: "Checks for pallets that were shipped out of fifo",
|
||||
checkInterval: 1,
|
||||
timeType: "hour",
|
||||
emails: "blake.matthes@alpla.com",
|
||||
active: false,
|
||||
notifiySettings: {
|
||||
prodID: 1,
|
||||
start: 36,
|
||||
end: 36,
|
||||
releases: [1, 2, 3],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bow2henkelincoming",
|
||||
description:
|
||||
"Checks for new incoming goods orders to be completed and sends an email for what truck and carrier it was",
|
||||
checkInterval: 15,
|
||||
timeType: "min",
|
||||
emails: "blake.matthes@alpla.com",
|
||||
active: false,
|
||||
notifiySettings: { processTime: 15 },
|
||||
},
|
||||
{
|
||||
name: "palletsRemovedAsWaste",
|
||||
description:
|
||||
"Validates stock to make sure, there are no pallets released that have been removed as waste already ",
|
||||
checkInterval: 15,
|
||||
timeType: "min",
|
||||
emails: "blake.matthes@alpla.com",
|
||||
active: false,
|
||||
notifiySettings: { prodID: 1 },
|
||||
},
|
||||
{
|
||||
name: "shortageBookings",
|
||||
description:
|
||||
"Checks for material shortage bookings by single av type or all types ",
|
||||
checkInterval: 15,
|
||||
timeType: "min",
|
||||
emails: "blake.matthes@alpla.com",
|
||||
active: false,
|
||||
notifiySettings: {
|
||||
time: 15,
|
||||
type: "all", // change this to something else or leave blank to use the av type
|
||||
avType: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const notificationCreate = async () => {
|
||||
for (let i = 0; i < note.length; i++) {
|
||||
try {
|
||||
const notify = await db
|
||||
.insert(notifications)
|
||||
.values(note[i])
|
||||
.onConflictDoUpdate({
|
||||
target: notifications.name,
|
||||
set: {
|
||||
name: note[i].name,
|
||||
description: note[i].description,
|
||||
//notifiySettings: note[i].notifiySettings,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`There was an error getting the notifications: ${JSON.stringify(
|
||||
error
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
createLog(
|
||||
"info",
|
||||
"lst",
|
||||
"nofity",
|
||||
"notifications were just added/updated due to server startup"
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
import { db } from "../../../../database/dbclient.js";
|
||||
import { notifications } from "../../../../database/schema/notifications.js";
|
||||
import { settings } from "../../../../database/schema/settings.js";
|
||||
import { tryCatch } from "../../../globalUtils/tryCatch.js";
|
||||
import type { JobInfo } from "../../../types/JobInfo.js";
|
||||
import { createLog } from "../../logger/logger.js";
|
||||
import { Cron } from "croner";
|
||||
|
||||
// Store active timeouts by notification ID
|
||||
export let runningCrons: Record<string, Cron> = {};
|
||||
|
||||
export const startNotificationMonitor = async () => {
|
||||
// if restarted or crashed we need to make sure the running notifications is cleared
|
||||
createLog("info", "notify", "notify", `Notification system is now active.`);
|
||||
|
||||
setInterval(async () => {
|
||||
const { data: notes, error } = (await tryCatch(
|
||||
db.select().from(notifications)
|
||||
)) as any;
|
||||
|
||||
if (error) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
"notify",
|
||||
`There was an error getting the notifications: ${JSON.stringify(
|
||||
error
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
for (const note of notes) {
|
||||
//if we get deactivated remove it.
|
||||
if (runningCrons[note.name] && !note.active) {
|
||||
createLog(
|
||||
"info",
|
||||
"notify",
|
||||
"notify",
|
||||
`${note.name} was just deactivated`
|
||||
);
|
||||
removeNotification(note.name);
|
||||
}
|
||||
|
||||
// if we are not active, no emails, and already in place just stop.
|
||||
|
||||
if (
|
||||
!note.active ||
|
||||
// note.emails === "" ||
|
||||
runningCrons[note.name]
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!runningCrons[note.name] && note.active) {
|
||||
createLog(
|
||||
"info",
|
||||
"notify",
|
||||
"notify",
|
||||
`${note.name} Is active and not already running.`
|
||||
);
|
||||
}
|
||||
|
||||
let time = `*/30 * * * *`; // default to be every 30 min
|
||||
|
||||
if (note.timeType === "min") {
|
||||
//console.log(`Creating the min mark here`);
|
||||
const totalMinutes = note.checkInterval;
|
||||
if (note.checkInterval > 60) {
|
||||
const hours = Math.floor(totalMinutes / 60); // 1 hour
|
||||
const minutes = totalMinutes % 60; // 45 minutes
|
||||
time = `*/${minutes} */${hours} * * *`;
|
||||
} else {
|
||||
time = `*/${note.checkInterval} * * * *`;
|
||||
}
|
||||
}
|
||||
if (note.timeType === "hour") {
|
||||
const totalHours = note.checkInterval;
|
||||
if (note.checkInterval > 60) {
|
||||
const days = Math.floor(totalHours / 24); // 1 hour
|
||||
const hours = totalHours % 24; // 45 minutes
|
||||
time = `* */${hours} */${days} * *`;
|
||||
} else {
|
||||
time = `* */${note.checkInterval} * * *`;
|
||||
}
|
||||
}
|
||||
|
||||
createJob(note.name, time, async () => {
|
||||
try {
|
||||
const { default: runFun } = await import(
|
||||
`../controller/notifications/${note.name}.js`
|
||||
);
|
||||
await runFun(note);
|
||||
} catch (error: any) {
|
||||
createLog(
|
||||
"error",
|
||||
"notify",
|
||||
note.name,
|
||||
`Error running notification: ${error.message}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
//testParse(runningNotifcations[note.name]);
|
||||
}
|
||||
}, 5 * 1000);
|
||||
};
|
||||
|
||||
export const createJob = async (
|
||||
id: string,
|
||||
schedule: string,
|
||||
task: () => Promise<void>
|
||||
) => {
|
||||
const { data, error } = (await tryCatch(db.select().from(settings))) as any;
|
||||
|
||||
const timeZone = data.filter((n: any) => n.name === "timezone");
|
||||
// Destroy existing job if it exists
|
||||
if (runningCrons[id]) {
|
||||
runningCrons[id].stop(); // Croner uses .stop() instead of .destroy()
|
||||
}
|
||||
|
||||
// Create new job with Croner
|
||||
runningCrons[id] = new Cron(
|
||||
schedule,
|
||||
{
|
||||
timezone: timeZone[0].timezone,
|
||||
catch: true, // Prevents unhandled rejections
|
||||
},
|
||||
task
|
||||
);
|
||||
|
||||
// Optional: Add error handling (Croner emits 'error' events)
|
||||
// runningNotifications[id].on("error", (err) => {
|
||||
// console.error(`Job ${id} failed:`, err);
|
||||
// });
|
||||
};
|
||||
|
||||
export const getAllJobs = (): JobInfo[] => {
|
||||
return Object.entries(runningCrons).map(([id, job]) => ({
|
||||
id,
|
||||
schedule: job.getPattern() || "invalid",
|
||||
nextRun: job.nextRun() || null,
|
||||
lastRun: job.previousRun() || null,
|
||||
isRunning: job ? !job.isStopped() : false,
|
||||
}));
|
||||
};
|
||||
|
||||
const removeNotification = (id: any) => {
|
||||
if (runningCrons[id]) {
|
||||
runningCrons[id].stop();
|
||||
delete runningCrons[id];
|
||||
}
|
||||
};
|
||||
|
||||
export const stopAllJobs = () => {
|
||||
Object.values(runningCrons).forEach((job: any) => job.stop());
|
||||
runningCrons = {}; // Clear the object
|
||||
};
|
||||
|
||||
/*
|
||||
// Pause a job
|
||||
app.post("/api/jobs/:id/pause", (req, res) => {
|
||||
runningNotifications[req.params.id]?.pause();
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Resume a job
|
||||
app.post("/api/jobs/:id/resume", (req, res) => {
|
||||
runningNotifications[req.params.id]?.resume();
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Delete a job
|
||||
app.delete("/api/jobs/:id", (req, res) => {
|
||||
runningNotifications[req.params.id]?.stop();
|
||||
delete runningNotifications[req.params.id];
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>New incomings goods have been received in the last {{time}}min.</p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Truck Number</th>
|
||||
<th>Carrier</th>
|
||||
<th>Add Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{truckNumber}}</td>
|
||||
<td>{{carrier}}</td>
|
||||
<td>{{Add_Date}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>The below downtimes have exceeded the max requested limit of {{secondarySetting.duration}}min</p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Current Durration</th>
|
||||
<th>machineAlias</th>
|
||||
<th>CTO_Code</th>
|
||||
<th>Downtime_Description</th>
|
||||
<th>groupDesc</th>
|
||||
<th>remark</th>
|
||||
<th>Downtime start</th>
|
||||
{{!-- <th>Downtime finish</th> --}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{totalDuration}}</td>
|
||||
<td>{{machineAlias}}</td>
|
||||
<td>{{CTO_Code}}</td>
|
||||
<td>{{Downtime_Description}}</td>
|
||||
<td>{{groupDesc}}</td>
|
||||
<td>{{remark}}</td>
|
||||
<td>{{dtStart}}</td>
|
||||
{{!-- <td>{{dtEnd}}</td> --}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Order Summary</title>
|
||||
{{> styles}}
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{name}}, your Order Summary</h1>
|
||||
<p>All,
|
||||
This is an example of the test email
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item Name</th>
|
||||
<th>Quantity</th>
|
||||
<th>Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{name}}</td>
|
||||
<td>{{quantity}}</td>
|
||||
<td>{{price}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>The below SKU's do not currently have an av and will be ignored in the forcast import.</p>
|
||||
<p>The date and qty are the first time needed showing from teh vmi report</p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer Article Number</th>
|
||||
<th>First Date Needed</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{customerArticleNo}}</td>
|
||||
<td>{{requirementDate}}</td>
|
||||
<td>{{quantity}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>The below labels have been brought back into the system either by relocate or by inventory taking order, please validate these labels and reblock them or reremove them.</p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>AV</th>
|
||||
<th>Desciption</th>
|
||||
<th>Label Number</th>
|
||||
<th>Last Moving Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{av}}</td>
|
||||
<td>{{alias}}</td>
|
||||
<td>{{runningnumber}}</td>
|
||||
<td>{{lastMovingDate}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<p>For a removal process logistcs will need to do this in lst so a reason for the removal can be added.</p>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{{!--<title>Order Summary</title> --}}
|
||||
{{> styles}}
|
||||
<style>
|
||||
pre {
|
||||
background-color: #f8f9fa;
|
||||
color: #d63384;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Dear {{username}},<br/><br/>
|
||||
|
||||
Your password was change. Please find your new temporary password below:<br/><br/>
|
||||
|
||||
Temporary Password: <em><b>{{password}}</b></em><br/><br/>
|
||||
|
||||
For security reasons, we strongly recommend changing your password as soon as possible.<br/><br/>
|
||||
|
||||
You can update it by logging into your account, clicking your profile at the top right and click password change.<br/><br/>
|
||||
|
||||
|
||||
Or <a href="http://{{server}}:{{port}}/passwordChange"
|
||||
style="display:inline-block; padding:10px 20px; text-decoration:none; border-radius:5px;">
|
||||
Click Here
|
||||
</a> to login and change your password.<br/><br/>
|
||||
|
||||
Best regards,<br/><br/>
|
||||
LST team<br/>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>There are currently {{count}} pallets sitting in ppoo that are older than {{checkTime}} {{timeCheck}}.</p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Article</th>
|
||||
<th>Description</th>
|
||||
<th>Lot</th>
|
||||
<th>ProductionDate</th>
|
||||
<th>Running Number</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{av}}</td>
|
||||
<td>{{articleDescription}}</td>
|
||||
<td>{{lot}}</td>
|
||||
<td>{{productionDate}}</td>
|
||||
<td>{{runningNumber}}</td>
|
||||
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,74 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
<style>
|
||||
.email-wrapper {
|
||||
max-width: 80%; /* Limit width to 80% of the window */
|
||||
margin: 0 auto; /* Center the content horizontally */
|
||||
}
|
||||
.email-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.email-table td {
|
||||
vertical-align: top;
|
||||
padding: 10px;
|
||||
border: 1px solid #000;
|
||||
border-radius: 25px; /* Rounded corners */
|
||||
background-color: #f0f0f0; /* Optional: Add a background color */
|
||||
}
|
||||
.email-table h2 {
|
||||
margin: 0;
|
||||
}
|
||||
.remarks {
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 25px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-wrapper">
|
||||
<p>All,</p>
|
||||
<p>Please see the new blocking order that was created.</p>
|
||||
{{#each items}}
|
||||
<div>
|
||||
<div class="email-table">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<p><strong>Blocking number: </strong>{{blockingNumber}}</p>
|
||||
<p><strong>Blocking Date: </strong>{{blockingDate}}</p>
|
||||
<p><strong>Article: </strong>{{av}}</p>
|
||||
<p><strong>Production Lot: </strong>{{lotNumber}}</p>
|
||||
<p><strong>Line: </strong>{{line}}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p><strong>Customer: </strong>{{customer}}</p>
|
||||
<p><strong>Blocked pieces /LUs: </strong>{{peicesAndLoadingUnits}}</p>
|
||||
<p><strong>Main defect group: </strong>{{mainDefectGroup}}</p>
|
||||
<p><strong>Main defect: </strong>{{mainDefect}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="remarks">
|
||||
<h4>Remarks:</h4>
|
||||
<p>{{remark}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
<br>
|
||||
<p>For further questions please reach out to quality.</p> <br>
|
||||
<p>Thank you,</p> <br>
|
||||
<p>Quality Department</p>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>The below labels have been reprinted.</p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>AV</th>
|
||||
<th>Desciption</th>
|
||||
<th>Label Number</th>
|
||||
<th>Date Added</th>
|
||||
<th>User that created</th>
|
||||
<th>Last time label was printed/updated in the system</th>
|
||||
<th>Who printed/Updated</th>
|
||||
<th>What printer it came from</th>
|
||||
<th>Total reprinted labels</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{av}}</td>
|
||||
<td>{{alias}}</td>
|
||||
<td>{{runningNumber}}</td>
|
||||
<td>{{Add_Date}}</td>
|
||||
<td>{{Add_User}}</td>
|
||||
<td>{{Upd_Date}}</td>
|
||||
<td>{{Upd_User}}</td>
|
||||
<td>{{printer}}</td>
|
||||
<td>{{totalPrinted}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{{!--<title>Order Summary</title> --}}
|
||||
{{> styles}}
|
||||
<style>
|
||||
pre {
|
||||
background-color: #f8f9fa;
|
||||
color: #d63384;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
</head>
|
||||
<body>
|
||||
<h3>{{plant}},<br/> Has encountered an unexpected error.</h1>
|
||||
<p>
|
||||
Please see below the stack error from the crash.
|
||||
</p>
|
||||
<hr/>
|
||||
<div>
|
||||
<h3>Error Message: </h3>
|
||||
<p>{{error.message}}</p>
|
||||
</div>
|
||||
<hr/>
|
||||
<div>
|
||||
<h3>Stack trace</h3>
|
||||
<pre>{{{error.stack}}}</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>$shortage bookings were just done on the below pallet(s). </p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Material AV</th>
|
||||
<th>Material Alias</th>
|
||||
<th>Production Lot</th>
|
||||
<th>Production Pallet Running number</th>
|
||||
<th>Machine</th>
|
||||
<th>Machine Name</th>
|
||||
<th>Quantity Shorted</th>
|
||||
<th>Shortage Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{materialAV}}</td>
|
||||
<td>{{materialAlias}}</td>
|
||||
<td>{{productionlot}}</td>
|
||||
<td>{{palletWithShortBookings}}</td>
|
||||
<td>{{machineNumber}}</td>
|
||||
<td>{{machineAlias}}</td>
|
||||
<td>{{qtyShortpcs}}</td>
|
||||
<td>{{bookingDate}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<p>This can be corrected by following the below simple instructions.</p>
|
||||
<ol type="1">
|
||||
<li>Bring the pallet back to PPOO</li>
|
||||
<li>Book out the pallet</li>
|
||||
<li>Make the corrections to stock for the above materials/packaging missing</li>
|
||||
<li>Book the pallet back in.</li>
|
||||
|
||||
</ol>
|
||||
<br/>
|
||||
<p>For further instructions please reach out to regional support via helpdesk ticket</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{{!--<title>Order Summary</title> --}}
|
||||
{{> styles}}
|
||||
<style>
|
||||
pre {
|
||||
background-color: #f8f9fa;
|
||||
color: #d63384;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
{{greeting}},<br/><br/>
|
||||
|
||||
A silo adjustment was just completed on {{siloName}}, with a variation of {{variance}}.<br/><br/>
|
||||
|
||||
The data that was passed over.<br/><br/>
|
||||
Current stock at the time of the adjustment: {{currentLevel}}.<br/><br/>
|
||||
|
||||
What was entered as the new number: {{newLevel}}<br/><br/>
|
||||
|
||||
Please add your comment as to why the variance greater than {{variancePer}}<br/><br/>
|
||||
|
||||
<a href="http://{{server}}:{{port}}/siloAdjustments/comment/{{adjustID}}"
|
||||
style="display:inline-block; padding:10px 20px; text-decoration:none; border-radius:5px;">
|
||||
Add a Comment
|
||||
</a><br/><br/>
|
||||
Best regards,<br/><br/>
|
||||
LST team<br/>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
{{> styles}}
|
||||
</head>
|
||||
<body>
|
||||
<p>All,</p>
|
||||
<p>There are currently {{count}} pallets sitting in staging that are older than {{checkTime}} {{timeCheck}}.</p>
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Article</th>
|
||||
<th>Description</th>
|
||||
<th>Lot</th>
|
||||
<th>ProductionDate</th>
|
||||
<th>Running Number</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{av}}</td>
|
||||
<td>{{articleDescription}}</td>
|
||||
<td>{{lot}}</td>
|
||||
<td>{{productionDate}}</td>
|
||||
<td>{{runningNumber}}</td>
|
||||
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<p>Thank you,</p>
|
||||
<p>LST Team</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
<style>
|
||||
table { width: 100%; background-color: #ffffff; border-collapse: collapse;
|
||||
border-width: 2px; border-color: #14BDEA; border-style: solid; color:
|
||||
#000000; } th, td { border: 1px solid #ddd; padding: 8px; } th {
|
||||
background-color: #f4f4f4; }
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{{!--<title>Order Summary</title> --}}
|
||||
{{> styles}}
|
||||
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>All,<br/>
|
||||
There has been {{count}} manual prints in the last {{hours}}.<br/>
|
||||
Please consider checking the reason for this.<br/>
|
||||
Thank you,<br/>
|
||||
LST
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user