feat(forecast data): added in a historical forecast data set

This commit is contained in:
2025-11-01 00:05:01 -05:00
parent 2ed6bf4d1f
commit c2ae445ea4
7 changed files with 314 additions and 242 deletions

View File

@@ -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
}

View File

@@ -8,10 +8,63 @@
* add_date * add_date
*/ */
type IncomingForecastData = { import { db } from "../../../../pkg/db/db.js";
CustomerArticleNumber: number; 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) => { export const forecastEdiData = async (data: ForecastData[]) => {
console.log(data); //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: [],
};
}; };

View File

@@ -1,10 +1,12 @@
import type { Express, Request, Response } from "express"; import type { Express, Request, Response } from "express";
import dm from "./routes/demandMgt/dmRoutes.js";
import labeling from "./routes/labeling/labelingRoutes.js"; import labeling from "./routes/labeling/labelingRoutes.js";
import schedule from "./routes/scheduler/scheduleRoutes.js"; import schedule from "./routes/scheduler/scheduleRoutes.js";
export const setupLogisticsRoutes = (app: Express, basePath: string) => { export const setupLogisticsRoutes = (app: Express, basePath: string) => {
app.use(basePath + "/api/logistics/schedule", schedule); app.use(basePath + "/api/logistics/schedule", schedule);
app.use(basePath + "/api/logistics/labeling", labeling); app.use(basePath + "/api/logistics/labeling", labeling);
app.use(basePath + "/api/logistics/dm", dm);
// app.use( // app.use(
// basePath + "/api/admin/users", // basePath + "/api/admin/users",

View File

@@ -4,6 +4,6 @@ import forecastData from "./forecastEDIData.js";
const router = Router(); const router = Router();
router.use("/", requireAuth(), forecastData); router.use("/", forecastData);
export default router; export default router;

View File

@@ -1,7 +1,7 @@
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { Router } from "express"; import { Router } from "express";
import z from "zod"; import z from "zod";
import { preprintLabels } from "../../controller/labeling/preprint.js"; import { forecastEdiData } from "../../controller/demandManagement/forecastEDIData.js";
export const Preprint = z.object({ export const Preprint = z.object({
scannerId: z.number(), scannerId: z.number(),
@@ -15,13 +15,10 @@ export const Preprint = z.object({
const router = Router(); const router = Router();
router.post("/preprint", async (req: Request, res: Response) => { router.post("/forecastData", async (req: Request, res: Response) => {
const parsed = Preprint.safeParse(req.body); await forecastEdiData(req.body);
const print = await preprintLabels(req.body, req.user?.username || "");
res res.status(200).json({ success: true, message: "Forecast Data", data: [] });
.status(200)
.json({ success: print.success, message: print.message, data: print.data });
}); });
export default router; export default router;

View File

@@ -13,12 +13,11 @@ import { z } from "zod";
export const forecastData = pgTable("forecast_Data", { export const forecastData = pgTable("forecast_Data", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
customerArticleNumber: text("customer_article_number"), customerArticleNo: text("customer_article_no"),
dateRequested: timestamp("date_requested").defaultNow(), dateRequested: timestamp("date_requested").defaultNow(),
quantity: real("quantity"), quantity: real("quantity"),
requestDate: timestamp("request_date").notNull(), requirementDate: timestamp("requirement_date").notNull(),
article: integer("article"), article: integer("article"),
customerID: integer("customer_id").notNull(),
createdAt: timestamp("created_at").defaultNow(), createdAt: timestamp("created_at").defaultNow(),
}); });

View File

@@ -1,286 +1,291 @@
import axios from "axios";
import { addDays } from "date-fns";
import XLSX from "xlsx";
import { db } from "../../../../../../../database/dbclient.js"; import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js"; import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js"; import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import XLSX from "xlsx"; import { createLog } from "../../../../../logger/logger.js";
import { excelDateStuff } from "../../../../utils/excelDateStuff.js"; import { sendEmail } from "../../../../../notifications/controller/sendMail.js";
import { postForecast } from "../postForecast.js";
import { query } from "../../../../../sqlServer/prodSqlServer.js"; import { query } from "../../../../../sqlServer/prodSqlServer.js";
import { activeArticle } from "../../../../../sqlServer/querys/dataMart/article.js"; import { activeArticle } from "../../../../../sqlServer/querys/dataMart/article.js";
import { addDays } from "date-fns"; import { excelDateStuff } from "../../../../utils/excelDateStuff.js";
import { sendEmail } from "../../../../../notifications/controller/sendMail.js"; import { postForecast } from "../postForecast.js";
import { createLog } from "../../../../../logger/logger.js";
let customerID = 4; let customerID = 4;
export const lorealForecast = async (data: any, user: any) => { 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) { if (e) {
return { return {
sucess: false, sucess: false,
message: `Error getting settings`, message: `Error getting settings`,
data: e, data: e,
}; };
} }
const plantToken = s.filter((s) => s.name === "plantToken"); const plantToken = s.filter((s) => s.name === "plantToken");
const arrayBuffer = await data.arrayBuffer(); const arrayBuffer = await data.arrayBuffer();
const buffer = Buffer.from(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 sheet: any = workbook.Sheets["Alpla HDPE"];
const range = XLSX.utils.decode_range(sheet["!ref"]); const range = XLSX.utils.decode_range(sheet["!ref"]);
const psheet: any = workbook.Sheets["Alpla PET"]; const psheet: any = workbook.Sheets["Alpla PET"];
const prange = XLSX.utils.decode_range(psheet["!ref"]); const prange = XLSX.utils.decode_range(psheet["!ref"]);
const headers = []; const headers = [];
for (let C = range.s.c; C <= range.e.c; ++C) { 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 cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1
const cell = sheet[cellAddress]; const cell = sheet[cellAddress];
headers.push(cell ? cell.v : undefined); headers.push(cell ? cell.v : undefined);
} }
const pheaders = []; const pheaders = [];
for (let C = prange.s.c; C <= prange.e.c; ++C) { 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 cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1
const cell = psheet[cellAddress]; const cell = psheet[cellAddress];
pheaders.push(cell ? cell.v : undefined); pheaders.push(cell ? cell.v : undefined);
} }
const ebmForeCastData: any = XLSX.utils.sheet_to_json(sheet, { const ebmForeCastData: any = XLSX.utils.sheet_to_json(sheet, {
defval: "", defval: "",
header: headers, header: headers,
range: 3, range: 3,
}); });
const petForeCastData: any = XLSX.utils.sheet_to_json(psheet, { const petForeCastData: any = XLSX.utils.sheet_to_json(psheet, {
defval: "", defval: "",
header: pheaders, header: pheaders,
range: 3, range: 3,
}); });
const ebmForecastData: any = []; const ebmForecastData: any = [];
const missingSku: any = []; const missingSku: any = [];
const { data: a, error: ae } = await tryCatch( const { data: a, error: ae } = await tryCatch(
query(activeArticle, "Loreal calling active av") query(activeArticle, "Loreal calling active av"),
); );
if (ae) { if (ae) {
return { return {
success: false, success: false,
message: "Error getting active av", message: "Error getting active av",
data: [], data: [],
}; };
} }
const article: any = a?.data; const article: any = a?.data;
// process the ebm forcast // process the ebm forcast
for (let i = 0; i < ebmForeCastData.length; i++) { for (let i = 0; i < ebmForeCastData.length; i++) {
// bottle code // bottle code
const sku = ebmForeCastData[i]["HDPE Bottle Code"]; const sku = ebmForeCastData[i]["HDPE Bottle Code"];
// ignore the blanks // ignore the blanks
if (sku === "") continue; if (sku === "") continue;
// ignore zero qty // ignore zero qty
// if (ebmForeCastData[i][`Day ${i}`]) continue; // if (ebmForeCastData[i][`Day ${i}`]) continue;
for (let f = 0; f <= 90; f++) { for (let f = 0; f <= 90; f++) {
const day = `Day ${f + 1}`; const day = `Day ${f + 1}`;
// if (ebmForeCastData[i][day] === 0) continue; // if (ebmForeCastData[i][day] === 0) continue;
const forcast = { const forcast = {
customerArticleNo: sku, customerArticleNo: sku,
requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)), requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)),
quantity: ebmForeCastData[i][day] ?? 0, 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. // checking to make sure there is a real av to add to.
const activeAV = article.filter( const activeAV = article.filter(
(c: any) => (c: any) =>
c?.CustomerArticleNumber === c?.CustomerArticleNumber === forcast.customerArticleNo.toString(),
forcast.customerArticleNo.toString() );
);
if (activeAV.length === 0) { if (activeAV.length === 0) {
if (typeof forcast.customerArticleNo === "number") { if (typeof forcast.customerArticleNo === "number") {
missingSku.push(forcast); missingSku.push(forcast);
} }
continue; continue;
} }
ebmForecastData.push(forcast); ebmForecastData.push(forcast);
} }
//console.log(ebmForeCastData.length); //console.log(ebmForeCastData.length);
} }
// petForeCastData.forEach((item: any, index: any) => { // petForeCastData.forEach((item: any, index: any) => {
// //console.log(`Processing item ${index + 1} of ${forecastData.length}`); // //console.log(`Processing item ${index + 1} of ${forecastData.length}`);
// // Extract the customer code // // Extract the customer code
// const customerCode = item["SOUTH PET BOTTLES"]; // const customerCode = item["SOUTH PET BOTTLES"];
// // Process each date in the current object // // Process each date in the current object
// for (const [date, qty] of Object.entries(item)) { // for (const [date, qty] of Object.entries(item)) {
// // Skip metadata fields // // Skip metadata fields
// if (petMetadataFields.includes(date)) continue; // if (petMetadataFields.includes(date)) continue;
// if (qty === 0) continue; // if (qty === 0) continue;
// // Create your transformed record // // Create your transformed record
// const record = { // const record = {
// customerArticleNo: customerCode, // customerArticleNo: customerCode,
// requirementDate: excelDateStuff(parseInt(date)), // requirementDate: excelDateStuff(parseInt(date)),
// quantity: qty, // quantity: qty,
// }; // };
// // Do something with this record // // Do something with this record
// petForecastData.push(record); // petForecastData.push(record);
// } // }
// }); // });
// pet forecast // pet forecast
for (let i = 0; i < petForeCastData.length; i++) { for (let i = 0; i < petForeCastData.length; i++) {
// bottle code // bottle code
const sku = petForeCastData[i]["South PET Bottle Code"]; const sku = petForeCastData[i]["South PET Bottle Code"];
// ignore the blanks // ignore the blanks
if (sku === "") continue; if (sku === "") continue;
// ignore zero qty // ignore zero qty
// if (ebmForeCastData[i][`Day ${i}`]) continue; // if (ebmForeCastData[i][`Day ${i}`]) continue;
for (let f = 0; f <= 90; f++) { for (let f = 0; f <= 90; f++) {
const day = `Day ${f + 1}`; const day = `Day ${f + 1}`;
// if (ebmForeCastData[i][day] === 0) continue; // if (ebmForeCastData[i][day] === 0) continue;
const forcast = { const forcast = {
customerArticleNo: sku, customerArticleNo: sku,
requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)), requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)),
quantity: petForeCastData[i][day] ?? 0, quantity: petForeCastData[i][day] ?? 0,
}; };
if (forcast.quantity === 0 || forcast.quantity === "") continue; if (forcast.quantity === 0 || forcast.quantity === "") continue;
if (forcast.customerArticleNo < 99999) { if (forcast.customerArticleNo < 99999) {
//console.log(`Sku a normal av ${forcast.customerArticleNo}`); //console.log(`Sku a normal av ${forcast.customerArticleNo}`);
continue; continue;
} }
// checking to make sure there is a real av to add to. // checking to make sure there is a real av to add to.
const activeAV = article.filter( const activeAV = article.filter(
(c: any) => (c: any) =>
c?.CustomerArticleNumber === c?.CustomerArticleNumber === forcast.customerArticleNo.toString(),
forcast.customerArticleNo.toString() );
);
if (activeAV.length === 0) { if (activeAV.length === 0) {
if (typeof forcast.customerArticleNo === "number") { if (typeof forcast.customerArticleNo === "number") {
missingSku.push(forcast); missingSku.push(forcast);
} }
continue; continue;
} }
ebmForecastData.push(forcast); ebmForecastData.push(forcast);
} }
} }
//console.log(comForecast); //console.log(comForecast);
// email the for the missing ones // email the for the missing ones
const missedGrouped = Object.values( const missedGrouped = Object.values(
missingSku.reduce((acc: any, item: any) => { missingSku.reduce((acc: any, item: any) => {
const key = item.customerArticleNo; const key = item.customerArticleNo;
if (!acc[key]) { if (!acc[key]) {
// first time we see this customer // first time we see this customer
acc[key] = item; acc[key] = item;
} else { } else {
// compare dates and keep the earliest // compare dates and keep the earliest
if ( if (
new Date(item.requirementDate) < new Date(item.requirementDate) < new Date(acc[key].requirementDate)
new Date(acc[key].requirementDate) ) {
) { acc[key] = item;
acc[key] = item; }
} }
}
return acc; return acc;
}, {}) }, {}),
); );
const emailSetup = { const emailSetup = {
email: "Blake.matthes@alpla.com; Stuart.Gladney@alpla.com; Harold.Mccalister@alpla.com; Jenn.Osbourn@alpla.com", email:
subject: "Blake.matthes@alpla.com; Stuart.Gladney@alpla.com; Harold.Mccalister@alpla.com; Jenn.Osbourn@alpla.com",
missedGrouped.length > 0 subject:
? `Alert! There are ${missedGrouped.length}, missing skus.` missedGrouped.length > 0
: `Alert! There is a missing SKU.`, ? `Alert! There are ${missedGrouped.length}, missing skus.`
template: "missingLorealSkus", : `Alert! There is a missing SKU.`,
context: { template: "missingLorealSkus",
items: missedGrouped, context: {
}, items: missedGrouped,
}; },
};
const { data: sentEmail, error: sendEmailError } = await tryCatch( const { data: sentEmail, error: sendEmailError } = await tryCatch(
sendEmail(emailSetup) sendEmail(emailSetup),
); );
if (sendEmailError) { if (sendEmailError) {
createLog( createLog(
"error", "error",
"blocking", "blocking",
"notify", "notify",
"Failed to send email, will try again on next interval" "Failed to send email, will try again on next interval",
); );
return { return {
success: false, success: false,
message: "Failed to send email, will try again on next interval", message: "Failed to send email, will try again on next interval",
}; };
} }
// if the customerarticle number is not matching just ignore it // if the customerarticle number is not matching just ignore it
const predefinedObject = { const predefinedObject = {
receivingPlantId: plantToken[0].value, receivingPlantId: plantToken[0].value,
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString( documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
"en-US" "en-US",
)}`, )}`,
sender: user.username || "lst-system", sender: user.username || "lst-system",
customerId: customerID, customerId: customerID,
positions: [], positions: [],
}; };
let updatedPredefinedObject = { let updatedPredefinedObject = {
...predefinedObject, ...predefinedObject,
positions: [ positions: [
...predefinedObject.positions, ...predefinedObject.positions,
...ebmForecastData, ...ebmForecastData,
// ...ebmForecastData.filter( // ...ebmForecastData.filter(
// (q: any) => // (q: any) =>
// q.customerArticleNo != "" && q.customerArticleNo != "Total" // q.customerArticleNo != "" && q.customerArticleNo != "Total"
// ), // ),
// ...petForecastData.filter( // ...petForecastData.filter(
// (q: any) => // (q: any) =>
// q.customerArticleNo != "" && q.customerArticleNo != "Total" // q.customerArticleNo != "" && q.customerArticleNo != "Total"
// ), // ),
], ],
}; };
// console.log(updatedPredefinedObject); // console.log(updatedPredefinedObject);
const posting: any = await postForecast(updatedPredefinedObject, user);
return { // posting the data to the new backend so we can store the data.
success: posting.success, axios.post(
message: posting.message, `http://localhost:${process.env.NODE_ENV?.trim() != "production" ? "4200/lst" : "4000"}/api/logistics/dm/forecastData`,
data: posting.data === "" ? ebmForecastData : posting.data, ebmForecastData,
}; );
const posting: any = await postForecast(updatedPredefinedObject, user);
return {
success: posting.success,
message: posting.message,
data: posting.data === "" ? ebmForecastData : posting.data,
};
}; };