feat(standard orders in): multi customer orders in plus ignore already started
This commit is contained in:
@@ -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: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
47
server/services/logistics/controller/dm/ordersIn/ordersIn.ts
Normal file
47
server/services/logistics/controller/dm/ordersIn/ordersIn.ts
Normal 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
57
server/services/logistics/route/dm/bulkOrdersIn.ts
Normal file
57
server/services/logistics/route/dm/bulkOrdersIn.ts
Normal 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;
|
||||||
65
server/services/logistics/route/dm/getStandardTemplate.ts
Normal file
65
server/services/logistics/route/dm/getStandardTemplate.ts
Normal 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;
|
||||||
27
server/services/logistics/utils/excelDateStuff.ts
Normal file
27
server/services/logistics/utils/excelDateStuff.ts
Normal 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
|
||||||
|
};
|
||||||
27
server/services/sqlServer/querys/dm/bulkOrderArticleInfo.ts
Normal file
27
server/services/sqlServer/querys/dm/bulkOrderArticleInfo.ts
Normal 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])
|
||||||
|
`;
|
||||||
15
server/services/sqlServer/querys/dm/invoiceAddress.ts
Normal file
15
server/services/sqlServer/querys/dm/invoiceAddress.ts
Normal 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
|
||||||
|
`;
|
||||||
8
server/services/sqlServer/querys/dm/orderState.ts
Normal file
8
server/services/sqlServer/querys/dm/orderState.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const orderState = `
|
||||||
|
SELECT top(500)
|
||||||
|
CustomerOrderNumber
|
||||||
|
, OrderState
|
||||||
|
--, *
|
||||||
|
FROM [test1_AlplaPROD2.0_Read].[order].[Header] (nolock)
|
||||||
|
where OrderState = 0
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user