500 lines
14 KiB
TypeScript
500 lines
14 KiB
TypeScript
import axios from "axios";
|
|
import { addHours } from "date-fns";
|
|
import { formatInTimeZone } from "date-fns-tz";
|
|
import { sql } from "drizzle-orm";
|
|
import { db } from "../../db/db.controller.js";
|
|
import { opendockApt } from "../../db/schema/opendock.schema.js";
|
|
import { prodQuery } from "../../prodSql/prodSqlQuery.controller.js";
|
|
import { tryCatch } from "../../utils/trycatch.utils.js";
|
|
|
|
const releaseQuery = `
|
|
SELECT
|
|
[Id]
|
|
,[ReleaseNumber]
|
|
,[CustomerReleaseNumber]
|
|
,[ReleaseState]
|
|
,[LineItemId]
|
|
,[BlanketOrderId]
|
|
,[DeliveryState]
|
|
,[ReleaseConfirmationState]
|
|
,[PlanningState]
|
|
,[OrderDate]
|
|
,cast([DeliveryDate] as datetime2) as DeliveryDate
|
|
,[LoadingDate]
|
|
,[Quantity]
|
|
,[DeliveredQuantity]
|
|
,[DeliveredQuantityTradeUnits]
|
|
,[DeliveredQuantityLoadingUnits]
|
|
,[PackagingId]
|
|
,[PackagingHumanReadableId]
|
|
,[PackagingDescription]
|
|
,[MainMaterialId]
|
|
,[MainMaterialHumanReadableId]
|
|
,[MainMaterialDescription]
|
|
,[AdditionalInformation1]
|
|
,[AdditionalInformation2]
|
|
,[D365SupplierLot]
|
|
,[TradeUnits]
|
|
,[LoadingUnits]
|
|
,[Trucks]
|
|
,[LoadingToleranceType]
|
|
,[UnderdeliveryDeviation]
|
|
,[OverdeliveryDeviation]
|
|
,[ArticleAccountRequirements_ArticleExact]
|
|
,[ArticleAccountRequirements_CustomerExact]
|
|
,[ArticleAccountRequirements_PackagingExact]
|
|
,[ArticleAccountRequirements_MainMaterialExact]
|
|
,[PriceLogicType]
|
|
,[AllowProductionLotMixing]
|
|
,[EnforceStrictPicking]
|
|
,[SalesPrice]
|
|
,[Currency]
|
|
,[QuantityUnit]
|
|
,[SalesPriceRemark]
|
|
,[DeliveryConditionId]
|
|
,[DeliveryConditionHumanReadableId]
|
|
,[DeliveryConditionDescription]
|
|
,[PaymentTermsId]
|
|
,[PaymentTermsHumanReadableId]
|
|
,[PaymentTermsDescription]
|
|
,[Remark]
|
|
,[DeliveryAddressId]
|
|
,[DeliveryAddressHumanReadableId]
|
|
,[DeliveryAddressDescription]
|
|
,[DeliveryStreetName]
|
|
,[DeliveryAddressZip]
|
|
,[DeliveryCity]
|
|
,[DeliveryCountry]
|
|
,[ReleaseDiscount]
|
|
,[CustomerArtNo]
|
|
,[LineItemHumanReadableId]
|
|
,[LineItemArticle]
|
|
,[LineItemArticleWeight]
|
|
,[LineItemQuantityType]
|
|
,[TotalPrice]
|
|
,[Add_User]
|
|
,[Add_Date]
|
|
,[Upd_User]
|
|
,cast([Upd_Date] as dateTime) as Upd_Date
|
|
,[VatRate]
|
|
,[ArticleAlias]
|
|
FROM [test1_AlplaPROD2.0_Reporting].[reporting_order].[Release] (nolock)
|
|
where format([Upd_Date], 'yyyy-MM-dd HH:mm:ss') > [dateCheck]`;
|
|
|
|
let lastCheck = formatInTimeZone(
|
|
new Date().toISOString(),
|
|
"America/New_York",
|
|
"yyyy-MM-dd HH:mm:ss",
|
|
);
|
|
|
|
const delay = (ms: number) => {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
};
|
|
|
|
//const queue: unknown[] = [];
|
|
//const isProcessing: boolean = false;
|
|
|
|
let odToken: string | null = null;
|
|
|
|
type Releases = {
|
|
ReleaseNumber: number;
|
|
DeliveryState: number;
|
|
DeliveryDate: Date;
|
|
LineItemHumanReadableId: number;
|
|
ArticleAlias: string;
|
|
LoadingUnits: string;
|
|
Quantity: number;
|
|
LineItemArticleWeight: number;
|
|
CustomerReleaseNumber: string;
|
|
};
|
|
const postRelease = async (release: Releases) => {
|
|
if (!odToken) {
|
|
console.info("Getting token");
|
|
// TODO: add a time stamp on the token so it gets refreshed every 24hours
|
|
await getToken();
|
|
}
|
|
/**
|
|
* ReleaseState
|
|
* 0 = open
|
|
* 1 = planned
|
|
* 2 = CustomCanceled
|
|
* 4 = internally canceled
|
|
*/
|
|
|
|
/**
|
|
* DeliveryState
|
|
* 0 = open
|
|
* 1 = inprogress
|
|
* 2 = loading
|
|
* 3 = partly shipped
|
|
* 4 = delivered
|
|
*/
|
|
|
|
const newDockApt = {
|
|
status:
|
|
release.DeliveryState === 0 || release.DeliveryState === 1
|
|
? "Scheduled"
|
|
: release.DeliveryState === 2
|
|
? "InProgress"
|
|
: release.DeliveryState === 3 // this will consider finished and if a correction needs made to the bol we need to cancel and reactivate the order
|
|
? "Completed"
|
|
: release.DeliveryState === 4 && "Completed",
|
|
userId: "2629b4f6-0003-472d-8b26-66a69ce5ac50", // this should be the carrierid
|
|
loadTypeId: "0aa7988e-b17b-4f10-acdd-3d029b44a773", // well get this and make it a default one
|
|
dockId: "00ba4386-ce5a-4dd1-9356-6e6d10a24609", // this the warehouse we want it in to start out
|
|
refNumbers: [release.ReleaseNumber],
|
|
refNumber: release.ReleaseNumber,
|
|
start: release.DeliveryDate,
|
|
end: addHours(release.DeliveryDate, 1),
|
|
notes: "",
|
|
ccEmails: [""],
|
|
muteNotifications: true,
|
|
metadata: {
|
|
externalValidationFailed: false,
|
|
externalValidationErrorMessage: null,
|
|
},
|
|
units: null,
|
|
customFields: [
|
|
{
|
|
name: "strArticle",
|
|
type: "str",
|
|
label: "Article",
|
|
value: `${release.LineItemHumanReadableId} - ${release.ArticleAlias}`,
|
|
description: "What bottle are we sending ",
|
|
placeholder: "",
|
|
dropDownValues: [],
|
|
minLengthOrValue: 1,
|
|
hiddenFromCarrier: false,
|
|
requiredForCarrier: false,
|
|
requiredForWarehouse: false,
|
|
},
|
|
{
|
|
name: "intPallet Count",
|
|
type: "int",
|
|
label: "Pallet Count",
|
|
value: parseInt(release.LoadingUnits, 10),
|
|
description: "How many pallets",
|
|
placeholder: "22",
|
|
dropDownValues: [],
|
|
minLengthOrValue: 1,
|
|
hiddenFromCarrier: false,
|
|
requiredForCarrier: false,
|
|
requiredForWarehouse: false,
|
|
},
|
|
{
|
|
name: "strTotal Weight",
|
|
type: "str",
|
|
label: "Total Weight",
|
|
value: `${(((release.Quantity * release.LineItemArticleWeight) / 1000) * 2.20462).toFixed(2)}`,
|
|
description: "What is the total weight of the load",
|
|
placeholder: "",
|
|
dropDownValues: [],
|
|
minLengthOrValue: 1,
|
|
hiddenFromCarrier: false,
|
|
requiredForCarrier: false,
|
|
requiredForWarehouse: false,
|
|
},
|
|
{
|
|
name: "strCustomer ReleaseNumber",
|
|
type: "str",
|
|
label: "Customer Release Number",
|
|
value: `${release.CustomerReleaseNumber}`,
|
|
description: "What is the customer release number",
|
|
placeholder: "",
|
|
dropDownValues: [],
|
|
minLengthOrValue: 1,
|
|
hiddenFromCarrier: false,
|
|
requiredForCarrier: false,
|
|
requiredForWarehouse: false,
|
|
},
|
|
],
|
|
};
|
|
|
|
// TODO: pull the current added releases from the db and if one matches then we want to get its id and run the update vs create
|
|
const { data: apt, error: aptError } = await tryCatch(
|
|
db.select().from(opendockApt),
|
|
);
|
|
|
|
if (aptError) {
|
|
console.error("Error getting apt data: ", aptError);
|
|
// TODO: send an error email on this one as it will cause issues
|
|
return;
|
|
}
|
|
|
|
const releaseCheck = apt.filter((r) => r.release === release.ReleaseNumber);
|
|
|
|
//console.log(releaseCheck);
|
|
|
|
if (releaseCheck.length > 0) {
|
|
const id = releaseCheck[0]?.openDockAptId;
|
|
try {
|
|
const response = await axios.patch(
|
|
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
|
newDockApt,
|
|
{
|
|
headers: {
|
|
"content-type": "application/json; charset=utf-8",
|
|
Authorization: `Bearer ${odToken}`,
|
|
},
|
|
},
|
|
);
|
|
|
|
if (response.status === 400) {
|
|
console.error(response.data.data.message);
|
|
return;
|
|
}
|
|
|
|
// update the release in the db leaving as insert just incase something weird happened
|
|
try {
|
|
await db
|
|
.insert(opendockApt)
|
|
.values({
|
|
release: release.ReleaseNumber,
|
|
openDockAptId: response.data.data.id,
|
|
appointment: response.data.data,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: opendockApt.release,
|
|
set: { appointment: response.data.data, upd_date: sql`NOW()` },
|
|
})
|
|
.returning();
|
|
|
|
console.info(`${release.ReleaseNumber} was updated`);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
} catch (e: any) {
|
|
//console.info(newDockApt);
|
|
console.error(e.response.data);
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
try {
|
|
const response = await axios.post(
|
|
`${process.env.OPENDOCK_URL}/appointment`,
|
|
newDockApt,
|
|
{
|
|
headers: {
|
|
"content-type": "application/json; charset=utf-8",
|
|
Authorization: `Bearer ${odToken}`,
|
|
},
|
|
},
|
|
);
|
|
|
|
// we need the id,release#,status from this response, store it in lst, check if we have a release so we can just update it.
|
|
// this will be utilized when we are listening for the changes to the apts. that way we can update the state to arrived. we will run our own checks on this guy during the incoming messages.
|
|
|
|
if (response.status === 400) {
|
|
console.error(response.data.data.message);
|
|
return;
|
|
}
|
|
|
|
// the response to make it simple we want response.data.id, response.data.relNumber, status will be defaulted to Scheduled if we created it here.
|
|
// TODO: add this release data to our db. but save it in json format and well parse it out. that way we future proof it and have everything in here vs just a few things
|
|
//console.info(response.data.data, "Was Created");
|
|
try {
|
|
await db
|
|
.insert(opendockApt)
|
|
.values({
|
|
release: release.ReleaseNumber,
|
|
openDockAptId: response.data.data.id,
|
|
appointment: response.data.data,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: opendockApt.id,
|
|
set: { appointment: response.data.data, upd_date: sql`NOW()` },
|
|
})
|
|
.returning();
|
|
|
|
console.info(`${release.ReleaseNumber} was created`);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
} catch (e: any) {
|
|
console.error(e.response.data);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
await delay(500); // rate limit protection
|
|
};
|
|
|
|
export const monitorReleaseChanges = async () => {
|
|
console.info("Starting release monitor", lastCheck);
|
|
|
|
// run the main game loop
|
|
while (true) {
|
|
try {
|
|
const result = await prodQuery(
|
|
releaseQuery.replace("[dateCheck]", `'${lastCheck}'`),
|
|
"get last release change",
|
|
);
|
|
|
|
if (result.data.length) {
|
|
for (const release of result.data) {
|
|
// potentially move this to a buffer table to easy up on memory
|
|
await postRelease(release);
|
|
|
|
// Move checkpoint AFTER successful post
|
|
lastCheck = formatInTimeZone(
|
|
new Date(release.Upd_Date).toISOString(),
|
|
"UTC",
|
|
"yyyy-MM-dd HH:mm:ss",
|
|
);
|
|
|
|
await delay(500);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error("Monitor error:", e);
|
|
}
|
|
|
|
await delay(15 * 1000); // making this 15 seconds as we would really only see issues if we have a mass burst.
|
|
}
|
|
};
|
|
|
|
const getToken = async () => {
|
|
try {
|
|
const { status, data } = await axios.post(
|
|
`${process.env.OPENDOCK_URL}/auth/login`,
|
|
{
|
|
email: "blake.matthes@alpla.com",
|
|
password: process.env.OPENDOCK_PASSWORD,
|
|
},
|
|
);
|
|
|
|
if (status === 400) {
|
|
console.error(data.message);
|
|
return;
|
|
}
|
|
|
|
odToken = data.access_token;
|
|
console.info("Token added");
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
// export const monitorReleaseChanges = async () => {
|
|
// console.log("Starting release monitor", lastCheck);
|
|
// setInterval(async () => {
|
|
// try {
|
|
// const result = await prodQuery(
|
|
// releaseQuery.replace("[dateCheck]", `'${lastCheck}'`),
|
|
// "get last release change",
|
|
// );
|
|
|
|
// //console.log(releaseQuery.replace("[dateCheck]", `'${lastCheck}'`));
|
|
// if (result.data.length > 0) {
|
|
// console.log(
|
|
// formatInTimeZone(
|
|
// result.data[result.data.length - 1].Upd_Date,
|
|
// "UTC",
|
|
// "yyyy-MM-dd HH:mm:ss",
|
|
// ),
|
|
// lastCheck,
|
|
// );
|
|
// lastCheck = formatInTimeZone(
|
|
// result.data[result.data.length - 1].Upd_Date,
|
|
// "UTC",
|
|
// "yyyy-MM-dd HH:mm:ss",
|
|
// );
|
|
// const releases = result.data;
|
|
// for (let i = 0; i < releases.length; i++) {
|
|
// const newDockApt = {
|
|
// status: "Scheduled",
|
|
// userId: "ee956455-e193-47fc-b53b-dff30fabdf4b", // this should be the carrierid
|
|
// loadTypeId: "0aa7988e-b17b-4f10-acdd-3d029b44a773", // well get this and make it a default one
|
|
// dockId: "00ba4386-ce5a-4dd1-9356-6e6d10a24609", // this the warehouse we want it in to start out
|
|
// refNumbers: [releases[i].ReleaseNumber],
|
|
// refNumber: releases[i].ReleaseNumber,
|
|
// start: releases[i].DeliveryDate,
|
|
// end: addHours(releases[i].DeliveryDate, 1),
|
|
// notes: "",
|
|
// ccEmails: [""],
|
|
// muteNotifications: true,
|
|
// metadata: {
|
|
// externalValidationFailed: false,
|
|
// externalValidationErrorMessage: null,
|
|
// },
|
|
// units: null,
|
|
// customFields: [
|
|
// {
|
|
// name: "strArticle",
|
|
// type: "str",
|
|
// label: "Article",
|
|
// value: `${releases[i].LineItemHumanReadableId} - ${releases[i].ArticleAlias}`,
|
|
// description: "What bottle are we sending ",
|
|
// placeholder: "",
|
|
// dropDownValues: [],
|
|
// minLengthOrValue: 1,
|
|
// hiddenFromCarrier: false,
|
|
// requiredForCarrier: false,
|
|
// requiredForWarehouse: false,
|
|
// },
|
|
// {
|
|
// name: "intPallet Count",
|
|
// type: "int",
|
|
// label: "Pallet Count",
|
|
// value: parseInt(releases[i].LoadingUnits, 10),
|
|
// description: "How many pallets",
|
|
// placeholder: "22",
|
|
// dropDownValues: [],
|
|
// minLengthOrValue: 1,
|
|
// hiddenFromCarrier: false,
|
|
// requiredForCarrier: false,
|
|
// requiredForWarehouse: false,
|
|
// },
|
|
// {
|
|
// name: "strTotal Weight",
|
|
// type: "str",
|
|
// label: "Total Weight",
|
|
// value: `${(((releases[i].Quantity * releases[i].LineItemArticleWeight) / 1000) * 2.20462).toFixed(2)}`,
|
|
// description: "What is the total weight of the load",
|
|
// placeholder: "",
|
|
// dropDownValues: [],
|
|
// minLengthOrValue: 1,
|
|
// hiddenFromCarrier: false,
|
|
// requiredForCarrier: false,
|
|
// requiredForWarehouse: false,
|
|
// },
|
|
// {
|
|
// name: "strCustomer ReleaseNumber",
|
|
// type: "str",
|
|
// label: "Customer Release Number",
|
|
// value: `${releases[i].CustomerReleaseNumber}`,
|
|
// description: "What is the customer release number",
|
|
// placeholder: "",
|
|
// dropDownValues: [],
|
|
// minLengthOrValue: 1,
|
|
// hiddenFromCarrier: false,
|
|
// requiredForCarrier: false,
|
|
// requiredForWarehouse: false,
|
|
// },
|
|
// ],
|
|
// };
|
|
|
|
// //console.log(newDockApt);
|
|
|
|
// const newDockResult = await axios.post(
|
|
// "https://neutron.staging.opendock.com/appointment",
|
|
// newDockApt,
|
|
// {
|
|
// headers: {
|
|
// "content-type": "application/json; charset=utf-8",
|
|
// },
|
|
// },
|
|
// );
|
|
|
|
// console.log(newDockResult.statusText);
|
|
// await delay(500);
|
|
// }
|
|
// }
|
|
// } catch (e) {
|
|
// console.log(e);
|
|
// }
|
|
// }, 5 * 1000);
|
|
// };
|