Compare commits
10 Commits
3f04609f82
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ff10dfcb2 | |||
| 9a0bb18c5b | |||
| 1838c6f1e9 | |||
| 3a24d62957 | |||
| 6a14bab30c | |||
| 2ebf695526 | |||
| 24af3ca403 | |||
| 6fbe3a9eed | |||
| 7dbc31c046 | |||
| ba09a77f29 |
90
README.md
90
README.md
@@ -7,7 +7,7 @@
|
|||||||
Quick summary of current rewrite/migration goal.
|
Quick summary of current rewrite/migration goal.
|
||||||
|
|
||||||
- **Phase:** Backend rewrite
|
- **Phase:** Backend rewrite
|
||||||
- **Last updated:** 2026-05-27
|
- **Last updated:** 2026-06-17
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,21 +39,91 @@ _Status legend:_
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Setup / Installation
|
# Install
|
||||||
|
|
||||||
How to run the current version of the app.
|
## Files needed to be downloaded before install.
|
||||||
|
|
||||||
|
### To run the server
|
||||||
|
|
||||||
|
- [PostgresSQL](https://www.postgresql.org/download/windows/) - current version using is 17
|
||||||
|
- [NodeJS](https://nodejs.org)
|
||||||
|
- [NSSM](https://nssm.cc/)
|
||||||
|
|
||||||
|
### To manage the server
|
||||||
|
|
||||||
|
- [VSCODE](https://code.visualstudio.com/)
|
||||||
|
|
||||||
|
## Creating directories needed
|
||||||
|
|
||||||
|
- Create a new folder where we will host the server files.
|
||||||
|
- Copy the nssm.exe into this folder
|
||||||
|
- Copy the get the build from the releases and extract.
|
||||||
|
- This will house all the compiles and minified files needed to start the server up, this includes the frontend.
|
||||||
|
- Save the nssm.exe into this folder as well, this will be used to control the service.
|
||||||
|
|
||||||
|
## Do the initial install
|
||||||
|
|
||||||
|
### DB instal setup
|
||||||
|
|
||||||
|
1. Install postgres
|
||||||
|
2. Open pgAdmin
|
||||||
|
3. create a new Database named lst_db_v3. this can also be to your liking
|
||||||
|
|
||||||
|
### Initial server setup
|
||||||
|
|
||||||
|
1. Open VSCode and navigate to the folder where you extracted the files.
|
||||||
|
2. Click trusted when it pops up.
|
||||||
|
3. Open a terminal window inside vscode.
|
||||||
|
4. Run the install script this will install all dependence's needed as well as do all the database migrations
|
||||||
|
|
||||||
|
|
||||||
|
### Create the .env file
|
||||||
|
|
||||||
|
In the root of the folder create a new .env file by renaming .env-example to .env
|
||||||
|
|
||||||
|
change all the parameters to your desired server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.tuffraid.net/cowch/lst_v3.git
|
npm run install --omit=dev
|
||||||
cd lst_v3
|
|
||||||
npm install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Rename the .env-example to .env
|
Next we want to do an initial db
|
||||||
|
|
||||||
Update all the fields
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev:db:migrate
|
npm run dev:db:migrate
|
||||||
npm run dev
|
```
|
||||||
|
|
||||||
|
### Run the start command to get all the basic settings and modules installed
|
||||||
|
|
||||||
|
1. Run the below
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating first user.
|
||||||
|
|
||||||
|
Open http://[SERVER]:[PORT]/api/docs or postman and create a user.
|
||||||
|
- Please do not try to manually enter a new user this is due to how the password is hashed, as well as setting systemAdmin for the first user.
|
||||||
|
- Change the server and port to what you changed in the DB.
|
||||||
|
|
||||||
|
### Running as a serivice.
|
||||||
|
|
||||||
|
You want to CD into the scripts folder.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd .\scripts\
|
||||||
|
```
|
||||||
|
|
||||||
|
Next use the example command below to get the service up and running.
|
||||||
|
|
||||||
|
- Options legend
|
||||||
|
- serviceName = not recommended to change to reduce issues with the update process
|
||||||
|
- option = use install for the install, but you can use this script later to stop, start, restart the service.
|
||||||
|
- appPath = where did you extract the server files
|
||||||
|
- description = no need to change this unless you want it to be something else
|
||||||
|
- command = do not change this unless you know what your doing and really need to change this.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\services.ps1 -serviceName "LSTV3_app" -option "install" -appPath "D:\LS_V3T" -description "Logistics Support Tool V3" -command "run start"
|
||||||
```
|
```
|
||||||
@@ -9,8 +9,6 @@ import os from "node:os";
|
|||||||
import { apiReference } from "@scalar/express-api-reference";
|
import { apiReference } from "@scalar/express-api-reference";
|
||||||
// const port = 3000;
|
// const port = 3000;
|
||||||
import type { OpenAPIV3_1 } from "openapi-types";
|
import type { OpenAPIV3_1 } from "openapi-types";
|
||||||
import { cronerActiveJobs } from "../scaler/cronerActiveJobs.spec.js";
|
|
||||||
import { cronerStatusChange } from "../scaler/cronerStatusChange.spec.js";
|
|
||||||
import { prodLoginSpec } from "../scaler/login.spec.js";
|
import { prodLoginSpec } from "../scaler/login.spec.js";
|
||||||
import { openDockApt } from "../scaler/opendockGetRelease.spec.js";
|
import { openDockApt } from "../scaler/opendockGetRelease.spec.js";
|
||||||
import { prodRestartSpec } from "../scaler/prodSqlRestart.spec.js";
|
import { prodRestartSpec } from "../scaler/prodSqlRestart.spec.js";
|
||||||
@@ -125,8 +123,6 @@ export const setupApiDocsRoutes = (baseUrl: string, app: Express) => {
|
|||||||
...prodLoginSpec,
|
...prodLoginSpec,
|
||||||
...prodRegisterSpec,
|
...prodRegisterSpec,
|
||||||
//...mergedDatamart,
|
//...mergedDatamart,
|
||||||
...cronerActiveJobs,
|
|
||||||
...cronerStatusChange,
|
|
||||||
...openDockApt,
|
...openDockApt,
|
||||||
|
|
||||||
// Add more specs here as you build features
|
// Add more specs here as you build features
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const lstDbRun = async (data: Data) => {
|
|||||||
if (data.options) {
|
if (data.options) {
|
||||||
if (data.name === "psiInventory") {
|
if (data.name === "psiInventory") {
|
||||||
const ids = data.options.articles.split(",").map((id: any) => id.trim());
|
const ids = data.options.articles.split(",").map((id: any) => id.trim());
|
||||||
|
|
||||||
const whse = data.options.whseToInclude
|
const whse = data.options.whseToInclude
|
||||||
? data.options.whseToInclude
|
? data.options.whseToInclude
|
||||||
.split(",")
|
.split(",")
|
||||||
@@ -274,10 +275,10 @@ export const runDatamartQuery = async (data: Data) => {
|
|||||||
.replace("[startDate]", `${data.options.startDate}`)
|
.replace("[startDate]", `${data.options.startDate}`)
|
||||||
.replace("[endDate]", `${data.options.endDate}`)
|
.replace("[endDate]", `${data.options.endDate}`)
|
||||||
.replace(
|
.replace(
|
||||||
"and p.IdArtikelvarianten in ([articles])",
|
"and pl.ArticleHumanReadableId IN ([articles]) ",
|
||||||
data.options.articles
|
data.options.articles
|
||||||
? `and p.IdArtikelvarianten in (${data.options.articles})`
|
? `and pl.ArticleHumanReadableId IN (${data.options.articles})`
|
||||||
: "--and p.IdArtikelvarianten in ([articles])",
|
: "--and pl.ArticleHumanReadableId IN ([articles])",
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { runDatamartQuery } from "./datamart.controller.js";
|
import { runDatamartQuery } from "./datamart.controller.js";
|
||||||
|
|
||||||
@@ -7,13 +8,73 @@ const r = Router();
|
|||||||
type Options = {
|
type Options = {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
format: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
r.get("/:name", async (req, res) => {
|
r.get("/:name", async (req, res) => {
|
||||||
const { name } = req.params;
|
const { name } = req.params;
|
||||||
const options = req.query as Options;
|
const options = { ...req.query } as Options;
|
||||||
|
|
||||||
const dataRan = await runDatamartQuery({ name, options });
|
const dataRan = await runDatamartQuery({ name, options });
|
||||||
|
|
||||||
|
if (!dataRan.success) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "datamart",
|
||||||
|
subModule: "query",
|
||||||
|
message: dataRan.message,
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// XLSX Export
|
||||||
|
if (options.format?.toLowerCase() === "xlsx") {
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(dataRan.data);
|
||||||
|
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, name);
|
||||||
|
|
||||||
|
const buffer = XLSX.write(wb, {
|
||||||
|
type: "buffer",
|
||||||
|
bookType: "xlsx",
|
||||||
|
});
|
||||||
|
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Type",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
);
|
||||||
|
|
||||||
|
res.setHeader("Content-Disposition", `attachment; filename="${name}.xlsx"`);
|
||||||
|
|
||||||
|
return res.send(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSV Export
|
||||||
|
if (options.format?.toLowerCase() === "csv") {
|
||||||
|
const rows = dataRan.data as any;
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
return res.status(200).send("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = Object.keys(rows[0]);
|
||||||
|
|
||||||
|
const csv = [
|
||||||
|
headers.join(","),
|
||||||
|
...rows.map((row: any) =>
|
||||||
|
headers
|
||||||
|
.map((h) => `"${String(row[h] ?? "").replace(/"/g, '""')}"`)
|
||||||
|
.join(","),
|
||||||
|
),
|
||||||
|
].join("\r\n");
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "text/csv");
|
||||||
|
res.setHeader("Content-Disposition", `attachment; filename="${name}.csv"`);
|
||||||
|
|
||||||
|
return res.send(csv);
|
||||||
|
}
|
||||||
|
|
||||||
return apiReturn(res, {
|
return apiReturn(res, {
|
||||||
success: dataRan.success,
|
success: dataRan.success,
|
||||||
level: "info",
|
level: "info",
|
||||||
|
|||||||
92
backend/logistics/logistics.dm.forecast.map.abbott.ts
Normal file
92
backend/logistics/logistics.dm.forecast.map.abbott.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import XLSX from "xlsx";
|
||||||
|
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||||
|
import { postData } from "./logistics.dm.postData.js";
|
||||||
|
|
||||||
|
export const abbottForecast = async (sheet: any, user: any) => {
|
||||||
|
/*
|
||||||
|
This is the forecast but will only be triggered when the actual sheet is passed over from the orders in. this is being done this way due to the truck list being sent over as well.
|
||||||
|
*/
|
||||||
|
const customerId = 8;
|
||||||
|
const posting: any = [];
|
||||||
|
|
||||||
|
const customHeaders = [
|
||||||
|
"date",
|
||||||
|
"time",
|
||||||
|
"newton8oz",
|
||||||
|
"newton10oz",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"fDate",
|
||||||
|
"f8ozqty",
|
||||||
|
"I",
|
||||||
|
"J",
|
||||||
|
"K",
|
||||||
|
"L",
|
||||||
|
"M",
|
||||||
|
"f10ozqty",
|
||||||
|
];
|
||||||
|
const forecastData = XLSX.utils.sheet_to_json(sheet, {
|
||||||
|
range: 5, // Start at row 5 (index 4)
|
||||||
|
header: customHeaders,
|
||||||
|
defval: "", // Default value for empty cells
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 1; i < forecastData.length; i++) {
|
||||||
|
const row: any = forecastData[i];
|
||||||
|
//console.log(row);
|
||||||
|
//if (row.fDate == undefined) continue;
|
||||||
|
|
||||||
|
if (row.fDate !== "") {
|
||||||
|
const date = isNaN(row.fDate)
|
||||||
|
? new Date(row.fDate)
|
||||||
|
: excelDateStuff(row.fDate);
|
||||||
|
// for 8oz do
|
||||||
|
if (row.f8ozqty > 0) {
|
||||||
|
posting.push({
|
||||||
|
customerArticleNo: "45300DA",
|
||||||
|
quantity: row.f8ozqty,
|
||||||
|
requirementDate: date,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.f10ozqty > 0) {
|
||||||
|
posting.push({
|
||||||
|
customerArticleNo: "43836DA",
|
||||||
|
quantity: row.f10ozqty,
|
||||||
|
requirementDate: date,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the predefined data that will never change
|
||||||
|
const predefinedObject = {
|
||||||
|
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||||
|
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||||
|
"en-US",
|
||||||
|
)}`,
|
||||||
|
sender: user.username || "lst-system",
|
||||||
|
customerId: customerId,
|
||||||
|
positions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// add the new forecast to the predefined data
|
||||||
|
const updatedPredefinedObject = {
|
||||||
|
...predefinedObject,
|
||||||
|
positions: [...predefinedObject.positions, ...posting],
|
||||||
|
};
|
||||||
|
|
||||||
|
const forecast: any = await postData(
|
||||||
|
{
|
||||||
|
type: "forecast",
|
||||||
|
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||||
|
data: updatedPredefinedObject as any,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: forecast.success,
|
||||||
|
message: forecast.message,
|
||||||
|
data: forecast.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
86
backend/logistics/logistics.dm.forecast.map.energizer.ts
Normal file
86
backend/logistics/logistics.dm.forecast.map.energizer.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import XLSX from "xlsx";
|
||||||
|
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||||
|
import { postData } from "./logistics.dm.postData.js";
|
||||||
|
|
||||||
|
export const energizerForecast = async (data: any, user: any) => {
|
||||||
|
/**
|
||||||
|
* Post a standard forecast based on the standard template.
|
||||||
|
*/
|
||||||
|
const buffer = Buffer.from(data.buffer);
|
||||||
|
|
||||||
|
const workbook = XLSX.read(buffer, { type: "buffer" });
|
||||||
|
|
||||||
|
const sheet: any = workbook.Sheets.Sheet1;
|
||||||
|
// const range = XLSX.utils.decode_range(sheet["!ref"]);
|
||||||
|
|
||||||
|
// const headers = [
|
||||||
|
// "CustomerArticleNumber",
|
||||||
|
// "Quantity",
|
||||||
|
// "RequirementDate",
|
||||||
|
// "CustomerID",
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// formatting the data
|
||||||
|
const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 }) as any;
|
||||||
|
|
||||||
|
const posting: any = [];
|
||||||
|
const customerId = 44;
|
||||||
|
|
||||||
|
for (let i = 1; i < rows.length; i++) {
|
||||||
|
const row: any = rows[i];
|
||||||
|
const material = row[0];
|
||||||
|
|
||||||
|
if (material === undefined) continue;
|
||||||
|
for (let j = 1; j < row.length; j++) {
|
||||||
|
const qty = row[j];
|
||||||
|
|
||||||
|
if (qty && qty > 0) {
|
||||||
|
const requirementDate = rows[0][j]; // first row is dates
|
||||||
|
const date = Number.isNaN(requirementDate)
|
||||||
|
? new Date(requirementDate)
|
||||||
|
: excelDateStuff(requirementDate);
|
||||||
|
|
||||||
|
posting.push({
|
||||||
|
customerArticleNo: material,
|
||||||
|
quantity: qty,
|
||||||
|
requirementDate: date,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(posting);
|
||||||
|
|
||||||
|
// the predefined data that will never change
|
||||||
|
const predefinedObject = {
|
||||||
|
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||||
|
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||||
|
"en-US",
|
||||||
|
)}`,
|
||||||
|
sender: user.username || "lst-system",
|
||||||
|
customerId: customerId,
|
||||||
|
positions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// add the new forecast to the predefined data
|
||||||
|
const updatedPredefinedObject = {
|
||||||
|
...predefinedObject,
|
||||||
|
positions: [...predefinedObject.positions, ...posting],
|
||||||
|
};
|
||||||
|
|
||||||
|
//post it
|
||||||
|
const forecastData: any = await postData(
|
||||||
|
{
|
||||||
|
type: "forecast",
|
||||||
|
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||||
|
data: updatedPredefinedObject as any,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: forecastData.success,
|
||||||
|
message: forecastData.message,
|
||||||
|
data: forecastData.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
245
backend/logistics/logistics.dm.forecast.map.loreal.ts
Normal file
245
backend/logistics/logistics.dm.forecast.map.loreal.ts
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
import { addDays } from "date-fns";
|
||||||
|
import XLSX from "xlsx";
|
||||||
|
import { runDatamartQuery } from "../datamart/datamart.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
//import { sendEmail } from "../utils/sendEmail.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
import { postData } from "./logistics.dm.postData.js";
|
||||||
|
|
||||||
|
const customerID = 4;
|
||||||
|
export const lorealForecast = async (data: any, user: any) => {
|
||||||
|
/**
|
||||||
|
* Post a standard forecast based on the standard template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const buffer = Buffer.from(data.buffer);
|
||||||
|
|
||||||
|
const workbook = XLSX.read(buffer, { type: "buffer" });
|
||||||
|
|
||||||
|
const sheet: any = workbook.Sheets["Alpla HDPE"];
|
||||||
|
const range = XLSX.utils.decode_range(sheet["!ref"]);
|
||||||
|
|
||||||
|
const psheet: any = workbook.Sheets["Alpla PET"];
|
||||||
|
const prange = XLSX.utils.decode_range(psheet["!ref"]);
|
||||||
|
|
||||||
|
const headers = [];
|
||||||
|
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||||
|
const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1
|
||||||
|
const cell = sheet[cellAddress];
|
||||||
|
headers.push(cell ? cell.v : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pheaders = [];
|
||||||
|
for (let C = prange.s.c; C <= prange.e.c; ++C) {
|
||||||
|
const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1
|
||||||
|
const cell = psheet[cellAddress];
|
||||||
|
pheaders.push(cell ? cell.v : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ebmForeCastData: any = XLSX.utils.sheet_to_json(sheet, {
|
||||||
|
defval: "",
|
||||||
|
header: headers,
|
||||||
|
range: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
const petForeCastData: any = XLSX.utils.sheet_to_json(psheet, {
|
||||||
|
defval: "",
|
||||||
|
header: pheaders,
|
||||||
|
range: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ebmForecastData: any = [];
|
||||||
|
const missingSku: any = [];
|
||||||
|
|
||||||
|
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||||
|
|
||||||
|
if (!avSQLQuery.success) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "logistics",
|
||||||
|
subModule: "forecast",
|
||||||
|
message: `Error getting Article info`,
|
||||||
|
data: [avSQLQuery.message],
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: a, error: ae } = await tryCatch(
|
||||||
|
runDatamartQuery({ name: "activeArticles", options: {} }),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ae) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Error getting active av",
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const article: any = a?.data;
|
||||||
|
|
||||||
|
//console.log(article);
|
||||||
|
|
||||||
|
// process the ebm forcast
|
||||||
|
for (let i = 0; i < ebmForeCastData.length; i++) {
|
||||||
|
// bottle code
|
||||||
|
const sku = ebmForeCastData[i]["HDPE Bottle Code"];
|
||||||
|
|
||||||
|
// ignore the blanks
|
||||||
|
if (sku === "") continue;
|
||||||
|
|
||||||
|
// ignore zero qty
|
||||||
|
// if (ebmForeCastData[i][`Day ${i}`]) continue;
|
||||||
|
|
||||||
|
for (let f = 0; f <= 90; f++) {
|
||||||
|
const day = `Day ${f + 1}`;
|
||||||
|
// if (ebmForeCastData[i][day] === 0) continue;
|
||||||
|
|
||||||
|
const forcast = {
|
||||||
|
customerArticleNo: sku,
|
||||||
|
requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)),
|
||||||
|
quantity: ebmForeCastData[i][day] ?? 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (forcast.quantity === 0) continue;
|
||||||
|
|
||||||
|
// checking to make sure there is a real av to add to.
|
||||||
|
const activeAV = article.filter(
|
||||||
|
(c: any) =>
|
||||||
|
c?.CustomerArticleNumber === forcast.customerArticleNo.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeAV.length === 0) {
|
||||||
|
if (typeof forcast.customerArticleNo === "number") {
|
||||||
|
missingSku.push(forcast);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ebmForecastData.push(forcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(ebmForeCastData.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pet forecast
|
||||||
|
for (let i = 0; i < petForeCastData.length; i++) {
|
||||||
|
// bottle code
|
||||||
|
const sku = petForeCastData[i]["South PET Bottle Code"];
|
||||||
|
|
||||||
|
// ignore the blanks
|
||||||
|
if (sku === "") continue;
|
||||||
|
|
||||||
|
// ignore zero qty
|
||||||
|
// if (ebmForeCastData[i][`Day ${i}`]) continue;
|
||||||
|
|
||||||
|
for (let f = 0; f <= 90; f++) {
|
||||||
|
const day = `Day ${f + 1}`;
|
||||||
|
// if (ebmForeCastData[i][day] === 0) continue;
|
||||||
|
|
||||||
|
const forcast = {
|
||||||
|
customerArticleNo: sku,
|
||||||
|
requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)),
|
||||||
|
quantity: petForeCastData[i][day] ?? 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (forcast.quantity === 0 || forcast.quantity === "") continue;
|
||||||
|
|
||||||
|
if (forcast.customerArticleNo < 99999) {
|
||||||
|
//console.log(`Sku a normal av ${forcast.customerArticleNo}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking to make sure there is a real av to add to.
|
||||||
|
const activeAV = article.filter(
|
||||||
|
(c: any) =>
|
||||||
|
c?.CustomerArticleNumber === forcast.customerArticleNo.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeAV.length === 0) {
|
||||||
|
if (typeof forcast.customerArticleNo === "number") {
|
||||||
|
missingSku.push(forcast);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ebmForecastData.push(forcast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(comForecast);
|
||||||
|
|
||||||
|
// email the for the missing ones
|
||||||
|
// const missedGrouped = Object.values(
|
||||||
|
// missingSku.reduce((acc: any, item: any) => {
|
||||||
|
// const key = item.customerArticleNo;
|
||||||
|
|
||||||
|
// if (!acc[key]) {
|
||||||
|
// // first time we see this customer
|
||||||
|
// acc[key] = item;
|
||||||
|
// } else {
|
||||||
|
// // compare dates and keep the earliest
|
||||||
|
// if (
|
||||||
|
// new Date(item.requirementDate) < new Date(acc[key].requirementDate)
|
||||||
|
// ) {
|
||||||
|
// acc[key] = item;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return acc;
|
||||||
|
// }, {}),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// TODO: change this to a flagged notification so that he users can subscribe or leave it. this removes the hardcody shit.
|
||||||
|
// const emailSetup = {
|
||||||
|
// email:
|
||||||
|
// "Blake.matthes@alpla.com; Stuart.Gladney@alpla.com; Harold.Mccalister@alpla.com; Jenn.Osbourn@alpla.com",
|
||||||
|
// subject:
|
||||||
|
// missedGrouped.length > 0
|
||||||
|
// ? `Alert! There are ${missedGrouped.length}, missing skus.`
|
||||||
|
// : `Alert! There is a missing SKU.`,
|
||||||
|
// template: "missingLorealSkus",
|
||||||
|
// context: {
|
||||||
|
// items: missedGrouped,
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// sendEmail(emailSetup);
|
||||||
|
|
||||||
|
// if the customerarticle number is not matching just ignore it
|
||||||
|
const predefinedObject = {
|
||||||
|
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||||
|
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||||
|
"en-US",
|
||||||
|
)}`,
|
||||||
|
sender: user.username || "lst-system",
|
||||||
|
customerId: customerID,
|
||||||
|
positions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedPredefinedObject = {
|
||||||
|
...predefinedObject,
|
||||||
|
positions: [...predefinedObject.positions, ...ebmForecastData],
|
||||||
|
};
|
||||||
|
// console.log(updatedPredefinedObject);
|
||||||
|
|
||||||
|
// posting the data to the new backend so we can store the data.
|
||||||
|
const posting: any = await postData(
|
||||||
|
{
|
||||||
|
type: "forecast",
|
||||||
|
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||||
|
data: updatedPredefinedObject as any,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: posting.success,
|
||||||
|
message: posting.message,
|
||||||
|
data: posting.data === "" ? ebmForecastData : posting.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
182
backend/logistics/logistics.dm.forecast.map.pNg.ts
Normal file
182
backend/logistics/logistics.dm.forecast.map.pNg.ts
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import XLSX from "xlsx";
|
||||||
|
import { runDatamartQuery } from "../datamart/datamart.controller.js";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { settings } from "../db/schema/settings.schema.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||||
|
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
import { postData } from "./logistics.dm.postData.js";
|
||||||
|
|
||||||
|
export const pNgForecast = async (data: any, user: any) => {
|
||||||
|
/**
|
||||||
|
* Post a standard forecast based on the standard template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { data: s, error: e } = await tryCatch(db.select().from(settings));
|
||||||
|
|
||||||
|
if (e) {
|
||||||
|
return {
|
||||||
|
sucess: false,
|
||||||
|
message: `Error getting settings`,
|
||||||
|
data: e,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const pNg = s.filter((n: any) => n.name === "pNgAddress");
|
||||||
|
|
||||||
|
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||||
|
|
||||||
|
if (!avSQLQuery.success) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "logistics",
|
||||||
|
subModule: "forecast",
|
||||||
|
message: `Error getting Article info`,
|
||||||
|
data: [avSQLQuery.message],
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: a, error: ae } = await tryCatch(
|
||||||
|
runDatamartQuery({ name: "activeArticles", options: {} }),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ae) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Error getting active av",
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const article: any = a?.data;
|
||||||
|
|
||||||
|
const buffer = Buffer.from(data.buffer);
|
||||||
|
|
||||||
|
const workbook = XLSX.read(buffer, { type: "buffer" });
|
||||||
|
|
||||||
|
//const sheet: any = workbook.Sheets[sheetName];
|
||||||
|
const sheet: any = workbook.Sheets["SchedAgreementUIConfigSpreadshe"];
|
||||||
|
const range = XLSX.utils.decode_range(sheet["!ref"]);
|
||||||
|
|
||||||
|
const headers = [];
|
||||||
|
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||||
|
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: C }); // row 0 = Excel row 1
|
||||||
|
const cell = sheet[cellAddress];
|
||||||
|
headers.push(cell ? cell.v : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(headers);
|
||||||
|
const forecastData: any = XLSX.utils.sheet_to_json(sheet, {
|
||||||
|
defval: "",
|
||||||
|
header: headers,
|
||||||
|
range: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedByCustomer: any = forecastData.reduce((acc: any, item: any) => {
|
||||||
|
const id = item.CustomerID;
|
||||||
|
if (!acc[id]) {
|
||||||
|
acc[id] = [];
|
||||||
|
}
|
||||||
|
acc[id].push(item);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const foreCastData: any = [];
|
||||||
|
|
||||||
|
for (const [customerID, forecast] of Object.entries(groupedByCustomer)) {
|
||||||
|
//console.log(`Running for Customer ID: ${customerID}`);
|
||||||
|
const newForecast: any = forecast;
|
||||||
|
|
||||||
|
const predefinedObject = {
|
||||||
|
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||||
|
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||||
|
"en-US",
|
||||||
|
)}`,
|
||||||
|
sender: user.username || "lst-system",
|
||||||
|
customerId: pNg[0]?.value,
|
||||||
|
positions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// map everything out for each order
|
||||||
|
const nForecast = newForecast.map((o: any) => {
|
||||||
|
// const invoice = i.filter(
|
||||||
|
// (i: any) => i.deliveryAddress === parseInt(customerID)
|
||||||
|
// );
|
||||||
|
// if (!invoice) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return {
|
||||||
|
customerArticleNo: parseInt(o["Customer Item No."] ?? "0", 10),
|
||||||
|
requirementDate: excelDateStuff(parseInt(o["Request Date"] ?? "0", 10)),
|
||||||
|
quantity: o["Remaining Qty to be Shipped"],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// check to make sure the av belongs in this plant.
|
||||||
|
const onlyNumbers = nForecast.filter((n: any) => n.quantity > 0);
|
||||||
|
const filteredForecast: any = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < nForecast.length; i++) {
|
||||||
|
//console.log(nForecast[i].customerArticleNo);
|
||||||
|
const activeAV = article.filter(
|
||||||
|
(c: any) =>
|
||||||
|
c?.CustomerArticleNumber ===
|
||||||
|
nForecast[i]?.customerArticleNo.toString() &&
|
||||||
|
// validate it works via the default address
|
||||||
|
c?.IdAdresse === parseInt(pNg[0]?.value ?? "139", 10),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeAV.length > 0) {
|
||||||
|
filteredForecast.push(onlyNumbers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredForecast.length === 0) {
|
||||||
|
console.log("Nothing to post");
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "No forecast to be posted",
|
||||||
|
data: foreCastData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// do that fun combining thing
|
||||||
|
const updatedPredefinedObject = {
|
||||||
|
...predefinedObject,
|
||||||
|
positions: [...predefinedObject.positions, ...filteredForecast],
|
||||||
|
};
|
||||||
|
|
||||||
|
//console.log(updatedPredefinedObject);
|
||||||
|
|
||||||
|
// post the orders to the server
|
||||||
|
const posting: any = await postData(
|
||||||
|
{
|
||||||
|
type: "forecast",
|
||||||
|
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||||
|
data: updatedPredefinedObject as any,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
|
foreCastData.push({
|
||||||
|
customer: customerID,
|
||||||
|
//totalOrders: orders?.length(),
|
||||||
|
success: posting.success,
|
||||||
|
message: posting.message,
|
||||||
|
data: posting.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: foreCastData[0].success,
|
||||||
|
message: foreCastData[0].message,
|
||||||
|
data: foreCastData,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as XLSX from "xlsx";
|
import * as XLSX from "xlsx";
|
||||||
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||||
import { postForecast } from "./logistics.dm.postForecast.js";
|
import { postData } from "./logistics.dm.postData.js";
|
||||||
export const standardForecast = async (data: any, user: any) => {
|
export const standardForecast = async (data: any, user: any) => {
|
||||||
/**
|
/**
|
||||||
* Post a standard forecast based on the standard template.
|
* Post a standard forecast based on the standard template.
|
||||||
@@ -78,7 +78,14 @@ export const standardForecast = async (data: any, user: any) => {
|
|||||||
//console.log(updatedPredefinedObject);
|
//console.log(updatedPredefinedObject);
|
||||||
|
|
||||||
// post the orders to the server
|
// post the orders to the server
|
||||||
const posting: any = await postForecast(updatedPredefinedObject, user);
|
const posting: any = await postData(
|
||||||
|
{
|
||||||
|
type: "forecast",
|
||||||
|
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||||
|
data: updatedPredefinedObject as any,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
foreCastData.push({
|
foreCastData.push({
|
||||||
customer: customerID,
|
customer: customerID,
|
||||||
@@ -2,7 +2,10 @@ import { Router } from "express";
|
|||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { standardForecast } from "./logsitcs.dm.forecast.map.standard.js";
|
import { energizerForecast } from "./logistics.dm.forecast.map.energizer.js";
|
||||||
|
import { lorealForecast } from "./logistics.dm.forecast.map.loreal.js";
|
||||||
|
import { pNgForecast } from "./logistics.dm.forecast.map.pNg.js";
|
||||||
|
import { standardForecast } from "./logistics.dm.forecast.map.standard.js";
|
||||||
|
|
||||||
type ForecastResult = {
|
type ForecastResult = {
|
||||||
success?: boolean;
|
success?: boolean;
|
||||||
@@ -53,18 +56,15 @@ r.post("/", requireAuth, upload.single("file"), async (req, res) => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "loreal":
|
case "loreal":
|
||||||
`result = await lorealForecast(req.file, req.user);`;
|
result = await lorealForecast(req.file, req.user);
|
||||||
result = { success: true, message: "standardForecast", data: [] };
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "pg":
|
case "pg":
|
||||||
`result = await pNgForecast(req.file, req.user);`;
|
result = await pNgForecast(req.file, req.user);
|
||||||
result = { success: true, message: "standardForecast", data: [] };
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "energizer":
|
case "energizer":
|
||||||
`result = await energizerForecast(req.file, req.user);`;
|
result = await energizerForecast(req.file, req.user);
|
||||||
result = { success: true, message: "standardForecast", data: [] };
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
65
backend/logistics/logistics.dm.postData.ts
Normal file
65
backend/logistics/logistics.dm.postData.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { forecastImport } from "../db/schema/forecastImports.schema.js";
|
||||||
|
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||||
|
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
type PostData = {
|
||||||
|
receivingPlantId: string;
|
||||||
|
documentName: string;
|
||||||
|
sender: string;
|
||||||
|
customerId: string;
|
||||||
|
positions: unknown[];
|
||||||
|
};
|
||||||
|
type Data = {
|
||||||
|
type: "orders" | "forecast";
|
||||||
|
endpoint: string;
|
||||||
|
data: PostData;
|
||||||
|
};
|
||||||
|
export const postData = async (data: Data, user: any) => {
|
||||||
|
const posting = await runProdApi(
|
||||||
|
{
|
||||||
|
method: "post",
|
||||||
|
endpoint: data.endpoint,
|
||||||
|
data: [data.data],
|
||||||
|
},
|
||||||
|
"Forecast post",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!posting?.success) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dm",
|
||||||
|
subModule: data.type === "orders" ? "orders" : "forecast",
|
||||||
|
message:
|
||||||
|
posting?.message ??
|
||||||
|
`Error in posting the ${data.type === "orders" ? "orders" : "forecast"} data`,
|
||||||
|
data: posting?.data ?? [],
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (posting.success) {
|
||||||
|
if (data.type === "forecast") {
|
||||||
|
await db.insert(forecastImport).values({
|
||||||
|
receivingPlantId: data.data.receivingPlantId ?? "test1",
|
||||||
|
documentName: data.data.documentName ?? "forecast-data-missing",
|
||||||
|
sender: data.data.sender ?? "lst-user",
|
||||||
|
customerId: data.data.customerId ?? "0",
|
||||||
|
rawData: data ?? [],
|
||||||
|
add_user: user.username ?? undefined,
|
||||||
|
upd_user: user.username ?? undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dm",
|
||||||
|
subModule: data.type === "orders" ? "orders" : "forecast",
|
||||||
|
message: posting?.message ?? "",
|
||||||
|
data: (data.data as any) ?? [],
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { db } from "../db/db.controller.js";
|
|
||||||
import { forecastImport } from "../db/schema/forecastImports.schema.js";
|
|
||||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
|
||||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
|
||||||
|
|
||||||
export const postForecast = async (data: any, user: any) => {
|
|
||||||
const forecast = await runProdApi(
|
|
||||||
{
|
|
||||||
method: "post",
|
|
||||||
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
|
||||||
data: [data],
|
|
||||||
},
|
|
||||||
"Forecast post",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!forecast?.success) {
|
|
||||||
return returnFunc({
|
|
||||||
success: false,
|
|
||||||
level: "error",
|
|
||||||
module: "dm",
|
|
||||||
subModule: "forecast",
|
|
||||||
message: forecast?.message ?? "Error in posting the forecast data",
|
|
||||||
data: forecast?.data ?? [],
|
|
||||||
notify: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forecast.success) {
|
|
||||||
await db.insert(forecastImport).values({
|
|
||||||
receivingPlantId: data.receivingPlantId ?? "test1",
|
|
||||||
documentName: data.documentName ?? "forecast-data-missing",
|
|
||||||
sender: data.sender ?? "lst-user",
|
|
||||||
customerId: data.customerId ?? "0",
|
|
||||||
rawData: data ?? [],
|
|
||||||
add_user: user.username ?? undefined,
|
|
||||||
upd_user: user.username ?? undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
return returnFunc({
|
|
||||||
success: true,
|
|
||||||
level: "info",
|
|
||||||
module: "dm",
|
|
||||||
subModule: "forecast",
|
|
||||||
message: forecast?.message ?? "",
|
|
||||||
data: data ?? [],
|
|
||||||
notify: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
57
backend/logistics/logistics.dm.repost.route.ts
Normal file
57
backend/logistics/logistics.dm.repost.route.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { postData } from "./logistics.dm.postData.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.post("/", requireAuth, async (req, res) => {
|
||||||
|
let posting: any;
|
||||||
|
|
||||||
|
|
||||||
|
if (req.body.type !== "forecast" || req.body.type !== "orders") {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dm",
|
||||||
|
subModule: "repost",
|
||||||
|
message: "You must pass over a proper type.",
|
||||||
|
data: [],
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body.type === "forecast") {
|
||||||
|
posting = await postData(
|
||||||
|
{
|
||||||
|
type: "forecast",
|
||||||
|
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||||
|
data: req.body.data as any,
|
||||||
|
},
|
||||||
|
req.user,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body.type === "orders") {
|
||||||
|
posting = await postData(
|
||||||
|
{
|
||||||
|
type: "orders",
|
||||||
|
endpoint: "/public/v1.0/DemandManagement/ORDERS",
|
||||||
|
data: req.body.data as any,
|
||||||
|
},
|
||||||
|
req.user,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: posting.success,
|
||||||
|
level: posting.success ? "info" : "error",
|
||||||
|
module: "dm",
|
||||||
|
subModule: "repost",
|
||||||
|
message: posting.message,
|
||||||
|
data: [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
@@ -1,16 +1,27 @@
|
|||||||
use [test1_AlplaPROD2.0_Read]
|
use [test1_AlplaPROD2.0_Read]
|
||||||
|
|
||||||
DECLARE @StartDate DATE = '[startDate]' -- 2025-1-1
|
DECLARE @StartDate DATE = '[startDate]'
|
||||||
DECLARE @EndDate DATE = '[endDate]' -- 2025-1-31
|
DECLARE @EndDate DATE = '[endDate]'
|
||||||
|
|
||||||
|
;WITH bol_20 AS ( -- 2.0 BOL, one per release (newest doc wins)
|
||||||
|
SELECT pos.ReleaseId,
|
||||||
|
dd.JournalNumber,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY pos.ReleaseId
|
||||||
|
ORDER BY dd.ShippingDate DESC) AS rn
|
||||||
|
FROM [outboundDelivery].[DeliveryDocumentPosition] (nolock) pos
|
||||||
|
JOIN [outboundDelivery].[DeliveryDocument] (nolock) dd
|
||||||
|
ON dd.Id = pos.DeliveryDocumentId
|
||||||
|
-- WHERE dd.DocumentType = <BOL value> -- see note below
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
r.[ArticleHumanReadableId]
|
r.[ArticleHumanReadableId]
|
||||||
,[ReleaseNumber]
|
,[ReleaseNumber]
|
||||||
,h.CustomerOrderNumber
|
,h.CustomerOrderNumber
|
||||||
,x.CustomerLineItemNumber
|
,x.CustomerLineItemNumber
|
||||||
,[CustomerReleaseNumber]
|
,[CustomerReleaseNumber]
|
||||||
,[ReleaseState]
|
,[ReleaseState]
|
||||||
,[DeliveryState]
|
,[DeliveryState]
|
||||||
,ea.JournalNummer as BOL_Number
|
,COALESCE(ea.JournalNummer, bol_20.JournalNumber) AS BOL_Number -- 1.0 or 2.0
|
||||||
,[ReleaseConfirmationState]
|
,[ReleaseConfirmationState]
|
||||||
,[PlanningState]
|
,[PlanningState]
|
||||||
,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
|
,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
|
||||||
@@ -39,36 +50,24 @@ r.[ArticleHumanReadableId]
|
|||||||
,[CustomerArtNo]
|
,[CustomerArtNo]
|
||||||
,[TotalPrice]
|
,[TotalPrice]
|
||||||
,r.[ArticleAlias]
|
,r.[ArticleAlias]
|
||||||
|
FROM [order].[Release] (nolock) AS r
|
||||||
|
LEFT JOIN [order].LineItem AS x ON r.LineItemId = x.id
|
||||||
|
LEFT JOIN [order].Header AS h ON x.HeaderId = h.id
|
||||||
|
|
||||||
FROM [order].[Release] (nolock) as r
|
-- 1.0 BOL (legacy) — unchanged
|
||||||
|
LEFT JOIN AlplaPROD_test1.dbo.V_LadePlanungenLadeAuftragAbruf (nolock) AS zz
|
||||||
|
ON zz.AbrufIdAuftragsAbruf = r.ReleaseNumber
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT * FROM (
|
||||||
|
SELECT ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum, *
|
||||||
|
FROM [AlplaPROD_test1].[dbo].[T_Lieferungen] (nolock)
|
||||||
|
) t WHERE RowNum = 1
|
||||||
|
) AS ea ON zz.IdLieferschein = ea.IdJournal
|
||||||
|
|
||||||
left join
|
-- 2.0 BOL (new)
|
||||||
[order].LineItem as x on
|
LEFT JOIN bol_20 ON bol_20.ReleaseId = r.Id AND bol_20.rn = 1
|
||||||
|
|
||||||
r.LineItemId = x.id
|
WHERE r.DeliveryDate BETWEEN @StartDate AND @EndDate
|
||||||
|
|
||||||
left join
|
|
||||||
[order].Header as h on
|
|
||||||
x.HeaderId = h.id
|
|
||||||
|
|
||||||
--bol stuff
|
|
||||||
left join
|
|
||||||
AlplaPROD_test1.dbo.V_LadePlanungenLadeAuftragAbruf (nolock) as zz
|
|
||||||
on zz.AbrufIdAuftragsAbruf = r.ReleaseNumber
|
|
||||||
|
|
||||||
left join
|
|
||||||
(select * from (SELECT
|
|
||||||
ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
|
|
||||||
,*
|
|
||||||
FROM [AlplaPROD_test1].[dbo].[T_Lieferungen] (nolock)) x
|
|
||||||
|
|
||||||
where RowNum = 1) as ea on
|
|
||||||
zz.IdLieferschein = ea.IdJournal
|
|
||||||
|
|
||||||
where
|
|
||||||
--r.ReleaseNumber = 1452
|
|
||||||
|
|
||||||
r.DeliveryDate between @StartDate AND @EndDate
|
|
||||||
and DeliveredQuantity > 0
|
and DeliveredQuantity > 0
|
||||||
--and r.ArticleHumanReadableId in ([articles])
|
--and r.ArticleHumanReadableId in ([articles])
|
||||||
--and Journalnummer = 169386
|
--and Journalnummer = 169386
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
use AlplaPROD_test1
|
|
||||||
/**
|
|
||||||
|
|
||||||
move this over to the delivery date range query once we have the shift data mapped over correctly.
|
|
||||||
|
|
||||||
update the psi stuff on this as well.
|
|
||||||
**/
|
|
||||||
DECLARE @StartDate DATE = '[startDate]' -- 2025-1-1
|
|
||||||
DECLARE @EndDate DATE = '[endDate]' -- 2025-1-31
|
|
||||||
SELECT
|
|
||||||
r.[ArticleHumanReadableId]
|
|
||||||
,[ReleaseNumber]
|
|
||||||
,h.CustomerOrderNumber
|
|
||||||
,x.CustomerLineItemNumber
|
|
||||||
,[CustomerReleaseNumber]
|
|
||||||
,[ReleaseState]
|
|
||||||
,[DeliveryState]
|
|
||||||
,ea.JournalNummer as BOL_Number
|
|
||||||
,[ReleaseConfirmationState]
|
|
||||||
,[PlanningState]
|
|
||||||
--,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
|
|
||||||
,r.[OrderDate]
|
|
||||||
--,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
|
|
||||||
,r.[DeliveryDate]
|
|
||||||
--,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
|
|
||||||
,r.[LoadingDate]
|
|
||||||
,[Quantity]
|
|
||||||
,[DeliveredQuantity]
|
|
||||||
,r.[AdditionalInformation1]
|
|
||||||
,r.[AdditionalInformation2]
|
|
||||||
,[TradeUnits]
|
|
||||||
,[LoadingUnits]
|
|
||||||
,[Trucks]
|
|
||||||
,[LoadingToleranceType]
|
|
||||||
,[SalesPrice]
|
|
||||||
,[Currency]
|
|
||||||
,[QuantityUnit]
|
|
||||||
,[SalesPriceRemark]
|
|
||||||
,r.[Remark]
|
|
||||||
,[Irradiated]
|
|
||||||
,r.[CreatedByEdi]
|
|
||||||
,[DeliveryAddressHumanReadableId]
|
|
||||||
,DeliveryAddressDescription
|
|
||||||
,[CustomerArtNo]
|
|
||||||
,[TotalPrice]
|
|
||||||
,r.[ArticleAlias]
|
|
||||||
|
|
||||||
FROM [order].[Release] (nolock) as r
|
|
||||||
|
|
||||||
left join
|
|
||||||
[order].LineItem as x on
|
|
||||||
|
|
||||||
r.LineItemId = x.id
|
|
||||||
|
|
||||||
left join
|
|
||||||
[order].Header as h on
|
|
||||||
x.HeaderId = h.id
|
|
||||||
|
|
||||||
--bol stuff
|
|
||||||
left join
|
|
||||||
AlplaPROD_test1.dbo.V_LadePlanungenLadeAuftragAbruf (nolock) as zz
|
|
||||||
on zz.AbrufIdAuftragsAbruf = r.ReleaseNumber
|
|
||||||
|
|
||||||
left join
|
|
||||||
(select * from (SELECT
|
|
||||||
ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
|
|
||||||
,*
|
|
||||||
FROM [AlplaPROD_test1].[dbo].[T_Lieferungen] (nolock)) x
|
|
||||||
|
|
||||||
where RowNum = 1) as ea on
|
|
||||||
zz.IdLieferschein = ea.IdJournal
|
|
||||||
|
|
||||||
where
|
|
||||||
r.ArticleHumanReadableId in ([articles])
|
|
||||||
--r.ReleaseNumber = 1452
|
|
||||||
|
|
||||||
and r.DeliveryDate between @StartDate AND @EndDate
|
|
||||||
--and DeliveredQuantity > 0
|
|
||||||
--and Journalnummer = 169386
|
|
||||||
@@ -1,32 +1,72 @@
|
|||||||
use AlplaPROD_test1
|
use [test1_AlplaPROD2.0_Read]
|
||||||
declare @start_date nvarchar(30) = '[startDate]' --'2025-01-01'
|
|
||||||
declare @end_date nvarchar(30) = '[endDate]' --'2025-08-09'
|
|
||||||
/*
|
|
||||||
articles will need to be passed over as well as the date structure we want to see
|
|
||||||
*/
|
|
||||||
|
|
||||||
select x.IdArtikelvarianten As Article,
|
DECLARE @start_date date = '[startDate]'; --'2025-01-01'
|
||||||
ProduktionAlias as Description,
|
DECLARE @end_date date = '[endDate]'; --'2025-08-09'
|
||||||
standort as MachineId,
|
DECLARE @tz sysname = 'Eastern Standard Time'; -- usday1; use 'Central Standard Time' for usksc1
|
||||||
MaschinenBezeichnung as MachineName,
|
DECLARE @shiftSeconds int = 7*3600; -- 07:00 production-day anchor
|
||||||
--MaschZyklus as PlanningCycleTime,
|
--TODO: add in the proper time zone based on the env, get correcr shift time as well
|
||||||
x.IdProdPlanung as LotNumber,
|
;WITH src AS (
|
||||||
FORMAT(ProdTag, 'MM/dd/yyyy') as ProductionDay,
|
SELECT
|
||||||
x.planMenge as TotalPlanned,
|
pl.RunningNumber, pl.ArticleHumanReadableId, pl.ArticleAlias, pl.ArticleDescription,
|
||||||
ProduktionMenge as QTYPerDay,
|
pl.MachineLocation, pl.MachineDescription, pl.ProductionLotState, pl.ProductionInterrupt,
|
||||||
round(ProduktionMengeVPK, 2) PalDay,
|
pl.PlanQuantityPieces, pl.PlanQuantityLoadingUnit,
|
||||||
Status as finished
|
CAST(pl.ProdStart AT TIME ZONE @tz AS datetime2(0)) AS StartLocal,
|
||||||
--MaschStdAuslastung as nee
|
CAST(pl.PlanEnd AT TIME ZONE @tz AS datetime2(0)) AS EndLocal
|
||||||
|
FROM productionScheduling.ProductionLot AS pl
|
||||||
from dbo.V_ProdLosProduktionJeProdTag_PLANNING (nolock) as x
|
WHERE pl.PlanEnd > pl.ProdStart
|
||||||
|
and pl.publishState = 1
|
||||||
left join
|
and pl.ArticleHumanReadableId IN ([articles]) -- <-- article filter (was IdArtikelvarianten)
|
||||||
dbo.V_ProdPlanung (nolock) as p on
|
--AND pl.RunningNumber = 28094 -- <-- lot filter (was IdProdPlanung); comment out for all
|
||||||
x.IdProdPlanung = p.IdProdPlanung
|
),
|
||||||
|
calc AS (
|
||||||
where ProdTag between @start_date and @end_date
|
SELECT *,
|
||||||
and p.IdArtikelvarianten in ([articles])
|
DATEADD(SECOND, @shiftSeconds,
|
||||||
--and V_ProdLosProduktionJeProdTag_PLANNING.IdKunde = 10
|
CAST(CASE WHEN DATEDIFF(SECOND, CAST(StartLocal AS date), StartLocal) >= @shiftSeconds
|
||||||
--and IdProdPlanung = 18442
|
THEN CAST(StartLocal AS date)
|
||||||
|
ELSE DATEADD(DAY,-1, CAST(StartLocal AS date)) END AS datetime2(0))) AS FirstBoundary
|
||||||
order by ProdTag desc
|
FROM src
|
||||||
|
),
|
||||||
|
days AS ( -- one row per production day the lot touches
|
||||||
|
SELECT RunningNumber, ArticleHumanReadableId, ArticleAlias, ArticleDescription, MachineLocation,
|
||||||
|
MachineDescription, ProductionLotState, PlanQuantityPieces, PlanQuantityLoadingUnit,
|
||||||
|
StartLocal, EndLocal, FirstBoundary AS DayStart
|
||||||
|
FROM calc
|
||||||
|
UNION ALL
|
||||||
|
SELECT RunningNumber, ArticleHumanReadableId, ArticleAlias, ArticleDescription, MachineLocation,
|
||||||
|
MachineDescription, ProductionLotState, PlanQuantityPieces, PlanQuantityLoadingUnit,
|
||||||
|
StartLocal, EndLocal, DATEADD(DAY,1,DayStart)
|
||||||
|
FROM days
|
||||||
|
WHERE DATEADD(DAY,1,DayStart) < EndLocal
|
||||||
|
),
|
||||||
|
seg AS ( -- working seconds inside each production day
|
||||||
|
SELECT *,
|
||||||
|
DATEDIFF(SECOND,
|
||||||
|
CASE WHEN StartLocal > DayStart THEN StartLocal ELSE DayStart END,
|
||||||
|
CASE WHEN EndLocal < DATEADD(DAY,1,DayStart) THEN EndLocal ELSE DATEADD(DAY,1,DayStart) END
|
||||||
|
) AS SegSec
|
||||||
|
FROM days
|
||||||
|
),
|
||||||
|
cum AS ( -- cumulative seconds for telescoping round
|
||||||
|
SELECT *,
|
||||||
|
SUM(SegSec) OVER (PARTITION BY RunningNumber ORDER BY DayStart ROWS UNBOUNDED PRECEDING) AS CumEnd,
|
||||||
|
SUM(SegSec) OVER (PARTITION BY RunningNumber ORDER BY DayStart ROWS UNBOUNDED PRECEDING) - SegSec AS CumStart,
|
||||||
|
SUM(SegSec) OVER (PARTITION BY RunningNumber) AS DenomSec
|
||||||
|
FROM seg
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
ArticleHumanReadableId AS Article,
|
||||||
|
ArticleAlias AS Description,
|
||||||
|
MachineLocation AS MachineId,
|
||||||
|
MachineDescription AS MachineName,
|
||||||
|
RunningNumber AS LotNumber,
|
||||||
|
FORMAT(DayStart, 'MM/dd/yyyy') AS ProductionDay,
|
||||||
|
PlanQuantityPieces AS TotalPlanned,
|
||||||
|
ROUND(PlanQuantityPieces * 1.0 * CumEnd / DenomSec, 0)
|
||||||
|
- ROUND(PlanQuantityPieces * 1.0 * CumStart / DenomSec, 0) AS QTYPerDay,
|
||||||
|
ROUND(PlanQuantityLoadingUnit * CumEnd / DenomSec, 2)
|
||||||
|
- ROUND(PlanQuantityLoadingUnit * CumStart / DenomSec, 2) AS PalDay,
|
||||||
|
ProductionLotState AS finished
|
||||||
|
FROM cum
|
||||||
|
WHERE CAST(DayStart AS date) BETWEEN @start_date AND @end_date -- filter AFTER cumulative calc
|
||||||
|
ORDER BY RunningNumber, DayStart DESC
|
||||||
|
OPTION (MAXRECURSION 366);
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ import { setupUtilsRoutes } from "./utils/utils.routes.js";
|
|||||||
|
|
||||||
export const setupRoutes = (baseUrl: string, app: Express) => {
|
export const setupRoutes = (baseUrl: string, app: Express) => {
|
||||||
//routes that are on by default
|
//routes that are on by default
|
||||||
|
setupDatamartRoutes(baseUrl, app);
|
||||||
setupMobileRoutes(baseUrl, app);
|
setupMobileRoutes(baseUrl, app);
|
||||||
setupSystemRoutes(baseUrl, app);
|
setupSystemRoutes(baseUrl, app);
|
||||||
setupAdminRoutes(baseUrl, app);
|
setupAdminRoutes(baseUrl, app);
|
||||||
setupApiDocsRoutes(baseUrl, app);
|
setupApiDocsRoutes(baseUrl, app);
|
||||||
setupProdSqlRoutes(baseUrl, app);
|
setupProdSqlRoutes(baseUrl, app);
|
||||||
setupGPSqlRoutes(baseUrl, app);
|
setupGPSqlRoutes(baseUrl, app);
|
||||||
setupDatamartRoutes(baseUrl, app);
|
|
||||||
setupAuthRoutes(baseUrl, app);
|
setupAuthRoutes(baseUrl, app);
|
||||||
setupUtilsRoutes(baseUrl, app);
|
setupUtilsRoutes(baseUrl, app);
|
||||||
setupOpendockRoutes(baseUrl, app);
|
setupOpendockRoutes(baseUrl, app);
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import type { OpenAPIV3_1 } from "openapi-types";
|
|
||||||
|
|
||||||
export const cronerActiveJobs: OpenAPIV3_1.PathsObject = {
|
|
||||||
"/api/utils/croner": {
|
|
||||||
get: {
|
|
||||||
summary: "Cron jobs",
|
|
||||||
description: "Returns all jobs on the server.",
|
|
||||||
tags: ["Utils"],
|
|
||||||
responses: {
|
|
||||||
"200": {
|
|
||||||
description: "Jobs returned",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
status: {
|
|
||||||
type: "boolean",
|
|
||||||
format: "boolean",
|
|
||||||
example: true,
|
|
||||||
},
|
|
||||||
uptime: {
|
|
||||||
type: "number",
|
|
||||||
format: "3454.34",
|
|
||||||
example: 3454.34,
|
|
||||||
},
|
|
||||||
memoryUsage: {
|
|
||||||
type: "string",
|
|
||||||
format: "Heap: 11.62 MB / RSS: 86.31 MB",
|
|
||||||
},
|
|
||||||
sqlServerStats: {
|
|
||||||
type: "number",
|
|
||||||
format: "442127",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import type { OpenAPIV3_1 } from "openapi-types";
|
|
||||||
|
|
||||||
export const cronerStatusChange: OpenAPIV3_1.PathsObject = {
|
|
||||||
"/api/utils/croner/{status}": {
|
|
||||||
patch: {
|
|
||||||
summary: "Pauses or Resume the Job",
|
|
||||||
description:
|
|
||||||
"When sending start or stop with job name it will resume or stop the job",
|
|
||||||
tags: ["Utils"],
|
|
||||||
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: "status",
|
|
||||||
in: "path",
|
|
||||||
required: true,
|
|
||||||
description: "Status change",
|
|
||||||
schema: {
|
|
||||||
type: "string",
|
|
||||||
},
|
|
||||||
example: "start",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "limit",
|
|
||||||
in: "query",
|
|
||||||
required: false, // 👈 optional
|
|
||||||
description: "Maximum number of records to return",
|
|
||||||
schema: {
|
|
||||||
type: "integer",
|
|
||||||
minimum: 1,
|
|
||||||
maximum: 100,
|
|
||||||
},
|
|
||||||
example: 10,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
requestBody: {
|
|
||||||
required: true,
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
required: ["name"],
|
|
||||||
properties: {
|
|
||||||
name: {
|
|
||||||
type: "string",
|
|
||||||
example: "start",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
responses: {
|
|
||||||
"200": {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
success: { type: "boolean", example: true },
|
|
||||||
data: {
|
|
||||||
type: "object",
|
|
||||||
example: {
|
|
||||||
name: "exampleName",
|
|
||||||
value: "some value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
description: "Bad request",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
success: { type: "boolean", example: false },
|
|
||||||
message: {
|
|
||||||
type: "string",
|
|
||||||
example: "Invalid name parameter",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -8,7 +8,7 @@ import { dbCleanup } from "./db/dbCleanup.controller.js";
|
|||||||
import { type Setting, settings } from "./db/schema/settings.schema.js";
|
import { type Setting, settings } from "./db/schema/settings.schema.js";
|
||||||
import { connectGPSql } from "./gpSql/gpSqlConnection.controller.js";
|
import { connectGPSql } from "./gpSql/gpSqlConnection.controller.js";
|
||||||
import { createLogger } from "./logger/logger.controller.js";
|
import { createLogger } from "./logger/logger.controller.js";
|
||||||
import { historicalSchedule } from "./logistics/logistics.historicalInv.js";
|
import { historicalSchedule } from "./logistics/logistics.utils.historicalInv.js";
|
||||||
import { startNotifications } from "./notification/notification.controller.js";
|
import { startNotifications } from "./notification/notification.controller.js";
|
||||||
import { sqlJobCleanUp } from "./notification/notification.SqlJobCleanUp.js";
|
import { sqlJobCleanUp } from "./notification/notification.SqlJobCleanUp.js";
|
||||||
import { createNotifications } from "./notification/notifications.master.js";
|
import { createNotifications } from "./notification/notifications.master.js";
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { LaptopMinimal } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarHeader,
|
SidebarFooter,
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useSession } from "@/lib/auth-client";
|
import { useSession } from "@/lib/auth-client";
|
||||||
import { getSettings } from "../../lib/queries/getSettings";
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
@@ -23,6 +27,7 @@ export function AppSidebar() {
|
|||||||
openDock: ["read"],
|
openDock: ["read"],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const { setOpen } = useSidebar();
|
||||||
|
|
||||||
// const { data: canReadWarehouse = false } = useQuery(
|
// const { data: canReadWarehouse = false } = useQuery(
|
||||||
// permissionQuery({
|
// permissionQuery({
|
||||||
@@ -36,7 +41,7 @@ export function AppSidebar() {
|
|||||||
collapsible="offcanvas"
|
collapsible="offcanvas"
|
||||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
||||||
>
|
>
|
||||||
<SidebarHeader>
|
<SidebarContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
@@ -64,7 +69,24 @@ export function AppSidebar() {
|
|||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarHeader>
|
</SidebarContent>
|
||||||
|
{session &&
|
||||||
|
(session.user.role === "admin" ||
|
||||||
|
session.user.role === "systemAdmin" ||
|
||||||
|
session.user.role === "manager") && (
|
||||||
|
<SidebarFooter>
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<Link to={"/apidocs"} onClick={() => setOpen(false)}>
|
||||||
|
<LaptopMinimal />
|
||||||
|
<span>Api docs</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarFooter>
|
||||||
|
)}
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@
|
|||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
import { Route as ForbiddenRouteImport } from './routes/forbidden'
|
import { Route as ForbiddenRouteImport } from './routes/forbidden'
|
||||||
import { Route as AppDownRouteImport } from './routes/app-down'
|
import { Route as AppDownRouteImport } from './routes/app-down'
|
||||||
|
import { Route as ApidocsRouteImport } from './routes/apidocs'
|
||||||
import { Route as AboutRouteImport } from './routes/about'
|
import { Route as AboutRouteImport } from './routes/about'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
||||||
|
import { Route as DocsDatamartRouteImport } from './routes/docs/datamart'
|
||||||
import { Route as DocsSplatRouteImport } from './routes/docs/$'
|
import { Route as DocsSplatRouteImport } from './routes/docs/$'
|
||||||
import { Route as AdminUsersRouteImport } from './routes/admin/users'
|
import { Route as AdminUsersRouteImport } from './routes/admin/users'
|
||||||
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
||||||
@@ -41,6 +43,11 @@ const AppDownRoute = AppDownRouteImport.update({
|
|||||||
path: '/app-down',
|
path: '/app-down',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const ApidocsRoute = ApidocsRouteImport.update({
|
||||||
|
id: '/apidocs',
|
||||||
|
path: '/apidocs',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const AboutRoute = AboutRouteImport.update({
|
const AboutRoute = AboutRouteImport.update({
|
||||||
id: '/about',
|
id: '/about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
@@ -56,6 +63,11 @@ const DocsIndexRoute = DocsIndexRouteImport.update({
|
|||||||
path: '/docs/',
|
path: '/docs/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const DocsDatamartRoute = DocsDatamartRouteImport.update({
|
||||||
|
id: '/docs/datamart',
|
||||||
|
path: '/docs/datamart',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const DocsSplatRoute = DocsSplatRouteImport.update({
|
const DocsSplatRoute = DocsSplatRouteImport.update({
|
||||||
id: '/docs/$',
|
id: '/docs/$',
|
||||||
path: '/docs/$',
|
path: '/docs/$',
|
||||||
@@ -145,6 +157,7 @@ const WarehouseDockdoorscanningScansDockScansRoute =
|
|||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/apidocs': typeof ApidocsRoute
|
||||||
'/app-down': typeof AppDownRoute
|
'/app-down': typeof AppDownRoute
|
||||||
'/forbidden': typeof ForbiddenRoute
|
'/forbidden': typeof ForbiddenRoute
|
||||||
'/login': typeof authLoginRoute
|
'/login': typeof authLoginRoute
|
||||||
@@ -155,6 +168,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/admin/users': typeof AdminUsersRoute
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/docs/$': typeof DocsSplatRoute
|
'/docs/$': typeof DocsSplatRoute
|
||||||
|
'/docs/datamart': typeof DocsDatamartRoute
|
||||||
'/docs/': typeof DocsIndexRoute
|
'/docs/': typeof DocsIndexRoute
|
||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
@@ -168,6 +182,7 @@ export interface FileRoutesByFullPath {
|
|||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/apidocs': typeof ApidocsRoute
|
||||||
'/app-down': typeof AppDownRoute
|
'/app-down': typeof AppDownRoute
|
||||||
'/forbidden': typeof ForbiddenRoute
|
'/forbidden': typeof ForbiddenRoute
|
||||||
'/login': typeof authLoginRoute
|
'/login': typeof authLoginRoute
|
||||||
@@ -178,6 +193,7 @@ export interface FileRoutesByTo {
|
|||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/admin/users': typeof AdminUsersRoute
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/docs/$': typeof DocsSplatRoute
|
'/docs/$': typeof DocsSplatRoute
|
||||||
|
'/docs/datamart': typeof DocsDatamartRoute
|
||||||
'/docs': typeof DocsIndexRoute
|
'/docs': typeof DocsIndexRoute
|
||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
@@ -192,6 +208,7 @@ export interface FileRoutesById {
|
|||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/apidocs': typeof ApidocsRoute
|
||||||
'/app-down': typeof AppDownRoute
|
'/app-down': typeof AppDownRoute
|
||||||
'/forbidden': typeof ForbiddenRoute
|
'/forbidden': typeof ForbiddenRoute
|
||||||
'/(auth)/login': typeof authLoginRoute
|
'/(auth)/login': typeof authLoginRoute
|
||||||
@@ -202,6 +219,7 @@ export interface FileRoutesById {
|
|||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/admin/users': typeof AdminUsersRoute
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/docs/$': typeof DocsSplatRoute
|
'/docs/$': typeof DocsSplatRoute
|
||||||
|
'/docs/datamart': typeof DocsDatamartRoute
|
||||||
'/docs/': typeof DocsIndexRoute
|
'/docs/': typeof DocsIndexRoute
|
||||||
'/(auth)/user/profile': typeof authUserProfileRoute
|
'/(auth)/user/profile': typeof authUserProfileRoute
|
||||||
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
@@ -217,6 +235,7 @@ export interface FileRouteTypes {
|
|||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/apidocs'
|
||||||
| '/app-down'
|
| '/app-down'
|
||||||
| '/forbidden'
|
| '/forbidden'
|
||||||
| '/login'
|
| '/login'
|
||||||
@@ -227,6 +246,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
| '/admin/users'
|
| '/admin/users'
|
||||||
| '/docs/$'
|
| '/docs/$'
|
||||||
|
| '/docs/datamart'
|
||||||
| '/docs/'
|
| '/docs/'
|
||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
| '/user/resetpassword'
|
| '/user/resetpassword'
|
||||||
@@ -240,6 +260,7 @@ export interface FileRouteTypes {
|
|||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/apidocs'
|
||||||
| '/app-down'
|
| '/app-down'
|
||||||
| '/forbidden'
|
| '/forbidden'
|
||||||
| '/login'
|
| '/login'
|
||||||
@@ -250,6 +271,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
| '/admin/users'
|
| '/admin/users'
|
||||||
| '/docs/$'
|
| '/docs/$'
|
||||||
|
| '/docs/datamart'
|
||||||
| '/docs'
|
| '/docs'
|
||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
| '/user/resetpassword'
|
| '/user/resetpassword'
|
||||||
@@ -263,6 +285,7 @@ export interface FileRouteTypes {
|
|||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/apidocs'
|
||||||
| '/app-down'
|
| '/app-down'
|
||||||
| '/forbidden'
|
| '/forbidden'
|
||||||
| '/(auth)/login'
|
| '/(auth)/login'
|
||||||
@@ -273,6 +296,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
| '/admin/users'
|
| '/admin/users'
|
||||||
| '/docs/$'
|
| '/docs/$'
|
||||||
|
| '/docs/datamart'
|
||||||
| '/docs/'
|
| '/docs/'
|
||||||
| '/(auth)/user/profile'
|
| '/(auth)/user/profile'
|
||||||
| '/(auth)/user/resetpassword'
|
| '/(auth)/user/resetpassword'
|
||||||
@@ -287,6 +311,7 @@ export interface FileRouteTypes {
|
|||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
AboutRoute: typeof AboutRoute
|
AboutRoute: typeof AboutRoute
|
||||||
|
ApidocsRoute: typeof ApidocsRoute
|
||||||
AppDownRoute: typeof AppDownRoute
|
AppDownRoute: typeof AppDownRoute
|
||||||
ForbiddenRoute: typeof ForbiddenRoute
|
ForbiddenRoute: typeof ForbiddenRoute
|
||||||
authLoginRoute: typeof authLoginRoute
|
authLoginRoute: typeof authLoginRoute
|
||||||
@@ -297,6 +322,7 @@ export interface RootRouteChildren {
|
|||||||
AdminSettingsRoute: typeof AdminSettingsRoute
|
AdminSettingsRoute: typeof AdminSettingsRoute
|
||||||
AdminUsersRoute: typeof AdminUsersRoute
|
AdminUsersRoute: typeof AdminUsersRoute
|
||||||
DocsSplatRoute: typeof DocsSplatRoute
|
DocsSplatRoute: typeof DocsSplatRoute
|
||||||
|
DocsDatamartRoute: typeof DocsDatamartRoute
|
||||||
DocsIndexRoute: typeof DocsIndexRoute
|
DocsIndexRoute: typeof DocsIndexRoute
|
||||||
authUserProfileRoute: typeof authUserProfileRoute
|
authUserProfileRoute: typeof authUserProfileRoute
|
||||||
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
||||||
@@ -324,6 +350,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AppDownRouteImport
|
preLoaderRoute: typeof AppDownRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/apidocs': {
|
||||||
|
id: '/apidocs'
|
||||||
|
path: '/apidocs'
|
||||||
|
fullPath: '/apidocs'
|
||||||
|
preLoaderRoute: typeof ApidocsRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/about': {
|
'/about': {
|
||||||
id: '/about'
|
id: '/about'
|
||||||
path: '/about'
|
path: '/about'
|
||||||
@@ -345,6 +378,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DocsIndexRouteImport
|
preLoaderRoute: typeof DocsIndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/docs/datamart': {
|
||||||
|
id: '/docs/datamart'
|
||||||
|
path: '/docs/datamart'
|
||||||
|
fullPath: '/docs/datamart'
|
||||||
|
preLoaderRoute: typeof DocsDatamartRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/docs/$': {
|
'/docs/$': {
|
||||||
id: '/docs/$'
|
id: '/docs/$'
|
||||||
path: '/docs/$'
|
path: '/docs/$'
|
||||||
@@ -463,6 +503,7 @@ declare module '@tanstack/react-router' {
|
|||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
AboutRoute: AboutRoute,
|
AboutRoute: AboutRoute,
|
||||||
|
ApidocsRoute: ApidocsRoute,
|
||||||
AppDownRoute: AppDownRoute,
|
AppDownRoute: AppDownRoute,
|
||||||
ForbiddenRoute: ForbiddenRoute,
|
ForbiddenRoute: ForbiddenRoute,
|
||||||
authLoginRoute: authLoginRoute,
|
authLoginRoute: authLoginRoute,
|
||||||
@@ -473,6 +514,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
AdminSettingsRoute: AdminSettingsRoute,
|
AdminSettingsRoute: AdminSettingsRoute,
|
||||||
AdminUsersRoute: AdminUsersRoute,
|
AdminUsersRoute: AdminUsersRoute,
|
||||||
DocsSplatRoute: DocsSplatRoute,
|
DocsSplatRoute: DocsSplatRoute,
|
||||||
|
DocsDatamartRoute: DocsDatamartRoute,
|
||||||
DocsIndexRoute: DocsIndexRoute,
|
DocsIndexRoute: DocsIndexRoute,
|
||||||
authUserProfileRoute: authUserProfileRoute,
|
authUserProfileRoute: authUserProfileRoute,
|
||||||
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ const RootLayout = () => {
|
|||||||
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
|
|
||||||
<main className="w-full p-4">
|
<main className="w-full">
|
||||||
<div className="mx-auto w-full max-w-7xl">
|
<div className="mx-auto w-full flex justify-center">
|
||||||
|
{" "}
|
||||||
|
{/* className="mx-auto w-full max-w-7xl" use this for dashboards and stuff*/}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -31,7 +33,7 @@ const RootLayout = () => {
|
|||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
{session && session.user.role === "systemAdmin" && (
|
{session && session.user.role === "systemAdmin" && (
|
||||||
<TanStackRouterDevtools />
|
<TanStackRouterDevtools position="bottom-right" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ function RouteComponent() {
|
|||||||
};
|
};
|
||||||
//console.log(logs);
|
//console.log(logs);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1 max-w-7xl">
|
||||||
<div className="flex gap-1 justify-end">
|
<div className="flex gap-1 justify-end">
|
||||||
<Button onClick={triggerBuild}>Trigger Build</Button>
|
<Button onClick={triggerBuild}>Trigger Build</Button>
|
||||||
<Button onClick={() => clearRoom()}>Clear Logs</Button>
|
<Button onClick={() => clearRoom()}>Clear Logs</Button>
|
||||||
|
|||||||
15
frontend/src/routes/apidocs.tsx
Normal file
15
frontend/src/routes/apidocs.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/apidocs")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
src="/lst/api/docs"
|
||||||
|
className="h-[calc(100vh-64px)] w-full border-0"
|
||||||
|
title="LST API Docs"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import { Trash } from "lucide-react";
|
||||||
import { Suspense, useState } from "react";
|
import { Suspense, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "../../../components/ui/button";
|
||||||
|
import { Spinner } from "../../../components/ui/spinner";
|
||||||
|
import { api } from "../../../lib/apiHelper";
|
||||||
import { authClient } from "../../../lib/auth-client";
|
import { authClient } from "../../../lib/auth-client";
|
||||||
import { getArticleLinks } from "../../../lib/queries/getArticleLinks";
|
import { getArticleLinks } from "../../../lib/queries/getArticleLinks";
|
||||||
import LstTable from "../../../lib/tableStuff/LstTable";
|
import LstTable from "../../../lib/tableStuff/LstTable";
|
||||||
import SearchableHeader from "../../../lib/tableStuff/SearchableHeader";
|
import SearchableHeader from "../../../lib/tableStuff/SearchableHeader";
|
||||||
import SkellyTable from "../../../lib/tableStuff/SkellyTable";
|
import SkellyTable from "../../../lib/tableStuff/SkellyTable";
|
||||||
import NewArticleLink from "./-components/NewArticleLink";
|
import NewArticleLink from "./-components/NewArticleLink";
|
||||||
import { api } from "../../../lib/apiHelper";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Button } from "../../../components/ui/button";
|
|
||||||
import { Spinner } from "../../../components/ui/spinner";
|
|
||||||
import { Trash } from "lucide-react";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/transportation/opendock/")({
|
export const Route = createFileRoute("/transportation/opendock/")({
|
||||||
beforeLoad: async ({ location }) => {
|
beforeLoad: async ({ location }) => {
|
||||||
@@ -167,7 +167,7 @@ const ArticleLinkTable = () => {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-end m-2">
|
<div className="flex justify-end m-2">
|
||||||
<Suspense
|
<Suspense
|
||||||
|
|||||||
90
package-lock.json
generated
90
package-lock.json
generated
@@ -10,7 +10,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dotenvx/dotenvx": "^1.57.0",
|
"@dotenvx/dotenvx": "^1.57.0",
|
||||||
"@scalar/express-api-reference": "^0.9.4",
|
"@scalar/express-api-reference": "^0.9.20",
|
||||||
"@socket.io/admin-ui": "^0.5.1",
|
"@socket.io/admin-ui": "^0.5.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
@@ -44,7 +44,8 @@
|
|||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "^4.8.3",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6",
|
||||||
|
"zod-openapi": "^6.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.4.8",
|
"@biomejs/biome": "2.4.8",
|
||||||
@@ -2424,46 +2425,61 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@scalar/core": {
|
"node_modules/@scalar/client-side-rendering": {
|
||||||
"version": "0.4.4",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/@scalar/core/-/core-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@scalar/client-side-rendering/-/client-side-rendering-0.1.13.tgz",
|
||||||
"integrity": "sha512-eXIG0opyQn45FzpTp0dAWFP1Vjcx+helgUAsa0uN36tyUR7DSmz2kRwHqqedzvPWryeRCKPz7/vwzKpETZp5lg==",
|
"integrity": "sha512-p8V4HgEWjaCpqsnhclg1pTfjE9JA0AWRr0ocBQHexoHo+pqnSs1d83Mv9rjH7R0FZJrlCSandZZeY3DMX2gYXQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scalar/types": "0.7.4"
|
"@scalar/schemas": "0.3.3",
|
||||||
|
"@scalar/types": "0.12.3",
|
||||||
|
"@scalar/validation": "0.6.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@scalar/express-api-reference": {
|
"node_modules/@scalar/express-api-reference": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.20",
|
||||||
"resolved": "https://registry.npmjs.org/@scalar/express-api-reference/-/express-api-reference-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@scalar/express-api-reference/-/express-api-reference-0.9.20.tgz",
|
||||||
"integrity": "sha512-KXG+VaMArCGcWhzDV2rfkHd+UF1HYevIFbO6cqFpd+az7QHvVT99BU8Yh60T1dmtCp504s0Pl/vcTyJ91fK1Ug==",
|
"integrity": "sha512-J0P6qpYoL0kXvs/A/vuAwCqQFCYnErbXSB5/3lEGTbARuK0oGyMvl55dQyW5Ucq3CX1npuRejlTX6bxEprSvJA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scalar/core": "0.4.4"
|
"@scalar/client-side-rendering": "0.1.13"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@scalar/helpers": {
|
"node_modules/@scalar/helpers": {
|
||||||
"version": "0.4.2",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.8.1.tgz",
|
||||||
"integrity": "sha512-IrgrGVSahCfYDNWITazz4Q1BOndp5eEzlimRkfxiYn++KqeWyLfALyym1omqcdKGYtiSx1KIbKaUJL9vkjaN7w==",
|
"integrity": "sha512-yuiuBCadP5bjAnIv23QvifVN/NaMi9xBF6b8Wdk4QOzwzLPJmp699MAdf33J0A5i2qKcvnu32iz/VkEJmQRe5g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@scalar/types": {
|
"node_modules/@scalar/schemas": {
|
||||||
"version": "0.7.4",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/@scalar/schemas/-/schemas-0.3.3.tgz",
|
||||||
"integrity": "sha512-1o9uf42lZ9YD0XP/HMWrwXN0unx6vFTTgtduA1F28Yloea9Pfv9N2R/t0wO91iSIzw4+NubEFolunbdb2QcgHA==",
|
"integrity": "sha512-qDcgFu6ta5Z90L9D2P6DFKzYesU+FW5+m55SGmdI4iRMRCwj5umHpec2Y2W/SJcCF6bbUZawMxuOH2Ja6rUNpQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scalar/helpers": "0.4.2",
|
"@scalar/helpers": "0.8.1",
|
||||||
|
"@scalar/validation": "0.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@scalar/types": {
|
||||||
|
"version": "0.12.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.12.3.tgz",
|
||||||
|
"integrity": "sha512-7zaXafbgTFmsJ/9AwYeExUWzXoZNyKOL0SEVAUWRaOndcjxpFCtwzuPrc1elMEWdHopWbY1Qe5pWKbE2aqG2HA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@scalar/helpers": "0.8.1",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"type-fest": "^5.3.1",
|
"type-fest": "^5.3.1",
|
||||||
"zod": "^4.3.5"
|
"zod": "^4.3.5"
|
||||||
@@ -2472,6 +2488,15 @@
|
|||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@scalar/validation": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scalar/validation/-/validation-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-tpmmG+/xRE2Kn9RpflU3AIyZv08v10+E1ZrJCx7z6+/91zHVxy0M73kC1LT4/8PbYNt85ywyC8+n+D99JdMcGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@serialport/binding-mock": {
|
"node_modules/@serialport/binding-mock": {
|
||||||
"version": "10.2.2",
|
"version": "10.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
|
||||||
@@ -10566,9 +10591,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "5.1.7",
|
"version": "5.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz",
|
||||||
"integrity": "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==",
|
"integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -13252,9 +13277,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/type-fest": {
|
"node_modules/type-fest": {
|
||||||
"version": "5.5.0",
|
"version": "5.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz",
|
||||||
"integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==",
|
"integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==",
|
||||||
"license": "(MIT OR CC0-1.0)",
|
"license": "(MIT OR CC0-1.0)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tagged-tag": "^1.0.0"
|
"tagged-tag": "^1.0.0"
|
||||||
@@ -13937,6 +13962,21 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod-openapi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod-openapi/-/zod-openapi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-mS4eRJ4DGCPrg6elRbJqc/3nLe4EPVi8KiHRKZ7dcTR5m5orPy8EfoWmceAyGZAq71MAWuyrTTOag7W5N61ZPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.14.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/samchungy/zod-openapi?sponsor=1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^4.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dotenvx/dotenvx": "^1.57.0",
|
"@dotenvx/dotenvx": "^1.57.0",
|
||||||
"@scalar/express-api-reference": "^0.9.4",
|
"@scalar/express-api-reference": "^0.9.20",
|
||||||
"@socket.io/admin-ui": "^0.5.1",
|
"@socket.io/admin-ui": "^0.5.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
@@ -102,7 +102,8 @@
|
|||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "^4.8.3",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6",
|
||||||
|
"zod-openapi": "^6.0.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "NodeNext",
|
|
||||||
"moduleResolution": "nodenext",
|
|
||||||
"strict": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"types": ["node", "better-auth"],
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"outDir": "./dist",
|
|
||||||
"removeComments": true,
|
|
||||||
"allowJs": false,
|
|
||||||
"rootDir": "./backend",
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"exactOptionalPropertyTypes": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["backend/*"],
|
|
||||||
"@features/*": ["backend/features/*"],
|
|
||||||
"@shared/*": ["backend/shared/*"],
|
|
||||||
"@config/*": ["backend/config/*"]
|
|
||||||
},
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
//"allowImportingTsExtensions": true,
|
|
||||||
"noEmit": false
|
|
||||||
},
|
|
||||||
"include": ["backend/**/*"],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"frontend",
|
|
||||||
"dist",
|
|
||||||
"lstDocs",
|
|
||||||
"database/testFiles",
|
|
||||||
"scripts"
|
|
||||||
]}
|
|
||||||
Reference in New Issue
Block a user