From 564f0b5addd109018a806edd6a1fed4399ea63aa Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Sat, 15 Nov 2025 16:22:52 -0600 Subject: [PATCH] feat(materials per day): more work on materials per day --- .../materialPerDay.ts | 59 ++++++++++++++++- .../materialsPerDay/materialPurchases.ts | 38 +++++++++++ .../materialsPerDay/materialWithInv.ts | 64 +++++++++++++++++++ .../notifications/routes/materialPerDay.ts | 2 +- .../querys/dataMart/materialPerDay.ts | 18 ++++++ 5 files changed, 177 insertions(+), 4 deletions(-) rename lstV2/server/services/notifications/controller/{notifications => materialsPerDay}/materialPerDay.ts (57%) create mode 100644 lstV2/server/services/notifications/controller/materialsPerDay/materialPurchases.ts create mode 100644 lstV2/server/services/notifications/controller/materialsPerDay/materialWithInv.ts diff --git a/lstV2/server/services/notifications/controller/notifications/materialPerDay.ts b/lstV2/server/services/notifications/controller/materialsPerDay/materialPerDay.ts similarity index 57% rename from lstV2/server/services/notifications/controller/notifications/materialPerDay.ts rename to lstV2/server/services/notifications/controller/materialsPerDay/materialPerDay.ts index 571b492..feb7a5b 100644 --- a/lstV2/server/services/notifications/controller/notifications/materialPerDay.ts +++ b/lstV2/server/services/notifications/controller/materialsPerDay/materialPerDay.ts @@ -6,9 +6,26 @@ import { parseISO, startOfWeek, } from "date-fns"; +import { db } from "../../../../../database/dbclient.js"; +import { invHistoricalData } from "../../../../../database/schema/historicalINV.js"; import { tryCatch } from "../../../../globalUtils/tryCatch.js"; import { query } from "../../../sqlServer/prodSqlServer.js"; -import { materialPerDay } from "../../../sqlServer/querys/dataMart/materialPerDay.js"; +import { + materialPerDay, + materialPurchasesPerDay, +} from "../../../sqlServer/querys/dataMart/materialPerDay.js"; +import { materialPurchases } from "./materialPurchases.js"; +import { buildInventoryTimeline } from "./materialWithInv.js"; + +const getInv = async () => { + const { data, error } = await tryCatch(db.select().from(invHistoricalData)); + + if (error) { + return []; + } + + return data; +}; function toDate(val: any) { if (val instanceof Date) return val; @@ -16,7 +33,7 @@ function toDate(val: any) { return new Date(val); } -export function sumByMaterialAndWeek(data: any) { +function sumByMaterialAndWeek(data: any) { /** @type {Record>} */ const grouped: any = {}; @@ -80,9 +97,45 @@ export default async function materialPerDayCheck() { }; } + // purchases + + const { data: p, error: pe } = (await tryCatch( + query( + materialPurchasesPerDay + .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: [], + }; + } + + const openingInventory: Record = {}; + const inventoryRows = await getInv(); + for (const row of inventoryRows) { + openingInventory[row.article] = Number(row.total_QTY) || 0; + } + return { success: true, message: "material data", - data: sumByMaterialAndWeek(data.data), + data: buildInventoryTimeline( + sumByMaterialAndWeek(data.data) as any, + openingInventory, + ), }; } diff --git a/lstV2/server/services/notifications/controller/materialsPerDay/materialPurchases.ts b/lstV2/server/services/notifications/controller/materialsPerDay/materialPurchases.ts new file mode 100644 index 0000000..a635943 --- /dev/null +++ b/lstV2/server/services/notifications/controller/materialsPerDay/materialPurchases.ts @@ -0,0 +1,38 @@ +import { formatISO, parseISO, startOfWeek } from "date-fns"; + +function toDate(val: any) { + if (val instanceof Date) return val; + if (typeof val === "string") return parseISO(val.replace(" ", "T")); + return new Date(val); +} + +export const materialPurchases = async (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; +}; diff --git a/lstV2/server/services/notifications/controller/materialsPerDay/materialWithInv.ts b/lstV2/server/services/notifications/controller/materialsPerDay/materialWithInv.ts new file mode 100644 index 0000000..268b658 --- /dev/null +++ b/lstV2/server/services/notifications/controller/materialsPerDay/materialWithInv.ts @@ -0,0 +1,64 @@ +export const buildInventoryTimeline = ( + weeklyDemand: Array<{ + MaterialHumanReadableId: string; + WeekStart: string; + WeeklyDemand: number; + }>, + opening: Record, +) => { + // group weekly demand by material + const grouped: Record< + string, + Array<{ WeekStart: string; Demand: number }> + > = {}; + + for (const d of weeklyDemand) { + const mat = d.MaterialHumanReadableId; + grouped[mat] ??= []; + grouped[mat].push({ + WeekStart: d.WeekStart, + Demand: Number(d.WeeklyDemand), + }); + } + + // sort weeks chronologically per material + for (const mat of Object.keys(grouped)) { + grouped[mat].sort( + (a, b) => + new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(), + ); + } + + const result: Array<{ + MaterialHumanReadableId: string; + WeekStart: string; + OpeningInventory: number; + Consumption: number; + ClosingInventory: number; + }> = []; + + for (const [mat, weeks] of Object.entries(grouped)) { + // get starting inventory from the ERP result + let inv = opening[mat] ?? 0; + + for (const w of weeks) { + const week = w.WeekStart; + const demand = Number(w.Demand); + + const openingInv = inv; + const closingInv = openingInv - demand; + + result.push({ + MaterialHumanReadableId: mat, + WeekStart: week, + OpeningInventory: Number(openingInv.toFixed(2)), + Consumption: Number(demand.toFixed(2)), + ClosingInventory: Number(closingInv.toFixed(2)), + }); + + inv = closingInv; + } + } + + return result; +}; diff --git a/lstV2/server/services/notifications/routes/materialPerDay.ts b/lstV2/server/services/notifications/routes/materialPerDay.ts index c005b18..8770b86 100644 --- a/lstV2/server/services/notifications/routes/materialPerDay.ts +++ b/lstV2/server/services/notifications/routes/materialPerDay.ts @@ -2,8 +2,8 @@ import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { apiHit } from "../../../globalUtils/apiHits.js"; import { responses } from "../../../globalUtils/routeDefs/responses.js"; +import materialPerDayCheck from "../controller/materialsPerDay/materialPerDay.js"; import fifoIndexCheck from "../controller/notifications/fifoIndex.js"; -import materialPerDayCheck from "../controller/notifications/materialPerDay.js"; const app = new OpenAPIHono({ strict: false }); diff --git a/lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts b/lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts index d7d5774..9b84397 100644 --- a/lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts +++ b/lstV2/server/services/sqlServer/querys/dataMart/materialPerDay.ts @@ -113,3 +113,21 @@ ORDER BY n.ProductionLotHumanReadableId OPTION (MAXRECURSION 0); `; + +export const materialPurchasesPerDay = ` +use AlplaPROD_test1 +declare @startDate nvarchar(max) = '[startDate]' +declare @endDate nvarchar(max) = '[endDate]' +SELECT + [IdBestellung] as purhcaseOrder + + ,[IdArtikelVarianten] + ,convert(date, [BestellDatum], 120) as orderDate + ,convert(date, [Lieferdatum], 120) as deliveryDate + ,[BestellMenge] as qty + ,[BestellMengeVPK] as pallets + + FROM [dbo].[T_Bestellpositionen] + + where PositionsStatus = 2 and convert(date, [Lieferdatum], 120) between @startDate and @endDate --and IdBestellung in( 5535,5534,5513) +`;