From c2ae445ea4d26b047a2ee5d16041ed230f7b2061 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Sat, 1 Nov 2025 00:05:01 -0500 Subject: [PATCH] feat(forecast data): added in a historical forecast data set --- .../app/logistics/warehouse/ForecastData.bru | 16 + .../demandManagement/forecastEDIData.ts | 63 ++- app/src/internal/logistics/routes.ts | 2 + .../logistics/routes/demandMgt/dmRoutes.ts | 2 +- .../routes/demandMgt/forecastEDIData.ts | 11 +- app/src/pkg/db/schema/forecastEDIData.ts | 5 +- .../dm/forecast/mappings/loralForecast.ts | 457 +++++++++--------- 7 files changed, 314 insertions(+), 242 deletions(-) create mode 100644 LogisticsSupportTool_API_DOCS/app/logistics/warehouse/ForecastData.bru diff --git a/LogisticsSupportTool_API_DOCS/app/logistics/warehouse/ForecastData.bru b/LogisticsSupportTool_API_DOCS/app/logistics/warehouse/ForecastData.bru new file mode 100644 index 0000000..e1c943b --- /dev/null +++ b/LogisticsSupportTool_API_DOCS/app/logistics/warehouse/ForecastData.bru @@ -0,0 +1,16 @@ +meta { + name: ForecastData + type: http + seq: 1 +} + +post { + url: {{url}}/lst/api/logistics/dm/forecastData + body: none + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/app/src/internal/logistics/controller/demandManagement/forecastEDIData.ts b/app/src/internal/logistics/controller/demandManagement/forecastEDIData.ts index 7411d6e..9e6d570 100644 --- a/app/src/internal/logistics/controller/demandManagement/forecastEDIData.ts +++ b/app/src/internal/logistics/controller/demandManagement/forecastEDIData.ts @@ -8,10 +8,63 @@ * add_date */ -type IncomingForecastData = { - CustomerArticleNumber: number; -}; +import { db } from "../../../../pkg/db/db.js"; +import { + type ForecastData, + forecastData, + forecastDataSchema, +} from "../../../../pkg/db/schema/forecastEDIData.js"; +import { prodQuery } from "../../../../pkg/prodSql/prodQuery.js"; +import { activeArticle } from "../../../../pkg/prodSql/querys/datamart/article.js"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; -export const forecastData = async (data: IncomingForecastData) => { - console.log(data); +export const forecastEdiData = async (data: ForecastData[]) => { + //console.log(data); + const { data: article, error } = await tryCatch( + prodQuery(activeArticle, "forecast data active articles"), + ); + + if (error) { + return { + success: false, + message: "Failed to get active articles", + error: error, + }; + } + + const forecaseEDIDATA = []; + for (let i = 0; i < data.length; i++) { + const activeAV = article?.data.filter( + (c: any) => + c?.CustomerArticleNumber === data[i].customerArticleNo?.toString(), + ); + const newData = data[i]; + //console.log(activeAV[0].IdArtikelvarianten); + + forecaseEDIDATA.push({ + ...newData, + article: activeAV[0].IdArtikelvarianten, + requirementDate: new Date(newData.requirementDate), + }); + } + + console.log(forecaseEDIDATA[0]); + const { data: f, error: ef } = await tryCatch( + db.insert(forecastData).values(forecaseEDIDATA), + ); + + if (ef) { + console.log(ef); + return { + success: false, + message: "There was an error posting the new data", + error: ef, + }; + } + + return { + success: true, + message: "All forecast Data was posted", + data: [], + }; }; diff --git a/app/src/internal/logistics/routes.ts b/app/src/internal/logistics/routes.ts index 5018446..57ae16a 100644 --- a/app/src/internal/logistics/routes.ts +++ b/app/src/internal/logistics/routes.ts @@ -1,10 +1,12 @@ import type { Express, Request, Response } from "express"; +import dm from "./routes/demandMgt/dmRoutes.js"; import labeling from "./routes/labeling/labelingRoutes.js"; import schedule from "./routes/scheduler/scheduleRoutes.js"; export const setupLogisticsRoutes = (app: Express, basePath: string) => { app.use(basePath + "/api/logistics/schedule", schedule); app.use(basePath + "/api/logistics/labeling", labeling); + app.use(basePath + "/api/logistics/dm", dm); // app.use( // basePath + "/api/admin/users", diff --git a/app/src/internal/logistics/routes/demandMgt/dmRoutes.ts b/app/src/internal/logistics/routes/demandMgt/dmRoutes.ts index 66387c2..2a4fd05 100644 --- a/app/src/internal/logistics/routes/demandMgt/dmRoutes.ts +++ b/app/src/internal/logistics/routes/demandMgt/dmRoutes.ts @@ -4,6 +4,6 @@ import forecastData from "./forecastEDIData.js"; const router = Router(); -router.use("/", requireAuth(), forecastData); +router.use("/", forecastData); export default router; diff --git a/app/src/internal/logistics/routes/demandMgt/forecastEDIData.ts b/app/src/internal/logistics/routes/demandMgt/forecastEDIData.ts index 2272223..53a0d2a 100644 --- a/app/src/internal/logistics/routes/demandMgt/forecastEDIData.ts +++ b/app/src/internal/logistics/routes/demandMgt/forecastEDIData.ts @@ -1,7 +1,7 @@ import type { Request, Response } from "express"; import { Router } from "express"; import z from "zod"; -import { preprintLabels } from "../../controller/labeling/preprint.js"; +import { forecastEdiData } from "../../controller/demandManagement/forecastEDIData.js"; export const Preprint = z.object({ scannerId: z.number(), @@ -15,13 +15,10 @@ export const Preprint = z.object({ const router = Router(); -router.post("/preprint", async (req: Request, res: Response) => { - const parsed = Preprint.safeParse(req.body); - const print = await preprintLabels(req.body, req.user?.username || ""); +router.post("/forecastData", async (req: Request, res: Response) => { + await forecastEdiData(req.body); - res - .status(200) - .json({ success: print.success, message: print.message, data: print.data }); + res.status(200).json({ success: true, message: "Forecast Data", data: [] }); }); export default router; diff --git a/app/src/pkg/db/schema/forecastEDIData.ts b/app/src/pkg/db/schema/forecastEDIData.ts index 47e37c8..ca74112 100644 --- a/app/src/pkg/db/schema/forecastEDIData.ts +++ b/app/src/pkg/db/schema/forecastEDIData.ts @@ -13,12 +13,11 @@ import { z } from "zod"; export const forecastData = pgTable("forecast_Data", { id: uuid("id").defaultRandom().primaryKey(), - customerArticleNumber: text("customer_article_number"), + customerArticleNo: text("customer_article_no"), dateRequested: timestamp("date_requested").defaultNow(), quantity: real("quantity"), - requestDate: timestamp("request_date").notNull(), + requirementDate: timestamp("requirement_date").notNull(), article: integer("article"), - customerID: integer("customer_id").notNull(), createdAt: timestamp("created_at").defaultNow(), }); diff --git a/lstV2/server/services/logistics/controller/dm/forecast/mappings/loralForecast.ts b/lstV2/server/services/logistics/controller/dm/forecast/mappings/loralForecast.ts index cff06f7..97ebbdd 100644 --- a/lstV2/server/services/logistics/controller/dm/forecast/mappings/loralForecast.ts +++ b/lstV2/server/services/logistics/controller/dm/forecast/mappings/loralForecast.ts @@ -1,286 +1,291 @@ +import axios from "axios"; +import { addDays } from "date-fns"; +import XLSX from "xlsx"; import { db } from "../../../../../../../database/dbclient.js"; import { settings } from "../../../../../../../database/schema/settings.js"; import { tryCatch } from "../../../../../../globalUtils/tryCatch.js"; -import XLSX from "xlsx"; -import { excelDateStuff } from "../../../../utils/excelDateStuff.js"; -import { postForecast } from "../postForecast.js"; +import { createLog } from "../../../../../logger/logger.js"; +import { sendEmail } from "../../../../../notifications/controller/sendMail.js"; import { query } from "../../../../../sqlServer/prodSqlServer.js"; import { activeArticle } from "../../../../../sqlServer/querys/dataMart/article.js"; -import { addDays } from "date-fns"; -import { sendEmail } from "../../../../../notifications/controller/sendMail.js"; -import { createLog } from "../../../../../logger/logger.js"; +import { excelDateStuff } from "../../../../utils/excelDateStuff.js"; +import { postForecast } from "../postForecast.js"; let customerID = 4; export const lorealForecast = async (data: any, user: any) => { - /** - * Post a standard forecast based on the standard template. - */ + /** + * Post a standard forecast based on the standard template. + */ - const { data: s, error: e } = await tryCatch(db.select().from(settings)); + const { data: s, error: e } = await tryCatch(db.select().from(settings)); - if (e) { - return { - sucess: false, - message: `Error getting settings`, - data: e, - }; - } + if (e) { + return { + sucess: false, + message: `Error getting settings`, + data: e, + }; + } - const plantToken = s.filter((s) => s.name === "plantToken"); + const plantToken = s.filter((s) => s.name === "plantToken"); - const arrayBuffer = await data.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); + const arrayBuffer = await data.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); - const workbook = XLSX.read(buffer, { type: "buffer" }); + const workbook = XLSX.read(buffer, { type: "buffer" }); - const sheet: any = workbook.Sheets["Alpla HDPE"]; - const range = XLSX.utils.decode_range(sheet["!ref"]); + const sheet: any = workbook.Sheets["Alpla HDPE"]; + const range = XLSX.utils.decode_range(sheet["!ref"]); - const psheet: any = workbook.Sheets["Alpla PET"]; - const prange = XLSX.utils.decode_range(psheet["!ref"]); + const psheet: any = workbook.Sheets["Alpla PET"]; + const prange = XLSX.utils.decode_range(psheet["!ref"]); - const headers = []; - for (let C = range.s.c; C <= range.e.c; ++C) { - const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1 - const cell = sheet[cellAddress]; - headers.push(cell ? cell.v : undefined); - } + const headers = []; + for (let C = range.s.c; C <= range.e.c; ++C) { + const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1 + const cell = sheet[cellAddress]; + headers.push(cell ? cell.v : undefined); + } - const pheaders = []; - for (let C = prange.s.c; C <= prange.e.c; ++C) { - const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1 - const cell = psheet[cellAddress]; - pheaders.push(cell ? cell.v : undefined); - } + const pheaders = []; + for (let C = prange.s.c; C <= prange.e.c; ++C) { + const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1 + const cell = psheet[cellAddress]; + pheaders.push(cell ? cell.v : undefined); + } - const ebmForeCastData: any = XLSX.utils.sheet_to_json(sheet, { - defval: "", - header: headers, - range: 3, - }); + const ebmForeCastData: any = XLSX.utils.sheet_to_json(sheet, { + defval: "", + header: headers, + range: 3, + }); - const petForeCastData: any = XLSX.utils.sheet_to_json(psheet, { - defval: "", - header: pheaders, - range: 3, - }); + const petForeCastData: any = XLSX.utils.sheet_to_json(psheet, { + defval: "", + header: pheaders, + range: 3, + }); - const ebmForecastData: any = []; - const missingSku: any = []; + const ebmForecastData: any = []; + const missingSku: any = []; - const { data: a, error: ae } = await tryCatch( - query(activeArticle, "Loreal calling active av") - ); + const { data: a, error: ae } = await tryCatch( + query(activeArticle, "Loreal calling active av"), + ); - if (ae) { - return { - success: false, - message: "Error getting active av", - data: [], - }; - } + if (ae) { + return { + success: false, + message: "Error getting active av", + data: [], + }; + } - const article: any = a?.data; + const article: any = a?.data; - // process the ebm forcast - for (let i = 0; i < ebmForeCastData.length; i++) { - // bottle code - const sku = ebmForeCastData[i]["HDPE Bottle Code"]; + // process the ebm forcast + for (let i = 0; i < ebmForeCastData.length; i++) { + // bottle code + const sku = ebmForeCastData[i]["HDPE Bottle Code"]; - // ignore the blanks - if (sku === "") continue; + // ignore the blanks + if (sku === "") continue; - // ignore zero qty - // if (ebmForeCastData[i][`Day ${i}`]) continue; + // ignore zero qty + // if (ebmForeCastData[i][`Day ${i}`]) continue; - for (let f = 0; f <= 90; f++) { - const day = `Day ${f + 1}`; - // if (ebmForeCastData[i][day] === 0) continue; + for (let f = 0; f <= 90; f++) { + const day = `Day ${f + 1}`; + // if (ebmForeCastData[i][day] === 0) continue; - const forcast = { - customerArticleNo: sku, - requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)), - quantity: ebmForeCastData[i][day] ?? 0, - }; + const forcast = { + customerArticleNo: sku, + requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)), + quantity: ebmForeCastData[i][day] ?? 0, + }; - if (forcast.quantity === 0) continue; + if (forcast.quantity === 0) continue; - // checking to make sure there is a real av to add to. - const activeAV = article.filter( - (c: any) => - c?.CustomerArticleNumber === - forcast.customerArticleNo.toString() - ); + // checking to make sure there is a real av to add to. + const activeAV = article.filter( + (c: any) => + c?.CustomerArticleNumber === forcast.customerArticleNo.toString(), + ); - if (activeAV.length === 0) { - if (typeof forcast.customerArticleNo === "number") { - missingSku.push(forcast); - } - continue; - } + if (activeAV.length === 0) { + if (typeof forcast.customerArticleNo === "number") { + missingSku.push(forcast); + } + continue; + } - ebmForecastData.push(forcast); - } + ebmForecastData.push(forcast); + } - //console.log(ebmForeCastData.length); - } + //console.log(ebmForeCastData.length); + } - // petForeCastData.forEach((item: any, index: any) => { - // //console.log(`Processing item ${index + 1} of ${forecastData.length}`); + // petForeCastData.forEach((item: any, index: any) => { + // //console.log(`Processing item ${index + 1} of ${forecastData.length}`); - // // Extract the customer code - // const customerCode = item["SOUTH PET BOTTLES"]; + // // Extract the customer code + // const customerCode = item["SOUTH PET BOTTLES"]; - // // Process each date in the current object - // for (const [date, qty] of Object.entries(item)) { - // // Skip metadata fields - // if (petMetadataFields.includes(date)) continue; + // // Process each date in the current object + // for (const [date, qty] of Object.entries(item)) { + // // Skip metadata fields + // if (petMetadataFields.includes(date)) continue; - // if (qty === 0) continue; + // if (qty === 0) continue; - // // Create your transformed record - // const record = { - // customerArticleNo: customerCode, - // requirementDate: excelDateStuff(parseInt(date)), - // quantity: qty, - // }; + // // Create your transformed record + // const record = { + // customerArticleNo: customerCode, + // requirementDate: excelDateStuff(parseInt(date)), + // quantity: qty, + // }; - // // Do something with this record - // petForecastData.push(record); - // } - // }); + // // Do something with this record + // petForecastData.push(record); + // } + // }); - // pet forecast - for (let i = 0; i < petForeCastData.length; i++) { - // bottle code - const sku = petForeCastData[i]["South PET Bottle Code"]; + // pet forecast + for (let i = 0; i < petForeCastData.length; i++) { + // bottle code + const sku = petForeCastData[i]["South PET Bottle Code"]; - // ignore the blanks - if (sku === "") continue; + // ignore the blanks + if (sku === "") continue; - // ignore zero qty - // if (ebmForeCastData[i][`Day ${i}`]) continue; + // ignore zero qty + // if (ebmForeCastData[i][`Day ${i}`]) continue; - for (let f = 0; f <= 90; f++) { - const day = `Day ${f + 1}`; - // if (ebmForeCastData[i][day] === 0) continue; + for (let f = 0; f <= 90; f++) { + const day = `Day ${f + 1}`; + // if (ebmForeCastData[i][day] === 0) continue; - const forcast = { - customerArticleNo: sku, - requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)), - quantity: petForeCastData[i][day] ?? 0, - }; + const forcast = { + customerArticleNo: sku, + requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)), + quantity: petForeCastData[i][day] ?? 0, + }; - if (forcast.quantity === 0 || forcast.quantity === "") continue; + if (forcast.quantity === 0 || forcast.quantity === "") continue; - if (forcast.customerArticleNo < 99999) { - //console.log(`Sku a normal av ${forcast.customerArticleNo}`); - continue; - } + if (forcast.customerArticleNo < 99999) { + //console.log(`Sku a normal av ${forcast.customerArticleNo}`); + continue; + } - // checking to make sure there is a real av to add to. - const activeAV = article.filter( - (c: any) => - c?.CustomerArticleNumber === - forcast.customerArticleNo.toString() - ); + // checking to make sure there is a real av to add to. + const activeAV = article.filter( + (c: any) => + c?.CustomerArticleNumber === forcast.customerArticleNo.toString(), + ); - if (activeAV.length === 0) { - if (typeof forcast.customerArticleNo === "number") { - missingSku.push(forcast); - } - continue; - } + if (activeAV.length === 0) { + if (typeof forcast.customerArticleNo === "number") { + missingSku.push(forcast); + } + continue; + } - ebmForecastData.push(forcast); - } - } + ebmForecastData.push(forcast); + } + } - //console.log(comForecast); + //console.log(comForecast); - // email the for the missing ones - const missedGrouped = Object.values( - missingSku.reduce((acc: any, item: any) => { - const key = item.customerArticleNo; + // email the for the missing ones + const missedGrouped = Object.values( + missingSku.reduce((acc: any, item: any) => { + const key = item.customerArticleNo; - if (!acc[key]) { - // first time we see this customer - acc[key] = item; - } else { - // compare dates and keep the earliest - if ( - new Date(item.requirementDate) < - new Date(acc[key].requirementDate) - ) { - acc[key] = item; - } - } + if (!acc[key]) { + // first time we see this customer + acc[key] = item; + } else { + // compare dates and keep the earliest + if ( + new Date(item.requirementDate) < new Date(acc[key].requirementDate) + ) { + acc[key] = item; + } + } - return acc; - }, {}) - ); + return acc; + }, {}), + ); - const emailSetup = { - email: "Blake.matthes@alpla.com; Stuart.Gladney@alpla.com; Harold.Mccalister@alpla.com; Jenn.Osbourn@alpla.com", - subject: - missedGrouped.length > 0 - ? `Alert! There are ${missedGrouped.length}, missing skus.` - : `Alert! There is a missing SKU.`, - template: "missingLorealSkus", - context: { - items: missedGrouped, - }, - }; + const emailSetup = { + email: + "Blake.matthes@alpla.com; Stuart.Gladney@alpla.com; Harold.Mccalister@alpla.com; Jenn.Osbourn@alpla.com", + subject: + missedGrouped.length > 0 + ? `Alert! There are ${missedGrouped.length}, missing skus.` + : `Alert! There is a missing SKU.`, + template: "missingLorealSkus", + context: { + items: missedGrouped, + }, + }; - const { data: sentEmail, error: sendEmailError } = await tryCatch( - sendEmail(emailSetup) - ); - if (sendEmailError) { - createLog( - "error", - "blocking", - "notify", - "Failed to send email, will try again on next interval" - ); - return { - success: false, - message: "Failed to send email, will try again on next interval", - }; - } + const { data: sentEmail, error: sendEmailError } = await tryCatch( + sendEmail(emailSetup), + ); + if (sendEmailError) { + createLog( + "error", + "blocking", + "notify", + "Failed to send email, will try again on next interval", + ); + return { + success: false, + message: "Failed to send email, will try again on next interval", + }; + } - // if the customerarticle number is not matching just ignore it - const predefinedObject = { - receivingPlantId: plantToken[0].value, - documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString( - "en-US" - )}`, - sender: user.username || "lst-system", - customerId: customerID, - positions: [], - }; + // if the customerarticle number is not matching just ignore it + const predefinedObject = { + receivingPlantId: plantToken[0].value, + documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString( + "en-US", + )}`, + sender: user.username || "lst-system", + customerId: customerID, + positions: [], + }; - let updatedPredefinedObject = { - ...predefinedObject, - positions: [ - ...predefinedObject.positions, - ...ebmForecastData, + let updatedPredefinedObject = { + ...predefinedObject, + positions: [ + ...predefinedObject.positions, + ...ebmForecastData, - // ...ebmForecastData.filter( - // (q: any) => - // q.customerArticleNo != "" && q.customerArticleNo != "Total" - // ), - // ...petForecastData.filter( - // (q: any) => - // q.customerArticleNo != "" && q.customerArticleNo != "Total" - // ), - ], - }; - // console.log(updatedPredefinedObject); - const posting: any = await postForecast(updatedPredefinedObject, user); + // ...ebmForecastData.filter( + // (q: any) => + // q.customerArticleNo != "" && q.customerArticleNo != "Total" + // ), + // ...petForecastData.filter( + // (q: any) => + // q.customerArticleNo != "" && q.customerArticleNo != "Total" + // ), + ], + }; + // console.log(updatedPredefinedObject); - return { - success: posting.success, - message: posting.message, - data: posting.data === "" ? ebmForecastData : posting.data, - }; + // posting the data to the new backend so we can store the data. + axios.post( + `http://localhost:${process.env.NODE_ENV?.trim() != "production" ? "4200/lst" : "4000"}/api/logistics/dm/forecastData`, + ebmForecastData, + ); + const posting: any = await postForecast(updatedPredefinedObject, user); + + return { + success: posting.success, + message: posting.message, + data: posting.data === "" ? ebmForecastData : posting.data, + }; };