30 Commits

Author SHA1 Message Date
d85f08cb19 chore(release): 0.1.0-alpha.3
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m17s
Release and Build Image / release (push) Successful in 9s
2026-06-10 16:31:04 -05:00
15c939ebe8 refactor(server builds): added in proper logging will still need fine tuned though
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m52s
2026-06-10 16:29:23 -05:00
9ff428f5ea refactor(logs): socket io setup to be properly logging 2026-06-10 16:28:52 -05:00
2e460c7f8a refactor(warehouse): fixed ppoo check to match the new changes 2026-06-10 16:27:52 -05:00
7c4c5f980a refactor(opendock): changed the subModule for better logging 2026-06-10 16:27:23 -05:00
86e1237509 refactor(dock door scanning): fixes and final writes for the intial trial went smooth 2026-06-10 16:26:58 -05:00
706ab8b448 refactor(db): added in notifications vs pulling from the db makes it easier on the system 2026-06-10 16:26:21 -05:00
9440b44f3b refactor(socket.io): complete rewrite to manage dynamic rooms and seeding better 2026-06-10 16:25:48 -05:00
a2d9a6c127 refactor(opendock): changed the article to look at the label to match all plants
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m26s
2026-06-09 21:23:49 -05:00
c0a7d4a125 refactor(opendock): added some new goodies to the app to help manage releases
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m24s
2026-06-09 21:09:03 -05:00
8fcb2c66ed refactor(socketio): changes that if we are in dock room to constant reseed the room
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m49s
this will be fixed later when we redo the socket io rooms with dynamic stuff
2026-06-09 12:07:53 -05:00
8fc3129f7d refactor(opend dock): added in default dock so it uses the default setup as a backup
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m40s
This will help the live/drop and dont know what dock
2026-06-09 12:00:50 -05:00
05c553e927 fix(eom): removed un needed imports 2026-06-09 11:59:48 -05:00
6eaae0f537 refactor(dock scanner): more work on dock scanner and semi finished 2026-06-09 11:59:27 -05:00
3ad84dab71 refactor(opendock): added in proper complete and ignore of picksheets 2026-06-09 11:58:25 -05:00
5a7046bacd refactor(opendock): if load type is drop we will not do anything unless its already scanned
this means that if we drag down a bunch of loads we wont instantly change to arrived we will need to
have a single scan before we do this
2026-06-09 11:46:22 -05:00
cfc497c1f2 refactor(opendock): added in check for really using the article link 2026-06-09 11:45:09 -05:00
7bbdd4e555 refactor(mobile): new error found and added in
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 1m24s
2026-06-09 07:01:02 -05:00
e909e8deec feat(eom): migrated eom endpoints from old version validated working 2026-06-09 07:00:27 -05:00
4249e90307 fix(dockscanner): removed console log 2026-06-08 15:29:09 -05:00
2f495739e6 fix(dock door scanning): correction to how the data is posted 2026-06-08 15:26:43 -05:00
7d722c4aac refactor(datamart): if added to query will include plant token now 2026-06-08 15:24:45 -05:00
5f2148f5f0 refactor(servers): all remaining servers added 2026-06-08 15:23:25 -05:00
7671172d97 refactor(dockscanning): more work on the dock door scanning
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m47s
ref #12
2026-06-08 07:49:41 -05:00
6dd1a5b332 fix(scanner): corrected teh endpoint to delete the user if needed 2026-06-04 07:17:18 -05:00
865f280cfa fix(opendock): changed the header of delete to be properly named
ref #23
2026-06-04 07:16:53 -05:00
a717260b8d fix(opendock): correction to article link success on delete
ref #23
2026-06-04 07:02:27 -05:00
4464cea022 feat(opendock): added delete button in the article tab
ref #23
2026-06-04 07:01:10 -05:00
55418e2f09 refactor(new role): added in warehouse role 2026-06-04 06:49:49 -05:00
5281118596 fix(notifications): missed api converstion in the front end for updating 2026-06-03 08:40:15 -05:00
89 changed files with 14323 additions and 461 deletions

View File

@@ -1,5 +1,84 @@
# All Changes to LST can be found below. # All Changes to LST can be found below.
## [0.1.0-alpha.3](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) (2026-06-10)
### 🌟 Enhancements
* **eom:** migrated eom endpoints from old version validated working ([e909e8d](https://git.tuffraid.net/cowch/lst_v3/commits/e909e8deecb54a3e4c39789609b0aa7435b9e08a))
* **mobile:** added auto download of latest ([6d0fb8a](https://git.tuffraid.net/cowch/lst_v3/commits/6d0fb8aee45c8b5c56ccd7d8a010e1dc803408bf))
* **mobile:** dock door scanning backend added ([fe0b157](https://git.tuffraid.net/cowch/lst_v3/commits/fe0b1573f3ba6fd220f181088b994588c52af139)), closes [#12](https://git.tuffraid.net/cowch/lst_v3/issues/12)
* **opendock:** added delete button in the article tab ([4464cea](https://git.tuffraid.net/cowch/lst_v3/commits/4464cea022ba48744b884b83fef0fc3f3421dea5)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
* **opendock:** added in a new article link system ([bb7931d](https://git.tuffraid.net/cowch/lst_v3/commits/bb7931d6c8d0f5ce4065955491e9ee1247b5e92d))
* **warehousing:** ppoo monitoring added ([8b07694](https://git.tuffraid.net/cowch/lst_v3/commits/8b076949a7f8e723bc87619f729082d2c1991b2d)), closes [#13](https://git.tuffraid.net/cowch/lst_v3/issues/13)
### 🐛 Bug fixes
* **app:** type in the templates... they all looked the same ([2a648f6](https://git.tuffraid.net/cowch/lst_v3/commits/2a648f63064a300b0a2888bae3322b857af6a238))
* **dock door scanning:** correction to how the data is posted ([2f49573](https://git.tuffraid.net/cowch/lst_v3/commits/2f495739e653c462eff7f10ff6343d9572f25563))
* **dockscanner:** removed console log ([4249e90](https://git.tuffraid.net/cowch/lst_v3/commits/4249e90307ba1a1992753803e1dc3ab7dd7ac95e))
* **eom:** removed un needed imports ([05c553e](https://git.tuffraid.net/cowch/lst_v3/commits/05c553e9279c6e8384d61073781bf915733b0ab5))
* **logistics:** historical checks for no data errors when feature is activeed ([4f848bb](https://git.tuffraid.net/cowch/lst_v3/commits/4f848bb649f350c9d370daa09c6fc48f7b76e2e2))
* **mobile users:** corrected and endpoint that prevented us from change the pin number ([347edb7](https://git.tuffraid.net/cowch/lst_v3/commits/347edb7078fb4ce959975cd968a6f026bacc98bf))
* **mobile:** scan log incorrect user ref ([9c0ef1f](https://git.tuffraid.net/cowch/lst_v3/commits/9c0ef1f5dfa13e8f7e1f72d47d0d5842b3da3c87))
* **mobile:** temp removed the autodownload as its causing issues ([3ef0f23](https://git.tuffraid.net/cowch/lst_v3/commits/3ef0f230ddbaef4c3d738cc531e7afee25b210dd))
* **mobile:** ui over lapping ([db28635](https://git.tuffraid.net/cowch/lst_v3/commits/db28635c8c260d0f378e109755d32201acdb2328)), closes [#25](https://git.tuffraid.net/cowch/lst_v3/issues/25)
* **notifications:** missed api converstion in the front end for updating ([5281118](https://git.tuffraid.net/cowch/lst_v3/commits/52811185965cb0fe4c9b42e73c447b67e917a5ca))
* **opendock:** changed the header of delete to be properly named ([865f280](https://git.tuffraid.net/cowch/lst_v3/commits/865f280cfaf82a7126ca4b57d658aed50cf19a73)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
* **opendock:** correction to article link success on delete ([a717260](https://git.tuffraid.net/cowch/lst_v3/commits/a717260b8d5cf72534b055721de2b79901506875)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
* **opendock:** ref wrong field oops ([f635415](https://git.tuffraid.net/cowch/lst_v3/commits/f635415b751e11d0e7beb557d18b83915155428a))
* **scanner:** corrected teh endpoint to delete the user if needed ([6dd1a5b](https://git.tuffraid.net/cowch/lst_v3/commits/6dd1a5b332a853f0e2c7226ac1f64e403ef752d7))
### 📚 Documentation
* **app:** updated last updated to the readme to show current progress ([78f7b8a](https://git.tuffraid.net/cowch/lst_v3/commits/78f7b8a179d078fc1b2740fd2bf4af71a3df8292))
* **app:** updated readme ([1a3d8a7](https://git.tuffraid.net/cowch/lst_v3/commits/1a3d8a7ddcb7a2e51da4b77968ffdefb7644a0b1))
### 🛠️ Code Refactor
* **backend:** dock door scanning socket and perms ([f8335f5](https://git.tuffraid.net/cowch/lst_v3/commits/f8335f5217c339a7dc09883fa8ba9c60aa4c35b1))
* **datamart:** if added to query will include plant token now ([7d722c4](https://git.tuffraid.net/cowch/lst_v3/commits/7d722c4aac16bc1d2cfbfbc70539783f34003f77))
* **db:** added in notifications vs pulling from the db makes it easier on the system ([706ab8b](https://git.tuffraid.net/cowch/lst_v3/commits/706ab8b448aafb81da94a76fbf8d3d400cba616b))
* **db:** added timezone check in so it comes over correct based on the backend timezone ([2558b2e](https://git.tuffraid.net/cowch/lst_v3/commits/2558b2e5bb68be7a3f46de09bb509c99423adeb6))
* **dock door scanning:** fixes and final writes for the intial trial went smooth ([86e1237](https://git.tuffraid.net/cowch/lst_v3/commits/86e1237509b81722dee7b42762d0bfced8d26fa3))
* **dock scanner:** more work on dock scanner and semi finished ([6eaae0f](https://git.tuffraid.net/cowch/lst_v3/commits/6eaae0f5378e39c4002dadd8325833698dd960e7))
* **dockscanning:** more work on the dock door scanning ([7671172](https://git.tuffraid.net/cowch/lst_v3/commits/7671172d975355b5d245a482b481726b49153578)), closes [#12](https://git.tuffraid.net/cowch/lst_v3/issues/12)
* **logger:** included error in the stack version so we dont have to remove it all ([da87e2e](https://git.tuffraid.net/cowch/lst_v3/commits/da87e2e1d322a45ca9a7b500d77499fe4c7b999e))
* **logs:** refactored to show the submodule and stack as well to make it more easy to watch ([45a0dee](https://git.tuffraid.net/cowch/lst_v3/commits/45a0dee9caae14df639439b905fa81b340791197))
* **logs:** socket io setup to be properly logging ([9ff428f](https://git.tuffraid.net/cowch/lst_v3/commits/9ff428f5ea1051e62521632947092f74eb93944d))
* **mobile:** intial addin of dockdoor scanning on mobile ([2a35381](https://git.tuffraid.net/cowch/lst_v3/commits/2a35381fe400fc46e6f10c4e72c3ab9ac435e0e5))
* **mobile:** moved logout to the tab bar ([bcdf956](https://git.tuffraid.net/cowch/lst_v3/commits/bcdf9566bcf721a085f46ce3befe783eb7b91949))
* **mobile:** new error found and added in ([7bbdd4e](https://git.tuffraid.net/cowch/lst_v3/commits/7bbdd4e5552889434aff81ed10ea7c64174f7d34))
* **mobile:** setup - added button to go home as it caused confustion ([c15ee07](https://git.tuffraid.net/cowch/lst_v3/commits/c15ee070e7057ad8a5e3d42f51c230d680db9e21))
* **new role:** added in warehouse role ([55418e2](https://git.tuffraid.net/cowch/lst_v3/commits/55418e2f098193b0891129a19532608dce2abd9c))
* **opend dock:** added in default dock so it uses the default setup as a backup ([8fc3129](https://git.tuffraid.net/cowch/lst_v3/commits/8fc3129f7d687d45dc242aca9a6e71f43d0352ab))
* **opendock:** added in check for really using the article link ([cfc497c](https://git.tuffraid.net/cowch/lst_v3/commits/cfc497c1f2254dc12a6e7bb15cfc0dce2a64c1e6))
* **opendock:** added in proper complete and ignore of picksheets ([3ad84da](https://git.tuffraid.net/cowch/lst_v3/commits/3ad84dab71ceea081efc824e97d075c6f33807ad))
* **opendock:** added some new goodies to the app to help manage releases ([c0a7d4a](https://git.tuffraid.net/cowch/lst_v3/commits/c0a7d4a1252e3f14240d14288a3ac4add44a6463))
* **opendock:** changed the article to look at the label to match all plants ([a2d9a6c](https://git.tuffraid.net/cowch/lst_v3/commits/a2d9a6c127cae3b6dc5beb1a996558ab1383fa8c))
* **opendock:** changed the subModule for better logging ([7c4c5f9](https://git.tuffraid.net/cowch/lst_v3/commits/7c4c5f980a15e13e2a43a8583a652622370ec1dd))
* **opendock:** if load type is drop we will not do anything unless its already scanned ([5a7046b](https://git.tuffraid.net/cowch/lst_v3/commits/5a7046bacd9323627a37c89d7a9a329383e8b1be))
* **server builds:** added in proper logging will still need fine tuned though ([15c939e](https://git.tuffraid.net/cowch/lst_v3/commits/15c939ebe8b808b3a9a46a4c1e62fb0488d3649b))
* **servers:** all remaining servers added ([5f2148f](https://git.tuffraid.net/cowch/lst_v3/commits/5f2148f5f0f7688cf71e1dad99d4a5f6c34241dd))
* **socket.io:** complete rewrite to manage dynamic rooms and seeding better ([9440b44](https://git.tuffraid.net/cowch/lst_v3/commits/9440b44f3bb1842d0e6ffc479d340ae9c5b84656))
* **socketio:** changes that if we are in dock room to constant reseed the room ([8fcb2c6](https://git.tuffraid.net/cowch/lst_v3/commits/8fcb2c66ed25c746dc1d63c5290e11fe6a56328e))
* **times:** added a better view for times as we save all db as there respective timezone ([40edbc3](https://git.tuffraid.net/cowch/lst_v3/commits/40edbc3eae9000afea447f81ec127884712b38da))
* **warehouse:** fixed ppoo check to match the new changes ([2e460c7](https://git.tuffraid.net/cowch/lst_v3/commits/2e460c7f8aeb7de1d641c42357001704a4d8d8b2))
### 📝 Testing Code
* **test:** added in vitest to start doing more testing before deploying ([e6b92ae](https://git.tuffraid.net/cowch/lst_v3/commits/e6b92aeb109394691dd2a7d548ddadd02769a159))
### 📈 Project changes
* **app:** added more labels to the templates ([69a9a81](https://git.tuffraid.net/cowch/lst_v3/commits/69a9a81a889aca010ec8835ffab7084f7cd4a9b0))
* **app:** changed the issues templates to be easier to add to the part of the app needed ([188fdd0](https://git.tuffraid.net/cowch/lst_v3/commits/188fdd0eb7c6bedf52ae1dcc9109a0fdbe969a21))
## [0.1.0-alpha.2](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) (2026-05-23) ## [0.1.0-alpha.2](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) (2026-05-23)

View File

@@ -29,7 +29,7 @@ Quick summary of current rewrite/migration goal.
| Transport Insight | Integrations | ⏳ Not Started | | Transport Insight | Integrations | ⏳ Not Started |
| Quality Request Tool | Add Pallet, Monitor for moved, status changes, alerts | ⏳ Not Started | | Quality Request Tool | Add Pallet, Monitor for moved, status changes, alerts | ⏳ Not Started |
| Logistics | Consume material, return and print, label info, relocate | ⏳ Not Started | | Logistics | Consume material, return and print, label info, relocate | ⏳ Not Started |
| EOM | Endpoints, Report Pull for finance | ⏳ Not Started | | EOM | ~~Endpoints~~, Report Pull for finance, SSRS report | 🟨 In Progress |
| ~~OCME~~ | ~~Custom integration~~ | Canceled | | ~~OCME~~ | ~~Custom integration~~ | Canceled |
| API Migration | Moving to new REST endpoints | 🔧 In Progress | | API Migration | Moving to new REST endpoints | 🔧 In Progress |
| System | Tests,Builds, Updates, Remote Logging, DB Backups, Alerting | ⏳ Not Started | | System | Tests,Builds, Updates, Remote Logging, DB Backups, Alerting | ⏳ Not Started |

View File

@@ -326,8 +326,13 @@ export const runDatamartQuery = async (data: Data) => {
level: "info", level: "info",
module: "datamart", module: "datamart",
subModule: "query", subModule: "query",
message: `Data for: ${data.name}`, message: `Data for: ${data.name} ${data.options.includePlantToken ? "including plant token" : ""}`,
data: queryRun.data, // if includePlantToken was passed we should map this into the data
data: data.options.includePlantToken
? queryRun.data.map((i) => {
return { plantToken: process.env.PROD_PLANT_TOKEN, ...i };
})
: queryRun.data,
notify: false, notify: false,
}); });
}; };

View File

@@ -1,5 +1,7 @@
import { drizzle } from "drizzle-orm/postgres-js"; import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres"; import postgres from "postgres";
import * as dockScans from "./schema/dockdoor.scans.schema.js";
import * as logs from "./schema/logs.schema.js";
import * as opendockAVCheck from "./schema/opendock_articleSetup.js"; import * as opendockAVCheck from "./schema/opendock_articleSetup.js";
import * as scanUserSchema from "./schema/scanUsers.js"; import * as scanUserSchema from "./schema/scanUsers.js";
import * as settingsSchema from "./schema/settings.schema.js"; import * as settingsSchema from "./schema/settings.schema.js";
@@ -23,5 +25,7 @@ export const db = drizzle(queryClient, {
...scanUserSchema, ...scanUserSchema,
...settingsSchema, ...settingsSchema,
...opendockAVCheck, ...opendockAVCheck,
...logs,
...dockScans,
}, },
}); });

59
backend/db/db.listener.ts Normal file
View File

@@ -0,0 +1,59 @@
import postgres from "postgres";
import { createLogger } from "../logger/logger.controller.js";
import { handleDbNotification } from "./db.router.js";
const log = createLogger({
module: "db",
subModule: "notifications",
});
const CHANNELS = [
"logs_inserted",
// "labels_inserted",
// "dock_scans_inserted",
] as const;
type DbNotificationChannel = (typeof CHANNELS)[number];
type DbNotificationPayload = {
table: string;
action: "INSERT" | "UPDATE" | "DELETE";
id: string;
};
export async function startDbNotificationListener() {
const sql = postgres({
host: `${process.env.DATABASE_HOST}`,
port: Number(process.env.DATABASE_PORT),
database: `${process.env.DATABASE_DB}`,
username: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
});
for (const channel of CHANNELS) {
await sql.listen(channel, async (rawPayload) => {
await processNotification(channel, rawPayload);
});
log.info({ stack: { channel } }, `Listening for ${channel}`);
}
}
async function processNotification(
channel: DbNotificationChannel,
rawPayload: string,
) {
try {
const payload = JSON.parse(rawPayload) as DbNotificationPayload;
await handleDbNotification({
channel,
payload,
});
} catch (e) {
log.error(
{ stack: { channel, rawPayload, e }, notify: true },
"Failed processing DB notification",
);
}
}

41
backend/db/db.router.ts Normal file
View File

@@ -0,0 +1,41 @@
import { handleDockScanInsertedNotification } from "../dockdoorScanning/dockdoor.socket.notifications.js";
import { createLogger } from "../logger/logger.controller.js";
import { handleLogInsertedNotification } from "../logger/logger.socket.notifications.js";
const log = createLogger({
module: "db",
subModule: "notifications-router",
});
type DbNotification = {
channel: string;
payload: {
table: string;
action: "INSERT" | "UPDATE" | "DELETE";
id: string;
};
};
export async function handleDbNotification(notification: DbNotification) {
const { channel, payload } = notification;
switch (channel) {
case "logs_inserted":
await handleLogInsertedNotification(payload.id);
return;
// case "labels_inserted":
// await handleLabelInsertedNotification(payload.id);
// return;
case "dock_scan_inserted":
await handleDockScanInsertedNotification(payload.id);
return;
default:
log.warn(
{ stack: notification },
`Unhandled DB notification channel: ${channel}`,
);
}
}

View File

@@ -0,0 +1,99 @@
import { sql } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { createLogger } from "../logger/logger.controller.js";
const log = createLogger({
module: "db",
subModule: "notifications",
});
/**
* Creates/updates Postgres notification functions + triggers.
*
* Safe to run on every app startup.
* CREATE OR REPLACE updates the function.
* DROP TRIGGER IF EXISTS prevents duplicate triggers.
*/
export async function setupDbNotifications() {
log.info({}, "Setting up DB notifications");
await setupLogsNotifications();
await setupDockScansNotifications();
log.info({}, "DB notifications setup complete");
}
/**
* Logs notification setup.
*
* Flow:
* 1. app inserts into logs table
* 2. trigger runs after insert
* 3. Postgres sends NOTIFY logs_inserted with the new log id
* 4. Node listener receives id and fetches/emits full row
*/
async function setupLogsNotifications() {
await db.execute(sql`
CREATE OR REPLACE FUNCTION notify_logs_inserted()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify(
'logs_inserted',
json_build_object(
'table', TG_TABLE_NAME,
'action', TG_OP,
'id', NEW.id
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`);
await db.execute(sql`
DROP TRIGGER IF EXISTS logs_inserted_notify_trigger ON logs;
`);
await db.execute(sql`
CREATE TRIGGER logs_inserted_notify_trigger
AFTER INSERT ON logs
FOR EACH ROW
EXECUTE FUNCTION notify_logs_inserted();
`);
log.info({}, "Logs DB notification trigger ready");
}
async function setupDockScansNotifications() {
await db.execute(sql`
CREATE OR REPLACE FUNCTION notify_dock_scan_inserted()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify(
'dock_scan_inserted',
json_build_object(
'table', TG_TABLE_NAME,
'action', TG_OP,
'id', NEW.id
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`);
await db.execute(sql`
DROP TRIGGER IF EXISTS dock_scan_inserted_notify_trigger ON dock_door_scans;
`);
await db.execute(sql`
CREATE TRIGGER dock_scan_inserted_notify_trigger
AFTER INSERT ON dock_door_scans
FOR EACH ROW
EXECUTE FUNCTION notify_dock_scan_inserted();
`);
log.info({}, "Dock scan DB notification trigger ready");
}

View File

@@ -0,0 +1,21 @@
import { db } from "./db.controller.js";
export const getRecentLogs = ({
module,
submodule,
limit = 200,
}: {
module?: string | undefined;
submodule?: string | undefined;
limit?: number | undefined;
}) => {
return db.query.logs.findMany({
where: (logs, { and, eq }) =>
and(
module ? eq(logs.module, module) : undefined,
submodule ? eq(logs.subModule, submodule) : undefined,
),
orderBy: (logs, { desc }) => [desc(logs.createdAt)],
limit,
});
};

View File

@@ -0,0 +1,23 @@
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const dockDoorScans = pgTable("dock_door_scans", {
id: uuid("id").defaultRandom().primaryKey(),
dockId: text("dock_id").notNull(),
loadingOrder: text("loading_order").notNull(),
loadingUnit: text("loading_Unit"), // can be running number or sscc depending on where it came from
loadingUnitStatus: text("loading_unit_status").default("loaded"), // TODO: add enums on the status of each load.
message: text("message"), // the response it gave when scanning
status: text("status").default("active"), // TODO: add in enums for this
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
add_user: text("add_user").default("lst-system"),
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
upd_user: text("upd_user").default("lst-system"),
});
export const dockDoorScansSchema = createSelectSchema(dockDoorScans);
export const newDockDoorScansSchema = createInsertSchema(dockDoorScans);
export type DockDoorScans = z.infer<typeof dockDoorScansSchema>;
export type NewDockDoorScans = z.infer<typeof newDockDoorScansSchema>;

View File

@@ -16,6 +16,7 @@ export const opendockApt = pgTable(
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
release: integer("release").notNull().unique("opendock_apt_release_unique"), release: integer("release").notNull().unique("opendock_apt_release_unique"),
openDockAptId: text("open_dock_apt_id").notNull(), openDockAptId: text("open_dock_apt_id").notNull(),
status: text("status").default("active"),
appointment: jsonb("appointment").notNull().default([]), appointment: jsonb("appointment").notNull().default([]),
upd_date: timestamp("upd_date", { withTimezone: true }) upd_date: timestamp("upd_date", { withTimezone: true })
.notNull() .notNull()

View File

@@ -16,6 +16,7 @@ r.get("/", async (_, res) => {
loadingDateTo: format(addDays(new Date(Date.now()), 3), "yyyy-MM-dd"), loadingDateTo: format(addDays(new Date(Date.now()), 3), "yyyy-MM-dd"),
states: [ states: [
1, // planned 1, // planned
2, // loading
], ],
//isCommissioned: true, //isCommissioned: true,
}, },

View File

@@ -2,7 +2,9 @@ import { eq, sql } from "drizzle-orm";
import { Router } from "express"; import { Router } from "express";
import z from "zod"; import z from "zod";
import { db } from "../db/db.controller.js"; import { db } from "../db/db.controller.js";
import { dockDoorScans } from "../db/schema/dockdoor.scans.schema.js";
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js"; import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
import { runProdApi } from "../utils/prodEndpoint.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js"; import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js"; import { tryCatch } from "../utils/trycatch.utils.js";
@@ -14,12 +16,23 @@ const endLoading = z.object({
}); });
r.post("/", async (req, res) => { r.post("/", async (req, res) => {
// close the loading order if (req.body.clear) {
// just clear the loading order and clear out all the pallets to keep it clean.
// clear the loading order off the dock await tryCatch(
db
try { .update(dockDoorScans)
const validated = endLoading.parse(req.body); .set({
status: "completed",
upd_date: sql`NOW()`,
upd_user: req.user?.username ?? "lst-dock-system",
})
.where(
req.body.loadingOrder
? eq(dockDoorScanners.currentLoadingOrder, req.body.loadingOrder)
: undefined,
)
.returning(),
);
const { data, error } = await tryCatch( const { data, error } = await tryCatch(
db db
@@ -27,9 +40,9 @@ r.post("/", async (req, res) => {
.set({ .set({
currentLoadingOrder: "", currentLoadingOrder: "",
upd_date: sql`NOW()`, upd_date: sql`NOW()`,
upd_user: req.user?.username, upd_user: req.user?.username ?? "lst-dock-system",
}) })
.where(eq(dockDoorScanners.dockId, validated.dockId)) .where(eq(dockDoorScanners.dockId, req.body.dockId))
.returning(), .returning(),
); );
@@ -50,17 +63,115 @@ r.post("/", async (req, res) => {
level: "info", level: "info",
module: "dockdoor", module: "dockdoor",
subModule: "loadingOrder", subModule: "loadingOrder",
message: `Loading order ${validated.loadingOrder} was just closed.`, message: `Loading order: ${req.body.loadingOrder} was just cleared out do to the process being completed in some other means. \nThis includes any scanned pallets as well.`,
data: data ?? [], data: data ?? [],
status: 200, status: 200,
}); });
}
try {
const validated = endLoading.parse(req.body);
const orders = (await runProdApi({
method: "post",
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${req.body.loadingOrder}/Finish`,
data: [
{
printDeliveryDocuments: true,
},
],
})) as any;
if (orders?.data.errors) {
//console.log(orders.data.errors);
// if (orders.data.errors[0].code === 20) {
// await db
// .update(dockDoorScanners)
// .set({
// currentLoadingOrder: "",
// upd_date: sql`NOW()`,
// upd_user: req.user?.username ?? "lst-dock-system",
// })
// .where(eq(dockDoorScanners.dockId, validated.dockId));
// return apiReturn(res, {
// success: false,
// level: "warn",
// module: "dockdoor",
// subModule: "loadingOrder",
// message: `Loading order: ${validated.loadingOrder} cleared, It was probable completed by a scanner.. or user...`,
// data: (orders.data.errors as any) ?? [],
// status: 200,
// });
// }
return apiReturn(res, {
success: false,
level: "error",
module: "dockdoor",
subModule: "loadingOrder",
message: `Failed to finish the order: ${orders.data.errors[0].message}`,
data: (orders.data.errors as any) ?? [],
status: 400,
});
}
await tryCatch(
db
.update(dockDoorScans)
.set({
status: "completed",
upd_date: sql`NOW()`,
upd_user: req.user?.username ?? "lst-dock-system",
})
.where(
eq(dockDoorScans.loadingOrder, validated.loadingOrder.toString()),
)
.returning(),
);
const { data, error } = await tryCatch(
db
.update(dockDoorScanners)
.set({
currentLoadingOrder: "",
upd_date: sql`NOW()`,
upd_user: req.user?.username ?? "lst-dock-system",
})
.where(eq(dockDoorScanners.dockId, validated.dockId))
.returning(),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "dockdoor",
subModule: "loadingOrder",
message: `Failed to updating the dock.`,
data: (error as any) ?? [],
status: 400,
});
}
return apiReturn(res, {
success: orders.data.errors ? false : true,
level: orders.data.errors ? "error" : "info",
module: "dockdoor",
subModule: "loadingOrder",
message: orders.data.errors
? `Loading order was cleared but encountered an error: \n${orders.data.errors[0].message} \nPossible reason for this is the loading order was completed via scanner or other means.`
: `Loading order ${validated.loadingOrder} was just closed.`,
data: data ?? [],
status: orders.data.errors ? 400 : 200,
});
} catch (error) { } catch (error) {
return apiReturn(res, { return apiReturn(res, {
success: false, success: false,
level: "error", level: "error",
module: "dockdoor", module: "dockdoor",
subModule: "loadingOrder", subModule: "loadingOrder",
message: `Failed to start loading order.`, message: `Failed to Close loading order.`,
data: (error as any) ?? [], data: (error as any) ?? [],
status: 400, status: 400,
}); });

View File

@@ -2,7 +2,9 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js"; import { db } from "../db/db.controller.js";
import { dockDoorScans } from "../db/schema/dockdoor.scans.schema.js";
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js"; import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
import { createLogger } from "../logger/logger.controller.js";
import { emitToRoom } from "../socket.io/roomEmitter.socket.js"; import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
import { runProdApi } from "../utils/prodEndpoint.utils.js"; import { runProdApi } from "../utils/prodEndpoint.utils.js";
import { returnFunc } from "../utils/returnHelper.utils.js"; import { returnFunc } from "../utils/returnHelper.utils.js";
@@ -15,12 +17,42 @@ type Data = {
runningNo?: string; runningNo?: string;
}; };
const postScan = async (data: any) => {
try {
const newScan = (await db
.insert(dockDoorScans)
.values({
dockId: data.dockId,
loadingOrder: data.loadingOrder,
loadingUnit: data.loadingUnit.sscc ?? data.loadingUnit.runningNo, // can be running number or sscc depending on where it came from
loadingUnitStatus: data.loadingUnitStatus, // TODO: add enums on the status of each load.
message: data.message, // the response it gave when scanning
})
.returning()) as any;
emitToRoom(`dockDoorLoading:${data.dockId}`, newScan[0]);
} catch (error) {
console.log("Error: ", error);
}
};
const loadUnit = async (data: Data) => { const loadUnit = async (data: Data) => {
const log = createLogger({ module: "dockdoor", subModule: "loadunit" });
log.info({ stack: data }, "Data Passed over from the scanner.");
// are we even active at this time? // are we even active at this time?
const dockDoorActive = await db.query.settings.findFirst({ const dockDoorActive = await db.query.settings.findFirst({
where: (u, { eq }) => eq(u.name, "dockDoorScanning"), where: (u, { eq }) => eq(u.name, "dockDoorScanning"),
}); });
const unitToScan = data.sscc
? { sscc: data.sscc !== "noread" ? data.sscc?.slice(2) : data.sscc }
: { runningNo: Number(data.runningNo) };
const dock = await db
.select()
.from(dockDoorScanners)
.where(eq(dockDoorScanners.dockId, data.dockId as string));
if (!dockDoorActive?.active) { if (!dockDoorActive?.active) {
return returnFunc({ return returnFunc({
success: false, success: false,
@@ -33,29 +65,18 @@ const loadUnit = async (data: Data) => {
room: "", room: "",
}); });
} }
// check if its a valids an sscc
if (data.sscc === "noread") {
return returnFunc({
success: false,
level: "error",
module: "dockdoor",
subModule: "loadUnit",
message:
"Failed to load the unit to the truck, there was no pallet read.",
data: [],
notify: false,
room: `dockDoorLoading:${data.dockId}`,
});
}
// check if we currently have a loading order attached to the dock door. // check if we currently have a loading order attached to the dock door.
const dock = await db
.select()
.from(dockDoorScanners)
.where(eq(dockDoorScanners.dockId, data.dockId as string));
if (dock[0]?.currentLoadingOrder === "") { if (dock[0]?.currentLoadingOrder === "") {
postScan({
dockId: data.dockId,
loadingOrder: dock[0]?.currentLoadingOrder,
loadingUnit: unitToScan,
loadingUnitStatus: "notLoaded",
message:
"There are know current active loading orders please start one and try again.",
});
return returnFunc({ return returnFunc({
success: true, success: true,
level: "error", level: "error",
@@ -68,6 +89,30 @@ const loadUnit = async (data: Data) => {
room: `dockDoorLoading:${data.dockId}`, room: `dockDoorLoading:${data.dockId}`,
}); });
} }
// check if its a valids an sscc
if (data.sscc === "noread") {
postScan({
dockId: data.dockId,
loadingOrder: dock[0]?.currentLoadingOrder,
loadingUnit: unitToScan,
loadingUnitStatus: "noread",
message:
"Failed to load the unit to the truck, there was no pallet read.",
});
return returnFunc({
success: false,
level: "error",
module: "dockdoor",
subModule: "loadUnit",
message:
"Failed to load the unit to the truck, there was no pallet read.",
data: [],
notify: false,
room: `dockDoorLoading:${data.dockId}`,
});
}
// TODO: pallet validation, check if we are on hold, then check if we have been in the staging warehouse for more than x time. // TODO: pallet validation, check if we are on hold, then check if we have been in the staging warehouse for more than x time.
@@ -77,10 +122,6 @@ const loadUnit = async (data: Data) => {
// add the loading units // add the loading units
try { try {
const unitToScan = data.sscc
? { sscc: data.sscc?.slice(2) }
: { runningNo: Number(data.runningNo) };
const prod = (await runProdApi({ const prod = (await runProdApi({
method: "post", method: "post",
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`, endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`,
@@ -90,7 +131,14 @@ const loadUnit = async (data: Data) => {
//emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data ?? []); //emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data ?? []);
if (!prod?.success) { if (!prod?.success) {
emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data.errors[0]); postScan({
dockId: data.dockId,
loadingOrder: dock[0]?.currentLoadingOrder,
loadingUnit: unitToScan,
loadingUnitStatus: "notLoaded",
message: prod?.data.errors[0].message,
});
return returnFunc({ return returnFunc({
success: false, success: false,
level: "error", level: "error",
@@ -107,7 +155,15 @@ const loadUnit = async (data: Data) => {
data: prod.data, data: prod.data,
code: 0, code: 0,
}; };
emitToRoom(`dockDoorLoading:${data.dockId}`, emitData as any);
postScan({
dockId: data.dockId,
loadingOrder: dock[0]?.currentLoadingOrder,
loadingUnit: unitToScan,
loadingUnitStatus: "loaded",
message: emitData.message,
});
return returnFunc({ return returnFunc({
success: true, success: true,
level: "info", level: "info",

View File

@@ -0,0 +1,17 @@
import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { logs } from "../db/schema/logs.schema.js";
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
export async function handleDockScanInsertedNotification(id: string) {
const row = await db.query.dockDoorScans.findFirst({
where: eq(logs.id, id),
});
if (!row) return;
// send only to the current dock door
if (row.dockId) {
emitToRoom(`dockDoorLoading:${row.dockId}`, row);
}
}

View File

@@ -0,0 +1,20 @@
import { db } from "../db/db.controller.js";
export const getRecentDockScans = ({
loadingOrder,
limit = 200,
}: {
loadingOrder: string;
limit?: number | undefined;
}) => {
return db.query.dockDoorScans.findMany({
//where: (scans, { eq }) => eq(scans.status, "active"),
where: (scans, { and, eq }) =>
and(
eq(scans.status, "active"),
loadingOrder ? eq(scans.loadingOrder, loadingOrder) : undefined,
),
orderBy: (scans, { desc }) => [desc(scans.upd_date)],
limit,
});
};

View File

@@ -0,0 +1,107 @@
import { formatInTimeZone } from "date-fns-tz";
import { Router } from "express";
import { gpQuery } from "../gpSql/gpSqlQuery.controller.js";
import {
type SqlGPQuery,
sqlGpQuerySelector,
} from "../gpSql/gpSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { startDate, endDate, glCode, includePlantToken } = req.query;
if (
!startDate ||
startDate === "" ||
!endDate ||
endDate === "" ||
!glCode ||
glCode === ""
) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "GpData",
message:
"The start date, end date, and gl code are required to run this query.",
data: [],
status: 400,
});
}
const sqlQuery = sqlGpQuerySelector(`gp.eom.data`) as SqlGPQuery;
if (!sqlQuery.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "GpData",
message:
"Failed to get GpData sql file please, please contact support if this continues.",
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
gpQuery(
sqlQuery.query
.replace("[startDate]", startDate as string)
.replace("[endDate]", endDate as string)
.replace("[gpCode]", glCode as string),
"Eom GpData data",
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "GpData",
message: `Error getting GpData data info`,
data: error as any,
notify: false,
status: 400,
});
}
return apiReturn(res, {
success: data.success,
level: data.success ? "info" : "error",
module: "eom",
subModule: "GpData",
message: data.message,
data:
includePlantToken === "true" && data.success
? data.data.map((i) => {
return {
plantToken: process.env.PROD_PLANT_TOKEN,
...i,
Date_Received: formatInTimeZone(
i.Date_Received,
"etc/utc",
"M/d/yyyy",
),
};
})
: data.data.map((i) => {
return {
...i,
Date_Received: formatInTimeZone(
i.Date_Received,
"etc/utc",
"M/d/yyyy",
),
};
}),
notify: false,
status: data.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,9 @@
/**
* The flow that will trigger all the history functions to run and store the data each day and clean up as needed
*
* if we are on usmcd1vms036 we will run a get request to all servers in the db so we can store that data as well.
*
* this will be stored in both. the vms036 functions will store in a bigger server so it can be pulled faster and in ssrs
*/
export const eomHistory = async () => {};

View File

@@ -0,0 +1,61 @@
import { format } from "date-fns";
import { eq } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { invHistoricalData } from "../db/schema/historicalInv.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
// the params we are wanting to add in. min required will be the month so we dont pass everything over
const { date } = req.query;
if (!date || date === "") {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "historical inv",
message:
"The day of the month is required to be included in order to pass.",
data: [],
status: 400,
});
}
// get the date passed over.
const { data, error } = await tryCatch(
db
.select()
.from(invHistoricalData)
.where(
eq(invHistoricalData.histDate, format(date as string, "yyyy-MM-dd")),
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "historical inv",
message:
"There was an error getting the historical data from the server.",
data: error as any,
status: 400,
});
}
return apiReturn(res, {
success: true,
level: "info",
module: "eom",
subModule: "historical inv",
message: "Eom Historical Inv Data",
data: data,
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,77 @@
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { includePlantToken } = req.query;
const sqlQuery = sqlQuerySelector(`eom.lastPurchasePrice`) as SqlQuery;
if (!sqlQuery.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "lastPurchasePrice",
message:
"Failed to get last sales price sql file please, please contact support if this continues.",
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
prodQuery(
sqlQuery.query,
"Eom last purchase price data",
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "lastPurchasePrice",
message: `Error getting last purchase Price data info`,
data: error as any,
notify: false,
status: 400,
});
}
return apiReturn(res, {
success: data.success,
level: data.success ? "info" : "error",
module: "eom",
subModule: "lastPurchasePrice",
message: data.message,
data:
includePlantToken === "true" && data.success
? data.data.map((i) => {
return {
plantToken: process.env.PROD_PLANT_TOKEN,
...i,
//validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
};
})
: data.data.map((i) => {
return {
...i,
//validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
};
}),
notify: false,
status: data.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,89 @@
import { formatInTimeZone } from "date-fns-tz";
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { date, includePlantToken } = req.query;
if (!date || date === "") {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "lastSalesPrice",
message: "A date is required to run this query.",
data: [],
status: 400,
});
}
const sqlQuery = sqlQuerySelector(`eom.lastSalesPrice`) as SqlQuery;
if (!sqlQuery.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "lastSalesPrice",
message:
"Failed to get last sales price sql file please, please contact support if this continues.",
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
prodQuery(
sqlQuery.query.replace("[date]", date as string),
"Eom last sales price data",
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "lastSalesPrice",
message: `Error getting last Sales Price data info`,
data: error as any,
notify: false,
status: 400,
});
}
return apiReturn(res, {
success: data.success,
level: data.success ? "info" : "error",
module: "eom",
subModule: "lastSalesPrice",
message: data.message,
data:
includePlantToken === "true" && data.success
? data.data.map((i) => {
return {
plantToken: process.env.PROD_PLANT_TOKEN,
...i,
validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
};
})
: data.data.map((i) => {
return {
...i,
validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
};
}),
notify: false,
status: data.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,97 @@
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { startDate, endDate, includePlantToken } = req.query;
if (!startDate || startDate === "" || !endDate || endDate === "") {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "productionConsumption",
message: "The start date and end date are required to run this query.",
data: [],
status: 400,
});
}
const sqlQuery = sqlQuerySelector(`eom.productionConsumption`) as SqlQuery;
if (!sqlQuery.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "productionConsumption",
message:
"Failed to get production consumption sql file please, please contact support if this continues.",
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
prodQuery(
sqlQuery.query
.replace("[startDate]", startDate as string)
.replace("[endDate]", endDate as string),
"Eom production consumption data",
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "productionConsumption",
message: `Error getting production consumption data info`,
data: error as any,
notify: false,
status: 400,
});
}
return apiReturn(res, {
success: data.success,
level: data.success ? "info" : "error",
module: "eom",
subModule: "productionConsumption",
message: data.message,
data:
includePlantToken === "true" && data.success
? data.data.map((i) => {
return {
plantToken: process.env.PROD_PLANT_TOKEN,
...i,
// Prod_Date: formatInTimeZone(
// i.Prod_Date,
// "etc/utc",
// "M/d/yyyy",
// ),
};
})
: data.data.map((i) => {
return {
...i,
// Prod_Date: formatInTimeZone(
// i.Prod_Date,
// "etc/utc",
// "M/d/yyyy",
// ),
};
}),
notify: false,
status: data.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,98 @@
import { formatInTimeZone } from "date-fns-tz";
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { startDate, endDate, includePlantToken } = req.query;
if (!startDate || startDate === "" || !endDate || endDate === "") {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "purchased",
message: "The start date and end date are required to run this query.",
data: [],
status: 400,
});
}
const sqlQuery = sqlQuerySelector(`eom.purchased`) as SqlQuery;
if (!sqlQuery.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "purchased",
message:
"Failed to get purchased sql file please, please contact support if this continues.",
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
prodQuery(
sqlQuery.query
.replace("[startDate]", startDate as string)
.replace("[endDate]", endDate as string),
"Eom purchased data",
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "purchased",
message: `Error getting purchased data info`,
data: error as any,
notify: false,
status: 400,
});
}
return apiReturn(res, {
success: data.success,
level: data.success ? "info" : "error",
module: "eom",
subModule: "purchased",
message: data.message,
data:
includePlantToken === "true" && data.success
? data.data.map((i) => {
return {
plantToken: process.env.PROD_PLANT_TOKEN,
...i,
Received_Date: formatInTimeZone(
i.Received_Date,
"etc/utc",
"M/d/yyyy",
),
};
})
: data.data.map((i) => {
return {
...i,
Received_Date: formatInTimeZone(
i.Received_Date,
"etc/utc",
"M/d/yyyy",
),
};
}),
notify: false,
status: data.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,98 @@
import { formatInTimeZone } from "date-fns-tz";
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { startDate, endDate, includePlantToken } = req.query;
if (!startDate || startDate === "" || !endDate || endDate === "") {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "regrind",
message: "The start date and end date are required to run this query.",
data: [],
status: 400,
});
}
const sqlQuery = sqlQuerySelector(`eom.regrind`) as SqlQuery;
if (!sqlQuery.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "regrind",
message:
"Failed to get regrind sql file please, please contact support if this continues.",
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
prodQuery(
sqlQuery.query
.replace("[startDate]", startDate as string)
.replace("[endDate]", endDate as string),
"Eom regrind data",
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "regrind",
message: `Error getting regrind data info`,
data: error as any,
notify: false,
status: 400,
});
}
return apiReturn(res, {
success: data.success,
level: data.success ? "info" : "error",
module: "eom",
subModule: "regrind",
message: data.message,
data:
includePlantToken === "true" && data.success
? data.data.map((i) => {
return {
plantToken: process.env.PROD_PLANT_TOKEN,
...i,
Buchungsdatum: formatInTimeZone(
i.Buchungsdatum,
"etc/utc",
"M/d/yyyy",
),
};
})
: data.data.map((i) => {
return {
...i,
Buchungsdatum: formatInTimeZone(
i.Buchungsdatum,
"etc/utc",
"M/d/yyyy",
),
};
}),
notify: false,
status: data.success ? 200 : 400,
});
});
export default r;

35
backend/eom/eom.routes.ts Normal file
View File

@@ -0,0 +1,35 @@
import type { Express } from "express";
import { featureCheck } from "../middleware/featureActive.middleware.js";
import gpData from "./eom.gpdata.route.js";
import historyInv from "./eom.historyInv.route.js";
import lastPurchasePrice from "./eom.lastPurchasePrice.route.js";
import lastSalesPrice from "./eom.lastSalesPrice.route.js";
import productionConsumption from "./eom.productionConsumption.route.js";
import purchased from "./eom.purchased.route.js";
import regrind from "./eom.regrind.route.js";
import soldItems from "./eom.soldItems.route.js";
export const setupEomRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/eom/historyInv`, featureCheck("eom"), historyInv);
app.use(`${baseUrl}/api/eom/purchased`, featureCheck("eom"), purchased);
app.use(
`${baseUrl}/api/eom/lastSalesPrice`,
featureCheck("eom"),
lastSalesPrice,
);
app.use(
`${baseUrl}/api/eom/lastPurchasePrice`,
featureCheck("eom"),
lastPurchasePrice,
);
app.use(
`${baseUrl}/api/eom/productionConsumption`,
featureCheck("eom"),
productionConsumption,
);
app.use(`${baseUrl}/api/eom/regrind`, featureCheck("eom"), regrind);
app.use(`${baseUrl}/api/eom/soldItems`, featureCheck("eom"), soldItems);
app.use(`${baseUrl}/api/eom/gpData`, featureCheck("eom"), gpData);
};

View File

@@ -0,0 +1,98 @@
import { formatInTimeZone } from "date-fns-tz";
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { startDate, endDate, includePlantToken } = req.query;
if (!startDate || startDate === "" || !endDate || endDate === "") {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "soldItems",
message: "The start date and end date are required to run this query.",
data: [],
status: 400,
});
}
const sqlQuery = sqlQuerySelector(`eom.soldItems`) as SqlQuery;
if (!sqlQuery.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "soldItems",
message:
"Failed to get soldItems sql file please, please contact support if this continues.",
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
prodQuery(
sqlQuery.query
.replace("[startDate]", startDate as string)
.replace("[endDate]", endDate as string),
"Eom soldItems data",
),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "eom",
subModule: "soldItems",
message: `Error getting soldItems data info`,
data: error as any,
notify: false,
status: 400,
});
}
return apiReturn(res, {
success: data.success,
level: data.success ? "info" : "error",
module: "eom",
subModule: "soldItems",
message: data.message,
data:
includePlantToken === "true" && data.success
? data.data.map((i) => {
return {
plantToken: process.env.PROD_PLANT_TOKEN,
...i,
DeliveryDate: formatInTimeZone(
i.DeliveryDate,
"etc/utc",
"M/d/yyyy",
),
};
})
: data.data.map((i) => {
return {
...i,
DeliveryDate: formatInTimeZone(
i.DeliveryDate,
"etc/utc",
"M/d/yyyy",
),
};
}),
notify: false,
status: data.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,16 @@
select * from (
select
case when x.POPRCTNM is null then p.POPRCTNM else p.POPRCTNM end as RCT_Num,
PONUMBER PO,
p.VENDORID Supplier,
ITEMNMBR Item,
QTYSHPPD shipped,
UOFM Type,
TRXLOCTN Location,
case when CONVERT(DATE, x.receiptdate) is null then convert(date, p.DATERECD) else CONVERT(DATE, x.receiptdate) end as Date_Received
from ALPLA.dbo.pop10500 (nolock) as p
left join
ALPLA.dbo.POP10300 as x on p.POPRCTNM = x.POPRCTNM
WHERE TRXLOCTN LIKE '[gpCode]%' and p.POPTYPE = 1) a
where Date_Received BETWEEN '[startDate]' AND '[endDate]'

View File

@@ -3,7 +3,6 @@ import { Writable } from "node:stream";
import pino, { type Logger } from "pino"; import pino, { type Logger } from "pino";
import { db } from "../db/db.controller.js"; import { db } from "../db/db.controller.js";
import { logs } from "../db/schema/logs.schema.js"; import { logs } from "../db/schema/logs.schema.js";
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
import { tryCatch } from "../utils/trycatch.utils.js"; import { tryCatch } from "../utils/trycatch.utils.js";
import { notifySystemIssue } from "./logger.notify.js"; import { notifySystemIssue } from "./logger.notify.js";
//import build from "pino-abstract-transport"; //import build from "pino-abstract-transport";
@@ -50,10 +49,10 @@ const dbStream = new Writable({
notifySystemIssue(obj); notifySystemIssue(obj);
} }
if (obj.room) { // if (obj.room) {
emitToRoom(obj.room, res.data ? res.data[0] : obj); // emitToRoom(obj.room, res.data ? res.data[0] : obj);
} // }
emitToRoom("logs", res.data ? res.data[0] : obj); // emitToRoom("logs", res.data ? res.data[0] : obj);
callback(); callback();
} catch (err) { } catch (err) {
console.error("DB log insert error:", err); console.error("DB log insert error:", err);

View File

@@ -0,0 +1,24 @@
import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { logs } from "../db/schema/logs.schema.js";
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
export async function handleLogInsertedNotification(id: string) {
const row = await db.query.logs.findFirst({
where: eq(logs.id, id),
});
if (!row) return;
// More targeted rooms.
if (row.module) {
emitToRoom(`logs:${row.module}`, row);
}
if (row.subModule) {
emitToRoom(`logs:${row.subModule}`, row);
}
// Everyone listening to all logs.
emitToRoom("logs", row);
}

View File

@@ -22,7 +22,6 @@ r.patch(
requirePermission({ notifications: ["update"] }), requirePermission({ notifications: ["update"] }),
async (req, res: Response) => { async (req, res: Response) => {
const { id } = req.params; const { id } = req.params;
try { try {
const validated = updateNote.parse(req.body); const validated = updateNote.parse(req.body);
@@ -37,6 +36,7 @@ r.patch(
await modifiedNotification(id as string); await modifiedNotification(id as string);
if (nError) { if (nError) {
return apiReturn(res, { return apiReturn(res, {
success: false, success: false,
level: "error", level: "error",
@@ -58,6 +58,7 @@ r.patch(
status: 200, status: 200,
}); });
} catch (err) { } catch (err) {
if (err instanceof z.ZodError) { if (err instanceof z.ZodError) {
const flattened = z.flattenError(err); const flattened = z.flattenError(err);
// return res.status(400).json({ // return res.status(400).json({

View File

@@ -21,6 +21,7 @@ type Releases = {
ReleaseNumber: number; ReleaseNumber: number;
DeliveryState: number; DeliveryState: number;
DeliveryDate: Date; DeliveryDate: Date;
ReleaseState: number;
LineItemHumanReadableId: number; LineItemHumanReadableId: number;
ArticleAlias: string; ArticleAlias: string;
LoadingUnits: string; LoadingUnits: string;
@@ -38,6 +39,8 @@ const actaulDocks = [
{ name: "matrix", dockId: "3e32cbfc-49f4-4138-b491-9d5df9c94754" }, { name: "matrix", dockId: "3e32cbfc-49f4-4138-b491-9d5df9c94754" },
{ name: "gerber", dockId: "9109e789-6c15-4cd9-87cb-de1b18627b6d" }, { name: "gerber", dockId: "9109e789-6c15-4cd9-87cb-de1b18627b6d" },
{ name: "rb", dockId: "6be02526-6183-4789-a73f-e0aa155e6d1e" }, { name: "rb", dockId: "6be02526-6183-4789-a73f-e0aa155e6d1e" },
//test server dock
{ name: "second", dockId: "e87c92bd-13b4-4f7e-bf5e-b0182884c47a" },
]; ];
const timeZone = process.env.TIMEZONE as string; const timeZone = process.env.TIMEZONE as string;
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000; const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
@@ -273,11 +276,65 @@ const postRelease = async (release: Releases) => {
if (existing) { if (existing) {
const id = existing.openDockAptId; const id = existing.openDockAptId;
// deal with canceled stuff as we want this gone off od
if (release.ReleaseState === 2 || release.ReleaseState === 4) {
// delete the order in od and change the state to canceled in lst
try {
const response = await axios.delete(
`${process.env.OPENDOCK_URL}/appointment/${id}`,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
// {
// hardDelete: true,
// },
);
if (response.status === 400) {
log.error({}, response.data.data.message);
return;
}
// update the release in the db leaving as insert just incase something weird happened
try {
await db
.update(opendockApt)
.set({
status: "canceled",
upd_date: sql`Now()`,
})
.where(eq(opendockApt.release, release.ReleaseNumber))
.returning();
log.info({}, `${release.ReleaseNumber} was canceled`);
} catch (e) {
log.error(
{ stack: e },
`Error canceling the release: ${release.ReleaseNumber}`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
//console.info(newDockApt);
log.error(
{ stack: e.response.data },
`An error has occurred during canceling of the release: ${release.ReleaseNumber}`,
);
return;
}
return;
}
if ( if (
(releaseLoadtypeCheck || (releaseLoadtypeCheck ||
opendDockArticleCheck?.loadType === "drop" || opendDockArticleCheck?.loadType === "drop" ||
defaultDock?.value === "drop") && defaultDock?.value === "drop") &&
(release.DeliveryState === 0 || release.DeliveryState === 1) release.DeliveryState === 2
) { ) {
const setArrival = { ...newDockApt, status: "Arrived" }; const setArrival = { ...newDockApt, status: "Arrived" };
@@ -391,7 +448,12 @@ const postRelease = async (release: Releases) => {
return; return;
} }
} else { // changing to only trigger the change if the state is 2 meaning it has a scan to it and already in progress of being loaded.
} else if (
release.DeliveryState === 0 ||
release.DeliveryState === 1 ||
release.DeliveryState === 2
) {
try { try {
const response = await axios.patch( const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}`, `${process.env.OPENDOCK_URL}/appointment/${id}`,
@@ -445,6 +507,117 @@ const postRelease = async (release: Releases) => {
return; return;
} }
// if we are finished we need to set to completed
} else if (release.DeliveryState === 3 || release.DeliveryState === 4) {
try {
const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}`,
newDockApt,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
);
if (response.status === 400) {
log.error({}, response.data.data.message);
return;
}
// update the release in the db leaving as insert just incase something weird happened
try {
await db
.insert(opendockApt)
.values({
release: release.ReleaseNumber,
openDockAptId: response.data.data.id,
appointment: response.data.data,
})
.onConflictDoUpdate({
target: opendockApt.release,
set: {
openDockAptId: response.data.data.id,
appointment: response.data.data,
status: "completed",
upd_date: sql`NOW()`,
},
})
.returning();
log.info({}, `${release.ReleaseNumber} was updated`);
} catch (e) {
log.error(
{ stack: e },
`Error updating the release: ${release.ReleaseNumber}`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
//console.info(newDockApt);
log.error(
{ stack: e.response.data },
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
);
return;
}
}
} else if (opendDockArticleCheck?.loadType === "live") {
try {
const response = await axios.post(
`${process.env.OPENDOCK_URL}/appointment`,
newDockApt,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
);
// we need the id,release#,status from this response, store it in lst, check if we have a release so we can just update it.
// this will be utilized when we are listening for the changes to the apts. that way we can update the state to arrived. we will run our own checks on this guy during the incoming messages.
if (response.status === 400) {
log.error({}, response.data.data.message);
return;
}
// the response to make it simple we want response.data.id, response.data.relNumber, status will be defaulted to Scheduled if we created it here.
// TODO: add this release data to our db. but save it in json format and well parse it out. that way we future proof it and have everything in here vs just a few things
//console.info(response.data.data, "Was Created");
try {
await db
.insert(opendockApt)
.values({
release: release.ReleaseNumber,
openDockAptId: response.data.data.id,
appointment: response.data.data,
})
.onConflictDoUpdate({
target: opendockApt.release,
set: {
openDockAptId: response.data.data.id,
appointment: response.data.data,
upd_date: sql`NOW()`,
},
})
.returning();
log.info({}, `${release.ReleaseNumber} was created`);
} catch (e) {
log.error({ stack: e }, "Error creating new release");
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
log.error(
{ stack: e?.response?.data },
`Error posting new release to opendock, ${release.ReleaseNumber}`,
);
return;
} }
} else if ( } else if (
(releaseLoadtypeCheck || (releaseLoadtypeCheck ||

View File

@@ -0,0 +1,73 @@
import axios from "axios";
import { eq, sql } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
import { odToken } from "./opendock.utils.js";
const r = Router();
r.patch("/:id", async (req, res) => {
//const limit
const { id } = req.params;
try {
const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}/undo-latest-status`,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
);
if (response.status === 400) {
return apiReturn(res, {
success: false,
level: "error",
module: "opendock",
subModule: "apt",
message: response.data.data.message,
data: [],
status: 400,
});
}
// update the release in the db
const { data } = await tryCatch(
db
.update(opendockApt)
.set({
appointment: response.data.data,
upd_date: sql`NOW()`,
})
.where(eq(opendockApt.openDockAptId, id)),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "apt",
message: `The release was reverted to the last state.`,
data: data as any,
status: 200,
});
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
return apiReturn(res, {
success: false,
level: "error",
module: "opendock",
subModule: "apt",
message: `An error updating the release in opendock`,
data: e.response.data,
status: 400,
});
}
});
export default r;

View File

@@ -137,7 +137,7 @@ r.delete("/:id", async (req, res) => {
.where(eq(opendockArticleSetup.id, id)) .where(eq(opendockArticleSetup.id, id))
.returning(); .returning();
return apiReturn(res, { return apiReturn(res, {
success: false, success: true,
level: "info", //connect.success ? "info" : "error", level: "info", //connect.success ? "info" : "error",
module: "opendock", module: "opendock",
subModule: "articleCheck", subModule: "articleCheck",

View File

@@ -1,9 +1,9 @@
import type { Express } from "express"; import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js"; import { featureCheck } from "../middleware/featureActive.middleware.js";
import undo from "./openDockUndoLastStatus.js";
import articleCheck from "./opendock.articleCheck.route.js"; import articleCheck from "./opendock.articleCheck.route.js";
import getApt from "./opendockRelease.route.js";
import getApt from "./opendockGetRelease.route.js";
export const setupOpendockRoutes = (baseUrl: string, app: Express) => { export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
//setup all the routes //setup all the routes
@@ -21,4 +21,11 @@ export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
requireAuth, requireAuth,
articleCheck, articleCheck,
); );
app.use(
`${baseUrl}/api/opendock/undo-latest-status`,
featureCheck("opendock_sync"),
requireAuth,
undo,
);
}; };

View File

@@ -12,7 +12,7 @@ export let odToken: ODToken = {
}; };
export const getToken = async () => { export const getToken = async () => {
const log = createLogger({ module: "opendock", subModule: "releaseMonitor" }); const log = createLogger({ module: "opendock", subModule: "auth" });
try { try {
const { status, data } = await axios.post( const { status, data } = await axios.post(
`${process.env.OPENDOCK_URL}/auth/login`, `${process.env.OPENDOCK_URL}/auth/login`,
@@ -29,7 +29,8 @@ export const getToken = async () => {
odToken = { odToken: data.access_token, tokenDate: new Date() }; odToken = { odToken: data.access_token, tokenDate: new Date() };
log.info({ odToken }, "Token added"); log.info({ odToken }, "Token added");
return;
} catch (e) { } catch (e) {
log.error({ error: e }, "Error getting/refreshing token"); log.error({ stack: e }, "Error getting/refreshing token");
} }
}; };

View File

@@ -1,40 +0,0 @@
import { desc, gte, sql } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
r.get("/", async (_, res) => {
//const limit
const daysCreated = 30;
const { data } = await tryCatch(
db
.select()
.from(opendockApt)
.where(
gte(
opendockApt.createdAt,
sql.raw(`NOW() - INTERVAL '${daysCreated} days'`),
),
)
.orderBy(desc(opendockApt.createdAt))
.limit(500),
);
apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "apt",
message: `The first ${data?.length} Apt(s) that were created in the last ${daysCreated} `,
data: data ?? [],
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,124 @@
import axios from "axios";
import { and, desc, eq, gte, sql } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
import { odToken } from "./opendock.utils.js";
const r = Router();
r.get("/", async (req, res) => {
//const limit
const daysCreated = req.query.daysCreated ?? 30;
const { data } = await tryCatch(
db
.select()
.from(opendockApt)
.where(
and(
gte(
opendockApt.upd_date,
sql.raw(`NOW() - INTERVAL '${daysCreated} days'`),
),
eq(opendockApt.status, "active"),
),
)
.orderBy(desc(opendockApt.upd_date))
.limit(500),
);
apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "apt",
message: `The first ${data?.length} Apt(s) that were created in the last ${daysCreated} `,
data: data ?? [],
status: 200,
});
});
r.delete("/:id", async (req, res) => {
const { id } = req.params;
const { data: releaseInfo } = (await tryCatch(
db
.select()
.from(opendockApt)
.where(eq(opendockApt.id, id as string)),
)) as any;
if (releaseInfo.length === 0) {
return apiReturn(res, {
success: false,
level: "error",
module: "opendock",
subModule: "apt",
message: "Invalid release id passed over please try again",
data: [],
status: 400,
});
}
try {
const response = await axios.delete(
`${process.env.OPENDOCK_URL}/appointment/${releaseInfo[0].appointment.id}`,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
// {
// hardDelete: true,
// },
);
if (response.status === 400) {
return apiReturn(res, {
success: false,
level: "error",
module: "opendock",
subModule: "apt",
message: response.data.data.message,
data: [],
status: 400,
});
}
// update the release in the db leaving as insert just in-case something weird happened
const { data } = await tryCatch(
db
.delete(opendockApt)
.where(eq(opendockApt.id, id as string))
.returning(),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "apt",
message: `The release was deleted, this is un unrecoverable`,
data: data as any,
status: 200,
});
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
return apiReturn(res, {
success: false,
level: "error",
module: "opendock",
subModule: "apt",
message: `An error deleting the release in opendock`,
data: e.response.data,
status: 400,
});
}
});
export default r;

View File

@@ -0,0 +1,15 @@
use AlplaPROD_test1
SELECT plant=(SELECT Wert FROM dbo.T_SystemParameter (nolock) WHERE (Bezeichnung = 'Werkskuerzel')),
plantName=(SELECT Wert FROM dbo.T_SystemParameter AS T_SystemParameter (nolock) WHERE (Bezeichnung = 'Mandant-intern')),*
from
(Select IdBestellung as 'Purchase order number',
IdArtikelVarianten AS AV,
BestellMenge,
BestellMengeVPK,
PreisProEinheit,
convert(varchar,Lieferdatum,23) as deliveryDate,
ROW_NUMBER() over(partition by IdArtikelVarianten order by Lieferdatum desc) rn
from dbo.V_Bestellpositionen_PURCHASE (nolock)
where PositionsStatus = '7' or PositionsStatus = '6' or PositionsStatus = '5' and convert(varchar,Lieferdatum,23) > DATEADD(year, -5, GetDate()) )a
where rn = 1

View File

@@ -0,0 +1,14 @@
use AlplaPROD_test1
select * from
(select IdArtikelvarianten as av,
VKPreis as salesPrice,
MPB, FWMPAlpla,
FWMPB,
ROW_NUMBER() over(partition by IdArtikelVarianten order by gueltigabdatum desc) rn,
convert(date, gueltigabdatum, 120) as validDate
from dbo.T_HistoryVK (nolock)
where convert(date, gueltigabdatum, 120) <= '[date]' and StandardKunde = 1) a
where rn =1
order by av asc,
validDate desc

View File

@@ -0,0 +1,7 @@
use alplaprod_test1
SELECT IdArtikelvarianten AS AV,
Menge AS Quantity,
CONVERT(DATE, BuchDatum) AS Prod_Date
FROM dbo.T_LBW (nolock)
WHERE BuchDatum BETWEEN '[startDate]' AND '[endDate]' ORDER BY BuchDatum DESC

View File

@@ -0,0 +1,40 @@
use AlplaPROD_test1
declare @start_date nvarchar(30) = '[startDate] '
declare @end_date nvarchar(30) = '[endDate] '
select T_Wareneingaenge.IdBestellung AS Purchase_order,
T_Adressen.IdAdressen,
T_Adressen.Bezeichnung,
T_Wareneingaenge.IdArtikelVarianten AS AV,
V_Artikel.Alias,
x.Bemerkung AS Remark,
T_Wareneingaenge.Bemerkung AS Purchase_Remark,
x.Add_User,
CONVERT(DATE, x.Add_Date) AS Received_Date,
x.IdWareneingangPlanung,
T_Wareneingaenge.SollMenge As Ordered_QTY,
x.EntladeMenge As Received_QTY,
case when T_Adressen.Bezeichnung LIKE '%Alpla%' Then 'AlplaPlant' Else 'Supplier' End AS
Supplier,
x.Typ as incoming_goods_type
from dbo.T_WareneingangPlanungen (nolock) as x
join
dbo.T_Wareneingaenge (nolock) on
x.IdWareneingang=
dbo.T_Wareneingaenge.IdWareneingang
join
dbo.V_Artikel (nolock) on
dbo.T_Wareneingaenge.IdArtikelVarianten=
dbo.V_Artikel.IdArtikelvarianten
join
dbo.T_Adressen (nolock) on dbo.T_Wareneingaenge.IdLieferantAdresse =
dbo.T_Adressen.IdAdressen
where x.add_date between @start_date + (select top(1) CONVERT(char(8), StartDate, 108) as startTime from [test1_AlplaPROD2.0_Read].masterData.ShiftDefinition (nolock) where TeamNumber = 1)
AND @end_date + (select top(1) CONVERT(char(8), StartDate, 108) as startTime from [test1_AlplaPROD2.0_Read].masterData.ShiftDefinition (nolock) where TeamNumber = 1)
order by x.add_date desc

View File

@@ -0,0 +1,13 @@
select IdArtikelVarianten,
ArtikelVariantenAlias,
IdRezeptur,
Menge,
IdBuchungsGrund,
Buchungsdatum,
ProduktionsLos,
IdReinheit,
ReinheitBez, HerkunftBez
from alplaprod_test1.[dbo].[V_AbfallLagerBuchungen] (nolock)
where Buchungsdatum between '[startDate] ' + (select top(1) CONVERT(char(8), StartDate, 108) as startTime from [test1_AlplaPROD2.0_Read].masterData.ShiftDefinition (nolock) where TeamNumber = 1)
and '[endDate] ' + (select top(1) CONVERT(char(8), StartDate, 108) as startTime from [test1_AlplaPROD2.0_Read].masterData.ShiftDefinition (nolock) where TeamNumber = 1)
and IdBuchungsGrund in (140, 240) and BuchungsTyp = 1

View File

@@ -0,0 +1,15 @@
select IdArtikelVarianten AS AV,
ArtikelVariantenAlias AS AVDescription,
convert(date,AbrufLadeDatum,23) As DeliveryDate,
idlieferadresse AS DeliveryAddress,
LieferAdressBez,
AuftragsNummer AS PO_Number,
IdAuftragsPosition AS LineITEM,
IdAuftragsAbruf AS ReleaseNumber,
AbrufMengeVPK AS PalletsRequested,
AbrufMenge AS PiecesRequested,
GelieferteMengeVPK AS DeliveredPallets,
GelieferteMenge AS DeliveredQTY,
case when LieferAdressBez Like '%alpla%' Then 'AlplaPlant' ELSE 'Customer' End as CustomerType
from alplaprod_test1.dbo.V_TrackerAuftragsAbrufe (nolock)
where AbrufLadeDatum between '[startDate]' and '[endDate]'

View File

@@ -5,6 +5,7 @@ import { setupAuthRoutes } from "./auth/auth.routes.js";
import { setupApiDocsRoutes } from "./configs/scaler.config.js"; import { setupApiDocsRoutes } from "./configs/scaler.config.js";
import { setupDatamartRoutes } from "./datamart/datamart.routes.js"; import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
import { setupDockDoorRoutes } from "./dockdoorScanning/dockdoor.routes.js"; import { setupDockDoorRoutes } from "./dockdoorScanning/dockdoor.routes.js";
import { setupEomRoutes } from "./eom/eom.routes.js";
import { setupGPSqlRoutes } from "./gpSql/gpSql.routes.js"; import { setupGPSqlRoutes } from "./gpSql/gpSql.routes.js";
import { setupMobileRoutes } from "./mobile/mobile.routes.js"; import { setupMobileRoutes } from "./mobile/mobile.routes.js";
import { setupNotificationRoutes } from "./notification/notification.routes.js"; import { setupNotificationRoutes } from "./notification/notification.routes.js";
@@ -31,4 +32,5 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
setupOCPRoutes(baseUrl, app); setupOCPRoutes(baseUrl, app);
setupTCPRoutes(baseUrl, app); setupTCPRoutes(baseUrl, app);
setupDockDoorRoutes(baseUrl, app); setupDockDoorRoutes(baseUrl, app);
setupEomRoutes(baseUrl, app);
}; };

View File

@@ -2,6 +2,8 @@ import { createServer } from "node:http";
import os from "node:os"; import os from "node:os";
import createApp from "./app.js"; import createApp from "./app.js";
import { db } from "./db/db.controller.js"; import { db } from "./db/db.controller.js";
import { startDbNotificationListener } from "./db/db.listener.js";
import { setupDbNotifications } from "./db/db.setupNotifications.js";
import { dbCleanup } from "./db/dbCleanup.controller.js"; 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";
@@ -43,12 +45,13 @@ const start = async () => {
startTCPServer(); startTCPServer();
connectProdSql(); connectProdSql();
connectGPSql(); connectGPSql();
startDbNotificationListener();
// trigger startup processes these must run before anything else can run // trigger startup processes these must run before anything else can run
await baseSettingValidationCheck(); await baseSettingValidationCheck();
systemSettings = await db.select().from(settings); systemSettings = await db.select().from(settings);
//when starting up long lived features the name must match the setting name. // when starting up long lived features the name must match the setting name.
// also we always want to have long lived processes inside a setting check. // also we always want to have long lived processes inside a setting check.
setTimeout(() => { setTimeout(() => {
if (systemSettings.filter((n) => n.name === "opendock_sync")[0]?.active) { if (systemSettings.filter((n) => n.name === "opendock_sync")[0]?.active) {
@@ -89,6 +92,7 @@ const start = async () => {
startNotifications(); startNotifications();
serversChecks(); serversChecks();
aggregateRouteHitsForBusinessDay(); aggregateRouteHitsForBusinessDay();
setupDbNotifications();
// can be removed at a later date // can be removed at a later date
sqlJobCleanUp(); sqlJobCleanUp();

View File

@@ -1,8 +0,0 @@
import type { RoomId } from "./roomDefinitions.socket.js";
export const MAX_HISTORY = 50;
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed
export const roomHistory = new Map<RoomId, unknown[]>();
export const roomBuffers = new Map<RoomId, any[]>();
export const roomFlushTimers = new Map<RoomId, NodeJS.Timeout>();

View File

@@ -1,109 +0,0 @@
import { desc } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { logs } from "../db/schema/logs.schema.js";
import { ppoRun } from "../warehousing/warehousing.ppooMonitor.js";
type RoomDefinition<T = unknown> = {
seed: (limit: number) => Promise<T[]>;
};
export type StaticRoomId =
| "logs"
| "labels"
| "admin"
| "admin:build"
| "ppoo"
| "dockDoorLoading:2";
export type DynamicRoomId = `dockDoorLoading:${string}`;
export type RoomId = StaticRoomId | DynamicRoomId;
export type RoomConfig = {
requiresAuth?: boolean;
role?: string[];
seed?: (limit: number, roomId: RoomId) => Promise<unknown[]>;
};
export const protectedRooms: Record<StaticRoomId, RoomConfig> = {
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
//admin: { requiresAuth: false, role: ["admin", "systemAdmin"] },
labels: {},
admin: {},
"admin:build": {},
ppoo: {},
"dockDoorLoading:2": {},
};
export function getRoomConfig(roomId: string): RoomConfig | null {
if (roomId in protectedRooms) {
return protectedRooms[roomId as StaticRoomId];
}
if (roomId.startsWith("dockDoorLoading:")) {
const dockId = roomId.split(":")[1];
if (!dockId) return null;
return {
requiresAuth: true,
role: ["admin", "systemAdmin", "dockDoor"],
};
}
return null;
}
export const roomDefinition: Record<RoomId, RoomDefinition> = {
logs: {
seed: async (limit) => {
try {
const rows = await db
.select()
.from(logs)
.orderBy(desc(logs.createdAt))
.limit(limit);
return rows; //.reverse();
} catch (e) {
console.error("Failed to seed logs:", e);
return [];
}
},
},
labels: {
seed: async (limit) => {
console.info(limit);
return [];
},
},
admin: {
seed: async (limit) => {
console.info(limit);
return [];
},
},
"admin:build": {
seed: async (limit) => {
console.info(limit);
return [];
},
},
ppoo: {
seed: async (limit) => {
console.log(limit);
return {
type: "snapshot",
items: await ppoRun(),
createdAt: new Date().toISOString(),
} as any;
},
},
// TODO: add in dynamic room seeding
"dockDoorLoading:2": {
seed: async (limit) => {
console.log(limit);
return [];
},
},
};

View File

@@ -1,27 +1,73 @@
// the emitter setup // the emitter setup
// TODO: validate if we want to add event back in later..
// let emitFn: ((roomId: string, event: string, payload: unknown) => void) | null =
// null;
import type { RoomId } from "./roomDefinitions.socket.js"; import { createLogger } from "../logger/logger.controller.js";
let addDataToRoom: ((roomId: RoomId, payload: unknown[]) => void) | null = null; type QueuedPayload = unknown;
let emitFn: ((roomId: string, payload: QueuedPayload[]) => void) | null = null;
const queues = new Map<string, QueuedPayload[]>();
const timers = new Map<string, NodeJS.Timeout>();
const FLUSH_MS = 500;
const MAX_QUEUE_SIZE = 200;
export const registerEmitter = ( export const registerEmitter = (
fn: (roomId: RoomId, payload: unknown[]) => void, fn: (roomId: string, payload: QueuedPayload) => void,
) => { ) => {
addDataToRoom = fn; emitFn = fn;
}; };
export const emitToRoom = (roomId: RoomId, payload: unknown[]) => { export const emitToRoom = (roomId: string, payload: QueuedPayload) => {
if (!addDataToRoom) { const log = createLogger({ module: "socket.io", subModule: "emitter" });
if (!emitFn) {
console.error("Socket emitter not initialized"); console.error("Socket emitter not initialized");
return; return;
} }
addDataToRoom(roomId, payload); const queue = queues.get(roomId) ?? [];
if (queue.length > MAX_QUEUE_SIZE) {
log.error(
{ stack: { roomId, size: queue.length }, notify: true },
`Socket queue exceeded max size for ${roomId}`,
);
}
queue.push(payload);
queues.set(roomId, queue);
if (timers.has(roomId)) return;
const timer = setTimeout(() => {
try {
const payloads = queues.get(roomId) ?? [];
if (payloads.length === 0) return;
emitFn?.(roomId, payloads);
queues.delete(roomId);
} catch (e) {
console.error("Socket emit failed", { roomId, e });
} finally {
timers.delete(roomId);
}
}, FLUSH_MS);
timers.set(roomId, timer);
}; };
/* /*
import { emitToRoom } from "../socket/socketEmitter.js"; example emitToRoom(room, payload)
// room name
// its payload payload can be anything json serilized example below.
emitToRoom("logs", newLogRow);
emitToRoom("inventory:ppoo", {
type: "snapshot",
location: "ppoo",
items,
createdAt: new Date().toISOString(),
});
*/ */

View File

@@ -1,88 +0,0 @@
import type { Server } from "socket.io";
import { createLogger } from "../logger/logger.controller.js";
import {
FLUSH_INTERVAL,
MAX_HISTORY,
roomBuffers,
roomFlushTimers,
roomHistory,
} from "./roomCache.socket.js";
import { type RoomId, roomDefinition } from "./roomDefinitions.socket.js";
// get the db data if not exiting already
const log = createLogger({ module: "socket.io", subModule: "roomService" });
let ioRef: Server | null = null;
export const registerRoomService = (io: Server) => {
ioRef = io;
};
export const hasRoomMembers = (roomId: string): boolean => {
if (!ioRef) return false;
return (ioRef.sockets.adapter.rooms.get(roomId)?.size ?? 0) > 0;
};
export const getRoomMemberCount = (roomId: string): number => {
if (!ioRef) return 0;
return ioRef.sockets.adapter.rooms.get(roomId)?.size ?? 0;
};
export const preseedRoom = async (roomId: RoomId) => {
if (roomHistory.has(roomId)) {
return roomHistory.get(roomId);
}
const roomDef = roomDefinition[roomId] as any;
if (!roomDef) {
log.error({}, `Room ${roomId} is not defined`);
}
const latestData = await roomDef.seed(MAX_HISTORY);
roomHistory.set(roomId, latestData);
return latestData;
};
export const createRoomEmitter = (io: Server) => {
const addDataToRoom = <T>(roomId: RoomId, payload: T[]) => {
if (!roomHistory.has(roomId)) {
roomHistory.set(roomId, []);
}
const history = roomHistory.get(roomId)!;
history?.push(payload);
if (history?.length > MAX_HISTORY) {
history?.shift();
}
if (!roomBuffers.has(roomId)) {
roomBuffers.set(roomId, []);
}
roomBuffers.get(roomId)!.push(payload);
if (!roomFlushTimers.has(roomId)) {
const timer = setTimeout(() => {
const buffered = roomBuffers.get(roomId) || [];
if (buffered.length > 0) {
io.to(roomId).emit("room-update", {
roomId,
payloads: buffered, // ✅ array now
});
}
roomBuffers.set(roomId, []);
roomFlushTimers.delete(roomId);
}, FLUSH_INTERVAL);
roomFlushTimers.set(roomId, timer);
}
};
return { addDataToRoom };
};

View File

@@ -1,33 +1,16 @@
import type { Server as HttpServer } from "node:http"; import type { Server as HttpServer } from "node:http";
//import { dirname, join } from "node:path";
//import { fileURLToPath } from "node:url";
import { instrument } from "@socket.io/admin-ui"; import { instrument } from "@socket.io/admin-ui";
import { Server } from "socket.io"; import { Server } from "socket.io";
import { createLogger } from "../logger/logger.controller.js"; import { createLogger } from "../logger/logger.controller.js";
import { auth } from "../utils/auth.utils.js";
import { allowedOrigins } from "../utils/cors.utils.js"; import { allowedOrigins } from "../utils/cors.utils.js";
import { registerEmitter } from "./roomEmitter.socket.js"; import { registerEmitter } from "./roomEmitter.socket.js";
import { import { registerHasRoomMembers } from "./socket.manager.js";
createRoomEmitter, import { isRoomKey, roomConfigs } from "./socket.roomConfig.js";
preseedRoom,
registerRoomService,
} from "./roomService.socket.js";
//const __filename = fileURLToPath(import.meta.url);
//const __dirname = dirname(__filename);
const log = createLogger({ module: "socket.io", subModule: "setup" }); const log = createLogger({ module: "socket.io", subModule: "setup" });
import { auth } from "../utils/auth.utils.js";
//import type { Session, User } from "better-auth"; // adjust if needed
import { getRoomConfig } from "./roomDefinitions.socket.js";
// declare module "socket.io" {
// interface Socket {
// user?: User | any;
// session?: Session;
// }
// }
export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => { export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
const io = new Server(server, { const io = new Server(server, {
path: `${baseUrl}/api/socket.io`, path: `${baseUrl}/api/socket.io`,
@@ -37,12 +20,16 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
}, },
}); });
// manage members of the rooms. registerHasRoomMembers((roomId) => {
registerRoomService(io); return (io.sockets.adapter.rooms.get(roomId)?.size ?? 0) > 0;
});
// ✅ Create emitter instance registerEmitter((roomId, payloads) => {
const { addDataToRoom } = createRoomEmitter(io); io.to(roomId).emit("room-update", {
registerEmitter(addDataToRoom); roomId,
payloads,
});
});
io.use(async (socket, next) => { io.use(async (socket, next) => {
try { try {
@@ -85,79 +72,95 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
version: "1.0.0", version: "1.0.0",
}); });
// s.on("join-room", async (rn) => { s.on("join-room", async ({ room, params }) => {
// const config = protectedRooms[rn]; if (!isRoomKey(room)) return;
// if (config?.requiresAuth && !s.user) { const config = roomConfigs[room];
// return s.emit("room-error", {
// room: rn,
// message: "Authentication required",
// });
// }
// const roles = Array.isArray(config?.role) ? config?.role : [config?.role];
// //if (config?.role && s.user?.role !== config.role) {
// if (config?.role && !roles.includes(s.user?.role)) {
// return s.emit("room-error", {
// roomId: rn,
// message: `Not authorized to be in room: ${rn}`,
// });
// }
// s.join(rn);
// // get room seeded
// const history = await preseedRoom(rn);
// log.info({}, `User joined ${rn}: ${s.id}`);
// // send the intial data
// s.emit("room-update", {
// roomId: rn,
// payloads: history,
// initial: true,
// });
// });
s.on("join-room", async (rn: string) => {
const config = getRoomConfig(rn);
if (!config) { if (!config) {
return s.emit("room-error", { return s.emit("room-error", {
roomId: rn, roomId: room,
message: `Unknown room: ${rn}`, message: `Unknown room: ${room}`,
}); });
} }
if (config.requiresAuth && !s.user) { const actualRoom = config.buildRoom
? config.buildRoom(params)
: (room as any);
const allowed = config.canJoin
? await config.canJoin({
socket: s,
user: s.user,
room,
actualRoom,
params,
})
: true;
if (!allowed) {
return s.emit("room-error", { return s.emit("room-error", {
roomId: rn, roomId: room,
message: "Authentication required", message: `Not authorized to be in room: ${room}`,
}); });
} }
const roles = Array.isArray(config.role) ? config.role : []; await s.join(actualRoom);
if (roles.length > 0 && !roles.includes(s.user?.role)) { s.emit("room-joined", {
return s.emit("room-error", { room,
roomId: rn, roomId: actualRoom,
message: `Not authorized to be in room: ${rn}`, params,
});
}
s.join(rn);
const history = await preseedRoom(rn as any);
log.info({}, `User joined ${rn}: ${s.id}`);
s.emit("room-update", {
roomId: rn,
payloads: history,
initial: true,
}); });
if (config.seed) {
const payloads = await config.seed({
room,
actualRoom,
params,
user: s.user,
});
s.emit("room-update", {
room,
roomId: actualRoom,
type: "snapshot",
payloads,
});
}
log.info(
{ room, actualRoom, params },
`User joined ${actualRoom}: ${s.id}`,
);
}); });
s.on("leave-room", (room) => { // s.on("leave-room", (room) => {
s.leave(room); // s.leave(room);
log.info({}, `${s.id} left room: ${room}`); // log.info({}, `${s.id} left room: ${JSON.stringify(room)}`);
// });
s.on("leave-room", async ({ room, params }) => {
if (!isRoomKey(room)) return;
const config = roomConfigs[room];
if (!config) return;
const actualRoom = config.buildRoom
? config.buildRoom(params)
: (room as any);
await s.leave(actualRoom);
s.emit("room-left", {
room,
roomId: actualRoom,
params,
});
log.info(
{ room, actualRoom, params },
`${s.id} left room: ${actualRoom}`,
);
}); });
}); });

View File

@@ -0,0 +1,10 @@
let hasMembersFn: ((roomId: string) => boolean) | null = null;
export const registerHasRoomMembers = (fn: (roomId: string) => boolean) => {
hasMembersFn = fn;
};
export const hasRoomMembers = (roomId: string) => {
if (!hasMembersFn) return false;
return hasMembersFn(roomId);
};

View File

@@ -0,0 +1,117 @@
import { getRecentLogs } from "../db/db.socketSeed.js";
import { getRecentDockScans } from "../dockdoorScanning/dockdoor.socket.seed.js";
export type RoomKey =
| "logs"
| "labels"
| "admin"
| "inventory"
| "dockDoorLoading";
export type SocketUser = {
id: string;
email?: string;
role?: string;
};
export type CanJoinArgs = {
socket: any;
user?: SocketUser;
room: string;
actualRoom: string;
params?: Record<string, unknown>;
};
type RoomConfig = {
//requiresAuth?: boolean;
//roles?: string[];
canJoin?: (args: CanJoinArgs) => boolean | Promise<boolean>;
buildRoom?: (params?: Record<string, unknown>) => string | null;
seed?: (args: {
room: string;
actualRoom: string;
params?: Record<string, unknown>;
user?: SocketUser;
}) => Promise<unknown[]>;
};
export function isRoomKey(room: string): room is RoomKey {
return room in roomConfigs;
}
export const roomConfigs: Record<RoomKey, RoomConfig> = {
logs: {
canJoin: ({ user, params }) => {
if (!params?.submodule && !params?.module) {
return user?.role === "systemAdmin";
}
return true;
},
buildRoom: (params) => {
const module = String(params?.module ?? "").toLowerCase();
const submodule = String(params?.submodule ?? "").toLowerCase();
if (module && submodule) return `logs:${module}:${submodule}`;
if (submodule) return `logs:${submodule}`;
if (module) return `logs:${module}`;
return "logs";
},
seed: async ({ params }) => {
const module = params?.module ? String(params.module) : undefined;
const submodule = params?.submodule
? String(params.submodule)
: undefined;
return await getRecentLogs({
module,
submodule,
limit: 200,
});
},
},
labels: {
canJoin: () => true,
buildRoom: () => "labels",
},
admin: {
canJoin: ({ user, params }) => {
if (params?.section === "system") {
return user?.role === "systemAdmin";
}
return true;
},
buildRoom: (params) =>
params?.section ? `admin:${params.section}` : "admin",
},
inventory: {
canJoin: () => true,
buildRoom: (params) =>
params?.location ? `inventory:${params.location}` : null,
},
dockDoorLoading: {
canJoin: () => true,
buildRoom: (params) =>
params?.dockId ? `dockDoorLoading:${params.dockId}` : null,
seed: async ({ params }) => {
return await getRecentDockScans({
loadingOrder: params?.loadingOrder as string,
limit: 200,
});
},
},
} satisfies Record<string, RoomConfig>;
/*
socket.emit("join-room", {
room: "dockDoorLoading",
params: { dockId: "2" },
});
*/

View File

@@ -140,6 +140,17 @@ const servers: NewServerData[] = [
serverLoc: "D$\\LST_V3", serverLoc: "D$\\LST_V3",
buildNumber: 1, buildNumber: 1,
}, },
{
name: "Iowa City PET",
server: "USIOW1VMS006",
plantToken: "usiow2",
idAddress: "10.75.0.26",
greatPlainsPlantCode: "31",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3_2",
buildNumber: 1,
},
{ {
name: "Bowling Green 1", name: "Bowling Green 1",
server: "USBOW1VMS006", server: "USBOW1VMS006",
@@ -173,6 +184,39 @@ const servers: NewServerData[] = [
serverLoc: "D$\\LST_V3", serverLoc: "D$\\LST_V3",
buildNumber: 1, buildNumber: 1,
}, },
{
name: "Bowling Green 2",
server: "USBOW2VMS006",
plantToken: "usbow2",
idAddress: "10.106.0.26",
greatPlainsPlantCode: "56",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Kansas City",
server: "USKSC1VMS006",
plantToken: "usksc1",
idAddress: "10.42.9.26",
greatPlainsPlantCode: "85",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Florence",
server: "USFLO1VMS006",
plantToken: "usflo1",
idAddress: "10.203.0.26",
greatPlainsPlantCode: "22",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
]; ];
// notes here for when it comes time to pull in all the server address info [test1_AlplaPROD2.0_Read].[masterData].[Plant] has everything from every server :D // notes here for when it comes time to pull in all the server address info [test1_AlplaPROD2.0_Read].[masterData].[Plant] has everything from every server :D

View File

@@ -96,6 +96,16 @@ const newSettings: NewSetting[] = [
roles: ["admin"], roles: ["admin"],
seedVersion: 1, seedVersion: 1,
}, },
{
name: "eom",
value: "0",
active: true,
description: "Eom processes to capture data",
moduleName: "eom",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
// standard settings // standard settings
{ {

View File

@@ -8,6 +8,7 @@ export const statement = {
logistics: ["read", "create", "update", "delete", "readAll"], logistics: ["read", "create", "update", "delete", "readAll"],
mobile: ["read", "create", "update", "delete", "readAll"], mobile: ["read", "create", "update", "delete", "readAll"],
openDock: ["read", "create", "update", "delete"], openDock: ["read", "create", "update", "delete"],
warehouse: ["read", "create", "update", "delete"],
notifications: ["read", "create", "update", "delete", "readAll"], notifications: ["read", "create", "update", "delete", "readAll"],
} as const; } as const;
@@ -17,17 +18,20 @@ export const user = ac.newRole({
app: ["read", "create"], app: ["read", "create"],
notifications: ["read", "create"], notifications: ["read", "create"],
openDock: ["read"], openDock: ["read"],
warehouse: ["read"],
}); });
export const manager = ac.newRole({ export const manager = ac.newRole({
app: ["read", "create", "update"], app: ["read", "create", "update"],
mobile: ["read", "create", "update"], mobile: ["read", "create", "update"],
openDock: ["read", "create", "update"], openDock: ["read", "create", "update"],
warehouse: ["read", "create"],
}); });
export const transport = ac.newRole({ export const transport = ac.newRole({
app: ["read", "create", "update"], app: ["read", "create", "update"],
openDock: ["read", "create", "update"], openDock: ["read", "create", "update"],
warehouse: ["read", "create"],
}); });
export const admin = ac.newRole({ export const admin = ac.newRole({
@@ -35,6 +39,7 @@ export const admin = ac.newRole({
mobile: ["read", "create", "update"], mobile: ["read", "create", "update"],
user: ["create", "update", "ban"], user: ["create", "update", "ban"],
openDock: ["read", "create", "update"], openDock: ["read", "create", "update"],
warehouse: ["read", "create", "update"],
}); });
export const systemAdmin = ac.newRole({ export const systemAdmin = ac.newRole({
@@ -44,6 +49,7 @@ export const systemAdmin = ac.newRole({
mobile: ["read", "create", "update", "delete", "readAll"], mobile: ["read", "create", "update", "delete", "readAll"],
logistics: ["read", "create", "update", "delete", "readAll"], logistics: ["read", "create", "update", "delete", "readAll"],
notifications: ["read", "create", "update", "delete", "readAll"], notifications: ["read", "create", "update", "delete", "readAll"],
warehouse: ["read", "create", "update", "delete"],
openDock: ["read", "create", "update", "delete"], openDock: ["read", "create", "update", "delete"],
}); });

View File

@@ -17,7 +17,8 @@ export interface ReturnHelper<T = unknown[]> {
| "logistics" | "logistics"
| "admin" | "admin"
| "mobile" | "mobile"
| "dockdoor"; | "dockdoor"
| "eom";
subModule: string; subModule: string;
level: "info" | "error" | "debug" | "fatal" | "warn"; level: "info" | "error" | "debug" | "fatal" | "warn";

View File

@@ -1,5 +1,5 @@
import { emitToRoom } from "../socket.io/roomEmitter.socket.js"; import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
import { hasRoomMembers } from "../socket.io/roomService.socket.js"; import { hasRoomMembers } from "../socket.io/socket.manager.js";
import { runProdApi } from "../utils/prodEndpoint.utils.js"; import { runProdApi } from "../utils/prodEndpoint.utils.js";
export const ppoRun = async () => { export const ppoRun = async () => {
@@ -17,11 +17,13 @@ export const ppoRun = async () => {
}; };
export const ppooMonitoring = async () => { export const ppooMonitoring = async () => {
if (!hasRoomMembers(`ppoo`)) { const roomId = "inventory:ppoo";
if (!hasRoomMembers(roomId)) {
return; return;
} }
emitToRoom("ppoo", { emitToRoom(roomId, {
type: "snapshot", type: "snapshot",
items: await ppoRun(), items: await ppoRun(),
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),

View File

@@ -40,6 +40,11 @@ export default function TransportationBar() {
icon: link, icon: link,
url: "/transportation/opendock", url: "/transportation/opendock",
}, },
{
title: "Active releases",
icon: link,
url: "/transportation/opendock/releases",
},
], ],
}, },
]; ];

View File

@@ -0,0 +1,94 @@
//import { useQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { ChevronRight, Link as link } from "lucide-react";
//import { permissionQuery } from "../../lib/queries/permsCheck";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "../ui/collapsible";
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
} from "../ui/sidebar";
export default function WarehouseBar() {
// const { data: canCreate = false } = useQuery(
// permissionQuery({
// warehouse: ["read"],
// }),
// );
const { setOpen } = useSidebar();
const items = [
{
title: "Dock Door Scanning",
url: "/warehouse",
//icon,
isActive: true,
items: [
{
title: "DockDoorScanning",
icon: link,
url: "/warehouse/dockdoorscanning",
},
],
},
];
return (
<SidebarGroup>
<SidebarGroupLabel>Warehouse</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<div key={item.title}>
{item.isActive && (
<Collapsible
asChild
//defaultOpen={isNotifications}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.title}
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild>
<Link
to={subItem.url}
onClick={() => setOpen(false)}
>
<subItem.icon />
<span>{subItem.title}</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)}
</div>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
);
}

View File

@@ -13,16 +13,23 @@ import AdminSidebar from "./AdminBar";
import DocBar from "./DocBar"; import DocBar from "./DocBar";
import MobileBar from "./MobileBar"; import MobileBar from "./MobileBar";
import TransportationBar from "./TransportationBar"; import TransportationBar from "./TransportationBar";
import WarehouseBar from "./Warhouse";
export function AppSidebar() { export function AppSidebar() {
const { data: session } = useSession(); const { data: session } = useSession();
const { data: settings, isLoading } = useSuspenseQuery(getSettings()); const { data: settings, isLoading } = useSuspenseQuery(getSettings());
const { data: canRead = false } = useQuery( const { data: canReadOpenDock = false } = useQuery(
permissionQuery({ permissionQuery({
openDock: ["read"], openDock: ["read"],
}), }),
); );
// const { data: canReadWarehouse = false } = useQuery(
// permissionQuery({
// warehouse: ["read"],
// }),
// );
return ( return (
<Sidebar <Sidebar
variant="sidebar" variant="sidebar"
@@ -42,7 +49,11 @@ export function AppSidebar() {
{!isLoading && {!isLoading &&
settings.filter((n: any) => n.name === "opendock_sync")[0] settings.filter((n: any) => n.name === "opendock_sync")[0]
?.active && ?.active &&
canRead && <TransportationBar />} canReadOpenDock && <TransportationBar />}
{!isLoading &&
settings.filter((n: any) => n.name === "dockDoorScanning")[0]
?.active && <WarehouseBar />}
{session && {session &&
(session.user.role === "admin" || (session.user.role === "admin" ||

View File

@@ -1,68 +1,110 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import socket from "@/lib/socket.io"; import socket from "@/lib/socket.io";
type RoomParams = Record<string, unknown>;
type JoinRoomPayload = {
room: string;
params?: RoomParams;
};
type RoomUpdatePayload<T> = { type RoomUpdatePayload<T> = {
roomId: string; roomId: string;
payloads: T[]; payloads: T[];
type: string;
};
type RoomJoinedPayload = {
room: string;
roomId: string;
}; };
type RoomErrorPayload = { type RoomErrorPayload = {
room?: string;
roomId?: string; roomId?: string;
message?: string; message?: string;
}; };
type UpdateMode = "append" | "replace"; export function useSocketRoom<T>(room: string, params?: RoomParams) {
const [actualRoomId, setActualRoomId] = useState<string | null>(null);
export function useSocketRoom<T>(
roomId: string,
getKey?: (item: T) => string | number,
updateMode: UpdateMode = "append",
) {
const [data, setData] = useState<T[]>([]); const [data, setData] = useState<T[]>([]);
const [info, setInfo] = useState( const [info, setInfo] = useState(
"No data yet — join the room to start receiving", "No data yet — join the room to start receiving",
); );
const clearRoom = useCallback( // This is the payload we send to the server.
(id?: string | number) => { // Example:
if (id !== undefined && getKey) { // { room: "inventory", params: { location: "ppoo" } }
setData((prev) => prev.filter((item) => getKey(item) !== id)); const joinPayload = useMemo<JoinRoomPayload>(
setInfo(`Removed item ${id}`); () => ({
return; room,
} params,
}),
setData([]); [room, params],
setInfo("Room data cleared");
},
[getKey],
); );
const clearRoom = useCallback((filterFn?: (item: T) => boolean) => {
if (filterFn) {
setData((prev) => prev.filter((item) => !filterFn(item)));
return;
}
setData([]);
setInfo("Room data cleared");
}, []);
useEffect(() => { useEffect(() => {
function handleConnect() { // Join the logical room.
socket.emit("join-room", roomId); // The server decides the real Socket.IO roomId.
setInfo(`Joined room: ${roomId}`); // Example:
// client sends: { room: "inventory", params: { location: "ppoo" } }
// server joins: "inventory:ppoo"
function joinRoom() {
socket.emit("join-room", joinPayload);
setInfo(`Joining room: ${room}`);
}
// Server should emit this after socket.join(actualRoom).
// This lets the client know the final roomId to filter updates by.
function handleJoined(payload: RoomJoinedPayload) {
//if (payload.room !== room) return;
setActualRoomId(payload.roomId);
setInfo(`Joined room: ${payload.roomId}`);
} }
function handleUpdate(payload: RoomUpdatePayload<T>) { function handleUpdate(payload: RoomUpdatePayload<T>) {
// protects against other room updates hitting this hook // If we know the actual roomId, only accept updates for that room.
if (payload.roomId !== roomId) return; // This protects against other pages/rooms also listening to "room-update".
// resetting room data for rooms that just need updated data. if (!actualRoomId) return;
if (updateMode === "replace") {
if (payload.roomId !== actualRoomId) return;
if (payload.type === "snapshot") {
setData(payload.payloads); setData(payload.payloads);
} else { return;
setData((prev) => [...payload.payloads, ...prev]);
} }
// Append mode is good for logs/scans/events.
setData((prev) => [...payload.payloads, ...prev]);
setInfo(""); setInfo("");
} }
function handleError(err: RoomErrorPayload) { function handleError(err: RoomErrorPayload) {
if (err.roomId && err.roomId !== roomId) return; // Ignore errors for other logical rooms.
if (err.room && err.room !== room) return;
// Ignore errors for other actual rooms.
if (err.roomId && room && err.roomId !== room) return;
toast.error(err.message);
setInfo(err.message ?? "Room error"); setInfo(err.message ?? "Room error");
} }
socket.on("connect", handleConnect); socket.on("connect", joinRoom);
socket.on("room-joined", handleJoined);
socket.on("room-update", handleUpdate); socket.on("room-update", handleUpdate);
socket.on("room-error", handleError); socket.on("room-error", handleError);
@@ -70,31 +112,26 @@ export function useSocketRoom<T>(
socket.connect(); socket.connect();
} }
// If already connected, join immediately // If socket is already connected, join immediately.
if (socket.connected) { if (socket.connected) {
socket.emit("join-room", roomId); joinRoom();
setInfo(`Joined room: ${roomId}`);
} }
return () => { return () => {
socket.emit("leave-room", roomId); // Leave using the same logical room payload.
// Server should rebuild the actual room and call socket.leave(actualRoom).
socket.emit("leave-room", joinPayload);
socket.off("connect", handleConnect); socket.off("connect", joinRoom);
socket.off("room-joined", handleJoined);
socket.off("room-update", handleUpdate); socket.off("room-update", handleUpdate);
socket.off("room-error", handleError); socket.off("room-error", handleError);
}; };
}, [roomId, updateMode]); }, [room, joinPayload, actualRoomId]);
return { data, info, clearRoom }; return {
data,
info,
clearRoom,
};
} }
/*
const isDockDoorPage = location.pathname.startsWith("/dockdoor");
useSocketRoom(
dockId ? `dockdoor:${dockId}` : null,
isDockDoorPage,
);
*/

View File

@@ -6,7 +6,14 @@ import {
} from "better-auth/client/plugins"; } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react"; import { createAuthClient } from "better-auth/react";
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions"; import {
ac,
admin,
manager,
systemAdmin,
transport,
user,
} from "./auth-permissions";
export const authClient = createAuthClient({ export const authClient = createAuthClient({
baseURL: `${window.location.origin}/lst/api/auth`, baseURL: `${window.location.origin}/lst/api/auth`,
@@ -17,6 +24,7 @@ export const authClient = createAuthClient({
admin, admin,
user, user,
manager, manager,
transport,
systemAdmin, systemAdmin,
}, },
}), }),

View File

@@ -14,6 +14,7 @@ export const selectableRoles: SelectableRole[] = [
{ label: "User", value: "user" }, { label: "User", value: "user" },
{ label: "Manager", value: "manager" }, { label: "Manager", value: "manager" },
{ label: "Transport", value: "transport" }, { label: "Transport", value: "transport" },
{ label: "Warehouse", value: "warehouse" },
{ label: "Admin", value: "admin" }, { label: "Admin", value: "admin" },
{ label: "System Admin", value: "systemAdmin" }, { label: "System Admin", value: "systemAdmin" },
]; ];
@@ -25,6 +26,7 @@ export const statement = {
logistics: ["read", "create", "update", "delete", "readAll"], logistics: ["read", "create", "update", "delete", "readAll"],
mobile: ["read", "create", "update", "delete", "readAll"], mobile: ["read", "create", "update", "delete", "readAll"],
openDock: ["read", "create", "update", "delete"], openDock: ["read", "create", "update", "delete"],
warehouse: ["read", "create", "update", "delete"],
notifications: ["read", "create", "update", "delete", "readAll"], notifications: ["read", "create", "update", "delete", "readAll"],
} as const; } as const;
@@ -34,17 +36,20 @@ export const user = ac.newRole({
app: ["read", "create"], app: ["read", "create"],
notifications: ["read", "create"], notifications: ["read", "create"],
openDock: ["read"], openDock: ["read"],
warehouse: ["read"],
}); });
export const manager = ac.newRole({ export const manager = ac.newRole({
app: ["read", "create", "update"], app: ["read", "create", "update"],
mobile: ["read", "create", "update"], mobile: ["read", "create", "update"],
openDock: ["read", "create", "update"], openDock: ["read", "create", "update"],
warehouse: ["read", "create"],
}); });
export const transport = ac.newRole({ export const transport = ac.newRole({
app: ["read", "create", "update"], app: ["read", "create", "update"],
openDock: ["read", "create", "update"], openDock: ["read", "create", "update"],
warehouse: ["read", "create"],
}); });
export const admin = ac.newRole({ export const admin = ac.newRole({
@@ -52,6 +57,7 @@ export const admin = ac.newRole({
mobile: ["read", "create", "update"], mobile: ["read", "create", "update"],
user: ["create", "update", "ban"], user: ["create", "update", "ban"],
openDock: ["read", "create", "update"], openDock: ["read", "create", "update"],
warehouse: ["read", "create", "update"],
}); });
export const systemAdmin = ac.newRole({ export const systemAdmin = ac.newRole({
@@ -61,6 +67,7 @@ export const systemAdmin = ac.newRole({
mobile: ["read", "create", "update", "delete", "readAll"], mobile: ["read", "create", "update", "delete", "readAll"],
logistics: ["read", "create", "update", "delete", "readAll"], logistics: ["read", "create", "update", "delete", "readAll"],
notifications: ["read", "create", "update", "delete", "readAll"], notifications: ["read", "create", "update", "delete", "readAll"],
warehouse: ["read", "create", "update", "delete"],
openDock: ["read", "create", "update", "delete"], openDock: ["read", "create", "update", "delete"],
}); });

View File

@@ -0,0 +1,21 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { api } from "../apiHelper";
export function getActiveLoadingOrders() {
return queryOptions({
queryKey: ["getActiveLoadingOrders"],
queryFn: () => dataFetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const dataFetch = async () => {
const { data } = await api.get("/dockDoor/activeLoadingOrders");
if (!data.success) {
throw new Error(data.message ?? "Failed to load articles");
}
return data.data ?? [];
};

View File

@@ -0,0 +1,25 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { api } from "../apiHelper";
export function getActiveDockScanners() {
return queryOptions({
queryKey: ["getActiveDockScanners"],
queryFn: () => dataFetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const dataFetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data } = await api.get("/dockDoor/scanners");
if (!data.success) {
throw new Error(data.message ?? "Failed to load articles");
}
return data.data ?? [];
};

View File

@@ -0,0 +1,23 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { api } from "../apiHelper";
export function opendockApt() {
return queryOptions({
queryKey: ["opendockApt"],
queryFn: () => fetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data } = await api.get("/opendock?daysCreated=90");
return data.data;
};

View File

@@ -22,10 +22,14 @@ import { Route as AdminScanUsersRouteImport } from './routes/admin/scanUsers'
import { Route as AdminNotificationsRouteImport } from './routes/admin/notifications' import { Route as AdminNotificationsRouteImport } from './routes/admin/notifications'
import { Route as AdminLogsRouteImport } from './routes/admin/logs' import { Route as AdminLogsRouteImport } from './routes/admin/logs'
import { Route as authLoginRouteImport } from './routes/(auth)/login' import { Route as authLoginRouteImport } from './routes/(auth)/login'
import { Route as WarehouseDockdoorscanningIndexRouteImport } from './routes/warehouse/dockdoorscanning/index'
import { Route as TransportationOpendockIndexRouteImport } from './routes/transportation/opendock/index' import { Route as TransportationOpendockIndexRouteImport } from './routes/transportation/opendock/index'
import { Route as WarehouseDockdoorscanningDockRouteImport } from './routes/warehouse/dockdoorscanning/$dock'
import { Route as TransportationOpendockReleasesRouteImport } from './routes/transportation/opendock/releases'
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup' import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword' import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile' import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile'
import { Route as WarehouseDockdoorscanningScansDockScansRouteImport } from './routes/warehouse/dockdoorscanning/scans/$dockScans'
const ForbiddenRoute = ForbiddenRouteImport.update({ const ForbiddenRoute = ForbiddenRouteImport.update({
id: '/forbidden', id: '/forbidden',
@@ -92,12 +96,30 @@ const authLoginRoute = authLoginRouteImport.update({
path: '/login', path: '/login',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const WarehouseDockdoorscanningIndexRoute =
WarehouseDockdoorscanningIndexRouteImport.update({
id: '/warehouse/dockdoorscanning/',
path: '/warehouse/dockdoorscanning/',
getParentRoute: () => rootRouteImport,
} as any)
const TransportationOpendockIndexRoute = const TransportationOpendockIndexRoute =
TransportationOpendockIndexRouteImport.update({ TransportationOpendockIndexRouteImport.update({
id: '/transportation/opendock/', id: '/transportation/opendock/',
path: '/transportation/opendock/', path: '/transportation/opendock/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const WarehouseDockdoorscanningDockRoute =
WarehouseDockdoorscanningDockRouteImport.update({
id: '/warehouse/dockdoorscanning/$dock',
path: '/warehouse/dockdoorscanning/$dock',
getParentRoute: () => rootRouteImport,
} as any)
const TransportationOpendockReleasesRoute =
TransportationOpendockReleasesRouteImport.update({
id: '/transportation/opendock/releases',
path: '/transportation/opendock/releases',
getParentRoute: () => rootRouteImport,
} as any)
const authUserSignupRoute = authUserSignupRouteImport.update({ const authUserSignupRoute = authUserSignupRouteImport.update({
id: '/(auth)/user/signup', id: '/(auth)/user/signup',
path: '/user/signup', path: '/user/signup',
@@ -113,6 +135,12 @@ const authUserProfileRoute = authUserProfileRouteImport.update({
path: '/user/profile', path: '/user/profile',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const WarehouseDockdoorscanningScansDockScansRoute =
WarehouseDockdoorscanningScansDockScansRouteImport.update({
id: '/warehouse/dockdoorscanning/scans/$dockScans',
path: '/warehouse/dockdoorscanning/scans/$dockScans',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
@@ -131,7 +159,11 @@ export interface FileRoutesByFullPath {
'/user/profile': typeof authUserProfileRoute '/user/profile': typeof authUserProfileRoute
'/user/resetpassword': typeof authUserResetpasswordRoute '/user/resetpassword': typeof authUserResetpasswordRoute
'/user/signup': typeof authUserSignupRoute '/user/signup': typeof authUserSignupRoute
'/transportation/opendock/releases': typeof TransportationOpendockReleasesRoute
'/warehouse/dockdoorscanning/$dock': typeof WarehouseDockdoorscanningDockRoute
'/transportation/opendock/': typeof TransportationOpendockIndexRoute '/transportation/opendock/': typeof TransportationOpendockIndexRoute
'/warehouse/dockdoorscanning/': typeof WarehouseDockdoorscanningIndexRoute
'/warehouse/dockdoorscanning/scans/$dockScans': typeof WarehouseDockdoorscanningScansDockScansRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
@@ -150,7 +182,11 @@ export interface FileRoutesByTo {
'/user/profile': typeof authUserProfileRoute '/user/profile': typeof authUserProfileRoute
'/user/resetpassword': typeof authUserResetpasswordRoute '/user/resetpassword': typeof authUserResetpasswordRoute
'/user/signup': typeof authUserSignupRoute '/user/signup': typeof authUserSignupRoute
'/transportation/opendock/releases': typeof TransportationOpendockReleasesRoute
'/warehouse/dockdoorscanning/$dock': typeof WarehouseDockdoorscanningDockRoute
'/transportation/opendock': typeof TransportationOpendockIndexRoute '/transportation/opendock': typeof TransportationOpendockIndexRoute
'/warehouse/dockdoorscanning': typeof WarehouseDockdoorscanningIndexRoute
'/warehouse/dockdoorscanning/scans/$dockScans': typeof WarehouseDockdoorscanningScansDockScansRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
@@ -170,7 +206,11 @@ export interface FileRoutesById {
'/(auth)/user/profile': typeof authUserProfileRoute '/(auth)/user/profile': typeof authUserProfileRoute
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute '/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
'/(auth)/user/signup': typeof authUserSignupRoute '/(auth)/user/signup': typeof authUserSignupRoute
'/transportation/opendock/releases': typeof TransportationOpendockReleasesRoute
'/warehouse/dockdoorscanning/$dock': typeof WarehouseDockdoorscanningDockRoute
'/transportation/opendock/': typeof TransportationOpendockIndexRoute '/transportation/opendock/': typeof TransportationOpendockIndexRoute
'/warehouse/dockdoorscanning/': typeof WarehouseDockdoorscanningIndexRoute
'/warehouse/dockdoorscanning/scans/$dockScans': typeof WarehouseDockdoorscanningScansDockScansRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
@@ -191,7 +231,11 @@ export interface FileRouteTypes {
| '/user/profile' | '/user/profile'
| '/user/resetpassword' | '/user/resetpassword'
| '/user/signup' | '/user/signup'
| '/transportation/opendock/releases'
| '/warehouse/dockdoorscanning/$dock'
| '/transportation/opendock/' | '/transportation/opendock/'
| '/warehouse/dockdoorscanning/'
| '/warehouse/dockdoorscanning/scans/$dockScans'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
| '/' | '/'
@@ -210,7 +254,11 @@ export interface FileRouteTypes {
| '/user/profile' | '/user/profile'
| '/user/resetpassword' | '/user/resetpassword'
| '/user/signup' | '/user/signup'
| '/transportation/opendock/releases'
| '/warehouse/dockdoorscanning/$dock'
| '/transportation/opendock' | '/transportation/opendock'
| '/warehouse/dockdoorscanning'
| '/warehouse/dockdoorscanning/scans/$dockScans'
id: id:
| '__root__' | '__root__'
| '/' | '/'
@@ -229,7 +277,11 @@ export interface FileRouteTypes {
| '/(auth)/user/profile' | '/(auth)/user/profile'
| '/(auth)/user/resetpassword' | '/(auth)/user/resetpassword'
| '/(auth)/user/signup' | '/(auth)/user/signup'
| '/transportation/opendock/releases'
| '/warehouse/dockdoorscanning/$dock'
| '/transportation/opendock/' | '/transportation/opendock/'
| '/warehouse/dockdoorscanning/'
| '/warehouse/dockdoorscanning/scans/$dockScans'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
@@ -249,7 +301,11 @@ export interface RootRouteChildren {
authUserProfileRoute: typeof authUserProfileRoute authUserProfileRoute: typeof authUserProfileRoute
authUserResetpasswordRoute: typeof authUserResetpasswordRoute authUserResetpasswordRoute: typeof authUserResetpasswordRoute
authUserSignupRoute: typeof authUserSignupRoute authUserSignupRoute: typeof authUserSignupRoute
TransportationOpendockReleasesRoute: typeof TransportationOpendockReleasesRoute
WarehouseDockdoorscanningDockRoute: typeof WarehouseDockdoorscanningDockRoute
TransportationOpendockIndexRoute: typeof TransportationOpendockIndexRoute TransportationOpendockIndexRoute: typeof TransportationOpendockIndexRoute
WarehouseDockdoorscanningIndexRoute: typeof WarehouseDockdoorscanningIndexRoute
WarehouseDockdoorscanningScansDockScansRoute: typeof WarehouseDockdoorscanningScansDockScansRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@@ -345,6 +401,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof authLoginRouteImport preLoaderRoute: typeof authLoginRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/warehouse/dockdoorscanning/': {
id: '/warehouse/dockdoorscanning/'
path: '/warehouse/dockdoorscanning'
fullPath: '/warehouse/dockdoorscanning/'
preLoaderRoute: typeof WarehouseDockdoorscanningIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/transportation/opendock/': { '/transportation/opendock/': {
id: '/transportation/opendock/' id: '/transportation/opendock/'
path: '/transportation/opendock' path: '/transportation/opendock'
@@ -352,6 +415,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof TransportationOpendockIndexRouteImport preLoaderRoute: typeof TransportationOpendockIndexRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/warehouse/dockdoorscanning/$dock': {
id: '/warehouse/dockdoorscanning/$dock'
path: '/warehouse/dockdoorscanning/$dock'
fullPath: '/warehouse/dockdoorscanning/$dock'
preLoaderRoute: typeof WarehouseDockdoorscanningDockRouteImport
parentRoute: typeof rootRouteImport
}
'/transportation/opendock/releases': {
id: '/transportation/opendock/releases'
path: '/transportation/opendock/releases'
fullPath: '/transportation/opendock/releases'
preLoaderRoute: typeof TransportationOpendockReleasesRouteImport
parentRoute: typeof rootRouteImport
}
'/(auth)/user/signup': { '/(auth)/user/signup': {
id: '/(auth)/user/signup' id: '/(auth)/user/signup'
path: '/user/signup' path: '/user/signup'
@@ -373,6 +450,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof authUserProfileRouteImport preLoaderRoute: typeof authUserProfileRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/warehouse/dockdoorscanning/scans/$dockScans': {
id: '/warehouse/dockdoorscanning/scans/$dockScans'
path: '/warehouse/dockdoorscanning/scans/$dockScans'
fullPath: '/warehouse/dockdoorscanning/scans/$dockScans'
preLoaderRoute: typeof WarehouseDockdoorscanningScansDockScansRouteImport
parentRoute: typeof rootRouteImport
}
} }
} }
@@ -393,7 +477,12 @@ const rootRouteChildren: RootRouteChildren = {
authUserProfileRoute: authUserProfileRoute, authUserProfileRoute: authUserProfileRoute,
authUserResetpasswordRoute: authUserResetpasswordRoute, authUserResetpasswordRoute: authUserResetpasswordRoute,
authUserSignupRoute: authUserSignupRoute, authUserSignupRoute: authUserSignupRoute,
TransportationOpendockReleasesRoute: TransportationOpendockReleasesRoute,
WarehouseDockdoorscanningDockRoute: WarehouseDockdoorscanningDockRoute,
TransportationOpendockIndexRoute: TransportationOpendockIndexRoute, TransportationOpendockIndexRoute: TransportationOpendockIndexRoute,
WarehouseDockdoorscanningIndexRoute: WarehouseDockdoorscanningIndexRoute,
WarehouseDockdoorscanningScansDockScansRoute:
WarehouseDockdoorscanningScansDockScansRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)

View File

@@ -1,6 +1,7 @@
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 { formatInTimeZone } from "date-fns-tz"; import { formatInTimeZone } from "date-fns-tz";
//import { useMemo } from "react";
import { useSocketRoom } from "@/hooks/socket.io.hook"; import { useSocketRoom } from "@/hooks/socket.io.hook";
import { authClient } from "@/lib/auth-client"; import { authClient } from "@/lib/auth-client";
import { Badge } from "../../components/ui/badge"; import { Badge } from "../../components/ui/badge";
@@ -71,9 +72,10 @@ function LevelBadge({ level }: { level: string }) {
} }
function RouteComponent() { function RouteComponent() {
// const logParams = useMemo(() => ({ subModule: "query" }), []);
// const { data: logs } = useSocketRoom<LogEntry>("logs", logParams);
const { data: logs } = useSocketRoom<LogEntry>("logs"); const { data: logs } = useSocketRoom<LogEntry>("logs");
const columnHelper = createColumnHelper<any>(); const columnHelper = createColumnHelper<any>();
const column = [ const column = [
columnHelper.accessor("createdAt", { columnHelper.accessor("createdAt", {
header: ({ column }) => <SearchableHeader column={column} title="Time" />, header: ({ column }) => <SearchableHeader column={column} title="Time" />,

View File

@@ -170,11 +170,11 @@ const NotificationTable = () => {
try { try {
const res = await api.patch( const res = await api.patch(
`/lst/api/notification/${i.row.original.id}`, `/notification/${i.row.original.id}`,
{ {
active: !activeToggle, active: !activeToggle,
}, },
{ withCredentials: true },
); );
if (res.data.success) { if (res.data.success) {

View File

@@ -180,7 +180,7 @@ const ScanUserTable = () => {
try { try {
const res = await api.delete( const res = await api.delete(
`/lst/api/mobile/auth/user/${i.row.original.id}`, `/mobile/auth/user/${i.row.original.id}`,
{ {
withCredentials: true, withCredentials: true,

View File

@@ -4,7 +4,7 @@ import { createColumnHelper } from "@tanstack/react-table";
import { format } from "date-fns-tz"; import { format } from "date-fns-tz";
import { CircleFadingArrowUp, Trash } from "lucide-react"; import { CircleFadingArrowUp, Trash } from "lucide-react";
import { Suspense, useState } from "react"; import { Suspense, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Spinner } from "../../components/ui/spinner"; import { Spinner } from "../../components/ui/spinner";
@@ -171,7 +171,13 @@ const ServerTable = () => {
}; };
function RouteComponent() { function RouteComponent() {
const { data: logs = [], clearRoom } = useSocketRoom<any>("admin:build"); const params = useMemo(
() => ({
submodule: "builds",
}),
[],
);
const { data: logs = [], clearRoom } = useSocketRoom<any>("logs", params);
const columnHelper = createColumnHelper<any>(); const columnHelper = createColumnHelper<any>();
@@ -181,7 +187,7 @@ function RouteComponent() {
<SearchableHeader column={column} title="Time" searchable={false} /> <SearchableHeader column={column} title="Time" searchable={false} />
), ),
filterFn: "includesString", filterFn: "includesString",
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"), cell: (i) => i.getValue(), //format(i.getValue(), "M/d/yyyy HH:mm") ,
}), }),
columnHelper.accessor("message", { columnHelper.accessor("message", {
header: ({ column }) => ( header: ({ column }) => (
@@ -210,7 +216,7 @@ function RouteComponent() {
<Button <Button
size="icon" size="icon"
variant={"destructive"} variant={"destructive"}
onClick={() => clearRoom(x.timestamp)} onClick={() => clearRoom((item) => item.timestamp === x.timestamp)}
> >
<Trash /> <Trash />
</Button> </Button>

View File

@@ -120,6 +120,10 @@ export default function NewArticleLink({ refetch }: { refetch: any }) {
//TODO: get the docks from lst to help refine and actually link the dock correctly //TODO: get the docks from lst to help refine and actually link the dock correctly
const dock = [ const dock = [
{
label: "Default",
value: "default",
},
{ {
label: "Cermac", label: "Cermac",
value: "cermac", value: "cermac",

View File

@@ -1,13 +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 { Suspense } from "react"; import { Suspense, useState } from "react";
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 }) => {
@@ -91,6 +96,75 @@ const ArticleLinkTable = () => {
filterFn: "includesString", filterFn: "includesString",
cell: (i) => i.getValue(), cell: (i) => i.getValue(),
}), }),
columnHelper.accessor("deleteUser", {
header: ({ column }) => (
<SearchableHeader
column={column}
title="Delete Link"
searchable={false}
/>
),
filterFn: "includesString",
cell: (i) => {
// biome-ignore lint: just removing the lint for now to get this going will maybe fix later
const [activeToggle, setActiveToggle] = useState(false);
const onTrigger = async () => {
setActiveToggle(true);
try {
const res = await api.delete(
`/opendock/articleCheck/${i.row.original.id}`,
{
withCredentials: true,
timeout: 5000,
validateStatus: () => true,
},
);
if (res.data.success) {
toast.success(`AV: ${i.row.original.av} was deleted.`);
refetch();
setActiveToggle(false);
}
if (!res.data.success) {
toast.error(
`AV: ${i.row.original.av} encountered an error when trying to delete: ${res.data.message}`,
);
refetch();
setActiveToggle(false);
}
} catch (error) {
setActiveToggle(false);
console.error(error);
}
};
return (
<div>
<div className="flex items-center space-x-2">
<Button
variant="destructive"
disabled={activeToggle}
onClick={onTrigger}
>
{activeToggle ? (
<span>
<Spinner />
</span>
) : (
<span>
<Trash />
</span>
)}
</Button>
</div>
</div>
);
},
}),
]; ];
return ( return (
<div> <div>

View File

@@ -0,0 +1,221 @@
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { createColumnHelper } from "@tanstack/react-table";
import { formatInTimeZone } from "date-fns-tz";
import { Trash } from "lucide-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 { opendockApt } from "../../../lib/queries/openDockApt";
import { permissionQuery } from "../../../lib/queries/permsCheck";
import LstTable from "../../../lib/tableStuff/LstTable";
import SearchableHeader from "../../../lib/tableStuff/SearchableHeader";
import SkellyTable from "../../../lib/tableStuff/SkellyTable";
export const Route = createFileRoute("/transportation/opendock/releases")({
beforeLoad: async ({ location }) => {
const { data: session } = await authClient.getSession();
//const allowedRole = ["systemAdmin", "admin", "manager"];
const canAccess = await authClient.admin.hasPermission({
permissions: {
openDock: ["create"],
},
});
if (!session?.user) {
throw redirect({
to: "/",
search: {
redirect: location.href,
},
});
}
//if (!allowedRole.includes(session.user.role as string)) {
if (!canAccess) {
throw redirect({
to: "/",
});
}
return { user: session.user };
},
component: RouteComponent,
});
const OpendockApts = () => {
const { data, refetch } = useSuspenseQuery(opendockApt());
const { data: canReadOpenDock = false } = useQuery(
permissionQuery({
openDock: ["update"],
}),
);
const columnHelper = createColumnHelper<any>();
const columns = [
columnHelper.accessor("release", {
header: ({ column }) => (
<SearchableHeader column={column} title="Release" searchable={true} />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor(
(row) =>
row.appointment.customFields.find((x: any) => x.label === "Article")
?.value ?? "",
{
id: "article",
header: ({ column }) => (
<SearchableHeader column={column} title="Article" searchable={true} />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
},
),
columnHelper.accessor("appointment.status", {
header: ({ column }) => (
<SearchableHeader
column={column}
title="Current Opendock status"
searchable={true}
/>
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("upd_date", {
header: ({ column }) => (
<SearchableHeader column={column} title="Last Updated" />
),
filterFn: "includesString",
cell: (i) => {
function isDateValid(date: any) {
return date instanceof Date && !isNaN(date.getTime());
}
const trueDate = isDateValid(new Date(i.getValue()))
? formatInTimeZone(
i.getValue(),
`${window.LST_CONFIG?.timezone}`,
"MM/dd/yyyy HH:mm:ss",
)
: "invalid time";
return <span>{trueDate}</span>;
},
}),
];
if (canReadOpenDock) {
columns.push(
columnHelper.accessor("deleteRelease", {
header: ({ column }) => (
<SearchableHeader
column={column}
title="Delete Link"
searchable={false}
/>
),
filterFn: "includesString",
cell: (i) => {
// biome-ignore lint: just removing the lint for now to get this going will maybe fix later
const [activeToggle, setActiveToggle] = useState(false);
const onTrigger = async () => {
setActiveToggle(true);
try {
const res = await api.delete(
`/opendock/${i.row.original.id}`,
{
withCredentials: true,
timeout: 5000,
validateStatus: () => true,
},
);
if (res.data.success) {
toast.success(
`Release: ${i.row.original.release} was deleted.`,
);
refetch();
setActiveToggle(false);
}
if (!res.data.success) {
toast.error(
`Release: ${i.row.original.release} encountered an error when trying to delete: ${res.data.message}`,
);
refetch();
setActiveToggle(false);
}
} catch (error) {
setActiveToggle(false);
console.error(error);
}
};
return (
<div>
<div className="flex items-center space-x-2">
<Button
variant="destructive"
disabled={
activeToggle ||
i.row.original.appointment.status !== "Scheduled"
}
onClick={onTrigger}
>
{activeToggle ? (
<span>
<Spinner />
</span>
) : (
<span>
<Trash />
</span>
)}
</Button>
</div>
</div>
);
},
}),
);
}
return (
<div>
<div>
<div className="flex justify-end m-2">
{/* <Suspense
fallback={
<div>
<p>Loading...</p>
</div>
}
>
<NewArticleLink refetch={refetch} />
</Suspense> */}
</div>
<div>
<LstTable data={data} columns={columns} pageSize={50} />
</div>
</div>
</div>
);
};
function RouteComponent() {
return (
<Suspense fallback={<SkellyTable />}>
<OpendockApts />
</Suspense>
);
}

View File

@@ -0,0 +1,167 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { format } from "date-fns";
import { toast } from "sonner";
import { Button } from "../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
} from "../../../components/ui/card";
import { Separator } from "../../../components/ui/separator";
import { api } from "../../../lib/apiHelper";
import { getActiveLoadingOrders } from "../../../lib/queries/getActiveDockScanners";
import { getActiveDockScanners } from "../../../lib/queries/getActiveLoadingOrders";
import { finishLoadingOrder } from "./index";
export const Route = createFileRoute("/warehouse/dockdoorscanning/$dock")({
component: RouteComponent,
});
export const startOrder = async (
loadingOrder: string,
dockId: string,
refetch: any,
refetchActiveLoading: any,
) => {
try {
const res = await api.post(
"/dockDoor/startLoad",
{
loadingOrder: String(loadingOrder),
dockId: dockId,
},
{ validateStatus: (status) => status < 500 },
);
if (!res.data.success) {
toast.error(res.data.message);
refetch();
refetchActiveLoading();
return;
}
toast.success(res.data.message);
refetch();
refetchActiveLoading();
} catch (error) {
console.log(error);
toast.error(`Encountered error: ${JSON.stringify(error)}`);
}
};
function RouteComponent() {
const { dock } = Route.useParams();
const { data, refetch } = useSuspenseQuery(getActiveDockScanners());
const { data: loadingPlanItems, refetch: refetchActiveLoading } =
useSuspenseQuery(getActiveLoadingOrders());
const navigate = useNavigate();
const dockData = data.filter((i: any) => i.dockId === dock);
const loadingPlans = loadingPlanItems.filter(
(l: any) => l.dockId === Number(dock),
);
return (
<div className="flex flex-col">
<div className="flex justify-center">
<p>Please select an active loading order for {dockData[0].name}</p>
</div>
<div className="flex flex-row justify-center mt-5 gap-2">
{loadingPlans && loadingPlans.length > 0 ? (
<div className="flex flex-row gap-3">
{loadingPlans.map((i: any) => {
return (
<Card key={i.id} className="max-w-96">
<CardHeader>
Loading order ID: {i.id}{" "}
<p>
Loading Date: {format(i.loadingDate, "MM/dd/yyyy HH:mm")}
</p>
</CardHeader>
<CardDescription className="">
<p className="p-3">
Below are the loading order details please validate the
loading order you are selecting before pressing start.
</p>
</CardDescription>
{/* Mapping the items out so in case we have more than 1 it will display correctly */}
<CardContent>
{i?.loadingPlanItems?.map((l: any) => {
return (
<div key={i.id}>
<p>Loading Sequence {l.loadingSequence}</p>
<p>
Article: {l.articleId} - {l.articleDescription}
</p>
<p>
Current loaded: {l.loadedQuantityLUs}/
{l.plannedQuantityLUs}
</p>
{l.remark !== "" && <p>Remark: {l.remark}</p>}
{i?.loadingPlanItems.length > 1 && (
<Separator className="m-2" />
)}
</div>
);
})}
</CardContent>
<CardFooter>
<div className="flex flex-row justify-between">
<Button
onClick={() =>
startOrder(i.id, dock, refetch, refetchActiveLoading)
}
disabled={
dockData[0].currentLoadingOrder !== "" ? true : false
}
>
Start Loading
</Button>
<Button
onClick={() =>
navigate({
to: "/warehouse/dockdoorscanning/scans/$dockScans",
params: { dockScans: dock },
})
}
>
Scan progress
</Button>
<Button
onClick={() =>
finishLoadingOrder(
String(i.id),
dock,
refetch,
refetchActiveLoading,
)
}
disabled={
dockData[0].currentLoadingOrder === "" ||
dockData[0].currentLoadingOrder !== i.id.toString()
}
>
Finish Loading
</Button>
</div>
</CardFooter>
</Card>
);
})}
</div>
) : (
<div>
<p>There are know loading orders currently active.</p>
<p>
Head over to Outbound deliveries to create a loading order to
start the flow
</p>
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,177 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { toast } from "sonner";
import { Button } from "../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
} from "../../../components/ui/card";
import { api } from "../../../lib/apiHelper";
import { getActiveLoadingOrders } from "../../../lib/queries/getActiveDockScanners";
import { getActiveDockScanners } from "../../../lib/queries/getActiveLoadingOrders";
export const Route = createFileRoute("/warehouse/dockdoorscanning/")({
component: RouteComponent,
});
export const finishLoadingOrder = async (
loadingOrder: string,
dockId: string,
refetch: any,
refetchActiveLoading: any,
clear?: boolean,
) => {
try {
const res = await api.post(
"/dockDoor/finishOrder",
{
loadingOrder: loadingOrder,
dockId: dockId,
clear,
},
{ validateStatus: (status) => status < 500 },
);
if (!res.data.success) {
toast.error(res.data.message);
refetch();
refetchActiveLoading();
return;
}
toast.info(res.data.message);
refetch();
refetchActiveLoading();
} catch (error) {
console.log(error);
toast.error(`Encountered error: ${JSON.stringify(error)}`);
}
};
function RouteComponent() {
const { data, isLoading, refetch } = useSuspenseQuery(
getActiveDockScanners(),
);
const { data: loadingPlanItems, refetch: refetchActiveLoading } =
useSuspenseQuery(getActiveLoadingOrders());
const navigate = useNavigate();
if (isLoading) {
return (
<div className="flex justify-center-safe">
<p>Loading active dock scanners....</p>
</div>
);
}
return (
<div className="flex flex-col ">
<div className="flex justify-center-safe">
<p className="text-2xl underline">
Select a dock you would like to work with
</p>
</div>
<div className="flex justify-center gap-3 mt-5">
{!isLoading &&
data.length > 0 &&
data.map((i: any) => {
const loadingPlan =
i.currentLoadingOrder !== ""
? loadingPlanItems.filter(
(x: any) => x.id === Number(i.currentLoadingOrder),
)
: [];
// console.log(loadingPlan);
// console.log(loadingPlanItems);
return (
<Card
key={i.id}
className="max-w-96"
onClick={() => {
if (i.currentLoadingOrder === "") {
navigate({
to: "/warehouse/dockdoorscanning/$dock",
params: { dock: i.dockId },
search: {
name: i.name,
},
});
}
}}
>
<CardHeader className="text-center">{i.name}</CardHeader>
<CardDescription>
For Abbott loads reminder: 3 lots per load max.
</CardDescription>
{i.currentLoadingOrder !== "" ? (
<CardContent>
<div className="flex flex-col gap-2">
Current loading order: {i.currentLoadingOrder}
</div>
<div>
{loadingPlan && loadingPlan.length !== 0 ? (
<>
<p>{`${loadingPlan[0].loadingPlanItems[0].articleId} - ${loadingPlan[0].loadingPlanItems[0].articleDescription}`}</p>
<p>
Current Loaded :{" "}
{
loadingPlan[0].loadingPlanItems[0]
.loadedQuantityLUs
}{" "}
/{" "}
{
loadingPlan[0].loadingPlanItems[0]
.plannedQuantityLUs
}
</p>
Check Scans:{" "}
<Link
to={"/warehouse/dockdoorscanning/scans/$dockScans"}
params={{ dockScans: i.dockId }}
className="underline"
>
CLICK HERE
</Link>
</>
) : (
<>
<p>The Current Loading order is invalid</p>
<p>
{" "}
It appears it could have been finished via another
process
</p>
<div className="m-2 flex justify-center">
<Button
onClick={() =>
finishLoadingOrder(
i.currentLoadingOrder,
i.dockId,
refetch,
refetchActiveLoading,
true,
)
}
>
Clear {i.currentLoadingOrder}
</Button>
</div>
</>
)}
</div>
</CardContent>
) : (
<CardContent>
No active loading orders please click me to select an order
</CardContent>
)}
</Card>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,245 @@
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import { createColumnHelper } from "@tanstack/react-table";
import { formatInTimeZone } from "date-fns-tz";
import { useEffect, useMemo } from "react";
import { toast } from "sonner";
import { Button } from "../../../../components/ui/button";
import { useSocketRoom } from "../../../../hooks/socket.io.hook";
import { api } from "../../../../lib/apiHelper";
import { useAppForm } from "../../../../lib/formSutff";
import { getActiveLoadingOrders } from "../../../../lib/queries/getActiveDockScanners";
import { getActiveDockScanners } from "../../../../lib/queries/getActiveLoadingOrders";
import { permissionQuery } from "../../../../lib/queries/permsCheck";
import LstTable from "../../../../lib/tableStuff/LstTable";
import SearchableHeader from "../../../../lib/tableStuff/SearchableHeader";
import { finishLoadingOrder } from "..";
export const Route = createFileRoute(
"/warehouse/dockdoorscanning/scans/$dockScans",
)({
component: RouteComponent,
});
function RouteComponent() {
const { data: canSee = false } = useQuery(
permissionQuery({
warehouse: ["update"],
}),
);
const { data, refetch } = useSuspenseQuery(getActiveDockScanners());
const { dockScans } = Route.useParams();
const params = useMemo(
() => ({
dockId: dockScans,
loadingOrder: data[0].currentLoadingOrder ?? undefined,
}),
[dockScans, data],
);
const { data: logs, clearRoom } = useSocketRoom<any>(
`dockDoorLoading`,
params,
);
const { data: loadingPlanItems, refetch: refetchActiveLoading } =
useSuspenseQuery(getActiveLoadingOrders());
const { data: canReadWarehouse = false } = useQuery(
permissionQuery({
warehouse: ["delete"],
}),
);
const columnHelper = createColumnHelper<any>();
const logCount = logs.length;
// TODO: move this to an onMessage: handFunction
/*
const handleLogMessage = useCallback(() => {
refetchActiveLoading();
}, [refetchActiveLoading]);
const { data: logs } = useSocketRoom<LogEntry>("logs", {
onMessage: handleLogMessage,
});
*/
// biome-ignore lint: false
useEffect(() => {
refetchActiveLoading();
}, [logCount, refetchActiveLoading]);
const column = [
columnHelper.accessor("loadingOrder", {
header: ({ column }) => (
<SearchableHeader column={column} title="Loading Order" />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("loadingUnit", {
header: ({ column }) => (
<SearchableHeader column={column} title="Loading Unit" />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("loadingUnitStatus", {
header: ({ column }) => (
<SearchableHeader column={column} title="Status" />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("message", {
header: ({ column }) => (
<SearchableHeader column={column} title="Message" />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("upd_date", {
header: ({ column }) => (
<SearchableHeader column={column} title="Scanned At" />
),
filterFn: "includesString",
cell: (i) => {
function isDateValid(date: any) {
return date instanceof Date && !isNaN(date.getTime());
}
const trueDate = isDateValid(new Date(i.getValue()))
? formatInTimeZone(
i.getValue(),
`${window.LST_CONFIG?.timezone}`,
"MM/dd/yyyy HH:mm:ss",
)
: "invalid time";
return <span>{trueDate}</span>;
},
}),
columnHelper.accessor("status", {
header: ({ column }) => (
<SearchableHeader column={column} title="Status" />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
];
const form = useAppForm({
defaultValues: {
runningNo: "",
dockId: data[0].dockId,
},
onSubmit: async ({ value }) => {
try {
const res = await api.post("/dockDoor/loadUnit", value, {
validateStatus: (status) => status < 500,
});
refetchActiveLoading();
form.reset();
if (!res.data.success) {
toast.error(res.data.data.message);
return;
}
toast.success(res.data.message);
} catch (error) {
console.log(error);
}
},
});
const loadingPlan =
data[0].currentLoadingOrder !== ""
? loadingPlanItems.filter(
(x: any) => x.id === Number(data[0].currentLoadingOrder),
)
: [];
return (
<div>
<div>
<p className="text-2xl text-center">
You are monitoring scans for {data[0].name}
</p>
<p className="text-center">
Current load status:{" "}
{loadingPlan && loadingPlan.length !== 0 ? (
<span>
{loadingPlan[0].loadingPlanItems[0].loadedQuantityLUs} /{" "}
{loadingPlan[0].loadingPlanItems[0].plannedQuantityLUs}
</span>
) : (
<span>"No active loading orders</span>
)}
</p>
</div>
<div className="flex flex-col">
{canSee && (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<div className="flex flex-row">
<div className="mb-2 mr-2 max-w-48">
<form.AppField name="runningNo">
{(field) => (
<field.InputField
label="Running Number"
inputType="text"
required={true}
/>
)}
</form.AppField>
</div>
<div className="flex justify-end mt-8 mr-3 ">
<form.AppForm>
<form.SubmitButton>Submit</form.SubmitButton>
</form.AppForm>
</div>
</div>
</form>
</div>
)}
{loadingPlan && loadingPlan.length > 0 && (
<div className="flex mb-2 gap-2">
<Button
type="button"
onClick={() => {
finishLoadingOrder(
String(data[0].currentLoadingOrder),
dockScans,
refetch,
refetchActiveLoading,
);
clearRoom();
}}
disabled={
(loadingPlan && loadingPlan.length < 0) ||
loadingPlan[0].loadingPlanItems[0].loadedQuantityLUs !==
loadingPlan[0].loadingPlanItems[0].plannedQuantityLUs
}
>
Finish Loading order
</Button>
{canReadWarehouse && (
<Button onClick={() => clearRoom()}>Clear Table</Button>
)}
</div>
)}
</div>
<div>
<LstTable data={logs} columns={column} pageSize={50} />
</div>
</div>
);
}

View File

@@ -15,7 +15,7 @@
"foregroundImage": "./assets/adaptive-icon-white.png", "foregroundImage": "./assets/adaptive-icon-white.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"versionCode": 42, "versionCode": 43,
"minSupportedVersionCode": 33, "minSupportedVersionCode": 33,
"predictiveBackGestureEnabled": false, "predictiveBackGestureEnabled": false,
"package": "net.alpla.lst.mobile", "package": "net.alpla.lst.mobile",

View File

@@ -27,6 +27,7 @@ type ScannerEvent = {
const ERROR_KEYWORDS = [ const ERROR_KEYWORDS = [
"invalid barcode", "invalid barcode",
"invalid command",
"already", "already",
"not on stock", "not on stock",
"article tolerance", "article tolerance",

View File

@@ -0,0 +1,13 @@
CREATE TABLE "dock_door_scans" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"loading_order" text NOT NULL,
"loading_Unit" text,
"loading_unit_status" text DEFAULT 'loaded',
"message" text,
"status" text DEFAULT 'active',
"add_date" timestamp with time zone DEFAULT now(),
"add_user" text DEFAULT 'lst-system',
"upd_date" timestamp with time zone DEFAULT now(),
"upd_user" text DEFAULT 'lst-system',
CONSTRAINT "dock_door_scans_loading_Unit_unique" UNIQUE("loading_Unit")
);

View File

@@ -0,0 +1 @@
ALTER TABLE "dock_door_scans" ADD COLUMN "dock_id" text NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "dock_door_scans" DROP CONSTRAINT "dock_door_scans_loading_Unit_unique";

View File

@@ -0,0 +1 @@
ALTER TABLE "opendock_apt" ADD COLUMN "status" text DEFAULT 'active';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -421,6 +421,34 @@
"when": 1780349486711, "when": 1780349486711,
"tag": "0059_sparkling_joystick", "tag": "0059_sparkling_joystick",
"breakpoints": true "breakpoints": true
},
{
"idx": 60,
"version": "7",
"when": 1780584999699,
"tag": "0060_freezing_hercules",
"breakpoints": true
},
{
"idx": 61,
"version": "7",
"when": 1780692629119,
"tag": "0061_wide_marrow",
"breakpoints": true
},
{
"idx": 62,
"version": "7",
"when": 1781009330205,
"tag": "0062_square_harrier",
"breakpoints": true
},
{
"idx": 63,
"version": "7",
"when": 1781045714275,
"tag": "0063_illegal_mauler",
"breakpoints": true
} }
] ]
} }

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "lst_v3", "name": "lst_v3",
"version": "0.1.0-alpha.2", "version": "0.1.0-alpha.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "lst_v3", "name": "lst_v3",
"version": "0.1.0-alpha.2", "version": "0.1.0-alpha.3",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@dotenvx/dotenvx": "^1.57.0", "@dotenvx/dotenvx": "^1.57.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "lst_v3", "name": "lst_v3",
"version": "0.1.0-alpha.2", "version": "0.1.0-alpha.3",
"description": "The tool that supports us in our everyday alplaprod", "description": "The tool that supports us in our everyday alplaprod",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {