feat(migrate): quality alert migrated
This commit is contained in:
96
backend/notification/notification.manualTrigger.ts
Normal file
96
backend/notification/notification.manualTrigger.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { type Response, Router } from "express";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notifications } from "../db/schema/notifications.schema.js";
|
||||||
|
import { auth } from "../utils/auth.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.post("/", async (req, res: Response) => {
|
||||||
|
const hasPermissions = await auth.api.userHasPermission({
|
||||||
|
body: {
|
||||||
|
//userId: req?.user?.id,
|
||||||
|
role: req.user?.roles as any,
|
||||||
|
permissions: {
|
||||||
|
notifications: ["readAll"], // This must match the structure in your access control
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasPermissions) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "post",
|
||||||
|
message: `You do not have permissions to be here`,
|
||||||
|
data: [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: nName, error: nError } = await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(notifications)
|
||||||
|
.where(eq(notifications.name, req.body.name)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nError) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "get",
|
||||||
|
message: `There was an error getting the notifications `,
|
||||||
|
data: [nError],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: sub, error: sError } = await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(notifications)
|
||||||
|
.where(eq(notifications.name, req.body.name)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sError) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "get",
|
||||||
|
message: `There was an error getting the subs `,
|
||||||
|
data: [sError],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailString = [
|
||||||
|
...new Set(
|
||||||
|
sub.flatMap((e: any) =>
|
||||||
|
e.emails?.map((email: any) => email.trim().toLowerCase()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].join(";");
|
||||||
|
|
||||||
|
console.log(emailString);
|
||||||
|
const { default: runFun } = await import(
|
||||||
|
`./notification.${req.body.name.trim()}.js`
|
||||||
|
);
|
||||||
|
const manual = await runFun(nName[0], "blake.matthes@alpla.com");
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "post",
|
||||||
|
message: `Manual Trigger ran`,
|
||||||
|
data: manual ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
export default r;
|
||||||
109
backend/notification/notification.qualityBlocking.ts
Normal file
109
backend/notification/notification.qualityBlocking.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notifications } from "../db/schema/notifications.schema.js";
|
||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { delay } from "../utils/delay.utils.js";
|
||||||
|
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
import { sendEmail } from "../utils/sendEmail.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
import { v1QueryRun } from "../utils/pgConnectToLst.utils.js";
|
||||||
|
|
||||||
|
const func = async (data: any, emails: string) => {
|
||||||
|
// TODO: remove this disable once all 17 plants are on this new lst
|
||||||
|
v1QueryRun(`update public.notifications set active = false where name = '${data.name}'`)
|
||||||
|
|
||||||
|
const { data: l, error: le } = (await tryCatch(
|
||||||
|
db.select().from(notifications).where(eq(notifications.id, data.id)),
|
||||||
|
)) as any;
|
||||||
|
|
||||||
|
if (le) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "query",
|
||||||
|
message: `${data.name} encountered an error while trying to get initial info`,
|
||||||
|
data: le as any,
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// search the query db for the query by name
|
||||||
|
const sqlQuery = sqlQuerySelector(`${data.name}`) as SqlQuery;
|
||||||
|
// create the ignore audit logs ids
|
||||||
|
|
||||||
|
// get get the latest blocking order id that was sent
|
||||||
|
const blockingOrderId = l[0].options[0].lastBlockingOrderIdSent ?? 69;
|
||||||
|
|
||||||
|
// run the check
|
||||||
|
const { data: queryRun, error } = await tryCatch(
|
||||||
|
prodQuery(
|
||||||
|
sqlQuery.query.replace("[lastBlocking]", blockingOrderId),
|
||||||
|
`Running notification query: ${l[0].name}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "query",
|
||||||
|
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
|
||||||
|
data: error as any,
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryRun.data.length > 0) {
|
||||||
|
for (const bo of queryRun.data) {
|
||||||
|
const sentEmail = await sendEmail({
|
||||||
|
email: emails,
|
||||||
|
subject: bo.subject,
|
||||||
|
template: "qualityBlocking",
|
||||||
|
context: {
|
||||||
|
items: bo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sentEmail?.success) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "email",
|
||||||
|
message: `${l[0].name} failed to send the email`,
|
||||||
|
data: sentEmail?.data as any,
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await delay(1500);
|
||||||
|
|
||||||
|
const { error: dbe } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({ options: [{ lastBlockingOrderIdSent: bo.blockingNumber }] })
|
||||||
|
.where(eq(notifications.id, data.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dbe) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "query",
|
||||||
|
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
|
||||||
|
data: dbe as any,
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default func;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
import manual from "./notification.manualTrigger.js";
|
||||||
import getNotifications from "./notification.route.js";
|
import getNotifications from "./notification.route.js";
|
||||||
import updateNote from "./notification.update.route.js";
|
import updateNote from "./notification.update.route.js";
|
||||||
import deleteSub from "./notificationSub.delete.route.js";
|
import deleteSub from "./notificationSub.delete.route.js";
|
||||||
@@ -11,6 +12,7 @@ export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
|
|||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
app.use(`${baseUrl}/api/notification`, requireAuth, getNotifications);
|
app.use(`${baseUrl}/api/notification`, requireAuth, getNotifications);
|
||||||
app.use(`${baseUrl}/api/notification`, requireAuth, updateNote);
|
app.use(`${baseUrl}/api/notification`, requireAuth, updateNote);
|
||||||
|
app.use(`${baseUrl}/api/notification/manual`, requireAuth, manual);
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, subs);
|
app.use(`${baseUrl}/api/notification/sub`, requireAuth, subs);
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, newSub);
|
app.use(`${baseUrl}/api/notification/sub`, requireAuth, newSub);
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, updateSub);
|
app.use(`${baseUrl}/api/notification/sub`, requireAuth, updateSub);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const note: NewNotification[] = [
|
|||||||
"Checks for new blocking orders that have been entered, recommend to get the most recent order in here before activating.",
|
"Checks for new blocking orders that have been entered, recommend to get the most recent order in here before activating.",
|
||||||
active: false,
|
active: false,
|
||||||
interval: "10",
|
interval: "10",
|
||||||
options: [{ sentBlockingOrders: [{ timeStamp: "0", blockingOrder: 1 }] }],
|
options: [{ lastBlockingOrderIdSent: 1 }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "alplaPurchaseHistory",
|
name: "alplaPurchaseHistory",
|
||||||
|
|||||||
44
backend/prodSql/queries/qualityBlocking.sql
Normal file
44
backend/prodSql/queries/qualityBlocking.sql
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use [test1_AlplaPROD2.0_Read]
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'Alert! new blocking order: #' + cast(bo.HumanReadableId as varchar) + ' - ' + bo.ArticleVariantDescription as subject
|
||||||
|
,cast(bo.[HumanReadableId] as varchar) as blockingNumber
|
||||||
|
,bo.[ArticleVariantDescription] as article
|
||||||
|
,cast(bo.[CustomerHumanReadableId] as varchar) + ' - ' + bo.[CustomerDescription] as customer
|
||||||
|
,convert(varchar(10), bo.[BlockingDate], 101) + ' ' + convert(varchar(5), bo.[BlockingDate], 108) as blockingDate
|
||||||
|
,cast(ArticleVariantHumanReadableId as varchar) + ' - ' + ArticleVariantDescription as av
|
||||||
|
,case when bo.Remark = '' or bo.Remark is NULL then 'Please reach out to quality for the reason this was placed on hold as a remark was not entered during the blocking processs' else bo.Remark end as remark
|
||||||
|
,cast(FORMAT(TotalAmountOfPieces, '###,###') as varchar) + ' / ' + cast(LoadingUnit as varchar) as peicesAndLoadingUnits
|
||||||
|
,bo.ProductionLotHumanReadableId as lotNumber
|
||||||
|
,cast(osd.IdBlockingDefectsGroup as varchar) + ' - ' + osd.Description as mainDefectGroup
|
||||||
|
,cast(df.HumanReadableId as varchar) + ' - ' + os.Description as mainDefect
|
||||||
|
,lot.MachineLocation as line
|
||||||
|
--,*
|
||||||
|
FROM [blocking].[BlockingOrder] (nolock) as bo
|
||||||
|
|
||||||
|
|
||||||
|
/*** get the defect details ***/
|
||||||
|
join
|
||||||
|
[blocking].[BlockingDefect] (nolock) AS df
|
||||||
|
on df.id = bo.MainDefectId
|
||||||
|
|
||||||
|
/*** pull description from 1.0 ***/
|
||||||
|
left join
|
||||||
|
[AlplaPROD_test1].[dbo].[T_BlockingDefects] (nolock) as os
|
||||||
|
on os.IdGlobalBlockingDefect = df.HumanReadableId
|
||||||
|
|
||||||
|
/*** join in 1.0 defect group ***/
|
||||||
|
left join
|
||||||
|
[AlplaPROD_test1].[dbo].[T_BlockingDefectsGroups] (nolock) as osd
|
||||||
|
on osd.IdBlockingDefectsGroup = os.IdBlockingDefectsGroup
|
||||||
|
|
||||||
|
left join
|
||||||
|
[productionControlling].[ProducedLot] (nolock) as lot
|
||||||
|
on lot.id = bo.ProductionLotId
|
||||||
|
|
||||||
|
|
||||||
|
where
|
||||||
|
bo.[BlockingDate] between getdate() - 2 and getdate() + 3 and
|
||||||
|
bo.BlockingTrigger = 1 -- so we only get the ir blocking and not coa
|
||||||
|
--and HumanReadableId NOT IN ([sentBlockingOrders])
|
||||||
|
and bo.HumanReadableId > [lastBlocking]
|
||||||
73
backend/utils/mailViews/qualityBlocking.hbs
Normal file
73
backend/utils/mailViews/qualityBlocking.hbs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<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>
|
||||||
|
<div>
|
||||||
|
<div class="email-table">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p><strong>Blocking number: </strong>{{items.blockingNumber}}</p>
|
||||||
|
<p><strong>Blocking Date: </strong>{{items.blockingDate}}</p>
|
||||||
|
<p><strong>Article: </strong>{{items.av}}</p>
|
||||||
|
<p><strong>Production Lot: </strong>{{items.lotNumber}}</p>
|
||||||
|
<p><strong>Line: </strong>{{items.line}}</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p><strong>Customer: </strong>{{items.customer}}</p>
|
||||||
|
<p><strong>Blocked pieces /LUs: </strong>{{items.peicesAndLoadingUnits}}</p>
|
||||||
|
<p><strong>Main defect group: </strong>{{items.mainDefectGroup}}</p>
|
||||||
|
<p><strong>Main defect: </strong>{{items.mainDefect}}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="remarks">
|
||||||
|
<h4>Remarks:</h4>
|
||||||
|
<p>{{items.remark}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<p>For further questions please reach out to quality.</p>
|
||||||
|
<p>Thank you,</p>
|
||||||
|
<p>Quality Department</p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
46
backend/utils/pgConnectToLst.utils.ts
Normal file
46
backend/utils/pgConnectToLst.utils.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import pkg from "pg";
|
||||||
|
const { Client } = pkg;
|
||||||
|
|
||||||
|
const v1client = new Client({
|
||||||
|
host: "localhost",
|
||||||
|
port: 5432,
|
||||||
|
user: "postgres",
|
||||||
|
password: "obelix",
|
||||||
|
database: "lst",
|
||||||
|
});
|
||||||
|
|
||||||
|
const v2client = new Client({
|
||||||
|
host: "localhost",
|
||||||
|
port: 5432,
|
||||||
|
user: "postgres",
|
||||||
|
password: "obelix",
|
||||||
|
database: "lst_db",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const v1QueryRun= async (query:string)=> {
|
||||||
|
try {
|
||||||
|
await v1client.connect();
|
||||||
|
|
||||||
|
const res = await v1client.query(query);
|
||||||
|
|
||||||
|
console.log("Rows affected:", res.rowCount);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error running query:", err);
|
||||||
|
} finally {
|
||||||
|
await v1client.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const v2QueryRun = async (query:string)=> {
|
||||||
|
try {
|
||||||
|
await v2client.connect();
|
||||||
|
|
||||||
|
const res = await v2client.query(query);
|
||||||
|
|
||||||
|
console.log("Rows affected:", res.rowCount);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error running query:", err);
|
||||||
|
} finally {
|
||||||
|
await v2client.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user