feat(notify): material per day for the next 90 days

This commit is contained in:
2025-11-12 20:22:53 -06:00
parent 6f632ecd68
commit c509c7fe28
4 changed files with 271 additions and 31 deletions

View File

@@ -0,0 +1,88 @@
import {
addDays,
format,
formatISO,
isBefore,
parseISO,
startOfWeek,
} from "date-fns";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { materialPerDay } from "../../../sqlServer/querys/dataMart/materialPerDay.js";
function toDate(val: any) {
if (val instanceof Date) return val;
if (typeof val === "string") return parseISO(val.replace(" ", "T"));
return new Date(val);
}
export function sumByMaterialAndWeek(data: any) {
/** @type {Record<string, Record<string, number>>} */
const grouped: any = {};
for (const r of data) {
const mat = String(r.MaterialHumanReadableId);
const d = toDate(r.CalDate);
const week = formatISO(startOfWeek(d, { weekStartsOn: 1 }), {
representation: "date",
});
grouped[mat] ??= {};
grouped[mat][week] ??= 0;
grouped[mat][week] += Number(r.DailyMaterialDemand) || 0;
}
const result = [];
for (const [mat, weeks] of Object.entries(grouped)) {
// @ts-ignore
for (const [week, total] of Object.entries(weeks)) {
result.push({
MaterialHumanReadableId: mat,
WeekStart: week,
WeeklyDemand: Number(total).toFixed(2),
});
}
}
return result;
}
export default async function materialPerDayCheck() {
/**
* getting the shipped pallets
*/
const startDate = format(new Date(Date.now()), "yyyy-MM-dd");
const endDate = format(addDays(new Date(Date.now()), 90), "yyyy-MM-dd");
const { data, error } = (await tryCatch(
query(
materialPerDay
.replace("[startDate]", startDate)
.replace("[endDate]", endDate),
"material check",
),
)) as any;
if (error) {
return {
success: false,
message: "Error getting the material data",
error,
};
}
if (!data.success) {
return {
success: false,
message: data.message,
data: [],
};
}
return {
success: true,
message: "material data",
data: sumByMaterialAndWeek(data.data),
};
}

View File

@@ -1,60 +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 { tryCatch } from "../../globalUtils/tryCatch.js";
import { createLog } from "../logger/logger.js";
import fifoIndex from "./routes/fifoIndex.js";
import notifyStats from "./routes/getActiveNotifications.js";
import notify from "./routes/getNotifications.js";
import tiTrigger from "./routes/manualTiggerTi.js";
import materialCheck from "./routes/materialPerDay.js";
import blocking from "./routes/qualityBlocking.js";
import sendemail from "./routes/sendMail.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,
sendemail,
notifyStats,
tiTrigger,
blocking,
notify,
fifoIndex,
materialCheck,
] as const;
const appRoutes = routes.forEach((route) => {
app.route("/notify", 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.",
});
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)
db.select().from(notifications),
);
if (notesError) {
createLog(
"error",
"notify",
"notify",
`There was an error getting the notifications: ${JSON.stringify(
notesError
)}`
);
createLog(
"error",
"notify",
"notify",
`There was an error getting the notifications: ${JSON.stringify(
notesError,
)}`,
);
}
setTimeout(() => {
notificationCreate();
startNotificationMonitor();
notificationCreate();
startNotificationMonitor();
}, 5 * 1000);
export default app;

View File

@@ -0,0 +1,37 @@
// 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 fifoIndexCheck from "../controller/notifications/fifoIndex.js";
import materialPerDayCheck from "../controller/notifications/materialPerDay.js";
const app = new OpenAPIHono({ strict: false });
app.openapi(
createRoute({
tags: ["notify"],
summary: "",
method: "get",
path: "/materialperday",
//middleware: authMiddleware,
responses: responses(),
}),
async (c) => {
/**
* get the blocking notification stuff
*/
apiHit(c, { endpoint: "/materialperday" });
/**
* getting the shipped pallets
*/
const checkedData = await materialPerDayCheck();
return c.json({
success: checkedData.success,
message: checkedData.message,
data: checkedData.data,
});
},
);
export default app;

View File

@@ -0,0 +1,115 @@
/**
* This will return the material demand per day
* startdate and end date should be passed over
*/
export const materialPerDay = `
use [test3_AlplaPROD2.0_Read]
DECLARE @ShiftStartHour INT = 6
declare @startDate nvarchar(max) = '[startDate]'
declare @endDate nvarchar(max) = '[endDate]'
;with Calendar as (
select cast(@startDate as date) CalDate
union all
select dateadd(day,1,CalDate)
from Calendar
where CalDate < @endDate
),
DailySplit AS (
SELECT
-- Lot fields
l.Id AS ProductionLotId,
l.ProductionLotHumanReadableId,
l.ArticleHumanReadableId,
l.ArticleDescription,
l.LocationId,
l.MachineHumanReadableId,
l.MachineDescription,
l.StartDate,
l.FinishDate,
l.ProductionCustomerDescription,
l.ProductionCustomerHumanReadableId,
l.PlannedQuantity,
l.PlannedLoadingUnit,
l.Cavity,
l.Utilisation,
l.TotalMaterialDemand AS LotTotalMaterialDemand,
-- Material fields
m.MaterialHumanReadableId,
m.MaterialDescription,
m.TotalDemand AS LotMaterialTotalDemand,
c.CalDate,
DATEDIFF(SECOND,l.StartDate,l.FinishDate) AS LotDurationSec,
-- build shiftbased 24hour window (e.g. 06:00 → next day06:00)
CASE
WHEN l.StartDate > DATEADD(HOUR,@ShiftStartHour,CAST(c.CalDate AS DATETIME2(7)))
THEN l.StartDate
ELSE DATEADD(HOUR,@ShiftStartHour,CAST(c.CalDate AS DATETIME2(7)))
END AS DayStart,
CASE
WHEN l.FinishDate < DATEADD(SECOND,-0.0000001,
DATEADD(HOUR,@ShiftStartHour,
DATEADD(DAY,1,CAST(c.CalDate AS DATETIME2(7)))))
THEN l.FinishDate
ELSE DATEADD(SECOND,-0.0000001,
DATEADD(HOUR,@ShiftStartHour,
DATEADD(DAY,1,CAST(c.CalDate AS DATETIME2(7)))))
END AS DayEnd
FROM [issueMaterial].[ProductionLot] (nolock) AS l
LEFT JOIN [issueMaterial].[MaterialDemand] (nolock) AS m
ON m.ProductionLotId = l.Id
CROSS JOIN Calendar AS c
WHERE DATEADD(HOUR,@ShiftStartHour,CAST(c.CalDate AS DATETIME2))
< l.FinishDate
AND DATEADD(HOUR,@ShiftStartHour+24,CAST(c.CalDate AS DATETIME2))
> l.StartDate
--and l.[ProductionLotHumanReadableId] = 26364
),
Fraction AS (
SELECT
ds.*,
DATEDIFF(SECOND,ds.DayStart,ds.DayEnd) AS OverlapSec
FROM DailySplit ds
),
Normalized AS (
SELECT
f.*,
f.OverlapSec * 1.0 /
NULLIF(SUM(f.OverlapSec) OVER
(PARTITION BY f.ProductionLotId, f.MaterialHumanReadableId),0)
AS NormalizedFraction
FROM Fraction f
)
SELECT
n.ProductionLotId,
n.ProductionLotHumanReadableId,
n.ArticleHumanReadableId,
n.ArticleDescription,
n.LocationId,
n.MachineHumanReadableId,
n.MachineDescription,
n.StartDate,
n.FinishDate,
n.ProductionCustomerDescription,
n.ProductionCustomerHumanReadableId,
n.PlannedQuantity,
n.PlannedLoadingUnit,
n.Cavity,
n.Utilisation,
n.LotTotalMaterialDemand,
n.MaterialHumanReadableId,
n.MaterialDescription,
n.LotMaterialTotalDemand,
n.CalDate,
n.NormalizedFraction * n.LotMaterialTotalDemand AS DailyMaterialDemand
FROM Normalized n
ORDER BY
n.MaterialHumanReadableId,
n.CalDate,
n.ProductionLotHumanReadableId
OPTION (MAXRECURSION 0);
`;