feat(siloadjustments): added email for comments :D

This commit is contained in:
2025-04-04 22:09:47 -05:00
parent 9f26f2334f
commit f1abe7b33d
24 changed files with 8565 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
import { getHours } from "date-fns";
export const greetingStuff = async (date = new Date()) => {
const hour = getHours(date);
if (hour < 12) {
return "Good morning";
} else if (hour < 18) {
return "Good afternoon";
} else {
return "Good evening";
}
};

View File

@@ -0,0 +1,11 @@
import crypto from "crypto";
export const generateOneTimeKey = async (length = 32) => {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let key = "";
const bytes = crypto.randomBytes(length);
for (let i = 0; i < length; i++) {
key += chars[bytes[i] % chars.length];
}
return key.match(/.{1,4}/g)!.join("-"); // group by 4 chars
};

View File

@@ -0,0 +1,130 @@
import { db } from "../../../../../database/dbclient.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { siloQuery } from "../../../sqlServer/querys/silo/siloQuery.js";
import { postAdjustment } from "./postAdjustment.js";
import { siloAdjustments } from "../../../../../database/schema/siloAdjustments.js";
import { greetingStuff } from "../../../../globalUtils/greetingEmail.js";
import { sendEmail } from "../../../notifications/controller/sendMail.js";
import { settings } from "../../../../../database/schema/settings.js";
import { generateOneTimeKey } from "../../../../globalUtils/singleUseKey.js";
import { eq } from "drizzle-orm";
export const createSiloAdjustment = async (
data: any | null,
user: any | null
) => {
/**
* Creates a silo adjustment based off warehouse, location, and qty.
* qty will come from the hmi, prolink, or silo patrol
*/
const { data: set, error: setError } = await tryCatch(
db.select().from(settings)
);
if (setError) {
return {
success: false,
message: `There was an error getting setting data to post to the server.`,
data: setError,
};
}
// getting stock data first so we have it prior to the adjustment
const { data: stock, error: stockError } = await tryCatch(
query(siloQuery, "Silo data Query")
);
if (stockError) {
return {
success: false,
message: `There was an error getting stock data to post to the server.`,
data: stockError,
};
}
const { data: a, error: errorAdj } = await tryCatch(
postAdjustment(data, user.prod)
);
if (errorAdj) {
return {
success: false,
message: `There was an error doing the silo adjustment.`,
data: errorAdj,
};
}
/**
* Checking to see the difference, and send email if +/- 5% will change later if needed
*/
const stockNummy = stock.filter((s: any) => s.LocationID === data.laneId);
const theDiff =
((data.quantity - stockNummy[0].Stock_Total) /
((data.quantity + stockNummy[0].Stock_Total) / 2)) *
100;
/**
* Post the data to our db.
*/
//console.log(stockNummy);
const { data: postAdj, error: postAdjError } = await tryCatch(
db
.insert(siloAdjustments)
.values({
warehouseID: data.warehouseId,
locationID: data.laneId,
currentStockLevel: stockNummy[0].Stock_Total,
newLevel: data.quantity,
lastDateAdjusted: new Date(stockNummy[0].LastAdjustment),
add_user: user.username,
})
.returning({ id: siloAdjustments.siloAdjust_id })
);
if (postAdjError) {
//console.log(postAdjError);
return {
success: false,
message: `There was an error posting the new adjustment.`,
data: postAdjError,
};
}
if (Math.abs(theDiff) > 5) {
// console.log(`Send for comment due to being: ${theDiff.toFixed(2)}%`);
const server = set.filter((n: any) => n.name === "server");
const port = set.filter((n: any) => n.name === "serverPort");
const key = await generateOneTimeKey();
const updateKey = await db
.update(siloAdjustments)
.set({ commentKey: key })
.where(eq(siloAdjustments.siloAdjust_id, postAdj[0].id));
const emailSetup = {
email: user.email,
subject: `Alert - Siloadjustment was done with a descrepancy of 5% or greater`,
template: "siloAdjustmentComment",
context: {
greeting: await greetingStuff(),
siloName: stockNummy[0].Description,
variance: `${theDiff.toFixed(2)}%`,
currentLevel: stockNummy[0].Stock_Total,
newLevel: data.quantity,
variancePer: 5,
adjustID: `${postAdj[0].id}&${key}`,
server: server[0].value,
port: port[0].value,
},
};
//console.log(emailSetup);
await sendEmail(emailSetup);
}
let adj: any = a;
return { success: adj.success, message: adj.message, data: adj.data };
};

View File

@@ -0,0 +1,81 @@
import axios from "axios";
import { prodEndpointCreation } from "../../../../globalUtils/createUrl.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
export const postAdjustment = async (data: any, prod: any) => {
if (data.warehouseId === undefined) {
return {
sucess: false,
message: `Missing mandatory field: warehouseID`,
data: { error: `Missing mandatory field: warehouseID` },
};
}
if (data.laneId === undefined) {
return {
sucess: false,
message: `Missing mandatory field: locationID`,
data: { error: `Missing mandatory field: locationID` },
};
}
if (data.quantity == "0") {
return {
sucess: false,
message: `You entered 0 for the quantity to post, quantity needs to be at leave 1`,
data: {
error: `You entered 0 for the quantity to post, quantity needs to be at leave 1`,
},
};
}
const siloAdjustment = {
warehouseId: data.warehouseId,
laneId: data.laneId,
quantity: data.quantity,
};
let url = await prodEndpointCreation(
"/public/v1.0/Warehousing/AdjustSiloStockLevel"
);
const { data: silo, error } = await tryCatch(
axios.post(url, siloAdjustment, {
headers: { Authorization: `Basic ${prod}` },
})
);
let e = error as any;
if (error) {
return {
success: false,
message: "Error in posting the silo adjustment.",
data: {
status: e.response?.status,
statusText: e.response?.statusText,
data: e.response?.data,
},
};
}
if (silo.status !== 200) {
return {
success: false,
message: "Error in posting the silo adjustment",
data: {
status: silo.status,
statusText: silo.statusText,
data: silo.data,
},
};
}
return {
success: true,
message: "Adjustment was completed",
data: {
status: silo.status,
statusText: silo.statusText,
data: silo.data,
},
};
};

View File

@@ -0,0 +1,64 @@
import { eq, sql } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { siloAdjustments } from "../../../../../database/schema/siloAdjustments.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
export const postSiloComment = async (
id: string,
comment: string,
commentk: string,
user: any
) => {
/**
* We will add the comment to the silo adjustment so we know the why we had this.
*/
// make sure we havea valid key
const { data: key, error: keyErro } = await tryCatch(
db
.select()
.from(siloAdjustments)
.where(eq(siloAdjustments.siloAdjust_id, id))
);
if (keyErro) {
return {
success: false,
message: "There was an error getting the adjustment.",
data: keyErro,
};
}
if (key[0].commentKey != commentk) {
return {
success: false,
message: "The key you provided is invalid.",
data: keyErro,
};
}
const { data, error } = await tryCatch(
db
.update(siloAdjustments)
.set({
comment: comment,
commentAddedBy: user.username,
commentDate: sql`NOW()`,
commentKey: null,
})
.where(eq(siloAdjustments.siloAdjust_id, id))
);
if (error) {
return {
success: false,
message: "There was an error adding the comment.",
data: error,
};
}
return {
success: true,
message: "Comment was successfully added.",
};
};

View File

@@ -0,0 +1,64 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { verify } from "hono/jwt";
import { authMiddleware } from "../../../auth/middleware/authMiddleware.js";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { createSiloAdjustment } from "../../controller/siloAdjustments/createSiloAdjustment.js";
const app = new OpenAPIHono();
const responseSchema = z.object({
success: z.boolean().optional().openapi({ example: true }),
message: z.string().optional().openapi({ example: "user access" }),
});
app.openapi(
createRoute({
tags: ["logistics"],
summary: "Creates silo adjustmennt",
method: "post",
path: "/createsiloadjustment",
middleware: authMiddleware,
description:
"Creates a silo adjustment for the silo if and stores the stock numbers.",
responses: responses(),
}),
async (c) => {
//apiHit(c, { endpoint: "api/sqlProd/close" });
const authHeader = c.req.header("Authorization");
const token = authHeader?.split("Bearer ")[1] || "";
try {
const payload = await verify(token, process.env.JWT_SECRET!);
try {
//return apiReturn(c, true, access?.message, access?.data, 200);
const data = await c.req.json();
const createSiloAdj = await createSiloAdjustment(
data,
payload.user
);
return c.json(
{
success: createSiloAdj.success,
message: createSiloAdj.message,
data: createSiloAdj.data,
},
200
);
} catch (error) {
//console.log(error);
//return apiReturn(c, false, "Error in setting the user access", error, 400);
return c.json(
{
success: false,
message: "Missing data please try again",
error,
},
400
);
}
} catch (error) {
return c.json({ success: false, message: "Unauthorized" }, 401);
}
}
);
export default app;

View File

@@ -0,0 +1,85 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { verify } from "hono/jwt";
import { authMiddleware } from "../../../auth/middleware/authMiddleware.js";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { createSiloAdjustment } from "../../controller/siloAdjustments/createSiloAdjustment.js";
import { postSiloComment } from "../../controller/siloAdjustments/postComment.js";
const app = new OpenAPIHono();
const ParamsSchema = z.object({
adjId: z
.string()
.min(3)
.openapi({
param: {
name: "adjId",
in: "path",
},
example: "3b555052-a960-4301-8d38-a6f1acb98dbe",
}),
});
const Body = z.object({
comment: z
.string()
.openapi({ example: "Reason to why i had a badd adjustment." }),
});
app.openapi(
createRoute({
tags: ["logistics"],
summary: "Post a comment to why you had a discrepancy",
method: "post",
path: "/postcomment/:adjId",
middleware: authMiddleware,
request: {
params: ParamsSchema,
body: { content: { "application/json": { schema: Body } } },
},
// description:
// "Creates a silo adjustment for the silo if and stores the stock numbers.",
responses: responses(),
}),
async (c: any) => {
//apiHit(c, { endpoint: "api/sqlProd/close" });
const authHeader = c.req.header("Authorization");
const token = authHeader?.split("Bearer ")[1] || "";
const { adjId } = c.req.valid("param");
try {
const payload = await verify(token, process.env.JWT_SECRET!);
try {
//return apiReturn(c, true, access?.message, access?.data, 200);
const data = await c.req.json();
const addComment = await postSiloComment(
adjId,
data.comment,
data.key,
payload.user
);
return c.json(
{
success: addComment.success,
message: addComment.message,
data: addComment.data,
},
200
);
} catch (error) {
return c.json(
{
success: false,
message: "Missing data please try again",
error,
},
400
);
}
} catch (error) {
return c.json({ success: false, message: "Unauthorized" }, 401);
}
}
);
export default app;

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}}:5173/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,26 @@
export const siloQuery = `
SELECT
V_LagerAbteilungen.Bezeichnung AS Description,
V_LagerAbteilungen.IdWarenLager AS WarehouseID,
V_LagerAbteilungen.IdLagerAbteilung AS LocationID,
ROUND(SUM(einlagerungsmengesum), 2) AS Stock_Total,
COALESCE(LastAdjustment, '1900-01-01') AS LastAdjustment
FROM AlplaPROD_test1.dbo.V_LagerAbteilungen (NOLOCK)
JOIN
AlplaPROD_test1.dbo.V_LagerPositionenBarcodes ON
AlplaPROD_test1.dbo.V_LagerAbteilungen.IdLagerAbteilung =
AlplaPROD_test1.dbo.V_LagerPositionenBarcodes.IdLagerAbteilung
LEFT JOIN (
SELECT
IdLagerAbteilung,
MAX(CASE WHEN CONVERT(CHAR(10), Buchungsdatum, 120) IS NULL THEN '1900-01-01' ELSE CONVERT(CHAR(10), Buchungsdatum, 120) END) AS LastAdjustment
FROM AlplaPROD_test1.dbo.V_LagerBuchungen (NOLOCK)
WHERE urheber = 2900
GROUP BY IdLagerAbteilung
) AS LastAdj ON AlplaPROD_test1.dbo.V_LagerAbteilungen.IdLagerAbteilung = LastAdj.IdLagerAbteilung
WHERE materialsilo = 1
AND aktiv = 1
GROUP BY V_LagerAbteilungen.Bezeichnung, V_LagerAbteilungen.IdWarenLager, V_LagerAbteilungen.IdLagerAbteilung, LastAdjustment
ORDER BY V_LagerAbteilungen.Bezeichnung
`;