diff --git a/server/services/logistics/controller/dm/ordersIn/customMappings/standardOrders.ts b/server/services/logistics/controller/dm/ordersIn/customMappings/standardOrders.ts new file mode 100644 index 0000000..1586e3b --- /dev/null +++ b/server/services/logistics/controller/dm/ordersIn/customMappings/standardOrders.ts @@ -0,0 +1,150 @@ +import { delay } from "../../../../../../globalUtils/delay.js"; +import XLSX from "xlsx"; +import { tryCatch } from "../../../../../../globalUtils/tryCatch.js"; +import { db } from "../../../../../../../database/dbclient.js"; +import { settings } from "../../../../../../../database/schema/settings.js"; +import { query } from "../../../../../sqlServer/prodSqlServer.js"; +import { orderState } from "../../../../../sqlServer/querys/dm/orderState.js"; +import { excelDateStuff } from "../../../../utils/excelDateStuff.js"; +import { invoiceAddress } from "../../../../../sqlServer/querys/dm/invoiceAddress.js"; +import { postOrders } from "../postOrders.js"; + +export const standardOrders = async (data: any, user: any) => { + /** + * Standard orders meaning that we get the standard file exported and fill it out and uplaod to lst. + */ + + const { data: s, error: e } = await tryCatch(db.select().from(settings)); + + if (e) { + return { + sucess: false, + message: `Error getting settings`, + data: e, + }; + } + + // order state + const { data: openOrders, error: oe } = await tryCatch( + query(orderState, "Gets the next 500 orders that have not been started") + ); + + if (oe) { + return { + sucess: false, + message: `Error getting article data`, + data: oe, + }; + } + + // order state + const { data: i, error: ie } = await tryCatch( + query(invoiceAddress, "Gets invoices addresses") + ); + + if (ie) { + return { + sucess: false, + message: `Error getting invoice address data`, + data: ie, + }; + } + const plantToken = s.filter((s) => s.name === "plantToken"); + const arrayBuffer = await data.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + const workbook = XLSX.read(buffer, { type: "buffer" }); + + const sheetName = workbook.SheetNames[0]; + const sheet = workbook.Sheets[sheetName]; + + // define custom headers + const headers = [ + "CustomerArticleNumber", + "CustomerOrderNumber", + "CustomerLineNumber", + "CustomerRealeaseNumber", + "Quantity", + "DeliveryDate", + "CustomerID", + ]; + const orderData = XLSX.utils.sheet_to_json(sheet, { + defval: "", + header: headers, + range: 1, + }); + + // the base of the import + const predefinedObject = { + receivingPlantId: plantToken[0].value, + documentName: `OrdersFromLST-${new Date(Date.now()).toLocaleString( + "en-US" + )}`, + sender: user.username || "lst-system", + externalRefNo: `OrdersFromLST-${new Date(Date.now()).toLocaleString( + "en-US" + )}`, + orders: [], + }; + + const groupedByCustomer: any = orderData.reduce((acc: any, item: any) => { + const id = item.CustomerID; + if (!acc[id]) { + acc[id] = []; + } + acc[id].push(item); + return acc; + }, {}); + + for (const [customerID, orders] of Object.entries(groupedByCustomer)) { + // console.log(`Running for Customer ID: ${customerID}`); + const newOrders: any = orders; + + // filter out the orders that have already been started just to reduce the risk of errors. + newOrders.filter((oo: any) => + openOrders.some( + (o: any) => o.CustomerOrderNumber === oo.CustomerOrderNumber + ) + ); + + // map everything out for each order + const nOrder = newOrders.map((o: any) => { + return { + customerId: parseInt(customerID), + invoiceAddressId: i.filter( + (i: any) => i.deliveryAddress === parseInt(customerID) + )[0].invoiceAddress, // matched to the default invoice address + customerOrderNo: o.CustomerOrderNumber, + orderDate: new Date(Date.now()).toLocaleString("en-US"), + positions: [ + { + deliveryAddressId: parseInt(customerID), + customerArticleNo: o.CustomerArticleNumber, + quantity: parseInt(o.Quantity), + deliveryDate: excelDateStuff(o.DeliveryDate), + customerLineItemNo: o.CustomerLineNumber, // this is how it is currently sent over from abbott + customerReleaseNo: o.CustomerRealeaseNumber, // same as above + }, + ], + }; + }); + + // do that fun combining thing + const updatedPredefinedObject = { + ...predefinedObject, + orders: [...predefinedObject.orders, ...nOrder], + }; + + //console.log(updatedPredefinedObject); + + // post the orders to the server + const posting = await postOrders(updatedPredefinedObject, user); + } + + return { + success: true, + message: + "Standard Template was just processed successfully, please check AlplaProd 2.0 to confirm no errors. ", + data: [], + }; +}; diff --git a/server/services/logistics/controller/dm/ordersIn/ordersIn.ts b/server/services/logistics/controller/dm/ordersIn/ordersIn.ts new file mode 100644 index 0000000..ca0991d --- /dev/null +++ b/server/services/logistics/controller/dm/ordersIn/ordersIn.ts @@ -0,0 +1,47 @@ +import { abbottOrders } from "./customMappings/abbottTruckList.js"; +import { standardOrders } from "./customMappings/standardOrders.js"; + +export const ordersIn = 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 orders in + const standard = await standardOrders(data["postOrders"], user); + success = standard.success ?? false; + message = standard.message ?? "Error posting Abbott Orders"; + orderData = standard.data; + } + + if (data["fileType"] === "abbott") { + // orders in + const abbott = await abbottOrders(data["postOrders"], user); + success = abbott.success ?? false; + message = abbott.message ?? "Error posting Abbott Orders"; + orderData = abbott.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/ordersIn/postOrders.ts b/server/services/logistics/controller/dm/ordersIn/postOrders.ts new file mode 100644 index 0000000..bebabb5 --- /dev/null +++ b/server/services/logistics/controller/dm/ordersIn/postOrders.ts @@ -0,0 +1,48 @@ +import axios from "axios"; +import { prodEndpointCreation } from "../../../../../globalUtils/createUrl.js"; + +export const postOrders = async (data: any, user: any) => { + let endpoint = await prodEndpointCreation( + "/public/v1.0/DemandManagement/ORDERS" + ); + + 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) { + return { + success: true, + message: "Error processing orders", + data: results.data.errors[0].message, + }; + } + + if (results.status === 200) { + return { + success: true, + message: "Success on posting orders", + data: data, + }; + } + } catch (error: any) { + //console.log(`There is an error`, error); + if (error) { + //console.log(error.response.data); + return { + success: false, + message: "There was an error processing data", + data: error.response.data, + }; + } + } +}; diff --git a/server/services/logistics/route/dm/bulkOrdersIn.ts b/server/services/logistics/route/dm/bulkOrdersIn.ts new file mode 100644 index 0000000..b1f14f4 --- /dev/null +++ b/server/services/logistics/route/dm/bulkOrdersIn.ts @@ -0,0 +1,57 @@ +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"; + +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 orders to DM", + method: "post", + path: "/postbulkorders", + // 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.`); + // } + try { + const payload = await verify(token, process.env.JWT_SECRET!); + const orders = await ordersIn(body, payload.user); + + return c.json({ + success: orders.success, + message: orders.message, + data: orders.data, + }); + } catch (error) { + console.log(error); + return c.json({ success: false, message: "Unauthorized" }, 401); + } + } +); +export default app; diff --git a/server/services/logistics/route/dm/getStandardTemplate.ts b/server/services/logistics/route/dm/getStandardTemplate.ts new file mode 100644 index 0000000..1a9e9c6 --- /dev/null +++ b/server/services/logistics/route/dm/getStandardTemplate.ts @@ -0,0 +1,65 @@ +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"; + +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 Template", + method: "get", + path: "/bulkorderstemplate", + // 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 = `bulkOrdersInTemplate-${format( + new Date(Date.now()), + "M-d-yyyy" + )}.xlsx`; + const filename = c.req.query("filename") || defaultFilename; + const { data, error } = await tryCatch(standardTemplate()); + + 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; diff --git a/server/services/logistics/utils/excelDateStuff.ts b/server/services/logistics/utils/excelDateStuff.ts new file mode 100644 index 0000000..48a227d --- /dev/null +++ b/server/services/logistics/utils/excelDateStuff.ts @@ -0,0 +1,27 @@ +import { getJsDateFromExcel } from "excel-date-to-js"; + +export const excelDateStuff = (serial: number, time: any = 0) => { + // console.log(serial); + // add 5 hours or the offset to utc + + // get the local timezone + const localoffset = new Date().getTimezoneOffset() / 60; // then divide by 60 to get the true number; + + const addHours = serial + localoffset / 24; + //console.log(getJsDateFromExcel(addHours)); + if (typeof serial !== "number" || serial <= 0) { + return "invalid Date"; + } + + const date = getJsDateFromExcel(addHours); // base date from Excel serial + + if (time != 0) { + // convert the time over to hour and min + const hours = Math.floor(time / 100); + const minutes = time % 100; + date.setHours(hours); + date.setMinutes(minutes); + } + + return date.toLocaleString("en-US"); // or .toISOString() if preferred +}; diff --git a/server/services/sqlServer/querys/dm/bulkOrderArticleInfo.ts b/server/services/sqlServer/querys/dm/bulkOrderArticleInfo.ts new file mode 100644 index 0000000..8e5b3ad --- /dev/null +++ b/server/services/sqlServer/querys/dm/bulkOrderArticleInfo.ts @@ -0,0 +1,27 @@ +export const bulkOrderArticleInfo = ` +SELECT +x.HumanReadableId as av +,x.Name +,Alias +,CustomerDescription +,CustomerArticleNumber +,LoadingUnitPieces +,LoadingUnitsPerTruck +,LoadingUnitPieces * LoadingUnitsPerTruck as totalTruckLoad + FROM [test1_AlplaPROD2.0_Read].[masterData].[Article] (nolock) as x + +--get the sales price stuff +left join +(select * from (select * +,ROW_NUMBER() OVER (PARTITION BY articleId ORDER BY validAfter DESC) as rn +from [test1_AlplaPROD2.0_Read].[masterData].[SalesPrice] (nolock))as b +where rn = 1) as s on +x.id = s.ArticleId + +-- link pkg info +left join +[test1_AlplaPROD2.0_Read].[masterData].[PackagingInstruction] (nolock) as p on +s.PackagingId = p.id + +where x.HumanReadableId in ([articles]) +`; diff --git a/server/services/sqlServer/querys/dm/invoiceAddress.ts b/server/services/sqlServer/querys/dm/invoiceAddress.ts new file mode 100644 index 0000000..1765397 --- /dev/null +++ b/server/services/sqlServer/querys/dm/invoiceAddress.ts @@ -0,0 +1,15 @@ +export const invoiceAddress = ` +SELECT deliveryAddress.humanreadableid as deliveryAddress +,invoice.HumanReadableId as invoiceAddress +,[Default] + + FROM [test1_AlplaPROD2.0_Read].[masterData].[InvoiceAddress] (nolock) as d + + join + [test1_AlplaPROD2.0_Read].[masterData].[Address] deliveryAddress (nolock) on deliveryAddress.id = d.AddressId + + join + [test1_AlplaPROD2.0_Read].[masterData].[Address] invoice (nolock) on invoice.id = d.InvoiceAddressId + + where [Default] = 1 + `; diff --git a/server/services/sqlServer/querys/dm/orderState.ts b/server/services/sqlServer/querys/dm/orderState.ts new file mode 100644 index 0000000..f6223dd --- /dev/null +++ b/server/services/sqlServer/querys/dm/orderState.ts @@ -0,0 +1,8 @@ +export const orderState = ` +SELECT top(500) +CustomerOrderNumber +, OrderState +--, * + FROM [test1_AlplaPROD2.0_Read].[order].[Header] (nolock) +where OrderState = 0 +`;