feat(notify): material per day for the next 90 days
This commit is contained in:
@@ -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),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
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 { db } from "../../../database/dbclient.js";
|
||||||
|
|
||||||
import { notifications } from "../../../database/schema/notifications.js";
|
import { notifications } from "../../../database/schema/notifications.js";
|
||||||
|
import { tryCatch } from "../../globalUtils/tryCatch.js";
|
||||||
import { createLog } from "../logger/logger.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 { note, notificationCreate } from "./utils/masterNotifications.js";
|
||||||
import { startNotificationMonitor } from "./utils/processNotifications.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 app = new OpenAPIHono();
|
||||||
|
|
||||||
@@ -23,6 +22,7 @@ const routes = [
|
|||||||
blocking,
|
blocking,
|
||||||
notify,
|
notify,
|
||||||
fifoIndex,
|
fifoIndex,
|
||||||
|
materialCheck,
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const appRoutes = routes.forEach((route) => {
|
const appRoutes = routes.forEach((route) => {
|
||||||
@@ -38,7 +38,7 @@ app.all("/notify/*", (c) => {
|
|||||||
|
|
||||||
// check if the mastNotications is changed compared to the db and add if needed.
|
// check if the mastNotications is changed compared to the db and add if needed.
|
||||||
const { data: notes, error: notesError } = await tryCatch(
|
const { data: notes, error: notesError } = await tryCatch(
|
||||||
db.select().from(notifications)
|
db.select().from(notifications),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (notesError) {
|
if (notesError) {
|
||||||
@@ -47,8 +47,8 @@ if (notesError) {
|
|||||||
"notify",
|
"notify",
|
||||||
"notify",
|
"notify",
|
||||||
`There was an error getting the notifications: ${JSON.stringify(
|
`There was an error getting the notifications: ${JSON.stringify(
|
||||||
notesError
|
notesError,
|
||||||
)}`
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
lstV2/server/services/notifications/routes/materialPerDay.ts
Normal file
37
lstV2/server/services/notifications/routes/materialPerDay.ts
Normal 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;
|
||||||
@@ -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);
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user