Compare commits

...

12 Commits

30 changed files with 4534 additions and 316 deletions

View File

@@ -16,10 +16,10 @@ headers {
body:json {
{
"name": "Bowling Green 1",
"serverDNS": "USBOW1VS006",
"plantToken": "usbow1",
"ipAddress": "10.25.0.26",
"name": "Dayton",
"serverDNS": "USDAY1VS006",
"plantToken": "usday1",
"ipAddress": "10.44.0.26",
"greatPlainsPlantCode": 55,
"lstServerPort": 4000,
"serverLoc": "E$\\LST"

View File

@@ -0,0 +1,15 @@
meta {
name: RFID Trigger
type: http
seq: 1
}
post {
url: {{url}}/lst/old/api/rfid/manualtrigger/wrapper1
body: none
auth: inherit
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,8 @@
meta {
name: RFID
seq: 3
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,16 @@
meta {
name: ForecastData
type: http
seq: 1
}
post {
url: {{url}}/lst/api/logistics/dm/forecastData
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -1,5 +1,5 @@
vars {
url: https://usbet1prod.alpla.net
url: http://localhost:5500
session_cookie:
urlv2: http://localhost:3000
jwtV2:

View File

@@ -8,6 +8,8 @@ import { createProxyMiddleware, fixRequestBody } from "http-proxy-middleware";
import morgan from "morgan";
import os from "os";
import { dirname, join } from "path";
import swaggerJsdoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import { fileURLToPath } from "url";
import { userMigrate } from "./src/internal/auth/controller/userMigrate.js";
import { schedulerManager } from "./src/internal/logistics/controller/schedulerManager.js";
@@ -15,6 +17,8 @@ import { printers } from "./src/internal/ocp/printers/printers.js";
import { setupRoutes } from "./src/internal/routerHandler/routeHandler.js";
import { baseModules } from "./src/internal/system/controller/modules/baseModules.js";
import { baseSettings } from "./src/internal/system/controller/settings/baseSettings.js";
import { addListeners } from "./src/internal/system/utlis/addListeners.js";
import { swaggerOptions } from "./src/pkg/apiDocs/swaggerOptions.js";
import { auth } from "./src/pkg/auth/auth.js";
import { db } from "./src/pkg/db/db.js";
import { settings } from "./src/pkg/db/schema/settings.js";
@@ -30,7 +34,7 @@ import { setupIoServer } from "./src/ws/server.js";
const main = async () => {
const env = validateEnv(process.env);
const PORT = Number(env.VITE_PORT) || 4200;
const PORT = Number(process.env.VITE_PORT) || 4200;
//create the logger
const log = createLogger({ module: "system", subModule: "main start" });
@@ -159,7 +163,14 @@ const main = async () => {
}),
);
// docs and api stuff
// docs and routes
const openapiSpec: any = swaggerJsdoc(swaggerOptions);
app.use(
basePath + "/api/docs",
swaggerUi.serve,
swaggerUi.setup(openapiSpec),
);
app.use(basePath + "/d", express.static(join(__dirname, "../lstDocs/build")));
app.use(
basePath + "/app",
@@ -192,6 +203,7 @@ const main = async () => {
// start up the v1listener
v1Listener();
addListeners();
userMigrate();
}, 5 * 1000);

View File

@@ -8,10 +8,63 @@
* add_date
*/
type IncomingForecastData = {
CustomerArticleNumber: number;
};
import { db } from "../../../../pkg/db/db.js";
import {
type ForecastData,
forecastData,
forecastDataSchema,
} from "../../../../pkg/db/schema/forecastEDIData.js";
import { prodQuery } from "../../../../pkg/prodSql/prodQuery.js";
import { activeArticle } from "../../../../pkg/prodSql/querys/datamart/article.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
export const forecastData = async (data: IncomingForecastData) => {
console.log(data);
export const forecastEdiData = async (data: ForecastData[]) => {
//console.log(data);
const { data: article, error } = await tryCatch(
prodQuery(activeArticle, "forecast data active articles"),
);
if (error) {
return {
success: false,
message: "Failed to get active articles",
error: error,
};
}
const forecaseEDIDATA = [];
for (let i = 0; i < data.length; i++) {
const activeAV = article?.data.filter(
(c: any) =>
c?.CustomerArticleNumber === data[i].customerArticleNo?.toString(),
);
const newData = data[i];
//console.log(activeAV[0].IdArtikelvarianten);
forecaseEDIDATA.push({
...newData,
article: activeAV[0].IdArtikelvarianten,
requirementDate: new Date(newData.requirementDate),
});
}
console.log(forecaseEDIDATA[0]);
const { data: f, error: ef } = await tryCatch(
db.insert(forecastData).values(forecaseEDIDATA),
);
if (ef) {
console.log(ef);
return {
success: false,
message: "There was an error posting the new data",
error: ef,
};
}
return {
success: true,
message: "All forecast Data was posted",
data: [],
};
};

View File

@@ -1,10 +1,12 @@
import type { Express, Request, Response } from "express";
import dm from "./routes/demandMgt/dmRoutes.js";
import labeling from "./routes/labeling/labelingRoutes.js";
import schedule from "./routes/scheduler/scheduleRoutes.js";
export const setupLogisticsRoutes = (app: Express, basePath: string) => {
app.use(basePath + "/api/logistics/schedule", schedule);
app.use(basePath + "/api/logistics/labeling", labeling);
app.use(basePath + "/api/logistics/dm", dm);
// app.use(
// basePath + "/api/admin/users",

View File

@@ -4,6 +4,6 @@ import forecastData from "./forecastEDIData.js";
const router = Router();
router.use("/", requireAuth(), forecastData);
router.use("/", forecastData);
export default router;

View File

@@ -1,7 +1,7 @@
import type { Request, Response } from "express";
import { Router } from "express";
import z from "zod";
import { preprintLabels } from "../../controller/labeling/preprint.js";
import { forecastEdiData } from "../../controller/demandManagement/forecastEDIData.js";
export const Preprint = z.object({
scannerId: z.number(),
@@ -15,13 +15,10 @@ export const Preprint = z.object({
const router = Router();
router.post("/preprint", async (req: Request, res: Response) => {
const parsed = Preprint.safeParse(req.body);
const print = await preprintLabels(req.body, req.user?.username || "");
router.post("/forecastData", async (req: Request, res: Response) => {
await forecastEdiData(req.body);
res
.status(200)
.json({ success: print.success, message: print.message, data: print.data });
res.status(200).json({ success: true, message: "Forecast Data", data: [] });
});
export default router;

View File

@@ -40,7 +40,7 @@ export const baseModules = async () => {
}
if (data) {
log.info({ newModulesAdded: data }, "New settings added");
//log.info({ newModulesAdded: data }, "New settings added");
}
}
};

View File

@@ -26,5 +26,12 @@
"description": "What is the port the v1app is on",
"moduleName": "system",
"roles": ["systemAdmin"]
},
{
"name": "dycoPrint",
"value": "0",
"description": "Are we using dyco to print the labels or rfid?",
"moduleName": "ocp",
"roles": ["systemAdmin", "admin", "manager"]
}
]

View File

@@ -11,7 +11,30 @@ import { checkBuildUpdate } from "../utlis/checkForBuild.js";
const router = Router();
// GET /health
/**
* @openapi
* /health:
* get:
* summary: Health check for the API server
* description: Returns basic system stats including memory usage, uptime, and build info.
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: ok
* uptime:
* type: number
* build:
* type: string
* memoryUsage:
* type: string
*/
router.get("/", async (req, res) => {
const { data, error } = await tryCatch(
db.select().from(serverStats).where(eq(serverStats.id, "serverStats")),

View File

@@ -0,0 +1,51 @@
import { Client } from "pg";
import { createLogger } from "../../../pkg/logger/logger.js";
export const addListeners = async () => {
const log = createLogger({ module: "utils", subModule: "listeners" });
const client = new Client({
connectionString: process.env.DATABASE_URL_V1,
});
await client.connect();
try {
log.info({}, "Running the new log listener");
await client.query(`
CREATE OR REPLACE FUNCTION notify_new_log()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('logs_channel', row_to_json(NEW)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER logs_notify_trigger
AFTER INSERT ON logs
FOR EACH ROW
EXECUTE FUNCTION notify_new_log();
`);
} catch (e) {
log.info({}, "log listener exist");
}
try {
log.info({}, "Running the new label listener");
await client.query(`
CREATE OR REPLACE FUNCTION notify_new_label()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('label_channel', row_to_json(NEW)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER label_notify_trigger
AFTER INSERT ON prodlabels
FOR EACH ROW
EXECUTE FUNCTION notify_new_label();
`);
} catch (e) {
log.info({}, "label listener exists");
}
};

View File

@@ -0,0 +1,11 @@
export const swaggerOptions = {
definition: {
openapi: "3.0.0",
info: {
title: "Logistics Support Tool",
version: "1.0.0",
},
},
// globs where swagger-jsdoc should look for annotations:
apis: ["../../src/**/*.ts"],
};

View File

@@ -13,12 +13,11 @@ import { z } from "zod";
export const forecastData = pgTable("forecast_Data", {
id: uuid("id").defaultRandom().primaryKey(),
customerArticleNumber: text("customer_article_number"),
customerArticleNo: text("customer_article_no"),
dateRequested: timestamp("date_requested").defaultNow(),
quantity: real("quantity"),
requestDate: timestamp("request_date").notNull(),
requirementDate: timestamp("requirement_date").notNull(),
article: integer("article"),
customerID: integer("customer_id").notNull(),
createdAt: timestamp("created_at").defaultNow(),
});

View File

@@ -0,0 +1,195 @@
export const activeArticle = `
use AlplaPROD_test1
SELECT V_Artikel.IdArtikelvarianten,
V_Artikel.Bezeichnung,
V_Artikel.ArtikelvariantenTypBez,
V_Artikel.PreisEinheitBez,
case when sales.price is null then 0 else sales.price end as salesPrice,
TypeOfMaterial=CASE
WHEN
V_Artikel.ArtikelvariantenTypBez LIKE'%Additive'
Then 'AD'
when V_Artikel.ArtikelvariantenTypBez Like '%Masterbatch'
THEN 'MB'
WHEN V_Artikel.ArtikelvariantenTypBez ='Pallet' or
V_Artikel.ArtikelvariantenTypBez ='Top' or
V_Artikel.ArtikelvariantenTypBez ='Bags' or
V_Artikel.ArtikelvariantenTypBez ='Bag' or
V_Artikel.ArtikelvariantenTypBez ='Stretch Wrap' or
V_Artikel.ArtikelvariantenTypBez ='Stretch Film' or
V_Artikel.ArtikelvariantenTypBez ='Banding Materials' or
V_Artikel.ArtikelvariantenTypBez ='Carton' or
V_Artikel.ArtikelvariantenTypBez ='Re-Shipper Box' or
V_Artikel.ArtikelvariantenTypBez ='Label' or
V_Artikel.ArtikelvariantenTypBez ='Pallet Label' or
V_Artikel.ArtikelvariantenTypBez ='Carton Label' or
V_Artikel.ArtikelvariantenTypBez ='Liner' or
V_Artikel.ArtikelvariantenTypBez ='Dose Cup' or
V_Artikel.ArtikelvariantenTypBez ='Metal Cage' or
V_Artikel.ArtikelvariantenTypBez ='Spout' or
V_Artikel.ArtikelvariantenTypBez = 'Slip Sheet' or
V_Artikel.ArtikelvariantenTypBez = 'Palet' or
V_Artikel.ArtikelvariantenTypBez = 'LID' or
V_Artikel.ArtikelvariantenTypBez= 'Metal' or
V_Artikel.ArtikelvariantenTypBez= 'Corner post' or
V_Artikel.ArtikelvariantenTypBez= 'Bottle Label' or
V_Artikel.ArtikelvariantenTypBez = 'Paper label' or
V_Artikel.ArtikelvariantenTypBez = 'Banding' or
V_Artikel.ArtikelvariantenTypBez = 'Glue' or
V_Artikel.ArtikelvariantenTypBez = 'Top Frame' or
V_Artikel.ArtikelvariantenTypBez = 'IML Label' or
V_Artikel.ArtikelvariantenTypBez = 'Purch EBM Bottle' or
V_Artikel.ArtikelvariantenTypBez = 'Purchased Spout' or
V_Artikel.ArtikelvariantenTypBez = 'Gaylord' or
V_Artikel.ArtikelvariantenTypBez = 'Misc. Packaging' or
V_Artikel.ArtikelvariantenTypBez = 'Sleeve' or
V_Artikel.ArtikelvariantenTypBez = 'Plastic Bag' or
V_Artikel.ArtikelvariantenTypBez = 'Purch Spout' or
V_Artikel.ArtikelvariantenTypBez = 'Seal' or
V_Artikel.ArtikelvariantenTypBez = 'Tape' or
V_Artikel.ArtikelvariantenTypBez = 'Box' or
V_Artikel.ArtikelvariantenTypBez = 'Label IML' or
V_Artikel.ArtikelvariantenTypBez = 'Pallet Runner'
THEN 'PKG'
WHEN V_Artikel.ArtikelvariantenTypBez='HD-PE' or
V_Artikel.ArtikelvariantenTypBez='HD-PE PCR' or
V_Artikel.ArtikelvariantenTypBez='HD-PP' or
V_Artikel.ArtikelvariantenTypBez= 'PP' or
V_Artikel.ArtikelvariantenTypBez LIKE '%PCR' or
V_Artikel.ArtikelvariantenTypBez= 'LDPE' or
V_Artikel.ArtikelvariantenTypBez= 'PP' or
V_Artikel.ArtikelvariantenTypBez= 'HDPE' or
V_Artikel.ArtikelvariantenTypBez= 'PET' or
V_Artikel.ArtikelvariantenTypBez= 'PET-P'
THEN 'MM'
WHEN
V_Artikel.ArtikelvariantenTypBez='HDPE-Waste' or
V_Artikel.ArtikelvariantenTypBez='$Waste Container' or
V_Artikel.ArtikelvariantenTypBez='Mixed-Waste' or
V_Artikel.ArtikelvariantenTypBez LIKE'%-Waste%'
THEN 'Waste'
WHEN
V_Artikel.ArtikelvariantenTypBez = 'Bottle' or
V_Artikel.ArtikelvariantenTypBez = 'SBM Bottle' or
V_Artikel.ArtikelvariantenTypBez = 'EBM Bottle' or
V_Artikel.ArtikelvariantenTypBez = 'ISBM Bottle' or
V_Artikel.ArtikelvariantenTypBez = 'Decorated Bottle'
THEN 'Bottle'
WHEN V_Artikel.ArtikelvariantenTypBez = 'Preform'
Then 'Preform'
When
V_Artikel.ArtikelvariantenTypBez = 'Purchased Preform' or
V_Artikel.ArtikelvariantenTypBez = 'Purchased Caps' or
V_Artikel.ArtikelvariantenTypBez = 'Purchased_preform'
THEN 'Purchased_preform'
When
V_Artikel.ArtikelvariantenTypBez = 'Closures' or
V_Artikel.ArtikelvariantenTypBez = 'Cap'
THEN 'Caps'
When
V_Artikel.ArtikelvariantenTypBez = 'Dummy'
THEN 'Not used'
ELSE 'Item not defined' END
,V_Artikel.IdArtikelvariantenTyp,
Round(V_Artikel.ArtikelGewicht, 3) as Article_Weight,
IdAdresse,
AdressBez,
AdressTypBez,
ProdBereichBez,
FG=case when
V_Artikel.ProdBereichBez = 'SBM' or
V_Artikel.ProdBereichBez = 'IM-Caps' or
V_Artikel.ProdBereichBez = 'IM-PET' or
V_Artikel.ProdBereichBez = 'PRINT OFFICE' or
V_Artikel.ProdBereichBez = 'EBM' or
V_Artikel.ProdBereichBez = 'ISBM' or
V_Artikel.ProdBereichBez = 'IM-Finishing'
Then 'FG'
Else 'not Defined Profit Center'
end,
V_Artikel.Umlaeufe as num_of_cycles,
V_FibuKonten_BASIS.FibuKontoNr as CostsCenterId,
V_FibuKonten_BASIS.Bezeichnung as CostCenterDescription,
sales.[KdArtNr] as CustomerArticleNumber,
sales.[KdArtBez] as CustomerArticleDescription,
round(V_Artikel.Zyklus, 2) as CycleTime,
Sypronummer as salesAgreement,
V_Artikel.ProdArtikelBez as ProductFamily
--,REPLACE(pur.UOM,'UOM:','')
,Case when LEFT(
LTRIM(REPLACE(pur.UOM,'UOM:','')),
CHARINDEX(' ', LTRIM(REPLACE(REPLACE(pur.UOM,'UOM:',''), CHAR(13)+CHAR(10), ' ')) + ' ') - 1
) is null then '1' else LEFT(
LTRIM(REPLACE(pur.UOM,'UOM:','')),
CHARINDEX(' ', LTRIM(REPLACE(REPLACE(pur.UOM,'UOM:',''), CHAR(13)+CHAR(10), ' ')) + ' ') - 1
) end AS UOM
--,*
FROM dbo.V_Artikel (nolock)
join
dbo.V_Artikelvarianten (nolock) on dbo.V_Artikel.IdArtikelvarianten =
dbo.V_Artikelvarianten.IdArtikelvarianten
join
dbo.V_FibuKonten_BASIS (nolock) on dbo.V_Artikelvarianten.IdFibuKonto =
dbo.V_FibuKonten_BASIS.IdFibuKonto
-- adding in the sales price
left join
(select * from
(select
ROW_NUMBER() OVER (PARTITION BY IdArtikelvarianten ORDER BY GueltigabDatum DESC) AS RN,
IdArtikelvarianten as av
,GueltigabDatum as validDate
,VKPreis as price
,[KdArtNr]
,[KdArtBez]
--,*
from dbo.T_HistoryVK (nolock)
where
--GueltigabDatum > getDate() - 120
--and
Aktiv = 1
and StandardKunde = 1 -- default address
) a
where RN = 1) as sales
on dbo.V_Artikel.IdArtikelvarianten = sales.av
/* adding the purchase price info */
left join
(select * from
(select
ROW_NUMBER() OVER (PARTITION BY IdArtikelvarianten ORDER BY GueltigabDatum DESC) AS RN,
IdArtikelvarianten as av
,GueltigabDatum as validDate
,EKPreis as price
,LiefArtNr as supplierNr
,CASE
WHEN Bemerkung IS NOT NULL AND Bemerkung LIKE '%UOM:%'
THEN
-- incase there is something funny going on in the remark well jsut check for new lines and what not
LEFT(
REPLACE(REPLACE(Bemerkung, CHAR(13)+CHAR(10), ' '), CHAR(10), ' '),
CASE
WHEN CHARINDEX(' ', REPLACE(REPLACE(Bemerkung, CHAR(13)+CHAR(10), ' '), CHAR(10), ' ')) > 0
THEN CHARINDEX(' ', REPLACE(REPLACE(Bemerkung, CHAR(13)+CHAR(10), ' '), CHAR(10), ' ')) - 1
ELSE LEN(Bemerkung)
END
)
ELSE 'UOM:1'
END AS UOM
,Bemerkung
--,*
from dbo.T_HistoryEK (nolock)
where
StandardLieferant = 1 -- default address
) a
where RN = 1) as pur
on dbo.V_Artikel.IdArtikelvarianten = pur.av
where V_Artikel.aktiv = 1 --and dbo.V_Artikel.IdArtikelvarianten = 1445
order by V_Artikel.IdArtikelvarianten /*, TypeOfMaterial */
`;

View File

@@ -37,6 +37,7 @@ type ServerData = {
pendingUpdateFile: string;
lastUpdate: Date;
memoryUsage: string;
greatPlainsPlantCode: string;
};
const updateServerItem = async (
@@ -66,6 +67,10 @@ function RouteComponent() {
);
const [sorting, setSorting] = useState<SortingState>([]);
const columnHelper = createColumnHelper<ServerData>();
const [pagination, setPagination] = useState({
pageIndex: 0, //initial page index
pageSize: 20, //default page size
});
const statsMap = React.useMemo(() => {
const map = new Map<string, any>();
@@ -93,7 +98,20 @@ function RouteComponent() {
const columns = [
columnHelper.accessor("name", {
cell: (i) => i.getValue(),
cell: ({ row, getValue }) => {
let url = "";
if (row.original.plantToken.includes("test")) {
url = `https://${row.original.serverDNS}.alpla.net/lst/app`;
} else {
url = `https://${row.original.plantToken}prod.alpla.net/lst/app`;
}
return (
<a href={url} target="_blank">
{getValue()}
</a>
);
},
header: ({ column }) => {
return (
<Button
@@ -204,6 +222,41 @@ function RouteComponent() {
},
}),
columnHelper.accessor("greatPlainsPlantCode", {
header: () => {
return <span className="flex flex-row gap-2">GP Code</span>;
},
cell: ({ row, getValue }) => {
const initialValue = String(getValue() ?? "");
const [localValue, setLocalValue] = React.useState(initialValue);
const token = row.original.plantToken;
const field = "greatPlainsPlantCode";
useEffect(() => setLocalValue(initialValue), [initialValue]);
const handleSubmit = (newValue: string) => {
if (newValue !== initialValue) {
setLocalValue(newValue); // keep new value locally immediately
updateServer.mutate({ token, field, value: newValue });
}
};
return (
<Input
value={localValue}
onChange={(e) => setLocalValue(e.currentTarget.value)}
onBlur={(e) => handleSubmit(e.currentTarget.value.trim())}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleSubmit(e.currentTarget.value.trim());
e.currentTarget.blur(); // exit edit mode
}
}}
/>
);
},
}),
columnHelper.accessor("uptime", {
header: () => {
return <span className="flex flex-row gap-2">UpTime</span>;
@@ -215,8 +268,12 @@ function RouteComponent() {
// use Skeleton from shadcn/ui (or any placeholder)
return <Skeleton className="h-4 w-16" />;
}
console.log(statsMap);
return <span>{statsRow ? `${statsRow.uptime.toFixed(2)}` : "--"}</span>;
//console.log(statsMap);
return (
<span>
{statsRow ? `${statsRow.uptime.toFixed(2)}` : "Server Offline?"}
</span>
);
},
}),
columnHelper.accessor("lastUpdate", {
@@ -230,8 +287,10 @@ function RouteComponent() {
// use Skeleton from shadcn/ui (or any placeholder)
return <Skeleton className="h-4 w-16" />;
}
console.log(statsMap);
return <span>{statsRow ? `${statsRow.lastUpdate}` : "--"}</span>;
//console.log(statsMap);
return (
<span>{statsRow ? `${statsRow.lastUpdate}` : "Server Offline?"}</span>
);
},
}),
columnHelper.accessor("build", {
@@ -245,8 +304,10 @@ function RouteComponent() {
// use Skeleton from shadcn/ui (or any placeholder)
return <Skeleton className="h-4 w-16" />;
}
console.log(statsMap);
return <span>{statsRow ? `${statsRow.build}` : "--"}</span>;
//console.log(statsMap);
return (
<span>{statsRow ? `${statsRow.build}` : "Server Offline?"}</span>
);
},
}),
columnHelper.accessor("pendingUpdateFile", {
@@ -260,8 +321,14 @@ function RouteComponent() {
// use Skeleton from shadcn/ui (or any placeholder)
return <Skeleton className="h-4 w-16" />;
}
console.log(statsMap);
return <span>{statsRow ? `${statsRow.pendingUpdateFile}` : "--"}</span>;
//console.log(statsMap);
return (
<span>
{statsRow
? `${statsRow.pendingUpdateFile === null ? "nothing pending" : statsRow.pendingUpdateFile}`
: "Server Offline?"}
</span>
);
},
}),
columnHelper.accessor("memoryUsage", {
@@ -276,7 +343,11 @@ function RouteComponent() {
return <Skeleton className="h-4 w-16" />;
}
//console.log(statsMap);
return <span>{statsRow ? `${statsRow.memoryUsage}` : "--"}</span>;
return (
<span>
{statsRow ? `${statsRow.memoryUsage}` : "Server Offline?"}
</span>
);
},
}),
];
@@ -288,10 +359,12 @@ function RouteComponent() {
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onPaginationChange: setPagination,
//renderSubComponent: ({ row }: { row: any }) => <ExpandedRow row={row} />,
//getRowCanExpand: () => true,
state: {
sorting,
pagination,
},
});

View File

@@ -10,7 +10,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { useAuth, userAccess } from "@/lib/authClient";
import { useSettingStore } from "../../-lib/store/useSettings";
//import { useSettingStore } from "../../-lib/store/useSettings";
import type { LotType } from "../../-types/lots";
import { getlots } from "../../-utils/querys/ocp/lots";
import { LstCard } from "../extendedUi/LstCard";
@@ -67,9 +67,9 @@ export default function Lots() {
const { data, isError, isLoading } = useQuery(getlots());
const { session } = useAuth();
const { settings } = useSettingStore();
//const { settings } = useSettingStore();
const server = settings.filter((n) => n.name === "dbServer")[0]?.value || "";
//const server = settings.filter((n) => n.name === "dbServer")[0]?.value || "";
const lotdata = data ? data : [];
const accessRoles = userAccess("ocp", [
@@ -229,7 +229,8 @@ export default function Lots() {
</TableCell>
{session?.user && accessRoles && (
<>
{server === "usday1vms006" || server === "localhost" ? (
{location.hostname.includes("usday1") ||
location.hostname.includes("localhost") ? (
<>
<TableCell className="flex justify-center">
<ManualPrintForm />

View File

@@ -37,11 +37,10 @@ const printReason = [
];
export default function ManualPrintForm() {
const token = localStorage.getItem("auth_token");
const { settings } = useSettingStore();
const [open, setOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const server = settings.filter((n) => n.name === "server")[0]?.value;
// const serverPort = settings.filter((n) => n.name === "serverPort")[0]?.value;
// const serverUrl = `http://${server}:${serverPort}`;
@@ -61,9 +60,7 @@ export default function ManualPrintForm() {
const logdataUrl = `/lst/old/api/ocp/manuallabellog`;
setIsSubmitting(true);
axios
.post(logdataUrl, logData, {
headers: { Authorization: `Bearer ${token}` },
})
.post(logdataUrl, logData, {})
.then((d) => {
console.log(d);
if (d.data.success) {
@@ -135,7 +132,7 @@ export default function ManualPrintForm() {
If you clicked this in error just click close
</p>
<hr className="mt-2 mb-2" />
{server == "usday1vms006" ? (
{location.hostname.includes("usday1") ? (
<Controller
control={control}
name="printReason"
@@ -206,7 +203,7 @@ export default function ManualPrintForm() {
/>
</div>
<hr />
{dyco[0].value === "0" && (
{dyco[0]?.value === "0" && (
<div>
<p>Enter the missing tag number.</p>
<hr />

View File

@@ -5,6 +5,7 @@ import {
} from "@/components/ui/resizable";
import { useSettingStore } from "../../-lib/store/useSettings";
import WrapperManualTrigger from "../ocme/WrapperCard";
//import WrapperManualTrigger from "../ocme/WrapperCard";
import LabelLog from "./LabelLog";
import LabelRatio from "./LabelRatio";
import Lots from "./Lots";
@@ -15,8 +16,6 @@ export default function OCPPage() {
const { settings } = useSettingStore();
if (settings.length === 0) return;
let token = settings.filter((n) => n.name === "plantToken");
return (
<div className="ml-5 w-11/12 h-9/10">
<ResizablePanelGroup
@@ -62,19 +61,31 @@ export default function OCPPage() {
<ResizableHandle />
<ResizablePanel className="min-h-[200px] min-w-[200px] max-w-[450px]">
<ResizablePanelGroup direction="vertical" autoSaveId="ocpPage_vert">
{token[0].value === "usday1vms006" ||
(location.pathname === "localhost" && (
<ResizablePanel
style={{
overflow: "auto",
scrollbarWidth: "none",
}}
defaultSize={50}
className="min-h-[200px]"
>
<WrapperManualTrigger />
</ResizablePanel>
))}
{location.hostname.includes("usday1") && (
<ResizablePanel
style={{
overflow: "auto",
scrollbarWidth: "none",
}}
defaultSize={50}
className="min-h-[200px]"
>
<WrapperManualTrigger />
</ResizablePanel>
)}
{location.hostname.includes("localhost") && (
<ResizablePanel
style={{
overflow: "auto",
scrollbarWidth: "none",
}}
defaultSize={50}
className="min-h-[200px]"
>
<WrapperManualTrigger />
</ResizablePanel>
)}
<ResizableHandle />
<ResizablePanel>

View File

@@ -1,286 +1,291 @@
import axios from "axios";
import { addDays } from "date-fns";
import XLSX from "xlsx";
import { db } from "../../../../../../../database/dbclient.js";
import { settings } from "../../../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../../../globalUtils/tryCatch.js";
import XLSX from "xlsx";
import { excelDateStuff } from "../../../../utils/excelDateStuff.js";
import { postForecast } from "../postForecast.js";
import { createLog } from "../../../../../logger/logger.js";
import { sendEmail } from "../../../../../notifications/controller/sendMail.js";
import { query } from "../../../../../sqlServer/prodSqlServer.js";
import { activeArticle } from "../../../../../sqlServer/querys/dataMart/article.js";
import { addDays } from "date-fns";
import { sendEmail } from "../../../../../notifications/controller/sendMail.js";
import { createLog } from "../../../../../logger/logger.js";
import { excelDateStuff } from "../../../../utils/excelDateStuff.js";
import { postForecast } from "../postForecast.js";
let customerID = 4;
export const lorealForecast = async (data: any, user: any) => {
/**
* Post a standard forecast based on the standard template.
*/
/**
* Post a standard forecast based on the standard template.
*/
const { data: s, error: e } = await tryCatch(db.select().from(settings));
const { data: s, error: e } = await tryCatch(db.select().from(settings));
if (e) {
return {
sucess: false,
message: `Error getting settings`,
data: e,
};
}
if (e) {
return {
sucess: false,
message: `Error getting settings`,
data: e,
};
}
const plantToken = s.filter((s) => s.name === "plantToken");
const plantToken = s.filter((s) => s.name === "plantToken");
const arrayBuffer = await data.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const arrayBuffer = await data.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const workbook = XLSX.read(buffer, { type: "buffer" });
const workbook = XLSX.read(buffer, { type: "buffer" });
const sheet: any = workbook.Sheets["Alpla HDPE"];
const range = XLSX.utils.decode_range(sheet["!ref"]);
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 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 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 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 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 petForeCastData: any = XLSX.utils.sheet_to_json(psheet, {
defval: "",
header: pheaders,
range: 3,
});
const ebmForecastData: any = [];
const missingSku: any = [];
const ebmForecastData: any = [];
const missingSku: any = [];
const { data: a, error: ae } = await tryCatch(
query(activeArticle, "Loreal calling active av")
);
const { data: a, error: ae } = await tryCatch(
query(activeArticle, "Loreal calling active av"),
);
if (ae) {
return {
success: false,
message: "Error getting active av",
data: [],
};
}
if (ae) {
return {
success: false,
message: "Error getting active av",
data: [],
};
}
const article: any = a?.data;
const article: any = a?.data;
// process the ebm forcast
for (let i = 0; i < ebmForeCastData.length; i++) {
// bottle code
const sku = ebmForeCastData[i]["HDPE Bottle Code"];
// 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 the blanks
if (sku === "") continue;
// ignore zero qty
// if (ebmForeCastData[i][`Day ${i}`]) 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;
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,
};
const forcast = {
customerArticleNo: sku,
requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)),
quantity: ebmForeCastData[i][day] ?? 0,
};
if (forcast.quantity === 0) continue;
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()
);
// 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;
}
if (activeAV.length === 0) {
if (typeof forcast.customerArticleNo === "number") {
missingSku.push(forcast);
}
continue;
}
ebmForecastData.push(forcast);
}
ebmForecastData.push(forcast);
}
//console.log(ebmForeCastData.length);
}
//console.log(ebmForeCastData.length);
}
// petForeCastData.forEach((item: any, index: any) => {
// //console.log(`Processing item ${index + 1} of ${forecastData.length}`);
// petForeCastData.forEach((item: any, index: any) => {
// //console.log(`Processing item ${index + 1} of ${forecastData.length}`);
// // Extract the customer code
// const customerCode = item["SOUTH PET BOTTLES"];
// // Extract the customer code
// const customerCode = item["SOUTH PET BOTTLES"];
// // Process each date in the current object
// for (const [date, qty] of Object.entries(item)) {
// // Skip metadata fields
// if (petMetadataFields.includes(date)) continue;
// // Process each date in the current object
// for (const [date, qty] of Object.entries(item)) {
// // Skip metadata fields
// if (petMetadataFields.includes(date)) continue;
// if (qty === 0) continue;
// if (qty === 0) continue;
// // Create your transformed record
// const record = {
// customerArticleNo: customerCode,
// requirementDate: excelDateStuff(parseInt(date)),
// quantity: qty,
// };
// // Create your transformed record
// const record = {
// customerArticleNo: customerCode,
// requirementDate: excelDateStuff(parseInt(date)),
// quantity: qty,
// };
// // Do something with this record
// petForecastData.push(record);
// }
// });
// // Do something with this record
// petForecastData.push(record);
// }
// });
// pet forecast
for (let i = 0; i < petForeCastData.length; i++) {
// bottle code
const sku = petForeCastData[i]["South PET Bottle Code"];
// 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 the blanks
if (sku === "") continue;
// ignore zero qty
// if (ebmForeCastData[i][`Day ${i}`]) 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;
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,
};
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.quantity === 0 || forcast.quantity === "") continue;
if (forcast.customerArticleNo < 99999) {
//console.log(`Sku a normal av ${forcast.customerArticleNo}`);
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()
);
// 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;
}
if (activeAV.length === 0) {
if (typeof forcast.customerArticleNo === "number") {
missingSku.push(forcast);
}
continue;
}
ebmForecastData.push(forcast);
}
}
ebmForecastData.push(forcast);
}
}
//console.log(comForecast);
//console.log(comForecast);
// email the for the missing ones
const missedGrouped = Object.values(
missingSku.reduce((acc: any, item: any) => {
const key = item.customerArticleNo;
// 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;
}
}
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;
}, {})
);
return acc;
}, {}),
);
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,
},
};
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,
},
};
const { data: sentEmail, error: sendEmailError } = await tryCatch(
sendEmail(emailSetup)
);
if (sendEmailError) {
createLog(
"error",
"blocking",
"notify",
"Failed to send email, will try again on next interval"
);
return {
success: false,
message: "Failed to send email, will try again on next interval",
};
}
const { data: sentEmail, error: sendEmailError } = await tryCatch(
sendEmail(emailSetup),
);
if (sendEmailError) {
createLog(
"error",
"blocking",
"notify",
"Failed to send email, will try again on next interval",
);
return {
success: false,
message: "Failed to send email, will try again on next interval",
};
}
// if the customerarticle number is not matching just ignore it
const predefinedObject = {
receivingPlantId: plantToken[0].value,
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
"en-US"
)}`,
sender: user.username || "lst-system",
customerId: customerID,
positions: [],
};
// if the customerarticle number is not matching just ignore it
const predefinedObject = {
receivingPlantId: plantToken[0].value,
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
"en-US",
)}`,
sender: user.username || "lst-system",
customerId: customerID,
positions: [],
};
let updatedPredefinedObject = {
...predefinedObject,
positions: [
...predefinedObject.positions,
...ebmForecastData,
let updatedPredefinedObject = {
...predefinedObject,
positions: [
...predefinedObject.positions,
...ebmForecastData,
// ...ebmForecastData.filter(
// (q: any) =>
// q.customerArticleNo != "" && q.customerArticleNo != "Total"
// ),
// ...petForecastData.filter(
// (q: any) =>
// q.customerArticleNo != "" && q.customerArticleNo != "Total"
// ),
],
};
// console.log(updatedPredefinedObject);
const posting: any = await postForecast(updatedPredefinedObject, user);
// ...ebmForecastData.filter(
// (q: any) =>
// q.customerArticleNo != "" && q.customerArticleNo != "Total"
// ),
// ...petForecastData.filter(
// (q: any) =>
// q.customerArticleNo != "" && q.customerArticleNo != "Total"
// ),
],
};
// console.log(updatedPredefinedObject);
return {
success: posting.success,
message: posting.message,
data: posting.data === "" ? ebmForecastData : posting.data,
};
// posting the data to the new backend so we can store the data.
axios.post(
`http://localhost:${process.env.NODE_ENV?.trim() != "production" ? "4200/lst" : "4000"}/api/logistics/dm/forecastData`,
ebmForecastData,
);
const posting: any = await postForecast(updatedPredefinedObject, user);
return {
success: posting.success,
message: posting.message,
data: posting.data === "" ? ebmForecastData : posting.data,
};
};

View File

@@ -30,20 +30,20 @@ app.UseForwardedHeaders(new ForwardedHeadersOptions
});
// Simple file logger
void LogToFile(string message)
{
try
{
string logDir = Path.Combine(AppContext.BaseDirectory, "logs");
Directory.CreateDirectory(logDir);
string logFilePath = Path.Combine(logDir, "proxy_log.txt");
File.AppendAllText(logFilePath, $"{DateTime.UtcNow:u}: {message}{Environment.NewLine}");
}
catch (Exception ex)
{
Console.WriteLine($"Logging error: {ex.Message}");
}
}
// void LogToFile(string message)
// {
// try
// {
// string logDir = Path.Combine(AppContext.BaseDirectory, "logs");
// Directory.CreateDirectory(logDir);
// string logFilePath = Path.Combine(logDir, "proxy_log.txt");
// File.AppendAllText(logFilePath, $"{DateTime.UtcNow:u}: {message}{Environment.NewLine}");
// }
// catch (Exception ex)
// {
// Console.WriteLine($"Logging error: {ex.Message}");
// }
// }
app.Use(async (context, next) =>
{

View File

@@ -0,0 +1,2 @@
ALTER TABLE "forecast_Data" RENAME COLUMN "customer_article_number" TO "customer_article_no";--> statement-breakpoint
ALTER TABLE "forecast_Data" DROP COLUMN "customer_id";

View File

@@ -0,0 +1 @@
ALTER TABLE "forecast_Data" RENAME COLUMN "request_date" TO "requirement_date";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -183,6 +183,20 @@
"when": 1761830426770,
"tag": "0025_foamy_mattie_franklin",
"breakpoints": true
},
{
"idx": 26,
"version": "7",
"when": 1761938410811,
"tag": "0026_heavy_robin_chapel",
"breakpoints": true
},
{
"idx": 27,
"version": "7",
"when": 1761938877395,
"tag": "0027_special_harpoon",
"breakpoints": true
}
]
}

285
package-lock.json generated
View File

@@ -33,6 +33,8 @@
"pino-pretty": "^13.1.2",
"postgres": "^3.4.7",
"socket.io": "^4.8.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"zod": "^4.1.12"
},
"devDependencies": {
@@ -44,6 +46,8 @@
"@types/nodemailer": "^7.0.2",
"@types/nodemailer-express-handlebars": "^4.0.5",
"@types/pg": "^8.15.5",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8",
"concurrently": "^9.2.1",
"cz-conventional-changelog": "^3.3.0",
"standard-version": "^9.5.0",
@@ -52,6 +56,50 @@
"typescript": "^5.9.3"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz",
"integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==",
"license": "MIT",
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.6",
"call-me-maybe": "^1.0.1",
"js-yaml": "^4.1.0"
}
},
"node_modules/@apidevtools/openapi-schemas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@apidevtools/swagger-methods": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==",
"license": "MIT"
},
"node_modules/@apidevtools/swagger-parser": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==",
"license": "MIT",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
"@apidevtools/openapi-schemas": "^2.0.4",
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"call-me-maybe": "^1.0.1",
"z-schema": "^5.0.1"
},
"peerDependencies": {
"openapi-types": ">=7"
}
},
"node_modules/@aws-crypto/sha256-browser": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
@@ -2446,6 +2494,12 @@
"integrity": "sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ==",
"license": "BSD-3-Clause"
},
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
"license": "MIT"
},
"node_modules/@levischuck/tiny-cbor": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
@@ -2556,6 +2610,13 @@
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
"license": "MIT"
},
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/@simplewebauthn/browser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.2.tgz",
@@ -3344,6 +3405,12 @@
"@types/node": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "1.3.5",
"dev": true,
@@ -3476,6 +3543,24 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/swagger-jsdoc": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz",
"integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/swagger-ui-express": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
"integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/express": "*",
"@types/serve-static": "*"
}
},
"node_modules/@typespec/ts-http-runtime": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.0.tgz",
@@ -3628,9 +3713,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0",
"optional": true
"license": "Python-2.0"
},
"node_modules/array-ify": {
"version": "1.0.0",
@@ -3701,7 +3784,6 @@
},
"node_modules/balanced-match": {
"version": "1.0.2",
"dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
@@ -3905,7 +3987,6 @@
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -4013,6 +4094,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-me-maybe": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
"license": "MIT"
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4282,7 +4369,6 @@
},
"node_modules/concat-map": {
"version": "0.0.1",
"dev": true,
"license": "MIT"
},
"node_modules/concat-stream": {
@@ -5124,6 +5210,18 @@
"node": ">=0.3.1"
}
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"license": "Apache-2.0",
"dependencies": {
"esutils": "^2.0.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/dot-prop": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@@ -5685,6 +5783,15 @@
"node": ">=0.8.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/etag": {
"version": "1.8.1",
"license": "MIT",
@@ -5764,6 +5871,7 @@
"node_modules/express": {
"version": "5.1.0",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
@@ -6159,7 +6267,6 @@
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"dev": true,
"license": "ISC"
},
"node_modules/fsevents": {
@@ -6807,7 +6914,6 @@
},
"node_modules/inflight": {
"version": "1.0.6",
"dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
@@ -7228,9 +7334,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -7440,6 +7544,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -7452,6 +7563,13 @@
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
@@ -7502,9 +7620,7 @@
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
"dev": true,
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
@@ -7933,7 +8049,6 @@
},
"node_modules/minimatch": {
"version": "3.1.2",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -8285,6 +8400,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
"license": "MIT",
"peer": true
},
"node_modules/ora": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
@@ -8505,7 +8627,6 @@
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -10057,6 +10178,92 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swagger-jsdoc": {
"version": "6.2.8",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz",
"integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==",
"license": "MIT",
"dependencies": {
"commander": "6.2.0",
"doctrine": "3.0.0",
"glob": "7.1.6",
"lodash.mergewith": "^4.6.2",
"swagger-parser": "^10.0.3",
"yaml": "2.0.0-1"
},
"bin": {
"swagger-jsdoc": "bin/swagger-jsdoc.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/swagger-jsdoc/node_modules/commander": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
"integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/swagger-jsdoc/node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/swagger-parser": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==",
"license": "MIT",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.30.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.1.tgz",
"integrity": "sha512-4mNAUM31sr52K3JcK9qiGbfsFKNh/dm3PkEe+F9FAM31YY/NoRYUgsR/L6d7LLFn6PgZXtBG2ygp8+7UnpUIPg==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/swagger-ui-express": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
"license": "MIT",
"dependencies": {
"swagger-ui-dist": ">=5.0.0"
},
"engines": {
"node": ">= v0.10.32"
},
"peerDependencies": {
"express": ">=4.0.0 || >=5.0.0-beta"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
@@ -10495,6 +10702,15 @@
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/validator": {
"version": "13.15.20",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz",
"integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vary": {
"version": "1.1.2",
"license": "MIT",
@@ -10712,6 +10928,15 @@
"dev": true,
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.0.0-1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
"integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
"license": "ISC",
"engines": {
"node": ">= 6"
}
},
"node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@@ -10762,6 +10987,36 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/z-schema": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
"integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
"license": "MIT",
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"validator": "^13.7.0"
},
"bin": {
"z-schema": "bin/z-schema"
},
"engines": {
"node": ">=8.0.0"
},
"optionalDependencies": {
"commander": "^9.4.1"
}
},
"node_modules/z-schema/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/zod": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",

View File

@@ -67,6 +67,8 @@
"pino-pretty": "^13.1.2",
"postgres": "^3.4.7",
"socket.io": "^4.8.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"zod": "^4.1.12"
},
"devDependencies": {
@@ -78,6 +80,8 @@
"@types/nodemailer": "^7.0.2",
"@types/nodemailer-express-handlebars": "^4.0.5",
"@types/pg": "^8.15.5",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8",
"concurrently": "^9.2.1",
"cz-conventional-changelog": "^3.3.0",
"standard-version": "^9.5.0",