Compare commits

...

3 Commits

14 changed files with 664 additions and 1 deletions

10
package-lock.json generated
View File

@@ -25,6 +25,7 @@
"drizzle-kit": "^0.30.5",
"drizzle-orm": "^0.41.0",
"drizzle-zod": "^0.7.0",
"excel-date-to-js": "^1.1.5",
"fast-xml-parser": "^5.0.9",
"fs-extra": "^11.3.0",
"jsonwebtoken": "^9.0.2",
@@ -4054,6 +4055,15 @@
"node": ">=0.8.x"
}
},
"node_modules/excel-date-to-js": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/excel-date-to-js/-/excel-date-to-js-1.1.5.tgz",
"integrity": "sha512-grZW0MPye0VGCzLNljI7H22QWgrI8/hkTCvIUczYsQTTSaPQU/UTcz1fBPHNxWKpiv8Zu2I/98z+aAnlp6STNw==",
"license": "MIT",
"engines": {
"node": ">=4.2.4"
}
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",

View File

@@ -73,6 +73,7 @@
"drizzle-kit": "^0.30.5",
"drizzle-orm": "^0.41.0",
"drizzle-zod": "^0.7.0",
"excel-date-to-js": "^1.1.5",
"fast-xml-parser": "^5.0.9",
"fs-extra": "^11.3.0",
"jsonwebtoken": "^9.0.2",

View File

@@ -0,0 +1,43 @@
import * as XLSX from "xlsx";
export const standardTemplate = async () => {
/**
* Creates the standard Template for bulk orders in
*/
const headers = [
[
"CustomerArticleNumber",
"CustomerOrderNumber",
"CustomerLineNumber",
"CustomerRealeaseNumber",
"Quantity",
"DeliveryDate",
"CustomerID",
// "InvoiceID",
],
];
// 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, "BulkOrdersTemplate");
// Write the workbook to a buffer and return it
const excelBuffer = XLSX.write(wb, { bookType: "xlsx", type: "buffer" });
return excelBuffer;
};

View File

@@ -0,0 +1,160 @@
import XLSX from "xlsx";
import { excelDateStuff } from "../../../../utils/excelDateStuff.js";
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 { bulkOrderArticleInfo } from "../../../../../sqlServer/querys/dm/bulkOrderArticleInfo.js";
import { isAfter, parse } from "date-fns";
import { orderState } from "../../../../../sqlServer/querys/dm/orderState.js";
import { postOrders } from "../postOrders.js";
// customeris/articles stuff will be in basis once we move to iowa
let customerID = 8;
let invoiceID = 9;
let articles = "118,120";
export const abbottOrders = 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,
};
}
// articleInfo
const { data: a, error: ae } = await tryCatch(
query(
bulkOrderArticleInfo.replace("[articles]", articles),
"Get Article data for bulk orders"
)
);
if (ae) {
return {
sucess: false,
message: `Error getting article data`,
data: ae,
};
}
// 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,
};
}
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 customHeaders = ["date", "time", "newton8oz", "newton10oz"];
const orderData = XLSX.utils.sheet_to_json(sheet, {
range: 5, // Start at row 5 (index 4)
header: customHeaders,
defval: "", // Default value for empty cells
});
// 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 oOrders: any = openOrders;
let correctedOrders: any = orderData
.filter(
(o: any) =>
(o.newton8oz && o.newton8oz.trim() !== "") ||
(o.newton10oz && o.newton10oz.trim() !== "")
)
.map((o: any) => ({
date: excelDateStuff(o.date, o.time),
po:
o.newton8oz.replace(" ", "") !== ""
? o.newton8oz.replace(" ", "")
: o.newton10oz.replace(" ", ""),
customerArticlenumber:
o.newton8oz != ""
? a.filter((a: any) => a.av === 118)[0]
.CustomerArticleNumber
: a.filter((a: any) => a.av === 120)[0]
.CustomerArticleNumber,
qty:
o.newton8oz != ""
? a.filter((a: any) => a.av === 118)[0].totalTruckLoad
: a.filter((a: any) => a.av === 120)[0].totalTruckLoad,
}));
// now we want to make sure we only correct orders that or after now
correctedOrders = correctedOrders.filter((o: any) => {
const parsedDate = parse(o.date, "M/d/yyyy, h:mm:ss a", new Date());
return isAfter(parsedDate, new Date());
});
// last map to remove orders that have already been started
correctedOrders = correctedOrders.filter((oo: any) =>
oOrders.some((o: any) => o.CustomerOrderNumber === oo.po)
);
// Map Excel data to predefinedObject format
const orders = correctedOrders.map((o: any) => {
return {
customerId: customerID,
invoiceAddressId: invoiceID,
customerOrderNo: o.po,
orderDate: new Date(Date.now()).toLocaleString("en-US"),
positions: [
{
deliveryAddressId: 8,
customerArticleNo: o.customerArticlenumber,
quantity: o.qty,
deliveryDate: o.date,
customerLineItemNo: 1, // this is how it is currently sent over from abbott
customerReleaseNo: 1, // same as above
},
],
};
});
// combine it all together.
const updatedPredefinedObject = {
...predefinedObject,
orders: [...predefinedObject.orders, ...orders],
};
// post the orders to the server
const posting = await postOrders(updatedPredefinedObject, user);
return {
success: posting?.success,
message: posting?.message,
data: posting?.data,
};
};

View File

@@ -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: [],
};
};

View File

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

View File

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

View File

@@ -11,6 +11,8 @@ import { getLanesToCycleCount } from "./controller/warehouse/cycleCountChecks/cy
import getCycleCountCheck from "./route/getCycleCountChecks.js";
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";
const app = new OpenAPIHono();
@@ -28,6 +30,9 @@ const routes = [
//warehouse
getPPOO,
getcyclecount,
//DM
postBulkOrders,
standardTemplate,
] as const;
// app.route("/server", modules);

View File

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

View File

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

View File

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

View File

@@ -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])
`;

View File

@@ -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
`;

View File

@@ -0,0 +1,8 @@
export const orderState = `
SELECT top(500)
CustomerOrderNumber
, OrderState
--, *
FROM [test1_AlplaPROD2.0_Read].[order].[Header] (nolock)
where OrderState = 0
`;