From c509c7fe286a43ab0ffbf86635631477237632b5 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Wed, 12 Nov 2025 20:22:53 -0600 Subject: [PATCH] feat(notify): material per day for the next 90 days --- .../notifications/materialPerDay.ts | 88 ++++++++++++++ .../services/notifications/notifyService.ts | 62 +++++----- .../notifications/routes/materialPerDay.ts | 37 ++++++ .../querys/dataMart/materialPerDay.ts | 115 ++++++++++++++++++ 4 files changed, 271 insertions(+), 31 deletions(-) create mode 100644 lstV2/server/services/notifications/controller/notifications/materialPerDay.ts create mode 100644 lstV2/server/services/notifications/routes/materialPerDay.ts create mode 100644 lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts diff --git a/lstV2/server/services/notifications/controller/notifications/materialPerDay.ts b/lstV2/server/services/notifications/controller/notifications/materialPerDay.ts new file mode 100644 index 0000000..571b492 --- /dev/null +++ b/lstV2/server/services/notifications/controller/notifications/materialPerDay.ts @@ -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>} */ + 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), + }; +} diff --git a/lstV2/server/services/notifications/notifyService.ts b/lstV2/server/services/notifications/notifyService.ts index 846e8f7..e990010 100644 --- a/lstV2/server/services/notifications/notifyService.ts +++ b/lstV2/server/services/notifications/notifyService.ts @@ -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; diff --git a/lstV2/server/services/notifications/routes/materialPerDay.ts b/lstV2/server/services/notifications/routes/materialPerDay.ts new file mode 100644 index 0000000..c005b18 --- /dev/null +++ b/lstV2/server/services/notifications/routes/materialPerDay.ts @@ -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; diff --git a/lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts b/lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts new file mode 100644 index 0000000..d7d5774 --- /dev/null +++ b/lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts @@ -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 shift‑based 24‑hour window (e.g. 06:00 → next day 06: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); +`;