From dcfa96b710333f96857a398aea9d8a694993e4ae Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Fri, 18 Apr 2025 16:26:48 -0500 Subject: [PATCH] feat(forecast): migrated forecast over --- .../controller/dm/forecast/createTemplate.ts | 34 +++++++++ .../controller/dm/forecast/forecastIn.ts | 38 ++++++++++ .../dm/forecast/mappings/loralForecast.ts | 0 .../dm/forecast/mappings/standardForcast.ts | 11 +++ .../controller/dm/forecast/postForecast.ts | 75 +++++++++++++++++++ server/services/logistics/logisticsService.ts | 4 + .../services/logistics/route/dm/forecastIn.ts | 74 ++++++++++++++++++ .../route/dm/getStandardForecastTemplate.ts | 66 ++++++++++++++++ 8 files changed, 302 insertions(+) create mode 100644 server/services/logistics/controller/dm/forecast/createTemplate.ts create mode 100644 server/services/logistics/controller/dm/forecast/forecastIn.ts create mode 100644 server/services/logistics/controller/dm/forecast/mappings/loralForecast.ts create mode 100644 server/services/logistics/controller/dm/forecast/mappings/standardForcast.ts create mode 100644 server/services/logistics/controller/dm/forecast/postForecast.ts create mode 100644 server/services/logistics/route/dm/forecastIn.ts create mode 100644 server/services/logistics/route/dm/getStandardForecastTemplate.ts diff --git a/server/services/logistics/controller/dm/forecast/createTemplate.ts b/server/services/logistics/controller/dm/forecast/createTemplate.ts new file mode 100644 index 0000000..a8a82d2 --- /dev/null +++ b/server/services/logistics/controller/dm/forecast/createTemplate.ts @@ -0,0 +1,34 @@ +import * as XLSX from "xlsx"; + +export const standardForCastTemplate = async () => { + /** + * Creates the standard Template for bulk orders in + */ + + const headers = [ + ["CustomerArticleNumber", "Quantity", "RequirementDate", "CustomerID"], + ]; + + // create a new workbook + const wb = XLSX.utils.book_new(); + const ws = XLSX.utils.aoa_to_sheet(headers); + //const ws2 = XLSX.utils.aoa_to_sheet(headers2); + + const columnWidths = headers[0].map((header) => ({ + width: header.length + 2, + })); + + ws["!cols"] = columnWidths; + + // append the worksheet to the workbook + XLSX.utils.book_append_sheet(wb, ws, `Sheet1`); + //XLSX.utils.book_append_sheet(wb, ws2, `Sheet2`); + + // Write the excel file and trigger the download' + XLSX.writeFile(wb, "BulkForecastTemplate"); + + // Write the workbook to a buffer and return it + const excelBuffer = XLSX.write(wb, { bookType: "xlsx", type: "buffer" }); + + return excelBuffer; +}; diff --git a/server/services/logistics/controller/dm/forecast/forecastIn.ts b/server/services/logistics/controller/dm/forecast/forecastIn.ts new file mode 100644 index 0000000..4788fc6 --- /dev/null +++ b/server/services/logistics/controller/dm/forecast/forecastIn.ts @@ -0,0 +1,38 @@ +import { standardForecast } from "./mappings/standardForcast.js"; + +export const forecastIn = async (data: any, user: any) => { + /** + * Bulk orders in, and custom file parsing. + */ + + let success = true; + let message = ""; + let orderData: any = []; + + // what type of order are we dealing with? + if (data["fileType"] === "standard") { + //run the standard forecast in + const standard = await standardForecast(data["postPostForecast"], user); + success = standard.success ?? false; + message = standard.message ?? "Error posting standard forecast"; + orderData = standard.data; + } + + if (data["fileType"] === "energizer") { + // orders in + } + + if (data["fileType"] === "loreal") { + // orders in + } + + if (data["fileType"] === "pg") { + // orders in + } + + return { + success, + message, + data: orderData, + }; +}; diff --git a/server/services/logistics/controller/dm/forecast/mappings/loralForecast.ts b/server/services/logistics/controller/dm/forecast/mappings/loralForecast.ts new file mode 100644 index 0000000..e69de29 diff --git a/server/services/logistics/controller/dm/forecast/mappings/standardForcast.ts b/server/services/logistics/controller/dm/forecast/mappings/standardForcast.ts new file mode 100644 index 0000000..f87343f --- /dev/null +++ b/server/services/logistics/controller/dm/forecast/mappings/standardForcast.ts @@ -0,0 +1,11 @@ +export const standardForecast = async (data: any, user: any) => { + /** + * Post a standard forecast based on the standard template. + */ + + return { + success: true, + message: "Forecast Posted", + data: [], + }; +}; diff --git a/server/services/logistics/controller/dm/forecast/postForecast.ts b/server/services/logistics/controller/dm/forecast/postForecast.ts new file mode 100644 index 0000000..14b1e35 --- /dev/null +++ b/server/services/logistics/controller/dm/forecast/postForecast.ts @@ -0,0 +1,75 @@ +import axios from "axios"; +import { prodEndpointCreation } from "../../../../../globalUtils/createUrl.js"; +import { createLog } from "../../../../logger/logger.js"; + +export const postForecast = async (data: any, user: any) => { + let endpoint = await prodEndpointCreation( + "/public/v1.0/DemandManagement/DELFOR" + ); + + //console.log(endpoint); + //console.log(req.body.orders[0]); + try { + const results = await axios({ + url: endpoint, + method: "POST", + headers: { + Authorization: `Basic ${user.prod}`, + }, + // if a body is sent over it would be like below + data: data, + }); + + //console.log(results.data); + //console.log(results.status); + if (results.data.errors) { + createLog( + "error", + "forecast", + "logistics", + `There was an error posting the Forecast: ${JSON.stringify( + results.data.errors + )}` + ); + return { + success: true, + message: "Error processing forecast", + data: results.data.errors[0].message, + }; + } + + if (results.status === 200) { + createLog( + "info", + "forecast", + "logistics", + `Forcast was successfully posted: ${JSON.stringify( + results.data + )}` + ); + return { + success: true, + message: "Success on posting forecast", + data: results.data, + }; + } + } catch (error: any) { + //console.log(`There is an error`, error); + if (error) { + //console.log(error.response.data); + createLog( + "error", + "forecast", + "logistics", + `There was an error posting the Forecast: ${JSON.stringify( + error.response.data + )}` + ); + return { + success: false, + message: "There was an error posting the Forecast", + data: error.response.data, + }; + } + } +}; diff --git a/server/services/logistics/logisticsService.ts b/server/services/logistics/logisticsService.ts index c22d2d7..cae79a2 100644 --- a/server/services/logistics/logisticsService.ts +++ b/server/services/logistics/logisticsService.ts @@ -13,6 +13,8 @@ import getPPOO from "./route/getPPOO.js"; import getcyclecount from "./route/getCycleCountLanes.js"; import postBulkOrders from "./route/dm/bulkOrdersIn.js"; import standardTemplate from "./route/dm/getStandardTemplate.js"; +import standardForcasttemplate from "./route/dm/getStandardForecastTemplate.js"; +import postForecast from "./route/dm/forecastIn.js"; const app = new OpenAPIHono(); @@ -33,6 +35,8 @@ const routes = [ //DM postBulkOrders, standardTemplate, + postForecast, + standardForcasttemplate, ] as const; // app.route("/server", modules); diff --git a/server/services/logistics/route/dm/forecastIn.ts b/server/services/logistics/route/dm/forecastIn.ts new file mode 100644 index 0000000..8310ed3 --- /dev/null +++ b/server/services/logistics/route/dm/forecastIn.ts @@ -0,0 +1,74 @@ +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { responses } from "../../../../globalUtils/routeDefs/responses.js"; +import { ordersIn } from "../../controller/dm/ordersIn/ordersIn.js"; +import { verify } from "hono/jwt"; +import { forecastIn } from "../../controller/dm/forecast/forecastIn.js"; +import { tryCatch } from "../../../../globalUtils/tryCatch.js"; + +const app = new OpenAPIHono(); + +// const Body = z +// .object({ +// age: z.number().optional().openapi({ example: 90 }), +// //email: z.string().optional().openapi({example: "s.smith@example.com"}), +// type: z.string().optional().openapi({ example: "fg" }), +// }) +// .openapi("User"); +app.openapi( + createRoute({ + tags: ["logistics"], + summary: "Post forecast to DM", + method: "post", + path: "/postforecastin", + // request: { + // body: { + // content: { + // "application/json": { schema: Body }, + // }, + // }, + // }, + // description: + // "Provided a running number and lot number you can consume material.", + responses: responses(), + }), + async (c) => { + //apiHit(c, { endpoint: "api/sqlProd/close" }); + const body = await c.req.parseBody(); + const authHeader = c.req.header("Authorization"); + const token = authHeader?.split("Bearer ")[1] || ""; + //console.log(body); // File | string + + // if (body["fileType"] === "standard") { + // console.log(`doing standard orders in.`); + // } + const { data: payload, error: pe } = await tryCatch( + verify(token, process.env.JWT_SECRET!) + ); + + if (pe) { + return c.json({ success: false, message: "Unauthorized" }, 401); + } + + const { data: orders, error } = await tryCatch( + forecastIn(body, payload.user) + ); + + if (error) { + return c.json( + { + success: false, + message: "Error posting forecast", + data: error, + }, + 400 + ); + } + + return c.json({ + success: orders?.success ?? false, + message: orders?.message ?? "Error posting forecast", + data: orders?.data ?? [], + }); + } +); +export default app; diff --git a/server/services/logistics/route/dm/getStandardForecastTemplate.ts b/server/services/logistics/route/dm/getStandardForecastTemplate.ts new file mode 100644 index 0000000..90ff079 --- /dev/null +++ b/server/services/logistics/route/dm/getStandardForecastTemplate.ts @@ -0,0 +1,66 @@ +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { format } from "date-fns"; +import { responses } from "../../../../globalUtils/routeDefs/responses.js"; +import { standardTemplate } from "../../controller/dm/ordersIn/createTemplate.js"; +import { tryCatch } from "../../../../globalUtils/tryCatch.js"; +import { standardForCastTemplate } from "../../controller/dm/forecast/createTemplate.js"; + +const app = new OpenAPIHono(); + +// const Body = z +// .object({ +// age: z.number().optional().openapi({ example: 90 }), +// //email: z.string().optional().openapi({example: "s.smith@example.com"}), +// type: z.string().optional().openapi({ example: "fg" }), +// }) +// .openapi("User"); +app.openapi( + createRoute({ + tags: ["logistics"], + summary: "Gets the standard Forecast Template", + method: "get", + path: "/bulkforcasttemplate", + // request: { + // body: { + // content: { + // "application/json": { schema: Body }, + // }, + // }, + // }, + // description: + // "Provided a running number and lot number you can consume material.", + responses: responses(), + }), + async (c: any) => { + //apiHit(c, { endpoint: "api/sqlProd/close" }); + const defaultFilename = `bulkForcastTemplate-${format( + new Date(Date.now()), + "M-d-yyyy" + )}.xlsx`; + const filename = c.req.query("filename") || defaultFilename; + const { data, error } = await tryCatch(standardForCastTemplate()); + + if (error) { + return c.json({ + success: false, + message: "Error creating template", + data: error, + }); + } + + return new Response(data, { + headers: { + "Content-Type": + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Content-Disposition": `attachment; filename="${filename}"`, + }, + }); + + // return c.json({ + // success: data.success, + // message: data.message, + // data: data.data, + // }); + } +); +export default app;