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

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

View File

@@ -0,0 +1,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"
);
};

View File

@@ -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 });
});
*/

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>