119 Commits

Author SHA1 Message Date
2a648f6306 fix(app): type in the templates... they all looked the same
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m41s
2026-05-27 21:08:02 -05:00
69a9a81a88 ci(app): added more labels to the templates
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
2026-05-27 21:05:57 -05:00
188fdd0eb7 ci(app): changed the issues templates to be easier to add to the part of the app needed
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m37s
2026-05-27 21:02:18 -05:00
db28635c8c fix(mobile): ui over lapping
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 3m1s
the ui elements would over lap and cause visual issues with the scanning and seeing the old labels

closes #25
2026-05-27 20:57:49 -05:00
bcdf9566bc refactor(mobile): moved logout to the tab bar 2026-05-27 20:56:35 -05:00
c15ee070e7 refactor(mobile): setup - added button to go home as it caused confustion 2026-05-27 20:56:08 -05:00
347edb7078 fix(mobile users): corrected and endpoint that prevented us from change the pin number 2026-05-27 20:55:25 -05:00
fe0b1573f3 feat(mobile): dock door scanning backend added
ref #12
2026-05-27 20:54:47 -05:00
9c0ef1f5df fix(mobile): scan log incorrect user ref 2026-05-27 20:53:07 -05:00
8b076949a7 feat(warehousing): ppoo monitoring added
this will monitor ppoo every 45 seconds as long as someone is on the page.

closes #13
2026-05-27 20:52:34 -05:00
6d0fb8aee4 feat(mobile): added auto download of latest
this will predownload the latest if its there, this will speed up the update as the user will only
need to scan a single command and it will install restart app
2026-05-27 20:50:07 -05:00
3a0c729b9a chore(release): 0.1.0-alpha.2
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 3m45s
Release and Build Image / release (push) Successful in 15s
2026-05-23 11:42:48 -05:00
057a570e43 fix(docs): wrong location for images
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m39s
2026-05-23 11:41:43 -05:00
52974aa0b4 refactor(mobile): added missing error to the scanner
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m55s
2026-05-23 11:22:44 -05:00
ecfbda9036 fix(mobile): correction to axios helper 2026-05-23 11:22:25 -05:00
389211186f feat(opendock): added in new article link setup for fine tuning how od works 2026-05-23 11:22:02 -05:00
3a27fd8542 docs(mobile): updated imgs to be a little smaller 2026-05-23 11:21:19 -05:00
1f6637c512 fix(build): crashes when files changed :(
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m34s
BREAKING CHANGE: gives a rabbit hole error

closes #24
2026-05-21 21:51:21 -05:00
1840ac5e58 feat(opendock): scheduing updates
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m39s
ref #23
2026-05-21 21:42:18 -05:00
636daaed0a fix(sql queries): disable job would error so now we will check if it exists before trying to kill it
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m37s
2026-05-20 20:49:54 -05:00
71c83062cb ci(docker): changes to the ignore file 2026-05-20 20:49:21 -05:00
cd67c4de80 refactor(opendock): changes to how we do the intergration scheduling
ref #23
2026-05-20 20:49:00 -05:00
36ac1dccb4 chore(release): 0.1.0-alpha.1
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 3m39s
Release and Build Image / release (push) Successful in 15s
2026-05-18 21:39:59 -05:00
514a44b6de refactor(servers): changed activeity around and trying to make use of it
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
2026-05-18 21:38:08 -05:00
a7bb364a2f fix(settings): failed build due it dormant import
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 3m42s
2026-05-18 21:23:34 -05:00
047cc7cdf0 refactor(users): lots of auth stuff added to make it more easy to manage users
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 2m9s
2026-05-18 21:19:20 -05:00
8dc4d70e28 ci(app): added in chokidar to monitor folders 2026-05-18 21:18:42 -05:00
c8931c7249 fix(notifications): reprinting
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m20s
correction to external labeling

ref #20
2026-05-14 14:18:40 -05:00
67f36c5499 chore(release): 0.1.0-alpha.0
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m55s
Release and Build Image / release (push) Successful in 9s
2026-05-13 20:56:14 -05:00
ebf1060475 refactor(servers): server name now links to the actual server:port
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m24s
2026-05-13 20:53:27 -05:00
c64392f457 refactor(api): changes to call a helper api to quit and redirect if needed 2026-05-13 20:52:55 -05:00
e9e73c829c ci(servives): helpers moved around 2026-05-13 20:52:16 -05:00
bcb7773007 ci(updateserver): changes to actually add the new env stuff 2026-05-13 20:51:54 -05:00
eb950d2c29 refactor(scanner): more scanner admin stuff 2026-05-13 20:51:22 -05:00
2616acf106 fix(notification subs): made it so only acitve show
closes #14
2026-05-13 20:50:51 -05:00
30ff7b71d9 refactor(app): changed ways we get data so we can have better reasons why app no worky 2026-05-13 20:49:43 -05:00
e7af3d1182 refactor(scanner): removed 69 as an option lol 2026-05-13 20:48:43 -05:00
3e66c3920d feat(notification): migrated sql cleanup 2026-05-13 20:48:22 -05:00
eb9d77c3d4 docs(scanner): added in westbend and dayton commands to scan for updates 2026-05-13 20:47:38 -05:00
342a97f6b1 refactor(users): some user refactoring and configuring 2026-05-13 16:42:36 -05:00
b0c7277a6c docs(scanner): added in instructions on how to update the scanner
only test2 stage now commands for now.
2026-05-12 20:26:19 -05:00
dc95e50a84 ci(mobile): added in ehs config to make it more easy for users to update the scanner app on the fly 2026-05-12 12:04:19 -05:00
d2a9e1d110 fix(app): required auth was in wrong spot caused entire app to want you logged in 2026-05-12 12:02:59 -05:00
a9c69250bd refactor(mobile): scanner response
ref #16
2026-05-12 08:53:12 -05:00
d61be61f44 refactor(scanner): logging - version of app 2026-05-11 19:06:25 -05:00
f5bae2c0c2 fix(anaylistics): changes to the daily section so it populates correctly now
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 1m9s
2026-05-11 15:41:20 -05:00
05758791be fix(scanner): fixes to be more clear that you need to scan a command to start
closes #16
2026-05-11 15:40:49 -05:00
51026e3e2c ci(notification): removal of more console logs that shouldnt be here 2026-05-11 15:38:44 -05:00
9631736e26 chore(mobile): removed console log that shouldnt be there 2026-05-11 15:38:04 -05:00
ce9d8eaaf5 feat(scan users): added in the place to add the new scanner users in 2026-05-11 15:37:38 -05:00
1bbf5c2a49 fix(table): skelly table causing hydration error 2026-05-11 15:35:46 -05:00
13718fe702 fix(anaylitics): unique values were missing causing a weird crash 2026-05-11 14:00:54 -05:00
0de2579942 fix(scanner): changed to not crash on logging
cloases #19
2026-05-11 13:35:07 -05:00
7c31b43a4a fix(app): emit.maxlistener issue
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m27s
BREAKING CHANGE: moved teh middleware to call the api hits to the main app and removed from
everywhere else

closes #18
2026-05-11 13:25:43 -05:00
85e96f5ed1 fix(scanner): logut out corrections
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m24s
refs #17
2026-05-11 07:59:17 -05:00
6b515c608f chore(release): 0.0.2-alpha.10
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m12s
Release and Build Image / release (push) Successful in 16s
2026-05-08 15:09:49 -05:00
d8869b103b fix(scan user): typo 2026-05-08 15:08:33 -05:00
1dba774abc chore(server): removed a console log that shouldnt be there
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 1m10s
2026-05-08 15:06:08 -05:00
505d7cea5d refactor(scan): bump in build and style update 2026-05-08 15:05:47 -05:00
1ff5e5032f test(scanusers): added in scan users as test 2026-05-08 15:05:09 -05:00
5fa70da90c chore(file): name changes.. spelled wrong 2026-05-08 15:04:31 -05:00
0459cd788a fix(spelling): corrected the spelling on the file 2026-05-08 15:03:53 -05:00
7d7d991122 fix(schema): typo in add_date 2026-05-08 15:03:33 -05:00
2721bb2a3b feat(api hits): added in api hits for monitoring 2026-05-08 15:03:03 -05:00
4424c742d2 refactor(analyitics): finished analyitics as a base 2026-05-08 15:02:34 -05:00
6d8499bfb8 ci(templates): force useage 2026-05-08 15:01:44 -05:00
9edafc9d28 feat(analytics): added in backend anaylitics 2026-05-07 10:20:50 -05:00
e9b0101095 ci(template): bug in getting the template to work correctly
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m28s
2026-05-07 09:01:15 -05:00
ca885fb01a ci(templates): added in templates for the repo to make it more easy to manage and add in new ideas
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m24s
2026-05-07 08:50:06 -05:00
edb3668548 refactor(scanner): added toasts in to make it look better
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m25s
2026-05-06 19:42:52 -05:00
87803eed43 feat(scanner): added in lanechecks 2026-05-06 19:42:22 -05:00
e61038e004 chore(release): 0.0.2-alpha.9
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m42s
Release and Build Image / release (push) Successful in 28s
2026-05-06 13:34:30 -05:00
d99449ddc4 test(scanner): lane check
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m41s
2026-05-06 13:30:58 -05:00
3552ca31f9 build(builds): changed to ip as its on the same server
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 4s
2026-05-06 12:27:20 -05:00
b578f05d64 build(release): bypass cloudflare upload limit
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 3m43s
2026-05-06 12:17:42 -05:00
4ca74de279 refactor(mobile): valildation of server after each scan
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 3m40s
2026-05-06 12:10:14 -05:00
12412536d1 refactor(scanner): finished login stuff for current routes 2026-05-06 12:09:47 -05:00
a38e2e0339 refactor(scanner): added in running number 2026-05-06 12:09:09 -05:00
8c253a90b6 chore(release): 0.0.2-alpha.8
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 4m12s
Release and Build Image / release (push) Failing after 2m19s
2026-05-06 05:08:27 -05:00
ba30281e59 feat(mobile): auth added in
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
2026-05-06 05:07:16 -05:00
2ad78e22f1 chore(release): 0.0.2-alpha.7
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 4m3s
Release and Build Image / release (push) Failing after 2m30s
2026-05-05 19:50:58 -05:00
518c0a8c19 refactor(scanner): format changes
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
2026-05-05 19:50:02 -05:00
cd13360cfb feat(intial auth): intial auth setup for the scanner
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
2026-05-05 19:48:36 -05:00
4e0cf8c54c refactor(docker compose): changed to have the correct url that will be used as this is for auth 2026-05-05 14:52:36 -05:00
36995e9fb4 refactor(gp connection): added in gp ip into env if not there use static name for dns
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 8m37s
2026-05-05 13:15:52 -05:00
30ffd843c7 feat(mobile): update notifications and more error handling added
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m24s
2026-04-30 17:02:21 -05:00
bb6155c969 refactor(mobile): more look and feel work
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m17s
2026-04-28 19:49:07 -05:00
7d2f048932 feat(mobile): shadcn like and tailwind added to make things look yummy
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m21s
2026-04-27 21:23:40 -05:00
649ae1ee9f feat(mobile): new route for the ehs launcher 2026-04-27 21:22:59 -05:00
8446dbc955 feat(servers): added iowa ebm
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m45s
2026-04-26 19:51:49 -05:00
0b7318f856 fix(mobile): typo for version checking 2026-04-26 19:51:12 -05:00
bddc9aca0d refactor(mobile): moved the versioning lookup at at the mobile folder plus renamed 2026-04-26 18:33:28 -05:00
77b4533dea feat(scanner): more work on the scanner and can now scan to prod no lst right now
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m41s
2026-04-25 18:13:07 -05:00
83a542d1b7 build(scripts): changing how the relase works so it purposly builds before it trys to release
this is to help prevent errors in the build and release stuff in git
2026-04-23 07:48:13 -05:00
4855412733 chore(release): 0.0.2-alpha.6
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m20s
Release and Build Image / release (push) Successful in 14s
2026-04-23 07:24:45 -05:00
251970ec8f chore(release): 0.0.2-alpha.1
All checks were successful
Release and Build Image / release (push) Successful in 2m36s
2026-04-23 07:24:12 -05:00
f7ea5f709e chore(release): 0.0.2-alpha.0
All checks were successful
Release and Build Image / release (push) Successful in 2m51s
2026-04-23 07:24:02 -05:00
3d3c2aa964 chore(release): 0.0.1
All checks were successful
Release and Build Image / release (push) Successful in 2m39s
2026-04-23 07:23:49 -05:00
781025dca0 fix(frontend): lingering import crashed us 2026-04-23 07:23:25 -05:00
a593bb2baa chore(doc remove): removed a doc and put it in the real area for docs 2026-04-23 07:23:01 -05:00
759f96b0b6 chore(release): 0.0.1-alpha.5
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 1m36s
Release and Build Image / release (push) Failing after 38s
2026-04-23 07:11:50 -05:00
de5df2b00b chore(scripts): added in a helper to remove old stuff
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
2026-04-23 07:10:53 -05:00
4d53af0338 feat(servers): added marked tree in to the mix 2026-04-23 07:10:27 -05:00
f7276ca2d7 feat(oidc): added in so we could use an oidc to login as well :D 2026-04-23 07:09:49 -05:00
d6328ab764 fix(gp): weird issue with db username and password
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m41s
2026-04-22 06:39:22 -05:00
a6d53f0266 refactor(sql): changed sql connection to ip:port
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m42s
this change was needed for when we run in docker so we can connect to the servers
2026-04-22 05:40:38 -05:00
7962463927 refactor(server): server updates can now only be done from a dev pc
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m54s
2026-04-21 19:01:52 -05:00
f716de1a58 chore(clean): removed bruno api a proper api doc will be added to lst later 2026-04-21 19:01:21 -05:00
88cef2a56c refactor(servers): added mcd and stp1 2026-04-21 19:00:30 -05:00
cb00addee9 feat(admin): moved server build/update to full app
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m27s
2026-04-21 07:36:04 -05:00
b832d7aa1e fix(datamart): fixes to correct how we handle activations of new features and legacy queries 2026-04-20 08:49:24 -05:00
32517d0c98 fix(inventory): changes to accruatly adjust the query and check the feature set 2026-04-20 07:25:33 -05:00
82f8369640 refactor(scanner): more basic work to get the scanner just running
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m33s
2026-04-19 17:20:57 -05:00
3734d9daac feat(lstmobile): intial scanner setup kinda working
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m7s
2026-04-17 16:47:09 -05:00
a1eeadeec4 fix(psi): refactor psi queries 2026-04-17 16:46:44 -05:00
3639c1b77c fix(logistics): purchasing monitoring was going off every 5th min instead of every 5 min 2026-04-17 14:47:23 -05:00
cfbc156517 fix(logistics): historical issue where it was being really weird 2026-04-17 08:02:44 -05:00
fb3cd85b41 fix(ocp): fixes to make sure we always hav printer.data as an array or dont do anything 2026-04-15 09:20:08 -05:00
5b1c88546f fix(datamart): if we do not have 2.0 warehousing activate we need to use legacy 2026-04-15 08:45:48 -05:00
395 changed files with 86161 additions and 1527 deletions

View File

@@ -9,4 +9,4 @@ builds
testFiles
nssm.exe
postgresql-17.9-2-windows-x64.exe
VSCodeUserSetup-x64-1.112.0.msi
VSCodeSetup-x64-1.120.0.msi

View File

@@ -50,3 +50,11 @@ GP_PASSWORD=
# how often to check for new/updated queries in min
QUERY_TIME_TYPE=m #valid options are m, h
QUERY_CHECK=1
# Oauth setup
PROVIDER=""
CLIENT_ID=""
CLIENT_SECRET=""
CLIENT_SCOPES="openid profile email groups"
DISCOVERY_URL=""

View File

@@ -0,0 +1,66 @@
---
name: Bug Report - Frontend
about: Report something that is broken or not working correctly
title: "[BUG - Frontend] "
ref: "main"
labels:
- bug
- frontend
---
# Summary
Briefly explain the issue.
---
# Steps To Reproduce
1. Go to ...
2. Click ...
3. Scan ...
4. Error occurs ...
---
# Expected Behavior
What should have happened?
---
# Actual Behavior
What actually happened instead?
---
# Severity
- [ ] Low
- [ ] Medium
- [ ] High
- [ ] Critical
---
# Environment
Example:
- Production
- Development
- Zebra Scanner
- Mobile Device
- Windows Server
- Docker
---
# Logs / Screenshots
Paste logs or upload screenshots here.
```txt
Paste logs here

View File

@@ -0,0 +1,66 @@
---
name: Bug Report - Mobile
about: Report something that is broken or not working correctly
title: "[BUG - Mobile] "
ref: "main"
labels:
- bug
- mobile
---
# Summary
Briefly explain the issue.
---
# Steps To Reproduce
1. Go to ...
2. Click ...
3. Scan ...
4. Error occurs ...
---
# Expected Behavior
What should have happened?
---
# Actual Behavior
What actually happened instead?
---
# Severity
- [ ] Low
- [ ] Medium
- [ ] High
- [ ] Critical
---
# Environment
Example:
- Production
- Development
- Zebra Scanner
- Mobile Device
- Windows Server
- Docker
---
# Logs / Screenshots
Paste logs or upload screenshots here.
```txt
Paste logs here

View File

@@ -0,0 +1,67 @@
---
name: Bug Report - Server
about: Report something that is broken or not working correctly
title: "[BUG - Server] "
ref: "main"
labels:
- bug
- server
---
# Summary
Briefly explain the issue.
---
# Steps To Reproduce
1. Go to ...
2. Click ...
3. Scan ...
4. Error occurs ...
---
# Expected Behavior
What should have happened?
---
# Actual Behavior
What actually happened instead?
---
# Severity
- [ ] Low
- [ ] Medium
- [ ] High
- [ ] Critical
---
# Environment
Example:
- Production
- Development
- Zebra Scanner
- Mobile Device
- Windows Server
- Docker
---
# Logs / Screenshots
Paste logs or upload screenshots here.
```txt
Paste logs here

View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,48 @@
---
name: Enhancement - Frontend
about: Improve or refine an existing feature
title: "[ENHANCEMENT - Frontend] "
ref: "main"
labels:
- enhancement
- frontend
---
# Existing Feature
What current feature or workflow is being improved?
Example:
- Notifications
- Scanner Login
- Release Monitor
- Printing
- Auth
---
# Proposed Improvement
Describe the improvement.
---
# Expected Benefit
Why would this improvement help?
---
# Impact
- [ ] Small
- [ ] Medium
- [ ] Large
---
# Additional Notes
Anything else worth mentioning.

View File

@@ -0,0 +1,48 @@
---
name: Enhancement - Mobile
about: Improve or refine an existing feature
title: "[ENHANCEMENT - Mobile] "
ref: "main"
labels:
- enhancement
- mobile
---
# Existing Feature
What current feature or workflow is being improved?
Example:
- Notifications
- Scanner Login
- Release Monitor
- Printing
- Auth
---
# Proposed Improvement
Describe the improvement.
---
# Expected Benefit
Why would this improvement help?
---
# Impact
- [ ] Small
- [ ] Medium
- [ ] Large
---
# Additional Notes
Anything else worth mentioning.

View File

@@ -0,0 +1,48 @@
---
name: Enhancement - Server
about: Improve or refine an existing feature
title: "[ENHANCEMENT - Server] "
ref: "main"
labels:
- enhancement
- server
---
# Existing Feature
What current feature or workflow is being improved?
Example:
- Notifications
- Scanner Login
- Release Monitor
- Printing
- Auth
---
# Proposed Improvement
Describe the improvement.
---
# Expected Benefit
Why would this improvement help?
---
# Impact
- [ ] Small
- [ ] Medium
- [ ] Large
---
# Additional Notes
Anything else worth mentioning.

View File

@@ -0,0 +1,41 @@
---
name: Feature Request - Frontend
about: Suggest a brand new feature or module
title: "[FEATURE - Frontend] "
ref: "main"
labels:
- feature
- frontend
---
# Problem Statement
What problem are you trying to solve?
---
# Proposed Solution
Describe the feature you would like added.
---
# Alternatives Considered
Any other ideas, workarounds, or approaches?
---
# Priority
- [ ] Nice to Have
- [ ] Medium Priority
- [ ] High Priority
- [ ] Critical
---
# Additional Context
Add mockups, screenshots, examples, or notes here.

View File

@@ -0,0 +1,41 @@
---
name: Feature Request - Mobile
about: Suggest a brand new feature or module
title: "[FEATURE - Mobile] "
ref: "main"
labels:
- feature
- mobile
---
# Problem Statement
What problem are you trying to solve?
---
# Proposed Solution
Describe the feature you would like added.
---
# Alternatives Considered
Any other ideas, workarounds, or approaches?
---
# Priority
- [ ] Nice to Have
- [ ] Medium Priority
- [ ] High Priority
- [ ] Critical
---
# Additional Context
Add mockups, screenshots, examples, or notes here.

View File

@@ -0,0 +1,41 @@
---
name: Feature Request - Server
about: Suggest a brand new feature or module
title: "[FEATURE - Server] "
ref: "main"
labels:
- feature
- server
---
# Problem Statement
What problem are you trying to solve?
---
# Proposed Solution
Describe the feature you would like added.
---
# Alternatives Considered
Any other ideas, workarounds, or approaches?
---
# Priority
- [ ] Nice to Have
- [ ] Medium Priority
- [ ] High Priority
- [ ] Critical
---
# Additional Context
Add mockups, screenshots, examples, or notes here.

View File

@@ -12,20 +12,20 @@ jobs:
steps:
- name: Checkout (local)
run: |
git clone https://git.tuffraid.net/cowch/lst_v3.git .
git clone http://10.75.9.150:3100/cowch/lst_v3.git .
git checkout ${{ gitea.sha }}
- name: Login to registry
run: echo "${{ secrets.PASSWORD }}" | docker login git.tuffraid.net -u "cowch" --password-stdin
run: echo "${{ secrets.PASSWORD }}" | docker login 10.75.9.150:3100 -u "cowch" --password-stdin
- name: Build image
run: |
docker build \
-t git.tuffraid.net/cowch/lst_v3:latest \
-t git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }} \
-t 10.75.9.150:3100/cowch/lst_v3:latest \
-t 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }} \
.
- name: Push
run: |
docker push git.tuffraid.net/cowch/lst_v3:latest
docker push git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }}
docker push 10.75.9.150:3100/cowch/lst_v3:latest
docker push 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }}

View File

@@ -14,12 +14,12 @@ jobs:
# Examples:
# http://gitea.internal.lan:3000
# https://gitea-origin.yourdomain.local
GITEA_INTERNAL_URL: "https://git.tuffraid.net"
GITEA_INTERNAL_URL: "http://10.75.9.150:3100" #"https://git.tuffraid.net"
# Internal/origin registry host. Usually same host as above, but without protocol.
# Example:
# gitea.internal:3000
REGISTRY_HOST: "git.tuffraid.net"
REGISTRY_HOST: "10.75.9.150:3100" #"git.tuffraid.net"
steps:
- name: Check out repository

5
.gitignore vendored
View File

@@ -5,11 +5,13 @@ builds
.buildNumber
temp
brunoApi
downloads
.scriptCreds
node-v24.14.0-x64.msi
postgresql-17.9-2-windows-x64.exe
VSCodeUserSetup-x64-1.112.0.exe
VSCodeSetup-x64-1.120.0.exe
nssm.exe
frontend/.tanstack
# Logs
logs
@@ -148,3 +150,4 @@ dist
.yarn/install-state.gz
.pnp.*
frontend/.tanstack/tmp/2249110e-da91fb0b1b87b6c4cc3e2c2cd25037fd

View File

@@ -1,6 +1,6 @@
{
"editor.defaultFormatter": "biomejs.biome",
"workbench.colorTheme": "Default Dark+",
"workbench.colorTheme": "Dark+",
"terminal.integrated.env.windows": {},
"editor.formatOnSave": true,
"typescript.preferences.importModuleSpecifier": "relative",

View File

@@ -1,5 +1,270 @@
# All Changes to LST can be found below.
## [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)
### ⚠ BREAKING CHANGES
* **build:** gives a rabbit hole error
### 🌟 Enhancements
* **opendock:** added in new article link setup for fine tuning how od works ([3892111](https://git.tuffraid.net/cowch/lst_v3/commits/389211186f00cb8a6fdd5de092a944fa7e5898aa))
* **opendock:** scheduing updates ([1840ac5](https://git.tuffraid.net/cowch/lst_v3/commits/1840ac5e580c726c452216480b6e14e7c52a0f35)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
### 🐛 Bug fixes
* **build:** crashes when files changed :( ([1f6637c](https://git.tuffraid.net/cowch/lst_v3/commits/1f6637c512dcd465c5000f8d1baaa8e76766edc1)), closes [#24](https://git.tuffraid.net/cowch/lst_v3/issues/24)
* **docs:** wrong location for images ([057a570](https://git.tuffraid.net/cowch/lst_v3/commits/057a570e43a8e1763652d98244c90999c3fccd42))
* **mobile:** correction to axios helper ([ecfbda9](https://git.tuffraid.net/cowch/lst_v3/commits/ecfbda9036f3d68c93e9c1d81021efa8093f18e2))
* **sql queries:** disable job would error so now we will check if it exists before trying to kill it ([636daae](https://git.tuffraid.net/cowch/lst_v3/commits/636daaed0adeda908e7e850a4f5bb20d7bbef861))
### 📚 Documentation
* **mobile:** updated imgs to be a little smaller ([3a27fd8](https://git.tuffraid.net/cowch/lst_v3/commits/3a27fd8542c3fa4ad5520532c2f10c6e3eaa951c))
### 🛠️ Code Refactor
* **mobile:** added missing error to the scanner ([52974aa](https://git.tuffraid.net/cowch/lst_v3/commits/52974aa0b4f21431777b773200a57f185b4babd2))
* **opendock:** changes to how we do the intergration scheduling ([cd67c4d](https://git.tuffraid.net/cowch/lst_v3/commits/cd67c4de80b6f0244afc639a7360e9dc2ba97a21)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
### 📈 Project changes
* **docker:** changes to the ignore file ([71c8306](https://git.tuffraid.net/cowch/lst_v3/commits/71c83062cb644796ebbfd845084ac6c019206faa))
## [0.1.0-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.0...v0.1.0-alpha.1) (2026-05-19)
### 🐛 Bug fixes
* **notifications:** reprinting ([c8931c7](https://git.tuffraid.net/cowch/lst_v3/commits/c8931c7249b8f532b5dd37df3271da98f14ee710)), closes [#20](https://git.tuffraid.net/cowch/lst_v3/issues/20)
* **settings:** failed build due it dormant import ([a7bb364](https://git.tuffraid.net/cowch/lst_v3/commits/a7bb364a2fd49d96b6195aca0cd58ba57c58f3a6))
### 🛠️ Code Refactor
* **servers:** changed activeity around and trying to make use of it ([514a44b](https://git.tuffraid.net/cowch/lst_v3/commits/514a44b6de3efe8dd8b308d98bdbc82e31ed8427))
* **users:** lots of auth stuff added to make it more easy to manage users ([047cc7c](https://git.tuffraid.net/cowch/lst_v3/commits/047cc7cdf036c39a89a0b87ab59dda8328efe0c0))
### 📈 Project changes
* **app:** added in chokidar to monitor folders ([8dc4d70](https://git.tuffraid.net/cowch/lst_v3/commits/8dc4d70e2827f0a40d2f54886fd757c8a2dc5ac4))
## [0.1.0-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.10...v0.1.0-alpha.0) (2026-05-14)
### ⚠ BREAKING CHANGES
* **app:** moved teh middleware to call the api hits to the main app and removed from
everywhere else
### 🌟 Enhancements
* **notification:** migrated sql cleanup ([3e66c39](https://git.tuffraid.net/cowch/lst_v3/commits/3e66c3920d65cee7a0a788f3910c1ddf09a07805))
* **scan users:** added in the place to add the new scanner users in ([ce9d8ea](https://git.tuffraid.net/cowch/lst_v3/commits/ce9d8eaaf5bcb8f53ea4bdc191347df8d589fdfa))
### 🐛 Bug fixes
* **anaylistics:** changes to the daily section so it populates correctly now ([f5bae2c](https://git.tuffraid.net/cowch/lst_v3/commits/f5bae2c0c24b85423c5c421164d94d58159ff70a))
* **anaylitics:** unique values were missing causing a weird crash ([13718fe](https://git.tuffraid.net/cowch/lst_v3/commits/13718fe70293c039bd1d9bf8cf395852e6ea6c21))
* **app:** emit.maxlistener issue ([7c31b43](https://git.tuffraid.net/cowch/lst_v3/commits/7c31b43a4a313237fa63c0c9bbc3690b74f63a6f)), closes [#18](https://git.tuffraid.net/cowch/lst_v3/issues/18)
* **app:** required auth was in wrong spot caused entire app to want you logged in ([d2a9e1d](https://git.tuffraid.net/cowch/lst_v3/commits/d2a9e1d1107ea05f13725e9528bc6ab1566c8efb))
* **notification subs:** made it so only acitve show ([2616acf](https://git.tuffraid.net/cowch/lst_v3/commits/2616acf106530f5c5ee04d1b79033795cf06b42d)), closes [#14](https://git.tuffraid.net/cowch/lst_v3/issues/14)
* **scanner:** changed to not crash on logging ([0de2579](https://git.tuffraid.net/cowch/lst_v3/commits/0de25799420f38a293ee9acc70eb36e3287145c4)), closes [#19](https://git.tuffraid.net/cowch/lst_v3/issues/19)
* **scanner:** fixes to be more clear that you need to scan a command to start ([0575879](https://git.tuffraid.net/cowch/lst_v3/commits/05758791be7a50e90b5da05d4977e618c311f654)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
* **scanner:** logut out corrections ([85e96f5](https://git.tuffraid.net/cowch/lst_v3/commits/85e96f5ed13a81fd466c6bbff31c539244750838)), closes [#17](https://git.tuffraid.net/cowch/lst_v3/issues/17)
* **table:** skelly table causing hydration error ([1bbf5c2](https://git.tuffraid.net/cowch/lst_v3/commits/1bbf5c2a4955107a36ace05595886d19cc8e64f4))
### 📝 Chore
* **mobile:** removed console log that shouldnt be there ([9631736](https://git.tuffraid.net/cowch/lst_v3/commits/9631736e263ed00189f8118f686690cab25f09d3))
### 📚 Documentation
* **scanner:** added in instructions on how to update the scanner ([b0c7277](https://git.tuffraid.net/cowch/lst_v3/commits/b0c7277a6cdb5becec3a994ea1d5cc2d7b0326aa))
* **scanner:** added in westbend and dayton commands to scan for updates ([eb9d77c](https://git.tuffraid.net/cowch/lst_v3/commits/eb9d77c3d4767fd961759662ef44c3e09e00946b))
### 🛠️ Code Refactor
* **api:** changes to call a helper api to quit and redirect if needed ([c64392f](https://git.tuffraid.net/cowch/lst_v3/commits/c64392f45769108aa4134c7fd865f3d4bc664179))
* **app:** changed ways we get data so we can have better reasons why app no worky ([30ff7b7](https://git.tuffraid.net/cowch/lst_v3/commits/30ff7b71d9d159ced263a5330d70d53b97393157))
* **mobile:** scanner response ([a9c6925](https://git.tuffraid.net/cowch/lst_v3/commits/a9c69250bd3272ad682751e41b671c119cb678f1)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
* **scanner:** logging - version of app ([d61be61](https://git.tuffraid.net/cowch/lst_v3/commits/d61be61f4433a2be2678d724f4724301931614c9))
* **scanner:** more scanner admin stuff ([eb950d2](https://git.tuffraid.net/cowch/lst_v3/commits/eb950d2c29f692b806d5cc4ab7014bd59a726a8d))
* **scanner:** removed 69 as an option lol ([e7af3d1](https://git.tuffraid.net/cowch/lst_v3/commits/e7af3d11824b42915cf6789f9c508a727511d678))
* **servers:** server name now links to the actual server:port ([ebf1060](https://git.tuffraid.net/cowch/lst_v3/commits/ebf1060475d37627b371bc6c79507cdde411600b))
* **users:** some user refactoring and configuring ([342a97f](https://git.tuffraid.net/cowch/lst_v3/commits/342a97f6b1054443b9126186d2c7872fbd8586da))
### 📈 Project changes
* **mobile:** added in ehs config to make it more easy for users to update the scanner app on the fly ([dc95e50](https://git.tuffraid.net/cowch/lst_v3/commits/dc95e50a8412b4fbc629fd44fcb5c77295583ca8))
* **notification:** removal of more console logs that shouldnt be here ([51026e3](https://git.tuffraid.net/cowch/lst_v3/commits/51026e3e2cce4d7f696d26aae305b3fd221f5bb1))
* **servives:** helpers moved around ([e9e73c8](https://git.tuffraid.net/cowch/lst_v3/commits/e9e73c829c2e5726650c0ac7ffa6a9055dbc982b))
* **updateserver:** changes to actually add the new env stuff ([bcb7773](https://git.tuffraid.net/cowch/lst_v3/commits/bcb7773007894ac2f85fe2a0b47faf14c7b474ad))
## [0.0.2-alpha.10](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.9...v0.0.2-alpha.10) (2026-05-08)
### 🌟 Enhancements
* **analytics:** added in backend anaylitics ([9edafc9](https://git.tuffraid.net/cowch/lst_v3/commits/9edafc9d2810f339d197c10dfc6a037b3352d81f))
* **api hits:** added in api hits for monitoring ([2721bb2](https://git.tuffraid.net/cowch/lst_v3/commits/2721bb2a3bf1f829591d26a0716f74c4f7fc0c79))
* **scanner:** added in lanechecks ([87803ee](https://git.tuffraid.net/cowch/lst_v3/commits/87803eed43069b73de3f66e6524bb45da9c46334))
### 🐛 Bug fixes
* **scan user:** typo ([d8869b1](https://git.tuffraid.net/cowch/lst_v3/commits/d8869b103b80e4208b3928a370a9524ef33d25cd))
* **schema:** typo in add_date ([7d7d991](https://git.tuffraid.net/cowch/lst_v3/commits/7d7d9911223905d6767b87d2471b6607a90f1ea7))
* **spelling:** corrected the spelling on the file ([0459cd7](https://git.tuffraid.net/cowch/lst_v3/commits/0459cd788aaad6ac54a67e23f798ce5e5a437394))
### 📝 Chore
* **file:** name changes.. spelled wrong ([5fa70da](https://git.tuffraid.net/cowch/lst_v3/commits/5fa70da90ca290ee45088e9c8eb06ba48a6677af))
* **server:** removed a console log that shouldnt be there ([1dba774](https://git.tuffraid.net/cowch/lst_v3/commits/1dba774abc54bf20850c3f26d49926e86d59712d))
### 🛠️ Code Refactor
* **analyitics:** finished analyitics as a base ([4424c74](https://git.tuffraid.net/cowch/lst_v3/commits/4424c742d24dc230b2bc1782e33535184c378cf0))
* **scan:** bump in build and style update ([505d7ce](https://git.tuffraid.net/cowch/lst_v3/commits/505d7cea5d2f52fc4a3ec1edff1878be703c4034))
* **scanner:** added toasts in to make it look better ([edb3668](https://git.tuffraid.net/cowch/lst_v3/commits/edb366854825f4c24ab5d77cf88759465d067f00))
### 📝 Testing Code
* **scanusers:** added in scan users as test ([1ff5e50](https://git.tuffraid.net/cowch/lst_v3/commits/1ff5e5032f9c8bf81f972dc99d6c86ba8d3936c6))
### 📈 Project changes
* **template:** bug in getting the template to work correctly ([e9b0101](https://git.tuffraid.net/cowch/lst_v3/commits/e9b01010954624aed738cd6e4b82fccbba195cc4))
* **templates:** added in templates for the repo to make it more easy to manage and add in new ideas ([ca885fb](https://git.tuffraid.net/cowch/lst_v3/commits/ca885fb01a3c8bc22694c2e05269c43fcd4de70e))
* **templates:** force useage ([6d8499b](https://git.tuffraid.net/cowch/lst_v3/commits/6d8499bfb85f7b9131b1ec7b31a17c4256d0f0cf))
## [0.0.2-alpha.9](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.8...v0.0.2-alpha.9) (2026-05-06)
### 🛠️ Code Refactor
* **mobile:** valildation of server after each scan ([4ca74de](https://git.tuffraid.net/cowch/lst_v3/commits/4ca74de2795cea7244e38697d16afe2822164ed6))
* **scanner:** added in running number ([a38e2e0](https://git.tuffraid.net/cowch/lst_v3/commits/a38e2e033977b725538e9a9046098d94194d549e))
* **scanner:** finished login stuff for current routes ([1241253](https://git.tuffraid.net/cowch/lst_v3/commits/12412536d10981013053c39d156c6c9cb0babd11))
### 📝 Testing Code
* **scanner:** lane check ([d99449d](https://git.tuffraid.net/cowch/lst_v3/commits/d99449ddc4e2777c1b0fe9189ba0a7c01fe1dd8f))
### 📈 Project Builds
* **builds:** changed to ip as its on the same server ([3552ca3](https://git.tuffraid.net/cowch/lst_v3/commits/3552ca31f9f7b3bcbe557a145e7eb154bfdae79c))
* **release:** bypass cloudflare upload limit ([b578f05](https://git.tuffraid.net/cowch/lst_v3/commits/b578f05d6482f9b6f30febeee6ab0b708a70f68b))
## [0.0.2-alpha.8](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.7...v0.0.2-alpha.8) (2026-05-06)
### 🌟 Enhancements
* **mobile:** auth added in ([ba30281](https://git.tuffraid.net/cowch/lst_v3/commits/ba30281e59040513a036fb7413e372457d04a7c8))
## [0.0.2-alpha.7](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.6...v0.0.2-alpha.7) (2026-05-06)
### 🌟 Enhancements
* **intial auth:** intial auth setup for the scanner ([cd13360](https://git.tuffraid.net/cowch/lst_v3/commits/cd13360cfb931daca50fd7b111e1c8f8ab09a909))
* **mobile:** new route for the ehs launcher ([649ae1e](https://git.tuffraid.net/cowch/lst_v3/commits/649ae1ee9f245a9b5d308ea8a636357bf72b1e34))
* **mobile:** shadcn like and tailwind added to make things look yummy ([7d2f048](https://git.tuffraid.net/cowch/lst_v3/commits/7d2f048932b77269568149de34351840b75486e2))
* **mobile:** update notifications and more error handling added ([30ffd84](https://git.tuffraid.net/cowch/lst_v3/commits/30ffd843c725da79ed035e2d9564f60a6babcda8))
* **scanner:** more work on the scanner and can now scan to prod no lst right now ([77b4533](https://git.tuffraid.net/cowch/lst_v3/commits/77b4533dea8314fd4fb81a597995cabd041fe188))
* **servers:** added iowa ebm ([8446dbc](https://git.tuffraid.net/cowch/lst_v3/commits/8446dbc955462235b9df35c501354761661e4f6a))
### 🐛 Bug fixes
* **mobile:** typo for version checking ([0b7318f](https://git.tuffraid.net/cowch/lst_v3/commits/0b7318f8566d15414edd3cd67c89fa5346058ab0))
### 🛠️ Code Refactor
* **docker compose:** changed to have the correct url that will be used as this is for auth ([4e0cf8c](https://git.tuffraid.net/cowch/lst_v3/commits/4e0cf8c54c4dfd68edba7e733518846a47c55064))
* **gp connection:** added in gp ip into env if not there use static name for dns ([36995e9](https://git.tuffraid.net/cowch/lst_v3/commits/36995e9fb42cfa1b72c096b8860866d70b86e70c))
* **mobile:** more look and feel work ([bb6155c](https://git.tuffraid.net/cowch/lst_v3/commits/bb6155c9692220542a52664848abf0b9eee91a43))
* **mobile:** moved the versioning lookup at at the mobile folder plus renamed ([bddc9ac](https://git.tuffraid.net/cowch/lst_v3/commits/bddc9aca0d2da2b2f53dec1250276d7a076a8601))
* **scanner:** format changes ([518c0a8](https://git.tuffraid.net/cowch/lst_v3/commits/518c0a8c19a4bff0b757bbd06ca5460d3565d8bd))
### 📈 Project Builds
* **scripts:** changing how the relase works so it purposly builds before it trys to release ([83a542d](https://git.tuffraid.net/cowch/lst_v3/commits/83a542d1b7beafe394949c001917f2b25056fac2))
## [0.0.2-alpha.6](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.1...v0.0.2-alpha.6) (2026-04-23)
## [0.0.2-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.0...v0.0.2-alpha.1) (2026-04-23)
## [0.0.2-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1...v0.0.2-alpha.0) (2026-04-23)
## [0.0.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.5...v0.0.1) (2026-04-23)
### 🐛 Bug fixes
* **frontend:** lingering import crashed us ([781025d](https://git.tuffraid.net/cowch/lst_v3/commits/781025dca00e9dd4b2ad9b283be944ed91bbc1e5))
### 📝 Chore
* **doc remove:** removed a doc and put it in the real area for docs ([a593bb2](https://git.tuffraid.net/cowch/lst_v3/commits/a593bb2baafd0166a178b80cd76dd8862f240e11))
## [0.0.1-alpha.5](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.4...v0.0.1-alpha.5) (2026-04-23)
### 🌟 Enhancements
* **admin:** moved server build/update to full app ([cb00add](https://git.tuffraid.net/cowch/lst_v3/commits/cb00addee96b3ecccf2694f85cb7882cac9c7e3d))
* **lstmobile:** intial scanner setup kinda working ([3734d9d](https://git.tuffraid.net/cowch/lst_v3/commits/3734d9daac143ad8fb4404c59990bc4f546f365b))
* **oidc:** added in so we could use an oidc to login as well :D ([f7276ca](https://git.tuffraid.net/cowch/lst_v3/commits/f7276ca2d722e30da65bbead23dc9bd57df25aa7))
* **servers:** added marked tree in to the mix ([4d53af0](https://git.tuffraid.net/cowch/lst_v3/commits/4d53af033876d81e0d38c148c15cb0af6f3d5bf0))
### 🐛 Bug fixes
* **datamart:** fixes to correct how we handle activations of new features and legacy queries ([b832d7a](https://git.tuffraid.net/cowch/lst_v3/commits/b832d7aa1ecd063be1bbb7e969617fc7a6376ffa))
* **datamart:** if we do not have 2.0 warehousing activate we need to use legacy ([5b1c885](https://git.tuffraid.net/cowch/lst_v3/commits/5b1c88546ff9a42dc572450fe05ad68015edb627))
* **gp:** weird issue with db username and password ([d6328ab](https://git.tuffraid.net/cowch/lst_v3/commits/d6328ab764c3626aef99727b873003384951d299))
* **inventory:** changes to accruatly adjust the query and check the feature set ([32517d0](https://git.tuffraid.net/cowch/lst_v3/commits/32517d0c98c42a0f0f60135b4a9951c4090ccd58))
* **logistics:** historical issue where it was being really weird ([cfbc156](https://git.tuffraid.net/cowch/lst_v3/commits/cfbc1565172f7c2e27f0a1593fe8e99b00d91bb7))
* **logistics:** purchasing monitoring was going off every 5th min instead of every 5 min ([3639c1b](https://git.tuffraid.net/cowch/lst_v3/commits/3639c1b77c597a94816bfedd0892f0c8980c6403))
* **ocp:** fixes to make sure we always hav printer.data as an array or dont do anything ([fb3cd85](https://git.tuffraid.net/cowch/lst_v3/commits/fb3cd85b411315cac0abd22d050ee88929754833))
* **psi:** refactor psi queries ([a1eeade](https://git.tuffraid.net/cowch/lst_v3/commits/a1eeadeec438f7c5c6d31f190fee5c22f83dc6b0))
### 📝 Chore
* **clean:** removed bruno api a proper api doc will be added to lst later ([f716de1](https://git.tuffraid.net/cowch/lst_v3/commits/f716de1a58a4a4c02d9a0a375444ceecea4a018b))
* **scripts:** added in a helper to remove old stuff ([de5df2b](https://git.tuffraid.net/cowch/lst_v3/commits/de5df2b00b1c6fe7c53d6ea075b4cf7e0fb845f9))
### 🛠️ Code Refactor
* **scanner:** more basic work to get the scanner just running ([82f8369](https://git.tuffraid.net/cowch/lst_v3/commits/82f8369640b2b0ff63dd640dc0aa0609a42c7dda))
* **servers:** added mcd and stp1 ([88cef2a](https://git.tuffraid.net/cowch/lst_v3/commits/88cef2a56c390b692866658ce519e59ffeaf4c17))
* **server:** server updates can now only be done from a dev pc ([7962463](https://git.tuffraid.net/cowch/lst_v3/commits/7962463927c4c5d2e12db9a0dd536b0f29fc65b2))
* **sql:** changed sql connection to ip:port ([a6d53f0](https://git.tuffraid.net/cowch/lst_v3/commits/a6d53f0266f1edc3f3946cd1f07d893c8a98d9c7))
## [0.0.1-alpha.4](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.3...v0.0.1-alpha.4) (2026-04-15)

View File

@@ -0,0 +1,38 @@
/**
* To be able to run this we need to set our dev pc in the .env.
* if its empty just ignore it. this will just be the double catch
*/
import { Router } from "express";
import { build, building } from "../utils/build.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const router = Router();
router.post("/release", async (_, res) => {
if (!building) {
build();
return apiReturn(res, {
success: true,
level: "info",
module: "admin",
subModule: "build",
message: `The build has been triggered see logs for progress of the current build.`,
data: [],
status: 200,
});
} else {
return apiReturn(res, {
success: false,
level: "error",
module: "admin",
subModule: "build",
message: `There is a build in progress already please check the logs for on going progress.`,
data: [],
status: 200,
});
}
});
export default router;

View File

@@ -0,0 +1,16 @@
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import build from "./admin.build.js";
import update from "./admin.updateServer.js";
import users from "./admin.users.js";
export const setupAdminRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/admin/build`, requireAuth, build);
app.use(`${baseUrl}/api/admin/build`, requireAuth, update);
app.use(`${baseUrl}/api/admin/user`, requireAuth, users);
// all other system should be under /api/system/*
};

View File

@@ -0,0 +1,86 @@
/**
* To be able to run this we need to set our dev pc in the .env.
* if its empty just ignore it. this will just be the double catch
*/
import { Router } from "express";
import z from "zod";
import { building } from "../utils/build.utils.js";
import { runUpdate, updating } from "../utils/deployApp.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const updateServer = z.object({
server: z.string(),
destination: z.string(),
token: z.string().min(5, "Plant tokens should be at least 5 characters long"),
});
const router = Router();
type Update = {
success: boolean;
message: string;
};
router.post("/updateServer", async (req, res) => {
try {
const validated = updateServer.parse(req.body);
if (!updating && !building) {
const update = (await runUpdate({
server: validated.server,
destination: validated.destination,
token: validated.token,
})) as Update;
return apiReturn(res, {
success: update.success,
level: update.success ? "info" : "error",
module: "admin",
subModule: "update",
message: update.message,
data: [],
status: 200,
});
} else {
return apiReturn(res, {
success: false,
level: "error",
module: "admin",
subModule: "update",
message: `${validated.server}: ${validated.token} is already being updated, or is currently building the app.`,
data: [],
status: 200,
});
}
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
// return res.status(400).json({
// error: "Validation failed",
// details: flattened,
// });
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "routes",
subModule: "auth",
message: "Validation failed",
data: [flattened.fieldErrors],
status: 400, //connect.success ? 200 : 400,
});
}
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "routes",
subModule: "auth",
message: "Internal Server Error creating user",
data: [err],
status: 400, //connect.success ? 200 : 400,
});
}
});
export default router;

View File

@@ -0,0 +1,46 @@
/**
* To be able to run this we need to set our dev pc in the .env.
* if its empty just ignore it. this will just be the double catch
*/
import { fromNodeHeaders } from "better-auth/node";
import { Router } from "express";
import { auth } from "../utils/auth.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { users } = await auth.api.listUsers({
query: {
limit: 50,
},
headers: fromNodeHeaders(req.headers),
});
// console.log(error);
// if (error) {
// return apiReturn(res, {
// success: false,
// level: "info",
// module: "admin",
// subModule: "user",
// message: `There was an error getting the users.`,
// data: users,
// status: 400,
// });
// }
return apiReturn(res, {
success: true,
level: "info",
module: "admin",
subModule: "users",
message: `Current active users.`,
data: users,
status: 200,
});
});
export default r;

View File

@@ -3,7 +3,9 @@ import { fileURLToPath } from "node:url";
import { toNodeHandler } from "better-auth/node";
import express from "express";
import morgan from "morgan";
import { umamiConfig } from "./configs/umami.config.js";
import { createLogger } from "./logger/logger.controller.js";
import { routeHitMiddleware } from "./middleware/routeHit.middleware.js";
import { setupRoutes } from "./routeHandler.routes.js";
import { auth } from "./utils/auth.utils.js";
import { lstCors } from "./utils/cors.utils.js";
@@ -29,8 +31,26 @@ const createApp = async () => {
app.use(morgan("dev"));
app.set("trust proxy", true);
app.use(lstCors());
app.use(routeHitMiddleware);
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
app.use(express.json());
app.get(`${baseUrl}/api/lst-config.js`, (_, res) => {
res.type("application/javascript");
res.setHeader("Cache-Control", "no-store");
res.send(`
window.LST_CONFIG = {
appName: ${JSON.stringify(umamiConfig.appName ?? "LST")},
site: ${JSON.stringify(umamiConfig.site ?? "unknown")},
server: ${JSON.stringify(umamiConfig.server ?? "unknown")},
appVersion: ${JSON.stringify(umamiConfig.appVersion ?? "dev")},
umamiHost: ${JSON.stringify(umamiConfig.umamiHost ?? "")},
umamiWebsiteId: ${JSON.stringify(umamiConfig.umamiWebsiteId ?? "")}
};
`);
});
setupRoutes(baseUrl, app);
app.use(

View File

@@ -1,9 +1,11 @@
import type { Express } from "express";
import login from "./login.route.js";
import register from "./register.route.js";
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
app.use(`${baseUrl}/api/authentication/login`, login);
app.use(`${baseUrl}/api/authentication/register`, register);
};

View File

@@ -1,10 +1,16 @@
import type sql from "mssql";
// TODO : Remove this later and get it onto the env
const username = "gpviewer";
const password = "gp$$ViewOnly!";
const port = process.env.SQL_PORT
? Number.parseInt(process.env.SQL_PORT, 10)
: undefined;
export const gpSqlConfig: sql.config = {
server: `USMCD1VMS011`,
server: `${process.env.GP_SERVER ?? "USMCD1VMS011"}`,
port: port,
database: `ALPLA`,
user: username,
password: password,

View File

@@ -1,7 +1,13 @@
import type sql from "mssql";
const port = process.env.SQL_PORT
? Number.parseInt(process.env.SQL_PORT, 10)
: undefined;
export const prodSqlConfig: sql.config = {
server: `${process.env.PROD_SERVER}`,
database: `AlplaPROD_${process.env.PROD_PLANT_TOKEN}_cus`,
port: port,
user: process.env.PROD_USER,
password: process.env.PROD_PASSWORD,
options: {

View File

@@ -0,0 +1,21 @@
export type UmamiRuntimeConfig = {
appName: string;
site: string;
server: string;
appVersion: string;
umamiHost: string;
umamiWebsiteId: string;
};
export const umamiConfig: UmamiRuntimeConfig = {
appName: process.env.APP_NAME ?? "LST",
site: process.env.URL ?? "unknown",
server: process.env.PROD_PLANT_TOKEN ?? "unknown", // could also be server name based on our setup.
appVersion: process.env.NODE_ENV ?? "dev",
umamiHost: process.env.UMAMI_HOST ?? "",
umamiWebsiteId: process.env.UMAMI_WEBSITE_ID ?? "",
};
export function isUmamiEnabled() {
return Boolean(umamiConfig.umamiHost && umamiConfig.umamiWebsiteId);
}

View File

@@ -95,7 +95,39 @@ export const runDatamartQuery = async (data: Data) => {
notify: false,
});
}
const sqlQuery = sqlQuerySelector(`datamart.${data.name}`) as SqlQuery;
const featureQ = sqlQuerySelector(`featureCheck`) as SqlQuery;
const { data: fd, error: fe } = await tryCatch(
prodQuery(featureQ.query, `Running feature check`),
);
if (fe) {
return returnFunc({
success: false,
level: "error",
module: "datamart",
subModule: "query",
message: `feature check failed`,
data: fe as any,
notify: false,
});
}
// for queries that will need to be ran on legacy until we get the plant updated need to go in here
const doubleQueries = ["inventory"];
let queryFile = "";
if (doubleQueries.includes(data.name)) {
queryFile = `datamart.${
fd.data[0].activated > 0 ? data.name : `legacy.${data.name}`
}`;
} else {
queryFile = `datamart.${data.name}`;
}
const sqlQuery = sqlQuerySelector(queryFile) as SqlQuery;
// checking if warehousing is as it will start to effect a lot of queries for plants that are not on 2.
const getDataMartInfo = datamartData.filter((x) => x.endpoint === data.name);
@@ -141,7 +173,19 @@ export const runDatamartQuery = async (data: Data) => {
case "deliveryByDateRange":
datamartQuery = datamartQuery
.replace("[startDate]", `${data.options.startDate}`)
.replace("[endDate]", `${data.options.endDate}`);
.replace("[endDate]", `${data.options.endDate}`)
.replace(
"--and r.ArticleHumanReadableId in ([articles]) ",
data.options.articles
? `and r.ArticleHumanReadableId in (${data.options.articles})`
: "--and r.ArticleHumanReadableId in ([articles]) ",
)
.replace(
"and DeliveredQuantity > 0",
data.options.all
? "--and DeliveredQuantity > 0"
: "and DeliveredQuantity > 0",
);
break;
case "customerInventory":
@@ -170,23 +214,15 @@ export const runDatamartQuery = async (data: Data) => {
"--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot",
`${data.options.lots ? `,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot` : `--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot`}`,
)
.replaceAll(
"--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber",
`${data.options.lots ? `,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber` : `--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber`}`,
)
.replaceAll(
"--,l.WarehouseDescription,l.LaneDescription",
`${data.options.locations ? `,l.WarehouseDescription,l.LaneDescription` : `--,l.WarehouseDescription,l.LaneDescription`}`,
);
// adding in a test for historical check.
if (data.options.historical) {
datamartQuery = datamartQuery
.replace(
"--,l.ProductionLotRunningNumber as lot,l.warehousehumanreadableid as warehouseId,l.WarehouseDescription as warehouseDescription,l.lanehumanreadableid as locationId,l.lanedescription as laneDescription",
",l.ProductionLotRunningNumber as lot,l.warehousehumanreadableid as warehouseId,l.WarehouseDescription as warehouseDescription,l.lanehumanreadableid as locationId,l.lanedescription as laneDescription",
)
.replace(
"--,l.ProductionLotRunningNumber,l.warehousehumanreadableid,l.WarehouseDescription,l.lanehumanreadableid,l.lanedescription",
",l.ProductionLotRunningNumber,l.warehousehumanreadableid,l.WarehouseDescription,l.lanehumanreadableid,l.lanedescription",
);
}
break;
case "fakeEDIUpdate":
datamartQuery = datamartQuery.replace(
@@ -213,16 +249,13 @@ export const runDatamartQuery = async (data: Data) => {
);
break;
case "psiDeliveryData":
datamartQuery = datamartQuery
.replace("[startDate]", `${data.options.startDate}`)
.replace("[endDate]", `${data.options.endDate}`)
.replace(
"and IdArtikelVarianten in ([articles])",
data.options.articles
? `and IdArtikelVarianten in (${data.options.articles})`
: "--and IdArtikelVarianten in ([articles])",
"[articles]",
data.options.articles ? `${data.options.articles}` : "[articles]",
);
break;
case "productionData":

View File

@@ -1,5 +1,8 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as opendockAVCheck from "./schema/opendock_articleSetup.js";
import * as scanUserSchema from "./schema/scanUsers.js";
import * as settingsSchema from "./schema/settings.schema.js";
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
@@ -13,4 +16,12 @@ const queryClient = postgres(dbURL, {
},
});
export const db = drizzle({ client: queryClient });
//export const db = drizzle({ client: queryClient });
export const db = drizzle(queryClient, {
schema: {
...scanUserSchema,
...settingsSchema,
...opendockAVCheck,
},
});

View File

@@ -0,0 +1,21 @@
import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
export const analytics = pgTable("analytics", {
id: uuid("id").defaultRandom().primaryKey(),
createdAt: timestamp("created_at").defaultNow().notNull(),
method: text("method").notNull(),
routePattern: text("route_pattern").notNull(),
actualPath: text("actual_path").notNull(),
statusCode: integer("status_code").notNull(),
durationMs: integer("duration_ms").notNull(),
module: text("module"),
userId: text("user_id"),
userEmail: text("user_email"),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
});

View File

@@ -0,0 +1,10 @@
import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
export const deploymentHistory = pgTable("deployment_history", {
id: uuid("id").defaultRandom().primaryKey(),
serverId: uuid("server_id"),
buildNumber: integer("build_number").notNull(),
status: text("status").notNull(), // started, success, failed
message: text("message"),
createdAt: timestamp("created_at").defaultNow(),
});

View File

@@ -0,0 +1,45 @@
import {
date,
integer,
pgTable,
text,
timestamp,
unique,
uuid,
} from "drizzle-orm/pg-core";
export const analyticsDaily = pgTable(
"analytics_daily",
{
id: uuid("id").defaultRandom().primaryKey(),
businessDate: date("business_date", { mode: "string" }).notNull(),
method: text("method").notNull(),
routePattern: text("route_pattern").notNull(),
module: text("module").notNull(),
totalHits: integer("total_hits").notNull(),
uniqueUsers: integer("unique_users").notNull(),
successCount: integer("success_count").notNull(),
errorCount: integer("error_count").notNull(),
avgDurationMs: integer("avg_duration_ms").notNull(),
maxDurationMs: integer("max_duration_ms").notNull(),
firstHitAt: timestamp("first_hit_at").notNull(),
lastHitAt: timestamp("last_hit_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(table) => [
unique("analytics_daily_business_route_unique").on(
table.businessDate,
table.method,
table.routePattern,
table.module,
),
],
);

View File

@@ -0,0 +1,22 @@
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const dockDoorScanners = pgTable("dock_door_scanners", {
id: uuid("id").defaultRandom().primaryKey(),
ip: text("ip").notNull(),
name: text("name").unique(),
dockId: text("dock_id"),
active: boolean("active").default(true),
currentLoadingOrder: text("current_loading_order").default(""),
add_date: timestamp("add_date").defaultNow(),
add_user: text("add_user").default("lst-system"),
upd_date: timestamp("upd_date").defaultNow(),
upd_user: text("upd_user").default("lst-system"),
});
export const dockDoorScannersSchema = createSelectSchema(dockDoorScanners);
export const newDockDoorScannersSchema = createInsertSchema(dockDoorScanners);
export type DockDoorScanners = z.infer<typeof dockDoorScannersSchema>;
export type NewDockDoorScanners = z.infer<typeof newDockDoorScannersSchema>;

View File

@@ -14,14 +14,13 @@ export const opendockApt = pgTable(
"opendock_apt",
{
id: uuid("id").defaultRandom().primaryKey(),
release: integer("release").notNull().unique(),
release: integer("release").notNull().unique("opendock_apt_release_unique"),
openDockAptId: text("open_dock_apt_id").notNull(),
appointment: jsonb("appointment").notNull().default([]),
upd_date: timestamp("upd_date").notNull().defaultNow(),
createdAt: timestamp("created_at").notNull().defaultNow(),
},
(table) => ({
releaseIdx: index("opendock_apt_release_idx").on(table.release),
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
table.openDockAptId,
),

View File

@@ -0,0 +1,46 @@
import {
integer,
pgEnum,
pgTable,
text,
timestamp,
unique,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const loadTypeEnum = pgEnum("load_type", ["drop", "live"]);
export const opendockArticleSetup = pgTable(
"opendock_article_setup",
{
id: uuid("id").defaultRandom().primaryKey(),
av: integer("av").notNull(),
description: text("description").notNull(),
customer: text("customer").notNull(), // customer should be a concat of the ID - Desc
customerDescription: text("customer_description").notNull(),
loadType: loadTypeEnum("load_type").notNull().default("drop"),
dock: text("dock").notNull(),
upd_date: timestamp("upd_date").notNull().defaultNow(),
upd_user: text("upd_user").notNull().default("lst-system"),
createdAt: timestamp("created_at").notNull().defaultNow(),
add_user: text("add_user").notNull().default("lst-system"),
},
(table) => ({
uniqueAvCustomer: unique("uq_opendock_article_setup_av_customer").on(
table.av,
table.customer,
),
}),
);
export const opendockArticleSetupSchema =
createSelectSchema(opendockArticleSetup);
export const newOpendockArticleSetupSchema =
createInsertSchema(opendockArticleSetup);
export type OpendockArticleSetup = z.infer<typeof opendockArticleSetupSchema>;
export type NewOpendockArticleSetup = z.infer<
typeof newOpendockArticleSetupSchema
>;

View File

@@ -0,0 +1,21 @@
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const opendockDockSetup = pgTable("opendock_dock_setup", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
dockID: text("dock_id").notNull(),
upd_date: timestamp("upd_date").notNull().defaultNow(),
upd_user: text("upd_user").notNull().default("lst-system"),
createdAt: timestamp("created_at").notNull().defaultNow(),
add_user: text("add_user").notNull().default("lst-system"),
});
export const opendockDockSetupSchema = createSelectSchema(opendockDockSetup);
export const newOpendockDockSetupSchema = createInsertSchema(opendockDockSetup);
export type OpendockArticleSetup = z.infer<typeof opendockDockSetupSchema>;
export type NewOpendockArticleSetup = z.infer<
typeof newOpendockDockSetupSchema
>;

View File

@@ -0,0 +1,48 @@
import {
boolean,
jsonb,
pgEnum,
pgTable,
text,
timestamp,
unique,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type z from "zod";
export const mobileRoleEnum = pgEnum("mobile_role", [
"user",
"lead",
"manager",
"admin",
]);
export const scanUser = pgTable(
"scan_users",
{
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(), // the user that will be using the scanner
scannerId: text("scanner_id").unique().notNull(),
pinNumber: text("pin_number").unique().notNull(),
pinHash: text("pin_hash").notNull(),
excludedCommand: jsonb("excluded_commands").default([]),
role: mobileRoleEnum("role").notNull().default("user"),
active: boolean("active").default(true),
lastScan: timestamp("last_scan").defaultNow(),
add_Date: timestamp("add_Date").defaultNow(),
upd_date: timestamp("upd_date").defaultNow(),
},
(table) => ({
userNotificationUnique: unique("scan_user_unique").on(
table.scannerId,
table.pinNumber,
),
}),
);
export const scanUserSchema = createSelectSchema(scanUser);
export const newsSanUserSchema = createInsertSchema(scanUser);
export type ScanUser = z.infer<typeof scanUserSchema>;
export type NewScanUser = z.infer<typeof newsSanUserSchema>;

View File

@@ -0,0 +1,23 @@
import { jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type z from "zod";
export const scanLog = pgTable("scan_log", {
id: uuid("id").defaultRandom().primaryKey(),
user: text("user"),
scannerId: text("scanner_id"),
message: text("message").notNull(),
prompt: text("prompt"),
commandDescription: text("command_description"),
runningNumber: text("running_number").default("0"),
status: text("status"),
scannerVersion: text("scanner_version").default("0"),
lines: jsonb("lines").default([]),
add_Date: timestamp("add_date").defaultNow(),
});
export const scanLogSchema = createSelectSchema(scanLog);
export const newScanLogSchema = createInsertSchema(scanLog);
export type Printer = z.infer<typeof scanLogSchema>;
export type NewPrinter = z.infer<typeof newScanLogSchema>;

View File

@@ -0,0 +1,40 @@
import {
boolean,
integer,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type z from "zod";
export const serverData = pgTable(
"server_data",
{
server_id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
server: text("server"),
plantToken: text("plant_token").notNull().unique(),
idAddress: text("id_address"),
greatPlainsPlantCode: text("great_plains_plant_code"),
contactEmail: text("contact_email"),
contactPhone: text("contact_phone"),
active: boolean("active").default(true),
serverLoc: text("server_loc"),
lastUpdated: timestamp("last_updated").defaultNow(),
buildNumber: integer("build_number"),
isUpgrading: boolean("is_upgrading").default(false),
},
// (table) => [
// // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
// uniqueIndex("plant_token").on(table.plantToken),
// ],
);
export const serverDataSchema = createSelectSchema(serverData);
export const newServerDataSchema = createInsertSchema(serverData);
export type ServerDataSchema = z.infer<typeof serverDataSchema>;
export type NewServerData = z.infer<typeof newServerDataSchema>;

View File

@@ -38,7 +38,7 @@ export const settings = pgTable(
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
uniqueIndex("name").on(table.name),
uniqueIndex("settings_name_unique").on(table.name),
],
);

View File

@@ -1,10 +1,27 @@
import type { InferSelectModel } from "drizzle-orm";
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import {
boolean,
integer,
jsonb,
pgTable,
text,
timestamp,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type z from "zod";
export const serverStats = pgTable("stats", {
id: text("id").primaryKey().default("serverStats"),
build: integer("build").notNull().default(1),
lastUpdate: timestamp("last_update").defaultNow(),
export const appStats = pgTable("app_stats", {
id: text("id").primaryKey().default("primary"),
currentBuild: integer("current_build").notNull().default(1),
lastBuildAt: timestamp("last_build_at"),
lastDeployAt: timestamp("last_deploy_at"),
building: boolean("building").notNull().default(false),
updating: boolean("updating").notNull().default(false),
lastUpdated: timestamp("last_updated").defaultNow(),
meta: jsonb("meta").$type<Record<string, unknown>>().default({}),
});
export type ServerStats = InferSelectModel<typeof serverStats>;
export const appStatsSchema = createSelectSchema(appStats);
export const newAppStatsSchema = createInsertSchema(appStats, {});
export type AppStats = z.infer<typeof appStatsSchema>;
export type NewAppStats = z.infer<typeof newAppStatsSchema>;

View File

@@ -0,0 +1,35 @@
import { addDays, subDays } from "date-fns";
import { format } from "date-fns-tz";
import { Router } from "express";
import { runProdApi } from "../utils/prodEndpoint.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.get("/", async (_, res) => {
const orders = await runProdApi({
method: "post",
endpoint: "/public/v1.0/OutboundDeliveries/LoadingOrders/Search",
data: [
{
loadingDateFrom: format(subDays(new Date(Date.now()), 3), "yyyy-MM-dd"),
loadingDateTo: format(addDays(new Date(Date.now()), 3), "yyyy-MM-dd"),
states: [
1, // planned
],
//isCommissioned: true,
},
],
});
return apiReturn(res, {
success: true,
level: "info",
module: "dockdoor",
subModule: "current Active loading orders",
message: `Current active loading loaders.`,
data: orders?.data ?? [],
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,18 @@
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.post("/", async (req, res) => {
return apiReturn(res, {
success: true,
level: "info",
module: "dockdoor",
subModule: "lane check",
message: `Release x is being closed now. the bol should come out at the default printer.`,
data: req.body ?? [],
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,89 @@
// sends the units from the dock door scanner here.
import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
import { runProdApi } from "../utils/prodEndpoint.utils.js";
import { returnFunc } from "../utils/returnHelper.utils.js";
// validate we are active
type Data = {
dockId?: string;
sscc?: string;
runningNr?: string;
};
export const loadUnit = async (data: Data) => {
// are we even active at this time?
const dockDoorActive = await db.query.settings.findFirst({
where: (u, { eq }) => eq(u.name, "dockDoorScanning"),
});
if (!dockDoorActive?.active) {
return returnFunc({
success: false,
level: "error",
module: "dockdoor",
subModule: "loadunit",
message: "Dock door scanning feature is not active.",
data: [],
notify: false,
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.
const dock = await db
.select()
.from(dockDoorScanners)
.where(eq(dockDoorScanners.dockId, data.dockId as string));
if (dock[0]?.currentLoadingOrder === "") {
return returnFunc({
success: true,
level: "error",
module: "dockdoor",
subModule: "loadingOrders",
message:
"There are know current active loading orders please start one and try again.",
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.
// if on hold stop the scan and send a bad read with the reason its on hold and what its on hold for, including coa.
// if precheck is active then check if we have a warehouse, then check if the pallet was in the warehouse for greater than the define min, all fails send a warning and still do the scan
// add the loading units
try {
const prod = await runProdApi({
method: "post",
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`,
data: [{ sscc: data.sscc?.slice(2) }],
});
console.log(prod?.data);
} catch (error) {
console.log(error);
}
};

View File

@@ -0,0 +1,43 @@
import type { Express } from "express";
import { featureCheck } from "../middleware/featureActive.middleware.js";
import activeLoadingOrders from "./dockdoor.activeLoadingOrders.route.js";
import closeLoadingOrder from "./dockdoor.closeLoadingOrder.route.js";
import startLoad from "./dockdoor.startLoad.route.js";
import prodDocks from "./dockdoors.docks.route.js";
import docks from "./dockdoors.route.js";
export const setupDockDoorRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(
`${baseUrl}/api/dockDoor/scanners`,
featureCheck("dockDoorScanning"),
docks,
);
app.use(
`${baseUrl}/api/dockDoor/closeLoadingOrder`,
featureCheck("dockDoorScanning"),
closeLoadingOrder,
);
app.use(
`${baseUrl}/api/dockDoor/activeLoadingOrders`,
featureCheck("dockDoorScanning"),
activeLoadingOrders,
);
app.use(
`${baseUrl}/api/dockDoor/startLoad`,
featureCheck("dockDoorScanning"),
startLoad,
);
app.use(
`${baseUrl}/api/dockDoor/docks`,
featureCheck("dockDoorScanning"),
prodDocks,
);
// TODO : add manual way to add pallets
// all other system should be under /api/system/*
};

View File

@@ -0,0 +1,65 @@
import { sql } from "drizzle-orm";
import { Router } from "express";
import z from "zod";
import { db } from "../db/db.controller.js";
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
const startLoading = z.object({
loadingOrder: z.string(),
dockId: z.string(),
});
r.post("/", async (req, res) => {
try {
const validated = startLoading.parse(req.body);
const { data, error } = await tryCatch(
db
.update(dockDoorScanners)
.set({
currentLoadingOrder: validated.loadingOrder,
upd_date: sql`NOW()`,
upd_user: req.user?.username,
})
.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: true,
level: "info",
module: "dockdoor",
subModule: "loadingOrder",
message: `Loading order ${validated.loadingOrder} was just added to dockId ${validated.dockId}.`,
data: data ?? [],
status: 200,
});
} catch (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "dockdoor",
subModule: "loadingOrder",
message: `Failed to start loading order.`,
data: (error as any) ?? [],
status: 400,
});
}
});
export default r;

View File

@@ -0,0 +1,54 @@
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 (_, res) => {
const activeDocks = sqlQuerySelector(`outbound.docks`) as SqlQuery;
if (!activeDocks.success) {
return apiReturn(res, {
success: false,
level: "error",
module: "dockdoor",
subModule: "docks",
message: `There was an error getting the docks query.`,
data: [],
status: 400,
});
}
const { data, error } = await tryCatch(
prodQuery(activeDocks.query, "Current Active Docks"),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "dockdoor",
subModule: "newDock",
message: `There was an error getting the docks.`,
data: (error as any) ?? ([] as any),
status: 400,
});
}
return apiReturn(res, {
success: true,
level: "info",
module: "dockdoor",
subModule: "docks",
message: `Current active docks.`,
data: (data.data as any) ?? ([] as any),
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,76 @@
import { Router } from "express";
import z from "zod";
import { db } from "../db/db.controller.js";
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
import { requireAuth } from "../middleware/auth.middleware.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
const newDockScanner = z.object({
ip: z.string(),
name: z.string(),
dockId: z.string(),
});
r.get("/", async (_, res) => {
try {
const docks = await db
.select()
.from(dockDoorScanners)
.orderBy(dockDoorScanners.name);
return apiReturn(res, {
success: true,
level: "info",
module: "dockdoor",
subModule: "lane check",
message: `All dock Doors.`,
data: docks ?? [],
status: 200,
});
} catch (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "dockdoor",
subModule: "newDock",
message: `There was an error adding in the new dock.`,
data: error ?? ([] as any),
status: 200,
});
}
});
r.post("/", requireAuth, async (req, res) => {
try {
const validated = newDockScanner.parse(req.body);
const newDock = await db
.insert(dockDoorScanners)
.values(validated)
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "dockdoor",
subModule: "lane check",
message: `${validated.name} was just added.`,
data: newDock ?? [],
status: 200,
});
} catch (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "dockdoor",
subModule: "newDock",
message: `There was an error adding in the new dock.`,
data: error ?? ([] as any),
status: 200,
});
}
});
export default r;

View File

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

View File

@@ -13,7 +13,9 @@ let attempt = 0;
const maxAttempts = 10;
export const connectGPSql = async () => {
const serverUp = await checkHostnamePort(`USMCD1VMS011:1433`);
const serverUp = await checkHostnamePort(
`${process.env.GP_SERVER ?? "usmcd1vms011"}:1433`,
);
if (!serverUp) {
// we will try to reconnect
connected = false;
@@ -53,13 +55,14 @@ export const connectGPSql = async () => {
notify: false,
});
} catch (error) {
console.log(error);
reconnectToSql;
return returnFunc({
success: false,
level: "error",
module: "system",
subModule: "db",
message: "Failed to connect to the prod sql server.",
message: "Failed to connect to the gp sql server.",
data: [error],
notify: false,
});
@@ -118,7 +121,9 @@ export const reconnectToSql = async () => {
await new Promise((res) => setTimeout(res, delayStart));
const serverUp = await checkHostnamePort(`${process.env.PROD_SERVER}:1433`);
const serverUp = await checkHostnamePort(
`${process.env.GP_SERVER ?? "usmcd1vms011"}:1433`,
);
if (!serverUp) {
delayStart = Math.min(delayStart * 2, 30000); // exponential backoff until up to 30000

View File

@@ -66,7 +66,10 @@ const historicalInvImport = async () => {
const { data: inv, error: invError } = await tryCatch(
//prodQuery(sqlQuery.query, "Inventory data"),
runDatamartQuery({ name: "inventory", options: { historical: "x" } }),
runDatamartQuery({
name: "inventory",
options: { lots: "x", locations: "x" },
}),
);
const { data: av, error: avError } = (await tryCatch(

View File

@@ -0,0 +1,83 @@
// routeHit.middleware.ts
import type { NextFunction, Request, Response } from "express";
import {
createRouteHit,
shouldIgnoreRoute,
} from "../utils/analyticRouteHits.utils.js";
export function routeHitMiddleware(
req: Request,
res: Response,
next: NextFunction,
) {
const start = performance.now();
res.on("finish", () => {
const actualPath = getActualPath(req);
if (shouldIgnoreRoute(actualPath)) {
return;
}
const durationMs = Math.round(performance.now() - start);
const routePattern = getRoutePattern(req) as string;
const module = getModuleName(req);
void createRouteHit({
method: req.method,
routePattern,
actualPath,
statusCode: res.statusCode,
durationMs,
module,
// adjust these names to your Better Auth/session shape
userId: req.user?.id ?? null,
userEmail: req.user?.email ?? null,
ipAddress: req.ip ?? null,
userAgent: req.get("user-agent") ?? null,
}).catch((err) => {
console.error("Failed to save route hit", err);
});
});
next();
}
function getActualPath(req: Request) {
return req.originalUrl.split("?")[0] ?? req.path ?? "unknown";
}
function getRoutePattern(req: Request) {
const baseUrl = req.baseUrl || "";
const routePath = req.route?.path;
if (typeof routePath === "string") {
return `${baseUrl}${routePath}`;
}
return getActualPath(req);
}
function getModuleName(req: Request) {
const path = req.originalUrl.split("?")[0];
if (path?.includes("/printers")) return "printers";
if (path?.includes("/releases")) return "releases";
if (path?.includes("/quality")) return "quality";
if (path?.includes("/scanner")) return "scanner";
if (path?.includes("/settings")) return "settings";
if (path?.includes("/users")) return "users";
if (path?.includes("/mobile")) return "mobile";
if (path?.includes("/servers")) return "servers";
if (path?.includes("/logistics")) return "servers";
if (path?.includes("/ocp")) return "ocp";
if (path?.includes("/auth")) return "auth";
if (path?.includes("/datamart")) return "datamart";
if (path?.includes("/opendock")) return "opendock";
return "unknown";
}

View File

@@ -0,0 +1,54 @@
import { eq } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { scanUser } from "../db/schema/scanUsers.js";
import { settings } from "../db/schema/settings.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
// scanners that are dedicated to specific users.
const SPECIAL_SCANNERS = [98];
const buildAllowedScannerIds = (scannerCount: number) => {
const generatedIds = Array.from({ length: scannerCount }, (_, i) => i + 1);
return Array.from(new Set([...generatedIds, ...SPECIAL_SCANNERS])).sort(
(a, b) => a - b,
);
};
r.get("/", async (_, res) => {
// get the scan users and setting
const scanusers = await db.select().from(scanUser);
const scannerIdSetting = await db
.select()
.from(settings)
.where(eq(settings.name, "scannerIds"));
const usedScannerIds = scanusers.map((x) => Number(x.scannerId));
const allowedScannerIds = buildAllowedScannerIds(
Number(scannerIdSetting[0]?.value ?? 0),
);
const availableScannerIds = allowedScannerIds.filter(
(id) => !usedScannerIds.includes(id),
);
const data = availableScannerIds.map((id) => ({
label: `${id}`,
value: id,
}));
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "scanner",
message: `There are ${availableScannerIds.length} scanner id's`,
data,
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,125 @@
import fs from "node:fs";
import { Router } from "express";
import path from "path";
import { fileURLToPath } from "url";
const router = Router();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const downloadDir = path.resolve(__dirname, "../../downloads/mobile");
const currentApk = {
fileName: "lst-mobile.apk",
};
router.get("/latest", (_, res) => {
const apkPath = path.join(downloadDir, currentApk.fileName);
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader(
"Content-Disposition",
`attachment; filename="${currentApk.fileName}"`,
);
return res.sendFile(apkPath);
});
router.get("/ehs", (_, res) => {
const apkPath = path.join(downloadDir, "EHS.apk");
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk"`);
return res.sendFile(apkPath);
});
router.get("/ehs/xml", (_, res) => {
const xmlPath = path.join(downloadDir, "enterprisehomescreen.xml");
if (!fs.existsSync(xmlPath)) {
return res.status(404).json({
success: false,
message: "EHS XML not found",
});
}
res.setHeader("Content-Type", "application/xml");
res.setHeader(
"Content-Disposition",
`attachment; filename="enterprisehomescreen.xml"`,
);
return res.sendFile(xmlPath);
});
router.get("/android/upgrade/11", (_, res) => {
const apkPath = path.join(
downloadDir,
"HE_FULL_UPDATE_11-70-20.00-RG-U00-STD-HEL-04.zip",
);
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Type", "application/zip");
res.setHeader(
"Content-Disposition",
`attachment; filename="HE_FULL_UPDATE_11.zip"`,
);
return res.sendFile(apkPath);
});
router.get("/android/upgrade/13", (_, res) => {
const apkPath = path.join(
downloadDir,
"HE_FULL_UPDATE_13-51-16.00-TG-U00-STD-HEL-04.zip",
);
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Type", "application/zip");
res.setHeader(
"Content-Disposition",
`attachment; filename="HE_FULL_UPDATE_13.zip"`,
);
return res.sendFile(apkPath);
});
router.get("/android/upgrade/14", (_, res) => {
const apkPath = path.join(
downloadDir,
"HE_FULL_UPDATE_14-38-04.00-UG-U15-STD-HEL-04.zip",
);
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Type", "application/zip");
res.setHeader(
"Content-Disposition",
`attachment; filename="HE_FULL_UPDATE_14.zip"`,
);
return res.sendFile(apkPath);
});
export default router;

View File

@@ -0,0 +1,60 @@
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { runProdApi } from "../utils/prodEndpoint.utils.js";
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const router = Router();
router.post("/", async (req, res) => {
const body = req.body;
const lane = body.lane.split("#");
// check if the plant has warehousing activated
const featureQ = sqlQuerySelector(`featureCheck`) as SqlQuery;
const { data: fd, error: fe } = await tryCatch(
prodQuery(featureQ.query, `Running feature check`),
);
if (fe) {
return returnFunc({
success: false,
level: "error",
module: "datamart",
subModule: "query",
message: `feature check failed`,
data: fe as any,
notify: false,
});
}
console.log(fd);
const laneData = await runProdApi({
method: "post",
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",
data: [
{
laneIds: [lane[2]],
},
],
});
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "lane check",
message: `all data for lane Id: ${lane}`,
data: laneData?.data ?? [],
status: 200,
});
});
export default router;

View File

@@ -0,0 +1,23 @@
import type { Express } from "express";
import { featureCheck } from "../middleware/featureActive.middleware.js";
import available from "./availableScanIds.route.js";
import downloads from "./downloadApps.route.js";
import lanes from "./laneCheck.js";
import authPin from "./mobileAuth.route.js";
import newPin from "./mobilePin.route.js";
import logs from "./scanLogs.route.js";
import version from "./version.route.js";
export const setupMobileRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/mobile/version`, featureCheck("mobile"), version);
app.use(`${baseUrl}/api/mobile/apk`, featureCheck("mobile"), downloads);
app.use(`${baseUrl}/api/mobile/logs`, featureCheck("mobile"), logs);
app.use(`${baseUrl}/api/mobile/auth`, featureCheck("mobile"), authPin);
app.use(`${baseUrl}/api/mobile/pin`, featureCheck("mobile"), newPin);
app.use(`${baseUrl}/api/mobile/laneCheck`, featureCheck("mobile"), lanes);
app.use(`${baseUrl}/api/mobile/available`, featureCheck("mobile"), available);
// all other system should be under /api/system/*
};

View File

@@ -0,0 +1,343 @@
import bcrypt from "bcryptjs";
import { eq, sql } from "drizzle-orm";
import { Router } from "express";
import z from "zod";
import { db } from "../db/db.controller.js";
import {
type NewScanUser,
type ScanUser,
scanUser,
} from "../db/schema/scanUsers.js";
import { requireAuth } from "../middleware/auth.middleware.js";
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
export async function hashPin(pin: string) {
// if (!/^\d{6}$/.test(pin)) {
// throw new Error("PIN must be exactly 6 digits");
// }
return bcrypt.hashSync(pin, 12);
}
const registerSchema = z.object({
name: z.string().min(2).max(100),
pinNumber: z.string(),
scannerId: z
.string()
.min(1)
.max(500)
.optional()
.describe("if you leave blank it will be the same as your username"),
role: z
.enum(["user", "lead", "manager", "admin"])
.optional()
.describe("What roles are available to use."),
pinHash: z.string().optional(),
});
r.post("/pin", async (req, res) => {
const { pin } = req.body;
if (!pin || pin.length !== 6) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `Pin number must be a min of 6 digits`,
data: [],
status: 401,
});
}
// const user = await db
// .select()
// .from(scanUser)
// .where(eq(scanUser.pinNumber, parseInt(pin, 10)));
const user = await db.query.scanUser.findFirst({
where: (u, { eq }) => eq(u.pinNumber, pin),
});
if (!user) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `Invalid login please try again.`,
data: [],
status: 401,
});
}
const validPin = bcrypt.compareSync(pin, user.pinHash);
if (!validPin) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `Invalid pin please try again.`,
data: [],
status: 401,
});
}
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "auth",
message: `Welcome back ${user.name}`,
data: user as ScanUser | any,
status: 200,
});
});
r.post("/user", async (req, res) => {
try {
// validate the body is correct before accepting it
let validated = registerSchema.parse(req.body);
validated = {
...validated,
pinHash: await hashPin(validated.pinNumber.toString()),
};
const values: NewScanUser = {
name: validated.name,
pinNumber: validated.pinNumber,
pinHash: validated.pinHash ?? "",
scannerId: validated.scannerId ?? "",
};
const newUser = await db.insert(scanUser).values(values).returning();
apiReturn(res, {
success: true,
level: "info", //connect.success ? "info" : "error",
module: "mobile",
subModule: "auth",
message: `${validated.name} was just created`,
data: newUser as any,
status: 200, //connect.success ? 200 : 400,
});
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
// return res.status(400).json({
// error: "Validation failed",
// details: flattened,
// });
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "mobile",
subModule: "auth",
message: "Validation failed",
data: [flattened.fieldErrors],
status: 400, //connect.success ? 200 : 400,
});
}
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "mobile",
subModule: "auth",
message:
"This User already exist with this pin or scanner id please try again",
data: [err],
status: 400, //connect.success ? 200 : 400,
});
}
});
r.get("/user", requireAuth, async (_, res) => {
const { data, error } = await tryCatch(db.select().from(scanUser));
// await trackLstEvent({
// eventName: "mobile_get_users",
// url: "/mobile/users",
// eventData: {
// module: "mobile",
// },
// });
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `There was an error getting the user`,
data: error as any,
status: 400,
});
}
if (!data) {
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "auth",
message: `There are no users you should add one . `,
data: [],
status: 200,
});
}
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "auth",
message: `All users. `,
data,
status: 200,
});
});
r.patch("/user/:id", requireAuth, async (req, res) => {
const updates: Record<string, unknown | null> = {};
const { id } = req.params;
const { data, error } = await tryCatch(
db.query.scanUser.findFirst({
where: (u, { eq }) => eq(u.id, `${id}`),
}),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `There was an error getting the user`,
data: error as any,
status: 400,
});
}
if (!data) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `Invalid user id was passed over. `,
data: [],
status: 400,
});
}
if (req.body?.name !== undefined) {
updates.name = req.body.name;
}
if (req.body?.pinNumber !== undefined) {
const existing = await db.query.scanUser.findFirst({
where: (u, { eq }) => eq(u.pinHash, req.body.pinNumber),
});
if (existing)
return returnFunc({
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `${req.body.pinNumber} already exists please try again`,
data: [],
notify: false,
room: "",
});
updates.pinNumber = req.body.pinNumber;
updates.pinHash = await hashPin(req.body.pinNumber);
}
if (req.body?.scannerId !== undefined) {
updates.scannerId = req.body.scannerId;
}
if (req.body?.active !== undefined) {
updates.active = req.body.active;
}
if (req.body?.excludedCommand !== undefined) {
updates.excludedCommand = req.body.excludedCommand;
}
if (req.body?.role !== undefined) {
updates.role = req.body.role;
}
updates.upd_date = sql`NOW()`;
const updatedSetting = await db
.update(scanUser)
.set(updates)
.where(eq(scanUser.id, `${id}`))
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "user",
message: `User ${data.name} was updated. `,
data: updatedSetting,
status: 200,
});
});
r.delete("/user/:id", requireAuth, async (req, res) => {
const { id } = req.params;
const { data, error } = await tryCatch(
db.delete(scanUser).where(eq(scanUser.id, `${id}`)),
);
if (error) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `There was an error deleting the user`,
data: error as any,
status: 400,
});
}
if (!data) {
return apiReturn(res, {
success: false,
level: "error",
module: "mobile",
subModule: "auth",
message: `There was no user to delete. `,
data: [],
status: 400,
});
}
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "user",
message: `User was deleted. `,
data: data ?? [],
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,21 @@
import { Router } from "express";
import { generateUniquePin } from "../utils/generateScannerPin.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.get("/new", async (_, res) => {
const getPin = await generateUniquePin();
return apiReturn(res, {
success: getPin.success,
level: getPin.level,
module: "mobile",
subModule: "auth",
message: getPin.message,
data: getPin.data,
status: getPin.success ? 200 : 400,
});
});
export default r;

View File

@@ -0,0 +1,46 @@
import { eq, sql } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { scanLog } from "../db/schema/scanlog.schema.js";
import { scanUser } from "../db/schema/scanUsers.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const router = Router();
router.post("/", async (req, res) => {
const body = req.body;
try {
await db
.update(scanUser)
.set({ lastScan: sql`NOW()` })
.where(eq(scanUser.name, body.user));
} catch (error) {
console.log(error);
}
const newLog = await db
.insert(scanLog)
.values({
scannerId: body.scannerId ?? "",
message: body.message ?? "",
prompt: body.prompt ?? "",
commandDescription: body.commandDescription ?? "",
status: body.status ?? "",
lines: body.lines ?? "",
user: body.user ?? "",
runningNumber: body.runningNumber ?? "",
scannerVersion: body.scannerVersion ?? "0",
})
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "scan logs",
message: `New log from ${body.scannerId}`,
data: newLog,
status: 200,
});
});
export default router;

View File

@@ -0,0 +1,40 @@
import fs from "node:fs";
import { and, eq } from "drizzle-orm";
import { Router } from "express";
import path from "path";
import { db } from "../db/db.controller.js";
import { settings } from "../db/schema/settings.schema.js";
const router = Router();
const projectRoot = path.resolve("./lstMobile"); // adjust as needed
const appJsonPath = path.join(projectRoot, "app.json");
router.get("/", async (req, res) => {
const baseUrl = `${req.protocol}://${req.get("host")}`;
const mobileSettings = await db
.select()
.from(settings)
.where(
and(
eq(settings.moduleName, "mobile"),
eq(settings.settingType, "standard"),
),
);
const raw = fs.readFileSync(appJsonPath, "utf-8");
const config = JSON.parse(raw);
const exp = config.expo;
res.json({
packageName: exp.android?.package,
versionName: exp.version,
versionCode: exp.android?.versionCode,
minSupportedVersionCode: exp?.android?.minSupportedVersionCode ?? 0,
downloadUrl: `${baseUrl}/lst/api/mobile/apk/latest`,
settings: mobileSettings,
});
});
export default router;

View File

@@ -0,0 +1,80 @@
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
// disable the jobs
const jobNames: string[] = [
"monitor_$_lots",
"monitor_$_lots_2",
"monitor$lots",
"Monitor_APO", //listen for people to cry this is no longer a thing
"Monitor_APO2",
"Monitor_AutoConsumeMaterials", // TODO: migrate to lst
"Monitor_AutoConsumeMaterials_iow1",
"Monitor_AutoConsumeMaterials_iow2",
"Monitor_BlockedINV_Loc",
"monitor_inv_cycle",
"monitor_inv_cycle_1",
"monitor_inv_cycle_2",
"monitor_edi_import", // TODO: migrate to lst -- for the query select count(*) from AlplaPROD_test3.dbo.T_EDIDokumente (nolock) where /* IdLieferant > 1 and */ add_date > DATEADD(MINUTE, -30, getdate())
"Monitor_Lot_Progression",
"Monitor_Lots", // TODO: migrate to lst -- this should be the one where we monitor the when a lot is assigned if its missing some data.
"Monitor_MinMax", // TODO:Migrate to lst
"Monitor_MinMax_iow2",
"Monitor_PM",
"Monitor_Purity",
"monitor_wastebookings", // TODO: Migrate
"LastPriceUpdate", // not even sure what this is
"GETLabelsCount", // seems like an old jc job
"jobforpuritycount", // was not even working correctly
"Monitor_EmptyAutoConsumLocations", // not sure who uses this one
"monitor_labelreprint", // Migrated but need to find out who really wants this
"test", // not even sure why this is active
"UpdateLastMoldUsed", // old jc inserts data into a table but not sure what its used for not linked to any other alert
"UpdateWhsePositions3", // old jc inserts data into a table but not sure what its used for not linked to any other alert
"UpdateWhsePositions4",
"delete_print", // i think this was in here for when we was having lag prints in iowa1
"INV_WHSE_1", // something random i wrote long time ago looks like an inv thing to see aged stuff
"INV_WHSE_2",
"laneAgeCheck", // another strange one thats been since moved to lst
"monitor_blocking_2",
"monitor_blocking", // already in lst
"monitor_min_inv", // do we still want this one? it has a description of: this checks m-f the min inventory of materials based on the min level set in stock
"Monitor_MixedLocations",
"Monitor_PM",
"Monitor_PM2",
"wrong_lots_1",
"wrong_lots_2",
"invenotry check", // spelling error one of my stupids
"monitor_hold_monitor",
"Monitor_Silo_adjustments",
"monitor_qualityLocMonitor", // validating with lima this is still needed
"Monitor_Stock_Change",
];
export const sqlJobCleanUp = async () => {
// running a query to disable jobs that are moved to lst to be better maintained
const sqlQuery = sqlQuerySelector("disableJob") as SqlQuery;
if (!sqlQuery.success) {
console.error("Failed to load the query: ", sqlQuery.message);
return;
}
for (const job of jobNames) {
const { error } = await tryCatch(
prodQuery(
sqlQuery.query.replace("[jobName]", `${job}`),
`Disabling job: ${job}`,
),
);
if (error) {
console.error(error);
}
//console.log(data);
}
};

View File

@@ -0,0 +1,113 @@
import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { notifications } from "../db/schema/notifications.schema.js";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { returnFunc } from "../utils/returnHelper.utils.js";
import { sendEmail } from "../utils/sendEmail.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
/**
*
*/
const func = async (data: any, emails: string) => {
// get the actual notification as items will be updated between intervals if no one touches
const { data: l, error: le } = (await tryCatch(
db.select().from(notifications).where(eq(notifications.id, data.id)),
)) as any;
if (le) {
return returnFunc({
success: false,
level: "error",
module: "notification",
subModule: "query",
message: `${data.name} encountered an error while trying to get initial info`,
data: [le],
notify: true,
});
}
// search the query db for the query by name
const sqlQuery = sqlQuerySelector(`${data.name}`) as SqlQuery;
// create the ignore audit logs ids
const ignoreIds = l[0].options[0]?.auditId
? `${l[0].options[0]?.auditId}`
: "0";
// run the check
const { data: queryRun, error } = await tryCatch(
prodQuery(
sqlQuery.query
.replace("[intervalCheck]", l[0].interval)
.replace("[ignoreList]", ignoreIds),
`Running notification query: ${l[0].name}`,
),
);
if (error) {
return returnFunc({
success: false,
level: "error",
module: "notification",
subModule: "query",
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
data: [error],
notify: true,
});
}
if (queryRun.data.length > 0) {
// update the latest audit id
const { error: dbe } = await tryCatch(
db
.update(notifications)
.set({ options: [{ auditId: `${queryRun.data[0].id}` }] })
.where(eq(notifications.id, data.id)),
);
if (dbe) {
return returnFunc({
success: false,
level: "error",
module: "notification",
subModule: "query",
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
data: [dbe],
notify: true,
});
}
// send the email
const sentEmail = await sendEmail({
email: emails,
subject: "Alert! Label Reprinted",
template: "reprintLabels",
context: {
items: queryRun.data,
},
});
if (!sentEmail?.success) {
return returnFunc({
success: false,
level: "error",
module: "email",
subModule: "notification",
message: `${l[0].name} failed to send the email`,
data: [sentEmail],
notify: true,
});
}
} else {
console.log("doing nothing as there is nothing to do.");
}
// TODO send the error to systemAdmin users so they do not always need to be on the notifications.
// these errors are defined per notification.
};
export default func;

View File

@@ -1,5 +1,6 @@
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import manual from "./notification.manualTrigger.js";
import getNotifications from "./notification.route.js";
import updateNote from "./notification.update.route.js";
@@ -10,13 +11,48 @@ import updateSub from "./notificationSub.update.route.js";
export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/notification`, requireAuth, getNotifications);
app.use(`${baseUrl}/api/notification`, requireAuth, updateNote);
app.use(`${baseUrl}/api/notification/manual`, requireAuth, manual);
app.use(`${baseUrl}/api/notification/sub`, requireAuth, subs);
app.use(`${baseUrl}/api/notification/sub`, requireAuth, newSub);
app.use(`${baseUrl}/api/notification/sub`, requireAuth, updateSub);
app.use(`${baseUrl}/api/notification/sub`, requireAuth, deleteSub);
app.use(
`${baseUrl}/api/notification`,
requireAuth,
getNotifications,
);
app.use(
`${baseUrl}/api/notification`,
requireAuth,
updateNote,
);
app.use(
`${baseUrl}/api/notification/manual`,
requireAuth,
manual,
);
app.use(
`${baseUrl}/api/notification/sub`,
requireAuth,
subs,
);
app.use(
`${baseUrl}/api/notification/sub`,
requireAuth,
newSub,
);
app.use(
`${baseUrl}/api/notification/sub`,
requireAuth,
updateSub,
);
app.use(
`${baseUrl}/api/notification/sub`,
requireAuth,
deleteSub,
);
// all other system should be under /api/system/*
};

View File

@@ -43,7 +43,7 @@ const parseZebraAlert = (body: any): PrinterEvent => {
};
};
r.post("/printer/listener/:printer", upload.any(), async (req, res) => {
r.post("/:printer", upload.any(), async (req, res) => {
const { printer: printerName } = req.params;
const event: PrinterEvent = parseZebraAlert(req.body);

View File

@@ -62,7 +62,7 @@ export const printerSync = async () => {
});
}
if (printers?.success) {
if (printers?.success && Array.isArray(printers.data)) {
const ignorePrinters = ["pdf24", "standard"];
const validPrinters =

View File

@@ -21,7 +21,7 @@ import { printerSync } from "./ocp.printer.manage.js";
const r = Router();
r.post("/printer/update", async (_, res) => {
r.post("/update", async (_, res) => {
printerSync();
return apiReturn(res, {
success: true,

View File

@@ -1,24 +1,16 @@
import { type Express, Router } from "express";
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js";
import listener from "./ocp.printer.listener.js";
import update from "./ocp.printer.update.js";
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
const router = Router();
// is the feature even on?
router.use(featureCheck("ocp"));
// non auth routes up here
router.use(listener);
// auth routes below here
router.use(requireAuth);
router.use(update);
//router.use("");
app.use(`${baseUrl}/api/ocp`, router);
app.use(`${baseUrl}/api/ocp/printer/listener`, featureCheck("ocp"), listener);
app.use(
`${baseUrl}/api/ocp/printer`,
featureCheck("ocp"),
requireAuth,
update,
);
};

View File

@@ -3,7 +3,7 @@ import { addHours } from "date-fns";
import { formatInTimeZone } from "date-fns-tz";
import { eq, sql } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock.schema.js";
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
import { settings } from "../db/schema/settings.schema.js";
import { createLogger } from "../logger/logger.controller.js";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
@@ -27,6 +27,9 @@ type Releases = {
Quantity: number;
LineItemArticleWeight: number;
CustomerReleaseNumber: string;
DeliveryAddressDescription: string;
DeliveryAddressHumanReadableId: string;
AdditionalInformation1: string;
};
const timeZone = process.env.TIMEZONE as string;
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
@@ -73,6 +76,28 @@ const postRelease = async (release: Releases) => {
log.info({}, "Refreshing Auth Token");
await getToken();
}
// load validation checks
const defaultDock = await db.query.settings.findFirst({
where: (u, { eq }) => eq(u.name, "defaultLoadType"),
});
// check if the release has the data in it
const releaseLoadtypeCheck = (release.AdditionalInformation1 ?? "")
.toLowerCase()
.split(",")
.map((x) => x.trim())
.includes("drop");
const opendDockArticleCheck = await db.query.opendockArticleSetup.findFirst({
where: (table, { and, eq }) =>
and(
eq(table.av, release.LineItemArticleWeight),
eq(table.customer, release.DeliveryAddressHumanReadableId),
),
});
// TODO: add in docks from lst db here to make it more universal for the team
/**
* ReleaseState
* 0 = open
@@ -101,6 +126,7 @@ const postRelease = async (release: Releases) => {
: release.DeliveryState === 4 && "Completed",
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
// TODO: look in the remarks in the release and if its says
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
refNumbers: [release.ReleaseNumber],
//refNumber: release.ReleaseNumber,
@@ -115,6 +141,19 @@ const postRelease = async (release: Releases) => {
},
units: null,
customFields: [
{
name: "strCustomer",
type: "str",
label: "Customer",
value: `${release.DeliveryAddressDescription}`,
description: "Who is the customer ",
placeholder: "",
dropDownValues: [],
minLengthOrValue: 1,
hiddenFromCarrier: false,
requiredForCarrier: false,
requiredForWarehouse: false,
},
{
name: "strArticle",
type: "str",
@@ -190,58 +229,179 @@ const postRelease = async (release: Releases) => {
if (existing) {
const id = existing.openDockAptId;
try {
const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}`,
newDockApt,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
);
if (response.status === 400) {
log.error({}, response.data.data.message);
if (
(releaseLoadtypeCheck ||
opendDockArticleCheck?.loadType === "drop" ||
defaultDock?.value === "drop") &&
(release.DeliveryState === 0 || release.DeliveryState === 1)
) {
const setArrival = { ...newDockApt, status: "Arrived" };
// set to arrived
try {
const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}`,
setArrival,
{
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,
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;
}
// update the release in the db leaving as insert just incase something weird happened
// set to inprogress
await delay(1500);
try {
await db
.insert(opendockApt)
.values({
release: release.ReleaseNumber,
openDockAptId: response.data.data.id,
appointment: response.data.data,
})
.onConflictDoUpdate({
target: opendockApt.release,
set: {
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,
upd_date: sql`NOW()`,
},
})
.returning();
})
.onConflictDoUpdate({
target: opendockApt.release,
set: {
openDockAptId: response.data.data.id,
appointment: response.data.data,
upd_date: sql`NOW()`,
},
})
.returning();
log.info({}, `${release.ReleaseNumber} was updated`);
} catch (e) {
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(
{ error: e },
`Error updating the release: ${release.ReleaseNumber}`,
{ stack: e.response.data },
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
//console.info(newDockApt);
log.error(
{ error: e.response.data },
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
);
return;
return;
}
} else {
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,
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 {
try {
@@ -287,13 +447,13 @@ const postRelease = async (release: Releases) => {
log.info({}, `${release.ReleaseNumber} was created`);
} catch (e) {
log.error({ error: e }, "Error creating new release");
log.error({ stack: e }, "Error creating new release");
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
log.error(
{ error: e?.response?.data },
"Error posting new release to opendock",
{ stack: e?.response?.data },
`Error posting new release to opendock, ${release.ReleaseNumber}`,
);
return;

View File

@@ -0,0 +1,277 @@
import { desc, eq, sql } from "drizzle-orm";
import { Router } from "express";
import z from "zod";
import { db } from "../db/db.controller.js";
import {
type NewOpendockArticleSetup,
opendockArticleSetup,
} from "../db/schema/opendock_articleSetup.js";
import { opendockDockSetup } from "../db/schema/opendock_docks.js";
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();
const newArticleLink = z.object({
av: z.number().int(),
description: z.string(),
customer: z.string().min(1).max(32),
customerDescription: z.string().min(2).max(100),
loadType: z
.enum(["drop", "live"])
.optional()
.describe("What roles are available to use."),
dock: z
//.record(z.string(), z.unknown())
.string()
.optional()
.describe(
"This allows us to add extra fields to the data to parse against",
),
});
const newDockLink = z.object({
name: z.string(),
dockID: z.string(),
});
r.post("/", async (req, res) => {
try {
const validated = newArticleLink.parse(req.body) as NewOpendockArticleSetup;
const newLink = await db
.insert(opendockArticleSetup)
.values({
av: validated.av,
description: validated.description,
customer: validated.customer,
customerDescription: validated.customerDescription,
loadType: validated.loadType,
dock: validated.dock,
add_user: req.user?.username ?? "lst_user",
})
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `${validated.av} was just added `,
data: newLink as any,
status: 200,
});
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
// return res.status(400).json({
// error: "Validation failed",
// details: flattened,
// });
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Validation failed",
data: [flattened.fieldErrors],
status: 400, //connect.success ? 200 : 400,
});
}
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Internal Server Error adding article link",
data: [err],
status: 400, //connect.success ? 200 : 400,
});
}
});
r.patch("/:id", async (req, res) => {
const { id } = req.params;
const updates: Record<string, unknown | null> = {};
if (req.body?.loadType !== undefined) {
updates.loadType = req.body.loadType;
}
if (req.body?.dock !== undefined) {
updates.dock = req.body.dock;
}
updates.upd_user = req.user?.username || "lst_user";
updates.upd_date = sql`NOW()`;
const updatedSetting = await db
.update(opendockArticleSetup)
.set(updates)
.where(eq(opendockArticleSetup.id, id))
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `${updatedSetting[0]?.av} was just updated. `,
data: updatedSetting,
status: 200,
});
});
r.delete("/:id", async (req, res) => {
const { id } = req.params;
const removeLink = await db
.delete(opendockArticleSetup)
.where(eq(opendockArticleSetup.id, id))
.returning();
return apiReturn(res, {
success: false,
level: "info", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Article link was deleted",
data: removeLink,
status: 200, //connect.success ? 200 : 400,
});
});
r.get("/", async (_, res) => {
const { data } = await tryCatch(
db
.select()
.from(opendockArticleSetup)
.orderBy(desc(opendockArticleSetup.customer))
.limit(1500),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `All links`,
data: data ?? [],
status: 200,
});
});
r.get("/customers/:av", async (req, res) => {
const { av } = req.params;
const avSQLQuery = sqlQuerySelector(`opendock.addressLink`) as SqlQuery;
if (!avSQLQuery.success) {
return apiReturn(res, {
success: true,
level: "error",
module: "opendock",
subModule: "articleCheck",
message: avSQLQuery.message,
data: [],
status: 200,
});
}
const { data } = await tryCatch(
prodQuery(
avSQLQuery.query.replace("[articleCheck]", av),
"openDock addressLink",
),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `All customers linked to av: ${av}`,
data: data?.data ?? ([] as any),
status: 200,
});
});
r.post("/dock", async (req, res) => {
try {
const validated = newDockLink.parse(req.body) as any;
const newLink = await db
.insert(opendockDockSetup)
.values({
name: validated.name,
dockID: validated.dockID,
add_user: req.user?.username ?? "lst_user",
})
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `${validated.name} was just added `,
data: newLink as any,
status: 200,
});
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
// return res.status(400).json({
// error: "Validation failed",
// details: flattened,
// });
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Validation failed",
data: [flattened.fieldErrors],
status: 400, //connect.success ? 200 : 400,
});
}
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Internal Server Error adding dock link",
data: [err],
status: 400, //connect.success ? 200 : 400,
});
}
});
r.get("/dock", async (_, res) => {
const { data } = await tryCatch(
db
.select()
.from(opendockDockSetup)
.orderBy(desc(opendockDockSetup.name))
.limit(1500),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `All dock links`,
data: data ?? [],
status: 200,
});
});
export default r;

View File

@@ -1,19 +1,24 @@
import { type Express, Router } from "express";
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js";
import articleCheck from "./opendock.articleCheck.route.js";
import getApt from "./opendockGetRelease.route.js";
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
// Apply auth to entire router
const router = Router();
// is the feature even on?
router.use(featureCheck("opendock_sync"));
app.use(
`${baseUrl}/api/opendock`,
featureCheck("opendock_sync"),
requireAuth,
getApt,
);
// we need to make sure we are authenticated to see the releases
router.use(requireAuth);
router.use(getApt);
app.use(`${baseUrl}/api/opendock`, router);
app.use(
`${baseUrl}/api/opendock/articleCheck`,
featureCheck("opendock_sync"),
requireAuth,
articleCheck,
);
};

View File

@@ -1,7 +1,7 @@
import { desc, gte, sql } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock.schema.js";
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";

View File

@@ -1,5 +1,6 @@
import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import restart from "./prodSqlRestart.route.js";
import start from "./prodSqlStart.route.js";
import stop from "./prodSqlStop.route.js";
@@ -9,9 +10,7 @@ export const setupProdSqlRoutes = (baseUrl: string, app: Express) => {
const router = Router();
router.use(requireAuth);
router.use(start);
router.use(stop);
router.use(restart);
app.use(`${baseUrl}/api/system/prodSql`, router);
app.use(`${baseUrl}/api/system/prodSql/start`, requireAuth, start);
app.use(`${baseUrl}/api/system/prodSql/stop`, requireAuth, stop);
app.use(`${baseUrl}/api/system/prodSql/restart`, requireAuth, restart);
};

View File

@@ -4,7 +4,7 @@ import { closePool, connectProdSql } from "./prodSqlConnection.controller.js";
const r = Router();
r.post("/restart", async (_, res) => {
r.post("/", async (_, res) => {
await closePool();
await new Promise((r) => setTimeout(r, 2000));

View File

@@ -4,7 +4,7 @@ import { connectProdSql } from "./prodSqlConnection.controller.js";
const r = Router();
r.post("/start", async (_, res) => {
r.post("/", async (_, res) => {
const connect = await connectProdSql();
apiReturn(res, {
success: connect.success,

View File

@@ -4,7 +4,7 @@ import { closePool } from "./prodSqlConnection.controller.js";
const r = Router();
r.post("/stop", async (_, res) => {
r.post("/", async (_, res) => {
const connect = await closePool();
apiReturn(res, {
success: connect.success,

View File

@@ -13,12 +13,12 @@ r.[ArticleHumanReadableId]
,ea.JournalNummer as BOL_Number
,[ReleaseConfirmationState]
,[PlanningState]
--,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
,r.[OrderDate]
--,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
,r.[DeliveryDate]
--,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
,r.[LoadingDate]
,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
--,r.[OrderDate]
,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
--,r.[DeliveryDate]
,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
--,r.[LoadingDate]
,[Quantity]
,[DeliveredQuantity]
,r.[AdditionalInformation1]
@@ -66,9 +66,9 @@ ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
zz.IdLieferschein = ea.IdJournal
where
--r.ArticleHumanReadableId in ([articles])
--r.ReleaseNumber = 1452
r.DeliveryDate between @StartDate AND @EndDate
and DeliveredQuantity > 0
--and r.ArticleHumanReadableId in ([articles])
--and Journalnummer = 169386

View File

@@ -21,9 +21,6 @@ ArticleHumanReadableId as article
/** data mart include location data **/
--,l.WarehouseDescription,l.LaneDescription
/** historical section **/
--,l.ProductionLotRunningNumber as lot,l.warehousehumanreadableid as warehouseId,l.WarehouseDescription as warehouseDescription,l.lanehumanreadableid as locationId,l.lanedescription as laneDescription
,articleTypeName
FROM [warehousing].[WarehouseUnit] as l (nolock)
@@ -58,7 +55,4 @@ ArticleTypeName
/** data mart include location data **/
--,l.WarehouseDescription,l.LaneDescription
/** historical section **/
--,l.ProductionLotRunningNumber,l.warehousehumanreadableid,l.WarehouseDescription,l.lanehumanreadableid,l.lanedescription
order by ArticleHumanReadableId

View File

@@ -0,0 +1,48 @@
select
x.idartikelVarianten as article,
x.ArtikelVariantenAlias as alias
--x.Lfdnr as RunningNumber,
,round(sum(EinlagerungsMengeVPKSum),2) as total_pallets
,sum(EinlagerungsMengeSum) as total_palletQTY
,round(sum(VerfuegbareMengeVPKSum),0) as available_Pallets
,sum(VerfuegbareMengeSum) as available_QTY
,sum(case when c.Description LIKE '%COA%' then GesperrteMengeVPKSum else 0 end) as coa_Pallets
,sum(case when c.Description LIKE '%COA%' then GesperrteMengeSum else 0 end) as coa_QTY
,sum(case when c.Description NOT LIKE '%COA%' or x.IdMainDefect = -1 then GesperrteMengeVPKSum else 0 end) as held_Pallets
,sum(case when c.Description NOT LIKE '%COA%' or x.IdMainDefect = -1 then GesperrteMengeSum else 0 end) as held_QTY
,sum(case when x.WarenLagerLagerTyp = 8 then VerfuegbareMengeSum else 0 end) as consignment_qty
,IdProdPlanung as lot
----,IdAdressen,
,x.AdressBez
,x.IdLagerAbteilung as locationId
,x.LagerAbteilungKurzBez as laneDescription
,x.IdWarenlager as warehouseId
,x.WarenLagerKurzBez as warehouseDescription
--,*
from [AlplaPROD_test1].dbo.[V_LagerPositionenBarcodes] (nolock) x
left join
[AlplaPROD_test1].dbo.T_EtikettenGedruckt as l(nolock) on
x.Lfdnr = l.Lfdnr AND l.Lfdnr > 1
left join
(SELECT *
FROM [AlplaPROD_test1].[dbo].[T_BlockingDefects] where Active = 1) as c
on x.IdMainDefect = c.IdBlockingDefect
/*
The data below will be controlled by the user in excell by default everything will be passed over
IdAdressen = 3
*/
where /*IdArtikelTyp = 1 and */x.IdWarenlager not in (6, 1)
group by x.idartikelVarianten, ArtikelVariantenAlias, c.Description
--,IdAdressen
,x.AdressBez
,IdProdPlanung
,x.IdLagerAbteilung
,x.LagerAbteilungKurzBez
,x.IdWarenlager
,x.WarenLagerKurzBez
--, x.Lfdnr
order by x.IdArtikelVarianten

View File

@@ -5,19 +5,75 @@ move this over to the delivery date range query once we have the shift data mapp
update the psi stuff on this as well.
**/
declare @start_date nvarchar(30) = '[startDate]' --'2025-01-01'
declare @end_date nvarchar(30) = '[endDate]' --'2025-08-09'
DECLARE @StartDate DATE = '[startDate]' -- 2025-1-1
DECLARE @EndDate DATE = '[endDate]' -- 2025-1-31
SELECT
r.[ArticleHumanReadableId]
,[ReleaseNumber]
,h.CustomerOrderNumber
,x.CustomerLineItemNumber
,[CustomerReleaseNumber]
,[ReleaseState]
,[DeliveryState]
,ea.JournalNummer as BOL_Number
,[ReleaseConfirmationState]
,[PlanningState]
--,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
,r.[OrderDate]
--,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
,r.[DeliveryDate]
--,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
,r.[LoadingDate]
,[Quantity]
,[DeliveredQuantity]
,r.[AdditionalInformation1]
,r.[AdditionalInformation2]
,[TradeUnits]
,[LoadingUnits]
,[Trucks]
,[LoadingToleranceType]
,[SalesPrice]
,[Currency]
,[QuantityUnit]
,[SalesPriceRemark]
,r.[Remark]
,[Irradiated]
,r.[CreatedByEdi]
,[DeliveryAddressHumanReadableId]
,DeliveryAddressDescription
,[CustomerArtNo]
,[TotalPrice]
,r.[ArticleAlias]
FROM [order].[Release] (nolock) as r
select IdArtikelVarianten,
ArtikelVariantenBez,
sum(Menge) totalDelivered,
case when convert(time, upd_date) between '00:00' and '07:00' then convert(date, upd_date - 1) else convert(date, upd_date) end as ShippingDate
left join
[order].LineItem as x on
from dbo.V_LadePlanungenLadeAuftragAbruf (nolock)
r.LineItemId = x.id
where upd_date between CONVERT(datetime, @start_date + ' 7:00') and CONVERT(datetime, @end_date + ' 7:00')
and IdArtikelVarianten in ([articles])
left join
[order].Header as h on
x.HeaderId = h.id
group by IdArtikelVarianten, upd_date,
ArtikelVariantenBez
--bol stuff
left join
AlplaPROD_test1.dbo.V_LadePlanungenLadeAuftragAbruf (nolock) as zz
on zz.AbrufIdAuftragsAbruf = r.ReleaseNumber
left join
(select * from (SELECT
ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
,*
FROM [AlplaPROD_test1].[dbo].[T_Lieferungen] (nolock)) x
where RowNum = 1) as ea on
zz.IdLieferschein = ea.IdJournal
where
r.ArticleHumanReadableId in ([articles])
--r.ReleaseNumber = 1452
and r.DeliveryDate between @StartDate AND @EndDate
--and DeliveredQuantity > 0
--and Journalnummer = 169386

View File

@@ -0,0 +1,21 @@
/*
disables sql jobs.
*/
--EXEC msdb.dbo.sp_update_job @job_name = N'[jobName]', @enabled = 0;
-- DECLARE @JobName varchar(max) = '[jobName]'
-- UPDATE msdb.dbo.sysjobs
-- SET enabled = 0
-- WHERE name = @JobName;
DECLARE @JobName NVARCHAR(128) = N'[jobName]';
IF EXISTS (
SELECT 1
FROM msdb.dbo.sysjobs
WHERE name = @JobName
)
BEGIN
EXEC msdb.dbo.sp_update_job
@job_name = @JobName,
@enabled = 0;
END

View File

@@ -0,0 +1,11 @@
SELECT count(*) as activated
FROM [test1_AlplaPROD2.0_Read].[support].[FeatureActivation]
where feature in (108,7)
/*
as more features get activated and need to have this checked to include the new endpoints add here so we can check this.
108 = waste
7 = warehousing
*/

View File

@@ -0,0 +1,34 @@
/*
This will return all address with a sales price.
*/
WITH ranked AS (
SELECT
av.id,
av.humanReadableId as av,
av.Alias as description,
-- CONCAT(ad.HumanReadableId, ' - ',ad.Name) as customer ,
ad.HumanReadableId as customer,
ad.Name as customerDescription,
ROW_NUMBER() OVER (
PARTITION BY AddressId, sp.articleId
ORDER BY ValidAfter DESC
) AS rn
FROM [test1_AlplaPROD2.0_Read].[masterData].[SalesPrice] as sp (nolock)
/* av */
left join
[test1_AlplaPROD2.0_Read].[masterData].[Article] as av (nolock) on
av.id = sp.articleId
/* address */
left join
[test1_AlplaPROD2.0_Read].[masterData].[Address] as ad (nolock) on
ad.id = AddressId
)
SELECT *
FROM ranked
WHERE rn = 1
and ranked.av = '[articleCheck]'
order by customerDescription

View File

@@ -0,0 +1,6 @@
USE [test1_AlplaPROD2.0_Read]
SELECT *
FROM [masterData].[Dock] (nolock)
where active = 1
order by Description desc

View File

@@ -21,7 +21,7 @@ SELECT
,[MainMaterialId]
,[MainMaterialHumanReadableId]
,[MainMaterialDescription]
,[AdditionalInformation1]
,[AdditionalInformation1] -- we will use this to reference as the first check
,[AdditionalInformation2]
,[D365SupplierLot]
,[TradeUnits]
@@ -47,9 +47,9 @@ SELECT
,[PaymentTermsId]
,[PaymentTermsHumanReadableId]
,[PaymentTermsDescription]
,[Remark]
,[Remark]
,[DeliveryAddressId]
,[DeliveryAddressHumanReadableId]
,[DeliveryAddressHumanReadableId] --use this to validate with the new drop or live check
,[DeliveryAddressDescription]
,[DeliveryStreetName]
,[DeliveryAddressZip]

View File

@@ -1,16 +1,17 @@
use [test1_AlplaPROD2.0_Read]
SELECT
--JSON_VALUE(content, '$.EntityId') as labelId
JSON_VALUE(content, '$.EntityId') as labelId,
a.id
,ActorName
,FORMAT(PrintDate, 'yyyy-MM-dd HH:mm') as printDate
--,FORMAT(l.PrintDate, 'yyyy-MM-dd HH:mm') as printDate
,Format(COALESCE(l.PrintDate, e.ProductionDate), 'yyyy-MM-dd HH:mm') as printDate
,FORMAT(CreatedDateTime, 'yyyy-MM-dd HH:mm') createdDateTime
,l.ArticleHumanReadableId as av
,l.ArticleDescription as alias
,PrintedCopies
,p.name as printerName
,RunningNumber
,COALESCE(l.ArticleHumanReadableId,e.ArticleHumanReadableId) as av
,COALESCE(l.ArticleDescription, av.Name) as alias
,COALESCE(l.PrintedCopies, 0) as PrintedCopies
,COALESCE(p.name,'External Label not tracked') as printerName
,COALESCE(l.RunningNumber, e.RunningNumber) as runningNumber
--,*
FROM [support].[AuditLog] (nolock) as a
@@ -18,10 +19,20 @@ left join
[labelling].[InternalLabel] (nolock) as l on
l.id = JSON_VALUE(content, '$.EntityId')
OUTER APPLY (
SELECT TOP 1 *
FROM labelling.ExternalLabel e
WHERE e.id = JSON_VALUE(a.content, '$.EntityId')
ORDER BY e.Id DESC
) e
left join
[masterData].[printer] (nolock) as p on
p.id = l.PrinterId
left join
[masterData].[article] (nolock) as av on
av.HumanReadableId = e.ArticleHumanReadableId
where message like '%reprint%'
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
and a.id > [ignoreList]

View File

@@ -45,7 +45,7 @@ export const monitorAlplaPurchase = async () => {
}
if (purchaseMonitor[0]?.active) {
createCronJob("purchaseMonitor", "0 */5 * * * *", async () => {
createCronJob("purchaseMonitor", "0 5 * * * *", async () => {
try {
const result = await prodQuery(
sqlQuery.query.replace(

View File

@@ -1,10 +1,12 @@
import type { Express } from "express";
import { setupAdminRoutes } from "./admin/admin.routes.js";
import { setupAuthRoutes } from "./auth/auth.routes.js";
// import the routes and route setups
import { setupApiDocsRoutes } from "./configs/scaler.config.js";
import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
import { setupDockDoorRoutes } from "./dockdoorScanning/dockdoor.routes.js";
import { setupGPSqlRoutes } from "./gpSql/gpSql.routes.js";
import { setupMobileRoutes } from "./mobile/mobile.routes.js";
import { setupNotificationRoutes } from "./notification/notification.routes.js";
import { setupOCPRoutes } from "./ocp/ocp.routes.js";
import { setupOpendockRoutes } from "./opendock/opendock.routes.js";
@@ -15,7 +17,9 @@ import { setupUtilsRoutes } from "./utils/utils.routes.js";
export const setupRoutes = (baseUrl: string, app: Express) => {
//routes that are on by default
setupMobileRoutes(baseUrl, app);
setupSystemRoutes(baseUrl, app);
setupAdminRoutes(baseUrl, app);
setupApiDocsRoutes(baseUrl, app);
setupProdSqlRoutes(baseUrl, app);
setupGPSqlRoutes(baseUrl, app);
@@ -26,4 +30,5 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
setupNotificationRoutes(baseUrl, app);
setupOCPRoutes(baseUrl, app);
setupTCPRoutes(baseUrl, app);
setupDockDoorRoutes(baseUrl, app);
};

View File

@@ -8,6 +8,7 @@ import { connectGPSql } from "./gpSql/gpSqlConnection.controller.js";
import { createLogger } from "./logger/logger.controller.js";
import { historicalSchedule } from "./logistics/logistics.historicalInv.js";
import { startNotifications } from "./notification/notification.controller.js";
import { sqlJobCleanUp } from "./notification/notification.SqlJobCleanUp.js";
import { createNotifications } from "./notification/notifications.master.js";
import { printerSync } from "./ocp/ocp.printer.manage.js";
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
@@ -15,10 +16,17 @@ import { opendockSocketMonitor } from "./opendock/opendockSocketMonitor.utils.js
import { connectProdSql } from "./prodSql/prodSqlConnection.controller.js";
import { monitorAlplaPurchase } from "./purchase/purchase.controller.js";
import { setupSocketIORoutes } from "./socket.io/serverSetup.js";
import { serversChecks } from "./system/serverData.controller.js";
import { baseSettingValidationCheck } from "./system/settingsBase.controller.js";
import { startTCPServer } from "./tcpServer/tcp.server.js";
import {
aggregateRouteHitsForBusinessDay,
cleanupOldRouteHits,
runRouteHitAnalyticsCron,
} from "./utils/analyticRouteHits.utils.js";
import { createCronJob } from "./utils/croner.utils.js";
import { sendEmail } from "./utils/sendEmail.utils.js";
import { ppooMonitoring } from "./warehousing/warehousing.ppooMonitor.js";
const port = Number(process.env.PORT) || 3000;
export let systemSettings: Setting[] = [];
@@ -67,9 +75,23 @@ const start = async () => {
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
historicalSchedule();
createCronJob("aggregateHits", "0 0 7 * * *", async () =>
runRouteHitAnalyticsCron(),
);
createCronJob("ppooMonitor", "*/45 * * * * *", async () =>
ppooMonitoring(),
);
createCronJob("cleanHitsUp", "0 0 7 * * *", () => cleanupOldRouteHits());
// one shots only needed to run on server startups
createNotifications();
startNotifications();
serversChecks();
aggregateRouteHitsForBusinessDay();
// can be removed at a later date
sqlJobCleanUp();
}, 5 * 1000);
process.on("uncaughtException", async (err) => {

View File

@@ -1,4 +1,4 @@
import type { RoomId } from "./types.socket.js";
import type { RoomId } from "./roomDefinitions.socket.js";
export const MAX_HISTORY = 50;
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed

View File

@@ -1,17 +1,50 @@
import { desc } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { logs } from "../db/schema/logs.schema.js";
import type { RoomId } from "./types.socket.js";
import { ppoRun } from "../warehousing/warehousing.ppooMonitor.js";
type RoomDefinition<T = unknown> = {
seed: (limit: number) => Promise<T[]>;
};
export const protectedRooms: any = {
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
admin: { requiresAuth: true, role: ["admin", "systemAdmin"] },
export type StaticRoomId = "logs" | "labels" | "admin" | "admin:build" | "ppoo";
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: {},
};
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) => {
@@ -36,4 +69,26 @@ export const roomDefinition: Record<RoomId, RoomDefinition> = {
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;
},
},
};

View File

@@ -1,6 +1,6 @@
// the emitter setup
import type { RoomId } from "./types.socket.js";
import type { RoomId } from "./roomDefinitions.socket.js";
let addDataToRoom: ((roomId: RoomId, payload: unknown[]) => void) | null = null;

View File

@@ -7,18 +7,33 @@ import {
roomFlushTimers,
roomHistory,
} from "./roomCache.socket.js";
import { roomDefinition } from "./roomDefinitions.socket.js";
import type { RoomId } from "./types.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];
const roomDef = roomDefinition[roomId] as any;
if (!roomDef) {
log.error({}, `Room ${roomId} is not defined`);
@@ -32,7 +47,7 @@ export const preseedRoom = async (roomId: RoomId) => {
};
export const createRoomEmitter = (io: Server) => {
const addDataToRoom = <T>(roomId: RoomId, payload: T) => {
const addDataToRoom = <T>(roomId: RoomId, payload: T[]) => {
if (!roomHistory.has(roomId)) {
roomHistory.set(roomId, []);
}

View File

@@ -7,7 +7,11 @@ import { Server } from "socket.io";
import { createLogger } from "../logger/logger.controller.js";
import { allowedOrigins } from "../utils/cors.utils.js";
import { registerEmitter } from "./roomEmitter.socket.js";
import { createRoomEmitter, preseedRoom } from "./roomService.socket.js";
import {
createRoomEmitter,
preseedRoom,
registerRoomService,
} from "./roomService.socket.js";
//const __filename = fileURLToPath(import.meta.url);
//const __dirname = dirname(__filename);
@@ -15,7 +19,7 @@ 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 { protectedRooms } from "./roomDefinitions.socket.js";
import { getRoomConfig } from "./roomDefinitions.socket.js";
// declare module "socket.io" {
// interface Socket {
@@ -33,6 +37,9 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
},
});
// manage members of the rooms.
registerRoomService(io);
// ✅ Create emitter instance
const { addDataToRoom } = createRoomEmitter(io);
registerEmitter(addDataToRoom);
@@ -78,40 +85,76 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
version: "1.0.0",
});
s.on("join-room", async (rn) => {
const config = protectedRooms[rn];
// s.on("join-room", async (rn) => {
// const config = protectedRooms[rn];
if (config?.requiresAuth && !s.user) {
// if (config?.requiresAuth && !s.user) {
// 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) {
return s.emit("room-error", {
room: rn,
roomId: rn,
message: `Unknown room: ${rn}`,
});
}
if (config.requiresAuth && !s.user) {
return s.emit("room-error", {
roomId: rn,
message: "Authentication required",
});
}
const roles = Array.isArray(config.role) ? config.role : [config.role];
const roles = Array.isArray(config.role) ? config.role : [];
console.log(roles, s.user.role);
//if (config?.role && s.user?.role !== config.role) {
if (config?.role && !roles.includes(s.user?.role)) {
if (roles.length > 0 && !roles.includes(s.user?.role)) {
return s.emit("room-error", {
room: rn,
roomId: rn,
message: `Not authorized to be in room: ${rn}`,
});
}
s.join(rn);
// get room seeded
const history = await preseedRoom(rn);
const history = await preseedRoom(rn as any);
log.info({}, `User joined ${rn}: ${s.id}`);
// send the intial data
s.emit("room-update", {
roomId: rn,
payloads: history,
initial: true,
});
});
s.on("leave-room", (room) => {
s.leave(room);
log.info({}, `${s.id} left room: ${room}`);

View File

@@ -1 +0,0 @@
export type RoomId = "logs" | "labels"; //| "alerts" | "metrics";

View File

@@ -0,0 +1,216 @@
import { sql } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import {
type NewServerData,
serverData,
} from "../db/schema/serverData.schema.js";
import { createLogger } from "../logger/logger.controller.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const servers: NewServerData[] = [
{
name: "Test server 1",
server: "USMCD1VMS036",
plantToken: "test3",
idAddress: "10.193.0.56",
greatPlainsPlantCode: "00",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Test server 2",
server: "USIOW1VMS036",
plantToken: "test2",
idAddress: "10.75.0.56",
greatPlainsPlantCode: "00",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Lima",
server: "USLIM1VMS006",
plantToken: "uslim1",
idAddress: "10.53.0.26", // port opened 3000 2222
greatPlainsPlantCode: "50",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Houston",
server: "ushou1VMS006",
plantToken: "ushou1",
idAddress: "10.195.0.26",
greatPlainsPlantCode: "20",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Dayton",
server: "usday1VMS006",
plantToken: "usday1",
idAddress: "10.44.0.56", // ports opened 3000 and 2222
greatPlainsPlantCode: "80",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "West Bend",
server: "usweb1VMS006",
plantToken: "usweb1",
idAddress: "10.80.0.26",
greatPlainsPlantCode: "65",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Jeff City",
server: "usjci1VMS006",
plantToken: "usjci",
idAddress: "10.167.0.26",
greatPlainsPlantCode: "40",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Sherman",
server: "usshe1vms006",
plantToken: "usshe1",
idAddress: "10.205.0.26",
greatPlainsPlantCode: "21",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "McDonough",
server: "USMCD1VMS006",
plantToken: "usmcd1",
idAddress: "10.193.0.26",
greatPlainsPlantCode: "10",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "St. Peters",
server: "USSTP1VMS006",
plantToken: "usstp1",
idAddress: "10.37.0.26",
greatPlainsPlantCode: "45",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Marked Tree",
server: "USMAR1VMS006",
plantToken: "usmar1",
idAddress: "10.206.9.26", // 3000,2222 requested REQ0236838
greatPlainsPlantCode: "90",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Iowa City EBM",
server: "USIOW1VMS006",
plantToken: "usiow1",
idAddress: "10.75.0.26",
greatPlainsPlantCode: "30",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Bowling Green 1",
server: "USBOW1VMS006",
plantToken: "usbow1",
idAddress: "10.25.0.26", // 3000 is open REQ0236527 2222 already open
greatPlainsPlantCode: "55",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Bethlehem",
server: "USBET1VMS006",
plantToken: "usbet1",
idAddress: "10.25.0.26",
greatPlainsPlantCode: "75",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Salt Lake City",
server: "USSLC1VMS006",
plantToken: "usslc1",
idAddress: "10.202.0.26",
greatPlainsPlantCode: "70",
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
export const serversChecks = async () => {
const log = createLogger({ module: "system", subModule: "serverData" });
const { data, error } = await tryCatch(
db
.insert(serverData)
.values(servers)
.onConflictDoUpdate({
target: serverData.plantToken,
set: {
server: sql`excluded.server`,
name: sql`excluded.name`,
idAddress: sql`excluded."id_address"`,
greatPlainsPlantCode: sql`excluded.great_plains_plant_code`,
contactEmail: sql`excluded."contact_email"`,
contactPhone: sql`excluded.contact_phone`,
serverLoc: sql`excluded.server_loc`,
},
})
.returning(),
);
if (error) {
log.error(
{ error: error },
"There was an error when adding or updating the servers.",
);
}
if (data) {
log.info({}, "All Servers were added/updated");
}
};
// Communication from logistic network to logisticsSupportTool (for printers and scanners)
// network justification
// scanners and printers are on dhcp

View File

@@ -0,0 +1,43 @@
import { type Response, Router } from "express";
import { db } from "../db/db.controller.js";
import { serverData } from "../db/schema/serverData.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
// export const updateSetting = async (setting: Setting) => {
// // TODO: when the setting is a feature setting we will need to have it run each kill switch on the crons well just stop them and during a reset it just wont start them
// // TODO: when the setting is a system we will need to force an app restart
// // TODO: when the setting is standard we don't do anything.
// };
const r = Router();
r.get("/", async (_, res: Response) => {
const { data: sName, error: sError } = await tryCatch(
db.select().from(serverData).orderBy(serverData.name),
);
if (sError) {
return apiReturn(res, {
success: false,
level: "error",
module: "system",
subModule: "serverData",
message: `There was an error getting the servers `,
data: [sError],
status: 400,
});
}
return apiReturn(res, {
success: true,
level: "info",
module: "system",
subModule: "serverData",
message: `All current servers`,
data: sName ?? [],
status: 200,
});
});
export default r;

View File

@@ -76,8 +76,50 @@ const newSettings: NewSetting[] = [
roles: ["admin"],
seedVersion: 1,
},
{
name: "mobile",
value: "0",
active: false,
description: "LST Android Mobile app",
moduleName: "mobile",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
{
name: "dockDoorScanning",
value: "0",
active: false,
description: "dock door scanning",
moduleName: "dockDoorScanning",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
// standard settings
{
name: "stagingWarehouse",
value: "30218",
active: true,
description:
"The warehouse we will use for staging, validation that we did our prechecks if required",
moduleName: "dockDoorScanning",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "precheck",
value: "5",
active: false,
description:
"Precheck is required, the value is in minute, 5 min should be 5",
moduleName: "dockDoorScanning",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "prolinkCheck",
value: "1",
@@ -304,6 +346,60 @@ const newSettings: NewSetting[] = [
roles: ["admin"],
seedVersion: 1,
},
{
name: "laneCheck",
value: "0",
active: false,
description:
"Allows the driver to scan a lane and see what is in the lane and details about each pallet.",
moduleName: "mobile",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "dockScan",
value: "0",
active: false,
description:
"Enables dock door scanning, must have a dock scanner setup for this to work.",
moduleName: "mobile",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "cycleCounting",
value: "0",
active: false,
description: "Enables a cycle count to be triggered from the scanner.",
moduleName: "mobile",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "scannerIds",
value: "10",
active: false,
description:
"How many scanners ids are setup for this, there should be a lst_scanner instance created.",
moduleName: "mobile",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "defaultLoadType",
value: "drop",
active: false,
description:
"What is the default load type we will use for creating new apt: drop or live are the current options.",
moduleName: "opendock",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
];
export const baseSettingValidationCheck = async () => {

View File

@@ -27,7 +27,7 @@ router.get("/", async (_, res) => {
? sqlServerStats?.data[0].UptimeSeconds
: [],
eomFGPkgSheetVersion: 1, // this is the excel file version when we have a change to the macro we want to grab this
masterMacroFile: 1,
masterMacroFile: 1.1,
tcpServerOnline: isServerRunning,
sqlServerConnected: prodSql,
gpServerConnected: gpSql,

View File

@@ -1,5 +1,7 @@
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import getServers from "./serverData.route.js";
import getSettings from "./settings.route.js";
import updSetting from "./settingsUpdate.route.js";
import stats from "./stats.route.js";
@@ -8,6 +10,7 @@ export const setupSystemRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/stats`, stats);
app.use(`${baseUrl}/api/settings`, getSettings);
app.use(`${baseUrl}/api/servers`, getServers);
app.use(`${baseUrl}/api/settings`, requireAuth, updSetting);
// all other system should be under /api/system/*

View File

@@ -1,14 +1,21 @@
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import restart from "./tcpRestart.route.js";
import start from "./tcpStart.route.js";
import stop from "./tcpStop.route.js";
export const setupTCPRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/tcp/start`, requireAuth, start);
app.use(`${baseUrl}/api/tcp/stop`, requireAuth, stop);
app.use(`${baseUrl}/api/tcp/restart`, requireAuth, restart);
app.use(
`${baseUrl}/api/tcp/restart`,
requireAuth,
restart,
);
// all other system should be under /api/system/*
};

View File

@@ -1,7 +1,9 @@
import net from "node:net";
import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
import { printerData } from "../db/schema/printers.schema.js";
import { loadUnit } from "../dockdoorScanning/dockdoor.loadUnits.js";
import { createLogger } from "../logger/logger.controller.js";
import { delay } from "../utils/delay.utils.js";
import { returnFunc } from "../utils/returnHelper.utils.js";
@@ -14,6 +16,7 @@ export let isServerRunning = false;
const port = parseInt(process.env.TCP_PORT ?? "2222", 10);
// This is the parser for zebra scanners
const parseTcpAlert = (input: string) => {
// guard
const colonIndex = input.indexOf(":");
@@ -74,6 +77,24 @@ export const startTCPServer = async () => {
printerListen(printerData as PrinterData);
}
// check if its a dock door scanner
// TODO: move to the db and get real info lol
const dockdoorScanners = await db.select().from(dockDoorScanners);
if (dockdoorScanners.some((s) => s.ip === ip.replace("::ffff:", ""))) {
console.log("dock door logic");
const currentDock = dockdoorScanners.filter(
(s) => s.ip === ip.replace("::ffff:", ""),
);
// send the data + dock scan over
loadUnit({
dockId: currentDock[0]?.dockId ?? "0",
sscc: data.toString(),
});
}
});
socket.on("end", () => {

Some files were not shown because too many files have changed in this diff Show More