feat(puchase hist): finished up purhcase historical / gp updates

This commit is contained in:
2026-04-09 21:12:43 -05:00
parent 8dfcbc5720
commit a691dc276e
22 changed files with 3686 additions and 12 deletions

View File

@@ -0,0 +1,17 @@
import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import restart from "./gpSqlRestart.route.js";
import start from "./gpSqlStart.route.js";
import stop from "./gpSqlStop.route.js";
export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
// Apply auth to entire router
const router = Router();
router.use(requireAuth);
router.use(start);
router.use(stop);
router.use(restart);
app.use(`${baseUrl}/api/system/gpSql`, router);
};

View File

@@ -0,0 +1,155 @@
import sql from "mssql";
import { gpSqlConfig } from "../configs/gpSql.config.js";
import { createLogger } from "../logger/logger.controller.js";
import { checkHostnamePort } from "../utils/checkHost.utils.js";
import { returnFunc } from "../utils/returnHelper.utils.js";
export let pool2: sql.ConnectionPool;
export let connected: boolean = false;
export let reconnecting = false;
export const connectGPSql = async () => {
const serverUp = await checkHostnamePort(`USMCD1VMS011:1433`);
if (!serverUp) {
// we will try to reconnect
connected = false;
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "db",
message: "GP server is offline or unreachable.",
});
}
// if we are trying to click restart from the api for some reason we want to kick back and say no
if (connected) {
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "db",
message: "The Sql server is already connected.",
});
}
// try to connect to the sql server
try {
pool2 = new sql.ConnectionPool(gpSqlConfig);
await pool2.connect();
connected = true;
return returnFunc({
success: true,
level: "info",
module: "system",
subModule: "db",
message: `${gpSqlConfig.server} is connected to ${gpSqlConfig.database}`,
data: [],
notify: false,
});
} catch (error) {
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "db",
message: "Failed to connect to the prod sql server.",
data: [error],
notify: false,
});
}
};
export const closePool = async () => {
if (!connected) {
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "db",
message: "There is no connection to the prod server currently.",
});
}
try {
await pool2.close();
connected = false;
return returnFunc({
success: true,
level: "info",
module: "system",
subModule: "db",
message: "The sql connection has been closed.",
});
} catch (error) {
connected = false;
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "db",
message: "There was an error closing the sql connection",
data: [error],
});
}
};
export const reconnectToSql = async () => {
const log = createLogger({
module: "system",
subModule: "db",
});
if (reconnecting) return;
//set reconnecting to true while we try to reconnect
reconnecting = true;
// start the delay out as 2 seconds
let delayStart = 2000;
let attempt = 0;
const maxAttempts = 10;
while (!connected && attempt < maxAttempts) {
attempt++;
log.info(
`Reconnect attempt ${attempt}/${maxAttempts} in ${delayStart / 1000}s ...`,
);
await new Promise((res) => setTimeout(res, delayStart));
const serverUp = await checkHostnamePort(`${process.env.PROD_SERVER}:1433`);
if (!serverUp) {
delayStart = Math.min(delayStart * 2, 30000); // exponential backoff until up to 30000
return;
}
try {
pool2 = await sql.connect(gpSqlConfig);
reconnecting = false;
connected = true;
log.info(`${gpSqlConfig.server} is connected to ${gpSqlConfig.database}`);
} catch (error) {
delayStart = Math.min(delayStart * 2, 30000);
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "db",
message: "Failed to reconnect to the prod sql server.",
data: [error],
notify: false,
});
}
}
if (!connected) {
log.error(
{ notify: true },
"Max reconnect attempts reached on the prodSql server. Stopping retries.",
);
reconnecting = false;
// TODO: exit alert someone here
}
};

View File

@@ -0,0 +1,97 @@
import { returnFunc } from "../utils/returnHelper.utils.js";
import {
connected,
pool2,
reconnecting,
reconnectToSql,
} from "./gpSqlConnection.controller.js";
interface SqlError extends Error {
code?: string;
originalError?: {
info?: { message?: string };
};
}
/**
* Run a prod query
* just pass over the query as a string and the name of the query.
* Query should be like below.
* * select * from AlplaPROD_test1.dbo.table
* You must use test1 always as it will be changed via query
*/
export const gpQuery = async (queryToRun: string, name: string) => {
if (!connected) {
reconnectToSql();
if (reconnecting) {
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "gpSql",
message: `The sql ${process.env.PROD_PLANT_TOKEN} is trying to reconnect already`,
data: [],
notify: false,
});
} else {
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "gpSql",
message: `${process.env.PROD_PLANT_TOKEN} is not connected, and failed to connect.`,
data: [],
notify: true,
});
}
}
//change to the correct server
const query = queryToRun.replaceAll(
"test1",
`${process.env.PROD_PLANT_TOKEN}`,
);
try {
const result = await pool2.request().query(query);
return {
success: true,
message: `Query results for: ${name}`,
data: result.recordset ?? [],
};
} catch (error: unknown) {
const err = error as SqlError;
if (err.code === "ETIMEOUT") {
return returnFunc({
success: false,
module: "system",
subModule: "gpSql",
level: "error",
message: `${name} did not run due to a timeout.`,
notify: false,
data: [],
});
}
if (err.code === "EREQUEST") {
return returnFunc({
success: false,
module: "system",
subModule: "gpSql",
level: "error",
message: `${name} encountered an error ${err.originalError?.info?.message || "undefined error"}`,
data: [],
});
}
return returnFunc({
success: false,
module: "system",
subModule: "gpSql",
level: "error",
message: `${name} encountered an unknown error.`,
data: [],
});
}
};

View File

@@ -0,0 +1,29 @@
import { readFileSync } from "node:fs";
export type SqlGPQuery = {
query: string;
success: boolean;
message: string;
};
export const sqlGpQuerySelector = (name: string) => {
try {
const queryFile = readFileSync(
new URL(`../gpSql/queries/${name}.sql`, import.meta.url),
"utf8",
);
return {
success: true,
message: `Query for: ${name}`,
query: queryFile,
};
} catch (e) {
console.error(e);
return {
success: false,
message:
"Error getting the query file, please make sure you have the correct name.",
};
}
};

View File

@@ -0,0 +1,23 @@
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { closePool, connectGPSql } from "./gpSqlConnection.controller.js";
const r = Router();
r.post("/restart", async (_, res) => {
await closePool();
await new Promise((r) => setTimeout(r, 2000));
const connect = await connectGPSql();
apiReturn(res, {
success: connect.success,
level: connect.success ? "info" : "error",
module: "routes",
subModule: "prodSql",
message: "Sql Server has been restarted",
data: connect.data,
status: connect.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,20 @@
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { connectGPSql } from "./gpSqlConnection.controller.js";
const r = Router();
r.post("/start", async (_, res) => {
const connect = await connectGPSql();
apiReturn(res, {
success: connect.success,
level: connect.success ? "info" : "error",
module: "routes",
subModule: "prodSql",
message: connect.message,
data: connect.data,
status: connect.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,20 @@
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { closePool } from "./gpSqlConnection.controller.js";
const r = Router();
r.post("/stop", async (_, res) => {
const connect = await closePool();
apiReturn(res, {
success: connect.success,
level: connect.success ? "info" : "error",
module: "routes",
subModule: "prodSql",
message: connect.message,
data: connect.data,
status: connect.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,39 @@
USE [ALPLA]
SELECT Distinct r.[POPRequisitionNumber] as req,
r.[ApprovalStatus] as approvalStatus,
r.[Requested By] requestedBy,
format(t.[Created Date], 'yyyy-MM-dd') as createdAt,
format(r.[Requisition Date], 'MM/dd/yyyy') as expectedDate,
r.[Requisition Amount] as glAccount,
case when r.[Account Segment 2] is null or r.[Account Segment 2] = '' then '999' else cast(r.[Account Segment 2] as varchar) end as plant
,t.Status as status
,t.[Document Status] as docStatus
,t.[Workflow Status] as reqState
,CASE
WHEN [Workflow Status] = 'Completed'
THEN 'Pending APO convertion'
WHEN [Workflow Status] = 'Pending User Action'
AND r.[ApprovalStatus] = 'Pending Approval'
THEN 'Pending plant approver'
WHEN [Workflow Status] = ''
AND r.[ApprovalStatus] = 'Not Submitted'
THEN 'Req not submited'
ELSE 'Unknown reason'
END AS approvedStatus
FROM [dbo].[PORequisitions] r (nolock)
left join
[dbo].[PurchaseRequisitions] as t (nolock) on
t.[Requisition Number] = r.[POPRequisitionNumber]
--where ApprovalStatus = 'Pending Approval'
--and [Account Segment 2] = 80
where r.POPRequisitionNumber in ([reqsToCheck])
Order By r.POPRequisitionNumber