Compare commits
131 Commits
d05a0ce930
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a0c729b9a | |||
| 057a570e43 | |||
| 52974aa0b4 | |||
| ecfbda9036 | |||
| 389211186f | |||
| 3a27fd8542 | |||
| 1f6637c512 | |||
| 1840ac5e58 | |||
| 636daaed0a | |||
| 71c83062cb | |||
| cd67c4de80 | |||
| 36ac1dccb4 | |||
| 514a44b6de | |||
| a7bb364a2f | |||
| 047cc7cdf0 | |||
| 8dc4d70e28 | |||
| c8931c7249 | |||
| 67f36c5499 | |||
| ebf1060475 | |||
| c64392f457 | |||
| e9e73c829c | |||
| bcb7773007 | |||
| eb950d2c29 | |||
| 2616acf106 | |||
| 30ff7b71d9 | |||
| e7af3d1182 | |||
| 3e66c3920d | |||
| eb9d77c3d4 | |||
| 342a97f6b1 | |||
| b0c7277a6c | |||
| dc95e50a84 | |||
| d2a9e1d110 | |||
| a9c69250bd | |||
| d61be61f44 | |||
| f5bae2c0c2 | |||
| 05758791be | |||
| 51026e3e2c | |||
| 9631736e26 | |||
| ce9d8eaaf5 | |||
| 1bbf5c2a49 | |||
| 13718fe702 | |||
| 0de2579942 | |||
| 7c31b43a4a | |||
| 85e96f5ed1 | |||
| 6b515c608f | |||
| d8869b103b | |||
| 1dba774abc | |||
| 505d7cea5d | |||
| 1ff5e5032f | |||
| 5fa70da90c | |||
| 0459cd788a | |||
| 7d7d991122 | |||
| 2721bb2a3b | |||
| 4424c742d2 | |||
| 6d8499bfb8 | |||
| 9edafc9d28 | |||
| e9b0101095 | |||
| ca885fb01a | |||
| edb3668548 | |||
| 87803eed43 | |||
| e61038e004 | |||
| d99449ddc4 | |||
| 3552ca31f9 | |||
| b578f05d64 | |||
| 4ca74de279 | |||
| 12412536d1 | |||
| a38e2e0339 | |||
| 8c253a90b6 | |||
| ba30281e59 | |||
| 2ad78e22f1 | |||
| 518c0a8c19 | |||
| cd13360cfb | |||
| 4e0cf8c54c | |||
| 36995e9fb4 | |||
| 30ffd843c7 | |||
| bb6155c969 | |||
| 7d2f048932 | |||
| 649ae1ee9f | |||
| 8446dbc955 | |||
| 0b7318f856 | |||
| bddc9aca0d | |||
| 77b4533dea | |||
| 83a542d1b7 | |||
| 4855412733 | |||
| 251970ec8f | |||
| f7ea5f709e | |||
| 3d3c2aa964 | |||
| 781025dca0 | |||
| a593bb2baa | |||
| 759f96b0b6 | |||
| de5df2b00b | |||
| 4d53af0338 | |||
| f7276ca2d7 | |||
| d6328ab764 | |||
| a6d53f0266 | |||
| 7962463927 | |||
| f716de1a58 | |||
| 88cef2a56c | |||
| cb00addee9 | |||
| b832d7aa1e | |||
| 32517d0c98 | |||
| 82f8369640 | |||
| 3734d9daac | |||
| a1eeadeec4 | |||
| 3639c1b77c | |||
| cfbc156517 | |||
| fb3cd85b41 | |||
| 5b1c88546f | |||
| ba3227545d | |||
| 84909bfcf8 | |||
| e0d0ac2077 | |||
| 52a6c821f4 | |||
| eccaf17332 | |||
| 6307037985 | |||
| 4b6061c478 | |||
| fc6dc82d84 | |||
| 6ba905a887 | |||
| f33587a3d9 | |||
| 80189baf90 | |||
| 87f738702a | |||
| 38a0b65e94 | |||
| 9a0ef8e51a | |||
| dcb3f2dd13 | |||
| e47ea9ec52 | |||
| ca3425d327 | |||
| 3bf024cfc9 | |||
| 9d39c13510 | |||
| c9eb59e2ad | |||
| b0e5fd7999 | |||
| 07ebf88806 | |||
| 79e653efa3 |
@@ -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
|
||||
66
.env-example
66
.env-example
@@ -1,32 +1,60 @@
|
||||
NODE_ENV=development
|
||||
# Server
|
||||
PORT=3000
|
||||
URL=http://localhost:3000
|
||||
SERVER_IP=10.75.2.38
|
||||
TIMEZONE=America/New_York
|
||||
TCP_PORT=2222
|
||||
|
||||
# authentication
|
||||
BETTER_AUTH_SECRET=""
|
||||
# Better auth Secret
|
||||
BETTER_AUTH_SECRET=
|
||||
RESET_EXPIRY_SECONDS=3600
|
||||
|
||||
# logging
|
||||
LOG_LEVEL=debug
|
||||
LOG_LEVEL=
|
||||
|
||||
# prodServer
|
||||
PROD_SERVER=usmcd1vms036
|
||||
PROD_PLANT_TOKEN=test3
|
||||
PROD_USER=alplaprod
|
||||
PROD_PASSWORD=password
|
||||
# SMTP password
|
||||
SMTP_PASSWORD=
|
||||
|
||||
# opendock
|
||||
OPENDOCK_URL=https://neutron.opendock.com
|
||||
OPENDOCK_PASSWORD=
|
||||
DEFAULT_DOCK=
|
||||
DEFAULT_LOAD_TYPE=
|
||||
DEFAULT_CARRIER=
|
||||
|
||||
# prodServer when ruining on an actual prod server use localhost this way we don't go out and back in.
|
||||
PROD_SERVER=
|
||||
PROD_PLANT_TOKEN=
|
||||
PROD_USER=
|
||||
PROD_PASSWORD=
|
||||
|
||||
# Tech user for alplaprod api
|
||||
TEC_API_KEY=
|
||||
|
||||
# AD STUFF
|
||||
# this is mainly used for purchase stuff to reference reqs
|
||||
LDAP_URL=
|
||||
|
||||
# postgres connection
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_PORT=5433
|
||||
DATABASE_USER=user
|
||||
DATABASE_PASSWORD=password
|
||||
DATABASE_DB=lst_dev
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_USER=
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_DB=
|
||||
|
||||
# how is the app running server or client when in client mode you must provide the server
|
||||
APP_RUNNING_IN=server
|
||||
SERVER_NAME=localhost
|
||||
# Gp connection
|
||||
GP_USER=
|
||||
GP_PASSWORD=
|
||||
|
||||
#dev stuff
|
||||
GITEA_TOKEN=""
|
||||
EMAIL_USER=""
|
||||
EMAIL_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=""
|
||||
|
||||
66
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
66
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report something that is broken or not working correctly
|
||||
title: "[BUG] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- bug
|
||||
|
||||
---
|
||||
|
||||
# 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
|
||||
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
47
.gitea/ISSUE_TEMPLATE/enhancement.md
Normal file
47
.gitea/ISSUE_TEMPLATE/enhancement.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: Enhancement
|
||||
about: Improve or refine an existing feature
|
||||
title: "[ENHANCEMENT] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- enhancement
|
||||
---
|
||||
|
||||
# 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.
|
||||
40
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
40
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a brand new feature or module
|
||||
title: "[FEATURE] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- feature
|
||||
---
|
||||
|
||||
# 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.
|
||||
@@ -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 }}
|
||||
@@ -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
5
.gitignore
vendored
@@ -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
|
||||
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -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",
|
||||
@@ -65,12 +65,14 @@
|
||||
"onnotice",
|
||||
"opendock",
|
||||
"opendocks",
|
||||
"palletizer",
|
||||
"ppoo",
|
||||
"preseed",
|
||||
"prodlabels",
|
||||
"prolink",
|
||||
"Skelly",
|
||||
"trycatch"
|
||||
"trycatch",
|
||||
"whse"
|
||||
],
|
||||
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
|
||||
"gitea.instanceURL": "https://git.tuffraid.net",
|
||||
|
||||
309
CHANGELOG.md
309
CHANGELOG.md
@@ -1,5 +1,314 @@
|
||||
# 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)
|
||||
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **datamart:** migrations completed remaining is the deactivation that will be ran by anylitics ([eccaf17](https://git.tuffraid.net/cowch/lst_v3/commits/eccaf17332fb1c63b8d6bbea6f668c3bb42d44b7))
|
||||
* **datamart:** psi data has been added :D ([e0d0ac2](https://git.tuffraid.net/cowch/lst_v3/commits/e0d0ac20773159373495d65023587b76b47df34f))
|
||||
* **migrate:** quality alert migrated ([b0e5fd7](https://git.tuffraid.net/cowch/lst_v3/commits/b0e5fd79998d551d4f155d58416157a324498fbd))
|
||||
* **ocp:** printer sync and logging logic added ([80189ba](https://git.tuffraid.net/cowch/lst_v3/commits/80189baf906224da43ec1b9b7521153d2a49e059))
|
||||
* **tcp crud:** tcp server start, stop, restart endpoints + status check ([6307037](https://git.tuffraid.net/cowch/lst_v3/commits/6307037985162bc6b49f9f711132853296f43eee))
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **datamart:** error when running build and crashed everything ([52a6c82](https://git.tuffraid.net/cowch/lst_v3/commits/52a6c821f4632e4b5b51e0528a0d620e2e0deffc))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* **docs:** removed docusorus as all docs will be inside lst now to better assist users ([6ba905a](https://git.tuffraid.net/cowch/lst_v3/commits/6ba905a887dbd8f306d71fed75bb34c71fee74c9))
|
||||
* **env example:** updated the file ([ca3425d](https://git.tuffraid.net/cowch/lst_v3/commits/ca3425d327757120c2cc876fff28e8668c76838d))
|
||||
* **notifcations:** docs for intro, notifcations, reprint added ([87f7387](https://git.tuffraid.net/cowch/lst_v3/commits/87f738702a935279a248d471541cdd9d49330565))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **agent:** changed to have the test servers on there own push for better testing ([3bf024c](https://git.tuffraid.net/cowch/lst_v3/commits/3bf024cfc97d2841130d54d1a7c5cb5f09f0f598))
|
||||
* **connection:** corrected the connection to the old system ([38a0b65](https://git.tuffraid.net/cowch/lst_v3/commits/38a0b65e9450c65b8300a10058a8f0357400f4e6))
|
||||
* **logging:** when notify is true send the error to systemAdmins ([79e653e](https://git.tuffraid.net/cowch/lst_v3/commits/79e653efa3bcb2941ccee06b28378e709e085ec0))
|
||||
* **notification:** blocking added ([9a0ef8e](https://git.tuffraid.net/cowch/lst_v3/commits/9a0ef8e51a36e3ab45b601b977f1b5cf35d56947))
|
||||
* **puchase:** changes how the error handling works so a better email can be sent ([9d39c13](https://git.tuffraid.net/cowch/lst_v3/commits/9d39c13510974b5ada2a6f6c2448da3f1b755a5c))
|
||||
* **reprint:** new query added to deactivate the old notifcation so no chance of duplicates ([c9eb59e](https://git.tuffraid.net/cowch/lst_v3/commits/c9eb59e2ad9847418ac55cb8a4a91c013f6c97bb))
|
||||
* **server:** added in serverCrash email ([dcb3f2d](https://git.tuffraid.net/cowch/lst_v3/commits/dcb3f2dd1382986639b722778fad113392533b28))
|
||||
* **services:** added in examples for migration stuff ([fc6dc82](https://git.tuffraid.net/cowch/lst_v3/commits/fc6dc82d8458a9928050dd3770778d6a6e1eea7f))
|
||||
* **sql:** corrections to the way we reconnect so the app can error out and be reactivated later ([f33587a](https://git.tuffraid.net/cowch/lst_v3/commits/f33587a3d9a72ca72806635fac9d1214bb1452f1))
|
||||
* **templates:** corrections for new notify process on critcal errors ([07ebf88](https://git.tuffraid.net/cowch/lst_v3/commits/07ebf88806b93b9320f8f9d36b867572dd9a9580))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **agent:** added in jeff city ([e47ea9e](https://git.tuffraid.net/cowch/lst_v3/commits/e47ea9ec52a6ebaf5a8f67a7e8bd2c73da6186fb))
|
||||
* **agent:** added in sherman ([4b6061c](https://git.tuffraid.net/cowch/lst_v3/commits/4b6061c478cbeba7c845dc1c8a015b9998721456))
|
||||
* **service:** changes to the script to allow running the powershell on execution palicy restrictions ([84909bf](https://git.tuffraid.net/cowch/lst_v3/commits/84909bfcf85b91d085ea9dca78be00482b7fd231))
|
||||
|
||||
## [0.0.1-alpha.3](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.2...v0.0.1-alpha.3) (2026-04-10)
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Quick summary of current rewrite/migration goal.
|
||||
| User Profile | ~~Edit profile~~, upload avatar | 🟨 In Progress |
|
||||
| User Admin | Edit user, create user, remove user, alplaprod user integration | ⏳ Not Started |
|
||||
| Notifications | ~~Subscribe~~, ~~Create~~, ~~Update~~, ~~~~Remove~~, Manual Trigger | 🟨 In Progress |
|
||||
| Datamart | Create, Update, Run, Deactivate | 🔧 In Progress |
|
||||
| Datamart | ~~Create~~, ~~Update~~, ~~Run~~, Deactivate | 🟨 In Progress |
|
||||
| Frontend | Analytics and charts | ⏳ Not Started |
|
||||
| Docs | Instructions and trouble shooting | ⏳ Not Started |
|
||||
| One Click Print | Get printers, monitor printers, label process, material process, Special processes | ⏳ Not Started |
|
||||
|
||||
38
backend/admin/admin.build.ts
Normal file
38
backend/admin/admin.build.ts
Normal 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;
|
||||
16
backend/admin/admin.routes.ts
Normal file
16
backend/admin/admin.routes.ts
Normal 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/*
|
||||
};
|
||||
86
backend/admin/admin.updateServer.ts
Normal file
86
backend/admin/admin.updateServer.ts
Normal 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;
|
||||
46
backend/admin/admin.users.ts
Normal file
46
backend/admin/admin.users.ts
Normal 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;
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
21
backend/configs/umami.config.ts
Normal file
21
backend/configs/umami.config.ts
Normal 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);
|
||||
}
|
||||
@@ -13,6 +13,10 @@
|
||||
*
|
||||
* when a criteria is password over we will handle it by counting how many were passed up to 3 then deal with each one respectively
|
||||
*/
|
||||
|
||||
import { and, between, inArray, notInArray } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { invHistoricalData } from "../db/schema/historicalInv.schema.js";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
@@ -22,37 +26,125 @@ import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { datamartData } from "./datamartData.utlis.js";
|
||||
|
||||
type Options = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
type Data = {
|
||||
name: string;
|
||||
options: Options;
|
||||
options: any;
|
||||
optionsRequired?: boolean;
|
||||
howManyOptionsRequired?: number;
|
||||
};
|
||||
|
||||
const lstDbRun = async (data: Data) => {
|
||||
if (data.options) {
|
||||
if (data.name === "psiInventory") {
|
||||
const ids = data.options.articles.split(",").map((id: any) => id.trim());
|
||||
const whse = data.options.whseToInclude
|
||||
? data.options.whseToInclude
|
||||
.split(",")
|
||||
.map((w: any) => w.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const locations = data.options.exludeLanes
|
||||
? data.options.exludeLanes
|
||||
.split(",")
|
||||
.map((l: any) => l.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const conditions = [
|
||||
inArray(invHistoricalData.article, ids),
|
||||
between(
|
||||
invHistoricalData.histDate,
|
||||
data.options.startDate,
|
||||
data.options.endDate,
|
||||
),
|
||||
];
|
||||
|
||||
// only add the warehouse condition if there are any whse values
|
||||
if (whse.length > 0) {
|
||||
conditions.push(inArray(invHistoricalData.whseId, whse));
|
||||
}
|
||||
|
||||
// locations we dont want in the system
|
||||
if (locations.length > 0) {
|
||||
conditions.push(notInArray(invHistoricalData.location, locations));
|
||||
}
|
||||
|
||||
return await db
|
||||
.select()
|
||||
.from(invHistoricalData)
|
||||
.where(and(...conditions));
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
export const runDatamartQuery = async (data: Data) => {
|
||||
// search the query db for the query by name
|
||||
const sqlQuery = sqlQuerySelector(`${data.name}`) as SqlQuery;
|
||||
const considerLstDBRuns = ["psiInventory"];
|
||||
|
||||
if (considerLstDBRuns.includes(data.name)) {
|
||||
const lstDB = await lstDbRun(data);
|
||||
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "datamart",
|
||||
subModule: "lstDBrn",
|
||||
message: `Data for: ${data.name}`,
|
||||
data: lstDB,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// const optionsMissing =
|
||||
// !data.options || Object.keys(data.options).length === 0;
|
||||
|
||||
const optionCount =
|
||||
Object.keys(data.options).length ===
|
||||
getDataMartInfo[0]?.howManyOptionsRequired;
|
||||
const isValid =
|
||||
Object.keys(data.options ?? {}).length >=
|
||||
(getDataMartInfo[0]?.howManyOptionsRequired ?? 0);
|
||||
|
||||
if (getDataMartInfo[0]?.optionsRequired && !optionCount) {
|
||||
if (getDataMartInfo[0]?.optionsRequired && !isValid) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "datamart",
|
||||
subModule: "query",
|
||||
message: `This query is required to have the ${getDataMartInfo[0]?.howManyOptionsRequired} options set in order use it.`,
|
||||
message: `This query is required to have ${getDataMartInfo[0]?.howManyOptionsRequired} option(s) set in order use it, please add in your option(s) data and try again.`,
|
||||
data: [getDataMartInfo[0].options],
|
||||
notify: false,
|
||||
});
|
||||
@@ -75,10 +167,130 @@ export const runDatamartQuery = async (data: Data) => {
|
||||
|
||||
// split the criteria by "," then and then update the query
|
||||
if (data.options) {
|
||||
Object.entries(data.options ?? {}).forEach(([key, value]) => {
|
||||
const pattern = new RegExp(`\\[${key.trim()}\\]`, "g");
|
||||
datamartQuery = datamartQuery.replace(pattern, String(value).trim());
|
||||
});
|
||||
switch (data.name) {
|
||||
case "activeArticles":
|
||||
break;
|
||||
case "deliveryByDateRange":
|
||||
datamartQuery = datamartQuery
|
||||
.replace("[startDate]", `${data.options.startDate}`)
|
||||
.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":
|
||||
datamartQuery = datamartQuery
|
||||
.replace(
|
||||
"--and IdAdressen",
|
||||
`and IdAdressen in (${data.options.customer})`,
|
||||
)
|
||||
.replace(
|
||||
"--and x.IdWarenlager in (0)",
|
||||
`${data.options.whseToInclude ? `and x.IdWarenlager in (${data.options.whseToInclude})` : `--and x.IdWarenlager in (0)`}`,
|
||||
);
|
||||
break;
|
||||
case "openOrders":
|
||||
datamartQuery = datamartQuery
|
||||
.replace("[startDay]", `${data.options.startDay}`)
|
||||
.replace("[endDay]", `${data.options.endDay}`);
|
||||
break;
|
||||
case "inventory":
|
||||
datamartQuery = datamartQuery
|
||||
.replaceAll(
|
||||
"--,l.RunningNumber",
|
||||
`${data.options.includeRunningNumbers ? `,l.RunningNumber` : `--,l.RunningNumber`}`,
|
||||
)
|
||||
.replaceAll(
|
||||
"--,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`}`,
|
||||
);
|
||||
|
||||
break;
|
||||
case "fakeEDIUpdate":
|
||||
datamartQuery = datamartQuery.replace(
|
||||
"--AND h.CustomerHumanReadableId in (0)",
|
||||
`${data.options.address ? `AND h.CustomerHumanReadableId in (${data.options.address})` : `--AND h.CustomerHumanReadableId in (0)`}`,
|
||||
);
|
||||
|
||||
break;
|
||||
case "forecast":
|
||||
datamartQuery = datamartQuery.replace(
|
||||
"where DeliveryAddressHumanReadableId in ([customers])",
|
||||
data.options.customers
|
||||
? `where DeliveryAddressHumanReadableId in (${data.options.customers})`
|
||||
: "--where DeliveryAddressHumanReadableId in ([customers])",
|
||||
);
|
||||
|
||||
break;
|
||||
case "activeArticles2":
|
||||
datamartQuery = datamartQuery.replace(
|
||||
"and a.HumanReadableId in ([articles])",
|
||||
data.options.articles
|
||||
? `and a.HumanReadableId in (${data.options.articles})`
|
||||
: "--and a.HumanReadableId in ([articles])",
|
||||
);
|
||||
|
||||
break;
|
||||
case "psiDeliveryData":
|
||||
datamartQuery = datamartQuery
|
||||
.replace("[startDate]", `${data.options.startDate}`)
|
||||
.replace("[endDate]", `${data.options.endDate}`)
|
||||
.replace(
|
||||
"[articles]",
|
||||
data.options.articles ? `${data.options.articles}` : "[articles]",
|
||||
);
|
||||
break;
|
||||
case "productionData":
|
||||
datamartQuery = datamartQuery
|
||||
.replace("[startDate]", `${data.options.startDate}`)
|
||||
.replace("[endDate]", `${data.options.endDate}`)
|
||||
.replace(
|
||||
"and ArticleHumanReadableId in ([articles])",
|
||||
data.options.articles
|
||||
? `and ArticleHumanReadableId in (${data.options.articles})`
|
||||
: "--and ArticleHumanReadableId in ([articles])",
|
||||
);
|
||||
break;
|
||||
case "psiPlanningData":
|
||||
datamartQuery = datamartQuery
|
||||
.replace("[startDate]", `${data.options.startDate}`)
|
||||
.replace("[endDate]", `${data.options.endDate}`)
|
||||
.replace(
|
||||
"and p.IdArtikelvarianten in ([articles])",
|
||||
data.options.articles
|
||||
? `and p.IdArtikelvarianten in (${data.options.articles})`
|
||||
: "--and p.IdArtikelvarianten in ([articles])",
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "datamart",
|
||||
subModule: "query",
|
||||
message: `${data.name} encountered an error as it might not exist in LST please contact support if this continues to happen`,
|
||||
data: [sqlQuery.message],
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const { data: queryRun, error } = await tryCatch(
|
||||
|
||||
@@ -10,14 +10,50 @@ export const datamartData = [
|
||||
name: "Active articles",
|
||||
endpoint: "activeArticles",
|
||||
description: "returns all active articles for the server with custom data",
|
||||
options: "", // set as a string and each item will be seperated by a , this way we can split it later in the excel file.
|
||||
options: "",
|
||||
optionsRequired: false,
|
||||
},
|
||||
{
|
||||
name: "Delivery by date range",
|
||||
endpoint: "deliveryByDateRange",
|
||||
description: `Returns all Deliverys in selected date range IE: 1/1/${new Date(Date.now()).getFullYear()} to 1/31/${new Date(Date.now()).getFullYear()}`,
|
||||
options: "startDate,endDate", // set as a string and each item will be seperated by a , this way we can split it later in the excel file.
|
||||
description: `Returns all Deliveries in selected date range IE: 1/1/${new Date(Date.now()).getFullYear()} to 1/31/${new Date(Date.now()).getFullYear()}`,
|
||||
options: "startDate,endDate",
|
||||
optionsRequired: true,
|
||||
howManyOptionsRequired: 2,
|
||||
},
|
||||
{
|
||||
name: "Get Customer Inventory",
|
||||
endpoint: "customerInventory",
|
||||
description: `Returns specific customer inventory based on there address ID, IE: 8,12,145. \nWith option to include specific warehousesIds, IE 36,41,5. \nNOTES: *leaving warehouse blank will just pull everything for the customer, Inventory dose not include PPOO or INV`,
|
||||
options: "customer,whseToInclude",
|
||||
optionsRequired: true,
|
||||
howManyOptionsRequired: 1,
|
||||
},
|
||||
{
|
||||
name: "Get open order",
|
||||
endpoint: "openOrders",
|
||||
description: `Returns open orders based on day count sent over, IE: startDay 15 days in the past endDay 5 days in the future, can be left empty for this default days`,
|
||||
options: "startDay,endDay",
|
||||
optionsRequired: true,
|
||||
howManyOptionsRequired: 2,
|
||||
},
|
||||
{
|
||||
name: "Get inventory",
|
||||
endpoint: "inventory",
|
||||
description: `Returns all inventory, excludes inv location. adding an x in one of the options will enable it.`,
|
||||
options: "includeRunningNumbers,locations,lots",
|
||||
},
|
||||
{
|
||||
name: "Fake EDI Update",
|
||||
endpoint: "fakeEDIUpdate",
|
||||
description: `Returns all open orders to correct and resubmit via lst demand mgt, leaving blank will get everything putting an address only returns the specified address. \nNOTE: only orders that were created via edi will populate here.`,
|
||||
options: "address",
|
||||
},
|
||||
{
|
||||
name: "Production Data",
|
||||
endpoint: "productionData",
|
||||
description: `Returns all production data from the date range with the option to have 1 to many avs to search by.`,
|
||||
options: "startDate,endDate,articles",
|
||||
optionsRequired: true,
|
||||
howManyOptionsRequired: 2,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
21
backend/db/schema/analytics.schema.ts
Normal file
21
backend/db/schema/analytics.schema.ts
Normal 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"),
|
||||
});
|
||||
10
backend/db/schema/buildHistory.schema.ts
Normal file
10
backend/db/schema/buildHistory.schema.ts
Normal 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(),
|
||||
});
|
||||
45
backend/db/schema/dailyAnalytics.schema.ts
Normal file
45
backend/db/schema/dailyAnalytics.schema.ts
Normal 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,
|
||||
),
|
||||
],
|
||||
);
|
||||
30
backend/db/schema/historicalInv.schema.ts
Normal file
30
backend/db/schema/historicalInv.schema.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { date, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type z from "zod";
|
||||
|
||||
export const invHistoricalData = pgTable("inv_historical_data", {
|
||||
inv: uuid("id").defaultRandom().primaryKey(),
|
||||
histDate: date("hist_date").notNull(), // this date should always be yesterday when we post it.
|
||||
plantToken: text("plant_token"),
|
||||
article: text("article").notNull(),
|
||||
articleDescription: text("article_description").notNull(),
|
||||
materialType: text("material_type"),
|
||||
total_QTY: text("total_QTY"),
|
||||
available_QTY: text("available_QTY"),
|
||||
coa_QTY: text("coa_QTY"),
|
||||
held_QTY: text("held_QTY"),
|
||||
consignment_QTY: text("consignment_qty"),
|
||||
lot_Number: text("lot_number"),
|
||||
locationId: text("location_id"),
|
||||
location: text("location"),
|
||||
whseId: text("whse_id").default(""),
|
||||
whseName: text("whse_name").default("missing whseName"),
|
||||
upd_user: text("upd_user").default("lst-system"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
});
|
||||
|
||||
export const invHistoricalDataSchema = createSelectSchema(invHistoricalData);
|
||||
export const newInvHistoricalDataSchema = createInsertSchema(invHistoricalData);
|
||||
|
||||
export type InvHistoricalData = z.infer<typeof invHistoricalDataSchema>;
|
||||
export type NewInvHistoricalData = z.infer<typeof newInvHistoricalDataSchema>;
|
||||
@@ -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,
|
||||
),
|
||||
46
backend/db/schema/opendock_articleSetup.ts
Normal file
46
backend/db/schema/opendock_articleSetup.ts
Normal 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
|
||||
>;
|
||||
21
backend/db/schema/opendock_docks.ts
Normal file
21
backend/db/schema/opendock_docks.ts
Normal 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
|
||||
>;
|
||||
@@ -1,6 +1,11 @@
|
||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
|
||||
export const opendockApt = pgTable("printer_log", {
|
||||
export const printerLog = pgTable("printer_log", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
name: text("name").notNull(),
|
||||
name: text("name"),
|
||||
ip: text("ip"),
|
||||
printerSN: text("printer_sn"),
|
||||
condition: text("condition").notNull(),
|
||||
message: text("message"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
44
backend/db/schema/printers.schema.ts
Normal file
44
backend/db/schema/printers.schema.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type z from "zod";
|
||||
|
||||
export const printerData = pgTable(
|
||||
"printer_data",
|
||||
{
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
humanReadableId: text("humanReadable_id").unique().notNull(),
|
||||
name: text("name").notNull(),
|
||||
ipAddress: text("ipAddress"),
|
||||
port: integer("port"),
|
||||
status: text("status"),
|
||||
statusText: text("statusText"),
|
||||
printerSN: text("printer_sn"),
|
||||
lastTimePrinted: timestamp("last_time_printed").notNull().defaultNow(),
|
||||
assigned: boolean("assigned").default(false),
|
||||
remark: text("remark"),
|
||||
printDelay: integer("printDelay").default(90),
|
||||
processes: jsonb("processes").default([]),
|
||||
printDelayOverride: boolean("print_delay_override").default(false), // this will be more for if we have the lot time active but want to over ride this single line for some reason
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
//uniqueIndex("emailUniqueIndex").on(sql`lower(${table.email})`),
|
||||
uniqueIndex("printer_id").on(table.humanReadableId),
|
||||
],
|
||||
);
|
||||
|
||||
export const printerSchema = createSelectSchema(printerData);
|
||||
export const newPrinterSchema = createInsertSchema(printerData);
|
||||
|
||||
export type Printer = z.infer<typeof printerSchema>;
|
||||
export type NewPrinter = z.infer<typeof newPrinterSchema>;
|
||||
48
backend/db/schema/scanUsers.ts
Normal file
48
backend/db/schema/scanUsers.ts
Normal 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>;
|
||||
23
backend/db/schema/scanlog.schema.ts
Normal file
23
backend/db/schema/scanlog.schema.ts
Normal 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>;
|
||||
40
backend/db/schema/serverData.schema.ts
Normal file
40
backend/db/schema/serverData.schema.ts
Normal 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>;
|
||||
@@ -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),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -7,12 +7,19 @@ import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
export let pool2: sql.ConnectionPool;
|
||||
export let connected: boolean = false;
|
||||
export let reconnecting = false;
|
||||
// start the delay out as 2 seconds
|
||||
let delayStart = 2000;
|
||||
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;
|
||||
reconnectToSql;
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
@@ -48,12 +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,
|
||||
});
|
||||
@@ -104,11 +113,6 @@ export const reconnectToSql = async () => {
|
||||
//set reconnecting to true while we try to reconnect
|
||||
reconnecting = true;
|
||||
|
||||
// start the delay out as 2 seconds
|
||||
let delayStart = 2000;
|
||||
let attempt = 0;
|
||||
const maxAttempts = 10;
|
||||
|
||||
while (!connected && attempt < maxAttempts) {
|
||||
attempt++;
|
||||
log.info(
|
||||
@@ -117,11 +121,13 @@ 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
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -131,19 +137,11 @@ export const reconnectToSql = async () => {
|
||||
log.info(`${gpSqlConfig.server} is connected to ${gpSqlConfig.database}`);
|
||||
} catch (error) {
|
||||
delayStart = Math.min(delayStart * 2, 30000);
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "db",
|
||||
message: "Failed to reconnect to the prod sql server.",
|
||||
data: [error],
|
||||
notify: false,
|
||||
});
|
||||
log.error({ error }, "Failed to reconnect to the prod sql server.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
if (!connected && attempt >= maxAttempts) {
|
||||
log.error(
|
||||
{ notify: true },
|
||||
"Max reconnect attempts reached on the prodSql server. Stopping retries.",
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import {
|
||||
connected,
|
||||
pool2,
|
||||
reconnecting,
|
||||
reconnectToSql,
|
||||
} from "./gpSqlConnection.controller.js";
|
||||
import { connected, pool2 } from "./gpSqlConnection.controller.js";
|
||||
|
||||
interface SqlError extends Error {
|
||||
code?: string;
|
||||
@@ -22,29 +17,15 @@ interface SqlError extends Error {
|
||||
*/
|
||||
export const gpQuery = async (queryToRun: string, name: string) => {
|
||||
if (!connected) {
|
||||
reconnectToSql();
|
||||
|
||||
if (reconnecting) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "gpSql",
|
||||
message: `The sql ${process.env.PROD_PLANT_TOKEN} is trying to reconnect already`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
} else {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "gpSql",
|
||||
message: `${process.env.PROD_PLANT_TOKEN} is not connected, and failed to connect.`,
|
||||
data: [],
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "gpSql",
|
||||
message: `${process.env.PROD_PLANT_TOKEN} is offline or attempting to reconnect`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
//change to the correct server
|
||||
|
||||
@@ -5,6 +5,7 @@ import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { notifySystemIssue } from "./logger.notify.js";
|
||||
//import build from "pino-abstract-transport";
|
||||
|
||||
export const logLevel = process.env.LOG_LEVEL || "info";
|
||||
@@ -45,6 +46,10 @@ const dbStream = new Writable({
|
||||
console.error(res.error);
|
||||
}
|
||||
|
||||
if (obj.notify) {
|
||||
notifySystemIssue(obj);
|
||||
}
|
||||
|
||||
if (obj.room) {
|
||||
emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
||||
}
|
||||
|
||||
44
backend/logger/logger.notify.ts
Normal file
44
backend/logger/logger.notify.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* For all logging that has notify set to true well send an email to the system admins, if we have a discord webhook set well send it there as well
|
||||
*/
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { user } from "../db/schema/auth.schema.js";
|
||||
import { sendEmail } from "../utils/sendEmail.utils.js";
|
||||
|
||||
type NotifyData = {
|
||||
module: string;
|
||||
submodule: string;
|
||||
hostname: string;
|
||||
msg: string;
|
||||
stack: unknown[];
|
||||
};
|
||||
|
||||
export const notifySystemIssue = async (data: NotifyData) => {
|
||||
// build the email out
|
||||
|
||||
const formattedError = Array.isArray(data.stack)
|
||||
? data.stack.map((e: any) => e.error || e)
|
||||
: data.stack;
|
||||
|
||||
const sysAdmin = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(eq(user.role, "systemAdmin"));
|
||||
|
||||
await sendEmail({
|
||||
email: sysAdmin.map((r) => r.email).join("; ") ?? "cowchmonkey@gmail.com", // change to pull in system admin emails
|
||||
subject: `${data.hostname} has encountered a critical issue.`,
|
||||
template: "serverCritialIssue",
|
||||
context: {
|
||||
plant: data.hostname,
|
||||
module: data.module,
|
||||
subModule: data.submodule,
|
||||
message: data.msg,
|
||||
error: JSON.stringify(formattedError, null, 2),
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: add discord
|
||||
};
|
||||
223
backend/logistics/logistics.historicalInv.ts
Normal file
223
backend/logistics/logistics.historicalInv.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import { format } from "date-fns";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { runDatamartQuery } from "../datamart/datamart.controller.js";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { invHistoricalData } from "../db/schema/historicalInv.schema.js";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { createCronJob } from "../utils/croner.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
type Inventory = {
|
||||
article: string;
|
||||
alias: string;
|
||||
materialType: string;
|
||||
total_palletQTY: string;
|
||||
available_QTY: string;
|
||||
coa_QTY: string;
|
||||
held_QTY: string;
|
||||
consignment_qty: string;
|
||||
lot: string;
|
||||
locationId: string;
|
||||
laneDescription: string;
|
||||
warehouseId: string;
|
||||
warehouseDescription: string;
|
||||
};
|
||||
|
||||
const historicalInvImport = async () => {
|
||||
const today = new Date();
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(invHistoricalData)
|
||||
.where(eq(invHistoricalData.histDate, format(today, "yyyy-MM-dd"))),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "query",
|
||||
message: `Error getting historical inv info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (data?.length === 0) {
|
||||
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||
|
||||
if (!avSQLQuery.success) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "inv",
|
||||
message: `Error getting Article info`,
|
||||
data: [avSQLQuery.message],
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
const { data: inv, error: invError } = await tryCatch(
|
||||
//prodQuery(sqlQuery.query, "Inventory data"),
|
||||
runDatamartQuery({
|
||||
name: "inventory",
|
||||
options: { lots: "x", locations: "x" },
|
||||
}),
|
||||
);
|
||||
|
||||
const { data: av, error: avError } = (await tryCatch(
|
||||
runDatamartQuery({ name: "activeArticles", options: {} }),
|
||||
)) as any;
|
||||
|
||||
if (invError) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "inv",
|
||||
message: `Error getting inventory info from prod query`,
|
||||
data: invError as any,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (avError) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "inv",
|
||||
message: `Error getting article info from prod query`,
|
||||
data: invError as any,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
// shape the data to go into our table
|
||||
const plantToken = process.env.PROD_PLANT_TOKEN ?? "test1";
|
||||
const importInv = (inv.data ? inv.data : []) as Inventory[];
|
||||
const importData = importInv.map((i) => {
|
||||
return {
|
||||
histDate: sql`(NOW())::date`,
|
||||
plantToken: plantToken,
|
||||
article: i.article,
|
||||
articleDescription: i.alias,
|
||||
materialType:
|
||||
av.data.filter((a: any) => a.article === i.article).length > 0
|
||||
? av.data.filter((a: any) => a.article === i.article)[0]
|
||||
?.TypeOfMaterial
|
||||
: "Item not defined",
|
||||
total_QTY: i.total_palletQTY ?? "0.00",
|
||||
available_QTY: i.available_QTY ?? "0.00",
|
||||
coa_QTY: i.coa_QTY ?? "0.00",
|
||||
held_QTY: i.held_QTY ?? "0.00",
|
||||
consignment_QTY: i.consignment_qty ?? "0.00",
|
||||
lot_Number: i.lot ?? "0",
|
||||
locationId: i.locationId ?? "0",
|
||||
location: i.laneDescription ?? "Missing lane",
|
||||
whseId: i.warehouseId ?? "0",
|
||||
whseName: i.warehouseDescription ?? "Missing warehouse",
|
||||
};
|
||||
});
|
||||
|
||||
const { data: dataImport, error: errorImport } = await tryCatch(
|
||||
db.insert(invHistoricalData).values(importData),
|
||||
);
|
||||
|
||||
if (errorImport) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "inv",
|
||||
message: `Error adding historical data to lst db`,
|
||||
data: errorImport as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (dataImport) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "info",
|
||||
module: "logistics",
|
||||
subModule: "inv",
|
||||
message: `Historical data was added to lst :D`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "info",
|
||||
module: "logistics",
|
||||
subModule: "inv",
|
||||
message: `Historical Data for: ${format(today, "yyyy-MM-dd")}, is already added and nothing to do.`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "info",
|
||||
module: "logistics",
|
||||
subModule: "inv",
|
||||
message: `Some weird crazy error just happened and didnt get captured during the historical inv check.`,
|
||||
data: [],
|
||||
notify: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const historicalSchedule = async () => {
|
||||
// running the history in case my silly ass dose an update around the shift change time lol, this will prevent loss data. it might be off a little but no one cares
|
||||
historicalInvImport();
|
||||
|
||||
const sqlQuery = sqlQuerySelector(`shiftChange`) as SqlQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "query",
|
||||
message: `Error getting shiftChange sql file`,
|
||||
data: [sqlQuery.message],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(sqlQuery.query, "Shift Change data"),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "query",
|
||||
message: `Error getting shiftChange info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
// shift split
|
||||
const shiftTimeSplit = data?.data[0]?.shiftChange.split(":");
|
||||
|
||||
const cronSetup = `0 ${
|
||||
shiftTimeSplit?.length > 0 ? `${parseInt(shiftTimeSplit[1])}` : "0"
|
||||
} ${
|
||||
shiftTimeSplit?.length > 0 ? `${parseInt(shiftTimeSplit[0])}` : "7"
|
||||
} * * *`;
|
||||
|
||||
createCronJob("historicalInv", cronSetup, () => historicalInvImport());
|
||||
};
|
||||
83
backend/middleware/routeHit.middleware.ts
Normal file
83
backend/middleware/routeHit.middleware.ts
Normal 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";
|
||||
}
|
||||
54
backend/mobile/availableScanIds.route.ts
Normal file
54
backend/mobile/availableScanIds.route.ts
Normal 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;
|
||||
125
backend/mobile/downloadApps.route.ts
Normal file
125
backend/mobile/downloadApps.route.ts
Normal 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;
|
||||
60
backend/mobile/laneCheck.ts
Normal file
60
backend/mobile/laneCheck.ts
Normal 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;
|
||||
23
backend/mobile/mobile.routes.ts
Normal file
23
backend/mobile/mobile.routes.ts
Normal 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/*
|
||||
};
|
||||
343
backend/mobile/mobileAuth.route.ts
Normal file
343
backend/mobile/mobileAuth.route.ts
Normal 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;
|
||||
21
backend/mobile/mobilePin.route.ts
Normal file
21
backend/mobile/mobilePin.route.ts
Normal 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;
|
||||
46
backend/mobile/scanLogs.route.ts
Normal file
46
backend/mobile/scanLogs.route.ts
Normal 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.name));
|
||||
} 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;
|
||||
40
backend/mobile/version.route.ts
Normal file
40
backend/mobile/version.route.ts
Normal 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;
|
||||
80
backend/notification/notification.SqlJobCleanUp.ts
Normal file
80
backend/notification/notification.SqlJobCleanUp.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
96
backend/notification/notification.manualTrigger.ts
Normal file
96
backend/notification/notification.manualTrigger.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { type Response, Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notifications } from "../db/schema/notifications.schema.js";
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/", async (req, res: Response) => {
|
||||
const hasPermissions = await auth.api.userHasPermission({
|
||||
body: {
|
||||
//userId: req?.user?.id,
|
||||
role: req.user?.roles as any,
|
||||
permissions: {
|
||||
notifications: ["readAll"], // This must match the structure in your access control
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!hasPermissions) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `You do not have permissions to be here`,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data: nName, error: nError } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(eq(notifications.name, req.body.name)),
|
||||
);
|
||||
|
||||
if (nError) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "get",
|
||||
message: `There was an error getting the notifications `,
|
||||
data: [nError],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data: sub, error: sError } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(eq(notifications.name, req.body.name)),
|
||||
);
|
||||
|
||||
if (sError) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "get",
|
||||
message: `There was an error getting the subs `,
|
||||
data: [sError],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const emailString = [
|
||||
...new Set(
|
||||
sub.flatMap((e: any) =>
|
||||
e.emails?.map((email: any) => email.trim().toLowerCase()),
|
||||
),
|
||||
),
|
||||
].join(";");
|
||||
|
||||
console.log(emailString);
|
||||
const { default: runFun } = await import(
|
||||
`./notification.${req.body.name.trim()}.js`
|
||||
);
|
||||
const manual = await runFun(nName[0], "blake.matthes@alpla.com");
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `Manual Trigger ran`,
|
||||
data: manual ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
export default r;
|
||||
113
backend/notification/notification.minLevel.ts
Normal file
113
backend/notification/notification.minLevel.ts
Normal 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;
|
||||
114
backend/notification/notification.qualityBlocking.ts
Normal file
114
backend/notification/notification.qualityBlocking.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
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 { delay } from "../utils/delay.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { sendEmail } from "../utils/sendEmail.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { v2QueryRun } from "../utils/pgConnectToLst.utils.js";
|
||||
|
||||
let shutoffv1 = false
|
||||
const func = async (data: any, emails: string) => {
|
||||
// TODO: remove this disable once all 17 plants are on this new lst
|
||||
if (!shutoffv1){
|
||||
v2QueryRun(`update public.notifications set active = false where name = '${data.name}'`)
|
||||
shutoffv1 = true
|
||||
}
|
||||
|
||||
|
||||
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 as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
// search the query db for the query by name
|
||||
const sqlQuery = sqlQuerySelector(`${data.name}`) as SqlQuery;
|
||||
// create the ignore audit logs ids
|
||||
|
||||
// get get the latest blocking order id that was sent
|
||||
const blockingOrderId = l[0].options[0].lastBlockingOrderIdSent ?? 69;
|
||||
|
||||
// run the check
|
||||
const { data: queryRun, error } = await tryCatch(
|
||||
prodQuery(
|
||||
sqlQuery.query.replace("[lastBlocking]", blockingOrderId),
|
||||
`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 as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (queryRun.data.length > 0) {
|
||||
for (const bo of queryRun.data) {
|
||||
const sentEmail = await sendEmail({
|
||||
email: emails,
|
||||
subject: bo.subject,
|
||||
template: "qualityBlocking",
|
||||
context: {
|
||||
items: bo,
|
||||
},
|
||||
});
|
||||
|
||||
if (!sentEmail?.success) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "email",
|
||||
message: `${l[0].name} failed to send the email`,
|
||||
data: sentEmail?.data as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
await delay(1500);
|
||||
|
||||
const { error: dbe } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set({ options: [{ lastBlockingOrderIdSent: bo.blockingNumber }] })
|
||||
.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 as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default func;
|
||||
@@ -9,9 +9,16 @@ import {
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { sendEmail } from "../utils/sendEmail.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { v2QueryRun } from "../utils/pgConnectToLst.utils.js";
|
||||
|
||||
let shutoffv1 = false
|
||||
const func = async (data: any, emails: string) => {
|
||||
// TODO: remove this disable once all 17 plants are on this new lst
|
||||
if (!shutoffv1){
|
||||
v2QueryRun(`update public.notifications set active = false where name = '${data.name}'`)
|
||||
shutoffv1 = true
|
||||
}
|
||||
|
||||
const reprint = async (data: any, emails: string) => {
|
||||
// TODO: do the actual logic for the notification.
|
||||
const { data: l, error: le } = (await tryCatch(
|
||||
db.select().from(notifications).where(eq(notifications.id, data.id)),
|
||||
)) as any;
|
||||
@@ -23,7 +30,7 @@ const reprint = async (data: any, emails: string) => {
|
||||
module: "notification",
|
||||
subModule: "query",
|
||||
message: `${data.name} encountered an error while trying to get initial info`,
|
||||
data: [le],
|
||||
data: le as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
@@ -52,7 +59,7 @@ const reprint = async (data: any, emails: string) => {
|
||||
module: "notification",
|
||||
subModule: "query",
|
||||
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
|
||||
data: [error],
|
||||
data: error as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
@@ -73,7 +80,7 @@ const reprint = async (data: any, emails: string) => {
|
||||
module: "notification",
|
||||
subModule: "query",
|
||||
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
|
||||
data: [dbe],
|
||||
data: dbe as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
@@ -90,26 +97,17 @@ const reprint = async (data: any, emails: string) => {
|
||||
});
|
||||
|
||||
if (!sentEmail?.success) {
|
||||
// sendEmail({
|
||||
// email: "Blake.matths@alpla.com",
|
||||
// subject: `${os.hostname()} failed to run ${data[0]?.name}.`,
|
||||
// template: "serverCrash",
|
||||
// context: {
|
||||
// error: sentEmail?.data,
|
||||
// plant: `${os.hostname()}`,
|
||||
// },
|
||||
// });
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "email",
|
||||
message: `${l[0].name} failed to send the email`,
|
||||
data: [sentEmail?.data],
|
||||
data: sentEmail?.data as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default reprint;
|
||||
export default func;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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";
|
||||
import deleteSub from "./notificationSub.delete.route.js";
|
||||
@@ -9,12 +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/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/*
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ const note: NewNotification[] = [
|
||||
"Checks for new blocking orders that have been entered, recommend to get the most recent order in here before activating.",
|
||||
active: false,
|
||||
interval: "10",
|
||||
options: [{ sentBlockingOrders: [{ timeStamp: "0", blockingOrder: 1 }] }],
|
||||
options: [{ lastBlockingOrderIdSent: 1 }],
|
||||
},
|
||||
{
|
||||
name: "alplaPurchaseHistory",
|
||||
|
||||
@@ -14,20 +14,82 @@
|
||||
*/
|
||||
|
||||
import { Router } from "express";
|
||||
import multer from "multer";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { printerLog } from "../db/schema/printerLogs.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
type PrinterEvent = {
|
||||
name: string;
|
||||
condition: string;
|
||||
message: string;
|
||||
};
|
||||
const r = Router();
|
||||
const upload = multer();
|
||||
|
||||
r.post("/printer/listener/:printer", async (req, res) => {
|
||||
const parseZebraAlert = (body: any): PrinterEvent => {
|
||||
const name = body.uniqueId || "unknown";
|
||||
const decoded = decodeURIComponent(body.alertMsg || "");
|
||||
|
||||
const [conditionRaw, ...rest] = decoded.split(":");
|
||||
const condition = conditionRaw?.toLowerCase()?.trim() || "unknown";
|
||||
const message = rest.join(":").trim();
|
||||
|
||||
return {
|
||||
name,
|
||||
condition,
|
||||
message,
|
||||
};
|
||||
};
|
||||
|
||||
r.post("/:printer", upload.any(), async (req, res) => {
|
||||
const { printer: printerName } = req.params;
|
||||
console.log(req.body);
|
||||
const event: PrinterEvent = parseZebraAlert(req.body);
|
||||
|
||||
const rawIp =
|
||||
req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() ||
|
||||
req.socket.remoteAddress ||
|
||||
req.ip;
|
||||
|
||||
const ip = rawIp?.replace("::ffff:", "");
|
||||
|
||||
// post the new message
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.insert(printerLog)
|
||||
.values({
|
||||
ip: ip?.replace("::ffff:", ""),
|
||||
name: printerName,
|
||||
printerSN: event.name,
|
||||
condition: event.condition,
|
||||
message: event.message,
|
||||
})
|
||||
.returning(),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "info",
|
||||
module: "ocp",
|
||||
subModule: "printing",
|
||||
message: `${printerName} encountered an error posting the log`,
|
||||
data: error as any,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (data) {
|
||||
// TODO: send message over to the controller to decide what to do next with it
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "ocp",
|
||||
subModule: "printing",
|
||||
message: `${printerName} just passed over a message`,
|
||||
message: `${printerName} just sent a message`,
|
||||
data: req.body ?? [],
|
||||
status: 200,
|
||||
});
|
||||
|
||||
@@ -10,10 +10,323 @@
|
||||
* printer status will live here this will be how we manage all the levels of status like 3 paused, 1 printing, 8 error, 10 power up, etc...
|
||||
*/
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import net from "net";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { printerData } from "../db/schema/printers.schema.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { delay } from "../utils/delay.utils.js";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
|
||||
type Printer = {
|
||||
name: string;
|
||||
humanReadableId: string;
|
||||
type: number;
|
||||
ipAddress: string;
|
||||
port: number;
|
||||
default: boolean;
|
||||
labelInstanceIpAddress: string;
|
||||
labelInstancePort: number;
|
||||
active: boolean;
|
||||
remark: string;
|
||||
processes: number[];
|
||||
};
|
||||
|
||||
const log = createLogger({ module: "ocp", subModule: "printers" });
|
||||
|
||||
export const printerManager = async () => {};
|
||||
|
||||
export const printerHeartBeat = async () => {
|
||||
// heat heats will be defaulted to 60 seconds no reason to allow anything else
|
||||
// heat heats will be defaulted to 60 seconds no reason to allow anything else, and heart beats will only go to assigned printers no need to be monitoring non labeling printers
|
||||
};
|
||||
|
||||
//export const printerStatus = async (statusNr: number, printerId: number) => {};
|
||||
export const printerSync = async () => {
|
||||
// pull the printers from alpla prod and update them in lst
|
||||
|
||||
const printers = await runProdApi({
|
||||
method: "get",
|
||||
endpoint: "/public/v1.0/Administration/Printers",
|
||||
});
|
||||
|
||||
if (!printers?.success) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "ocp",
|
||||
subModule: "printer",
|
||||
message: printers?.message ?? "",
|
||||
data: printers?.data ?? [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (printers?.success && Array.isArray(printers.data)) {
|
||||
const ignorePrinters = ["pdf24", "standard"];
|
||||
|
||||
const validPrinters =
|
||||
printers.data.filter(
|
||||
(n: any) =>
|
||||
!ignorePrinters.includes(n.name.toLowerCase()) && n.ipAddress,
|
||||
) ?? [];
|
||||
if (validPrinters.length) {
|
||||
for (const printer of validPrinters as Printer[]) {
|
||||
// run an update for each printer, do on conflicts based on the printer id
|
||||
log.debug({}, `Add/Updating ${printer.name}`);
|
||||
|
||||
if (printer.active) {
|
||||
await db
|
||||
.insert(printerData)
|
||||
.values({
|
||||
name: printer.name,
|
||||
humanReadableId: printer.humanReadableId,
|
||||
ipAddress: printer.ipAddress,
|
||||
port: printer.port,
|
||||
remark: printer.remark,
|
||||
processes: printer.processes,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: printerData.humanReadableId,
|
||||
set: {
|
||||
name: printer.name,
|
||||
humanReadableId: printer.humanReadableId,
|
||||
ipAddress: printer.ipAddress,
|
||||
port: printer.port,
|
||||
remark: printer.remark,
|
||||
processes: printer.processes,
|
||||
},
|
||||
})
|
||||
.returning();
|
||||
await tcpPrinter(printer);
|
||||
}
|
||||
|
||||
if (!printer.active) {
|
||||
log.warn({}, `${printer.name} is not active so removing from lst.`);
|
||||
await db
|
||||
.delete(printerData)
|
||||
.where(eq(printerData.humanReadableId, printer.humanReadableId));
|
||||
}
|
||||
}
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "ocp",
|
||||
subModule: "printer",
|
||||
message: `${printers.data.length} printers were just synced, this includes new and old printers`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "ocp",
|
||||
subModule: "printer",
|
||||
message: `No printers to update`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
};
|
||||
|
||||
const tcpPrinter = (printer: Printer) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const socket = new net.Socket();
|
||||
const timeoutMs = 15 * 1000;
|
||||
|
||||
const commands = [
|
||||
{
|
||||
key: "clearAlerts",
|
||||
command: '! U1 setvar "alerts.configured" ""\r\n',
|
||||
},
|
||||
{
|
||||
key: "addAlert",
|
||||
command: `! U1 setvar "alerts.add" "ALL MESSAGES,HTTP-POST,Y,Y,http://${process.env.SERVER_IP}:${process.env.PORT}/lst/api/ocp/printer/listener/${printer.name},0,N,printer"\r\n`,
|
||||
},
|
||||
{
|
||||
key: "setFriendlyName",
|
||||
command: `! U1 setvar "device.friendly_name" "${printer.name}"\r\n`,
|
||||
},
|
||||
{
|
||||
key: "getUniqueId",
|
||||
command: '! U1 getvar "device.unique_id"\r\n',
|
||||
},
|
||||
] as const;
|
||||
|
||||
let currentCommandIndex = 0;
|
||||
let awaitingSerial = false;
|
||||
let settled = false;
|
||||
|
||||
const cleanup = () => {
|
||||
socket.removeAllListeners();
|
||||
socket.destroy();
|
||||
};
|
||||
|
||||
const finish = (err?: unknown) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timeout);
|
||||
cleanup();
|
||||
|
||||
if (err) {
|
||||
log.error(
|
||||
{ err, printer: printer.name },
|
||||
`Printer update failed for ${printer.name}: doing the name and alert add directly on the printer.`,
|
||||
);
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
finish(`${printer.name} timed out while updating printer config`);
|
||||
}, timeoutMs);
|
||||
|
||||
const sendNext = async () => {
|
||||
if (currentCommandIndex >= commands.length) {
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const current = commands[currentCommandIndex];
|
||||
|
||||
if (!current) {
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
awaitingSerial = current.key === "getUniqueId";
|
||||
|
||||
log.info(
|
||||
{ printer: printer.name, command: current.key },
|
||||
`Sending command to ${printer.name}`,
|
||||
);
|
||||
|
||||
socket.write(current.command);
|
||||
|
||||
currentCommandIndex++;
|
||||
|
||||
// Small pause between commands so the printer has breathing room
|
||||
if (currentCommandIndex < commands.length) {
|
||||
await delay(1500);
|
||||
await sendNext();
|
||||
} else {
|
||||
// last command was sent, now wait for final data/close
|
||||
await delay(1500);
|
||||
socket.end();
|
||||
}
|
||||
};
|
||||
|
||||
socket.connect(printer.port, printer.ipAddress, async () => {
|
||||
log.info({}, `Connected to ${printer.name}`);
|
||||
|
||||
try {
|
||||
await sendNext();
|
||||
} catch (error) {
|
||||
finish(
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error(
|
||||
`Unknown error while sending commands to ${printer.name}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("data", async (data) => {
|
||||
const response = data.toString().trim().replaceAll('"', "");
|
||||
|
||||
log.info(
|
||||
{ printer: printer.name, response },
|
||||
`Received printer response from ${printer.name}`,
|
||||
);
|
||||
|
||||
if (!awaitingSerial) return;
|
||||
|
||||
awaitingSerial = false;
|
||||
|
||||
try {
|
||||
await db
|
||||
.update(printerData)
|
||||
.set({ printerSN: response })
|
||||
.where(eq(printerData.humanReadableId, printer.humanReadableId));
|
||||
} catch (error) {
|
||||
finish(
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error(`Failed to update printer SN for ${printer.name}`),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
log.info({}, `Closed connection to ${printer.name}`);
|
||||
finish();
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
finish(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// const tcpPrinter = async (printer: Printer) => {
|
||||
// const p = new net.Socket();
|
||||
// const commands = [
|
||||
// '! U1 setvar "alerts.configured" ""\r\n', // clean install just remove all alerts
|
||||
// `! U1 setvar "alerts.add" "ALL MESSAGES,HTTP-POST,Y,Y,http://${process.env.SERVER_IP}:${process.env.PORT}/lst/api/ocp/printer/listener/${printer.name},0,N,printer"\r\n`, // add in the all alert
|
||||
// `! U1 setvar "device.friendly_name" "${printer.name}"\r\n`, // change the name to match the alplaprod name
|
||||
// `! U1 getvar "device.unique_id"\r\n`, // this will get mapped into the printer as this is the one we will link to in the db.
|
||||
// //'! U1 getvar "alerts.configured" ""\r\n',
|
||||
// ];
|
||||
|
||||
// let index = 0;
|
||||
// const sendNext = async () => {
|
||||
// if (index >= commands.length) {
|
||||
// p.end();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const cmd = commands[index] as string;
|
||||
// p.write(cmd);
|
||||
// return;
|
||||
// };
|
||||
|
||||
// p.connect(printer.port, printer.ipAddress, async () => {
|
||||
// log.info({}, `Connected to ${printer.name}`);
|
||||
// while (index < commands.length) {
|
||||
// await sendNext();
|
||||
// await delay(2000);
|
||||
// index++;
|
||||
// }
|
||||
// });
|
||||
|
||||
// p.on("data", async (data) => {
|
||||
// // this is just the sn that comes over so we will update this printer.
|
||||
// await db
|
||||
// .update(printerData)
|
||||
// .set({ printerSN: data.toString().trim().replaceAll('"', "") })
|
||||
// .where(eq(printerData.humanReadableId, printer.humanReadableId));
|
||||
|
||||
// // get the name
|
||||
// // p.write('! U1 getvar "device.friendly_name"\r\n');
|
||||
// // p.write('! U1 getvar "device.unique_id"\r\n');
|
||||
// // p.write('! U1 getvar "alerts.configured"\r\n');
|
||||
// });
|
||||
|
||||
// p.on("close", () => {
|
||||
// log.info({}, `Closed connection to ${printer.name}`);
|
||||
// p.destroy();
|
||||
// return;
|
||||
// });
|
||||
|
||||
// p.on("error", (err) => {
|
||||
// log.info(
|
||||
// { stack: err },
|
||||
// `${printer.name} encountered an error while trying to update`,
|
||||
// );
|
||||
// return;
|
||||
// });
|
||||
// };
|
||||
|
||||
38
backend/ocp/ocp.printer.update.ts
Normal file
38
backend/ocp/ocp.printer.update.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* the route that listens for the printers post.
|
||||
*
|
||||
* and http-post alert should be setup on each printer pointing to at min you will want to make the alert for
|
||||
* pause printer, you can have all on here as it will also monitor and do things on all messages
|
||||
*
|
||||
* http://{serverIP}:2222/lst/api/ocp/printer/listener/{printerName}
|
||||
*
|
||||
* the messages will be sent over to the db for logging as well as specific ones will do something
|
||||
*
|
||||
* pause will validate if can print
|
||||
* close head will repause the printer so it wont print a label
|
||||
* power up will just repause the printer so it wont print a label
|
||||
*/
|
||||
|
||||
import { Router } from "express";
|
||||
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
//import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { printerSync } from "./ocp.printer.manage.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/update", async (_, res) => {
|
||||
printerSync();
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "ocp",
|
||||
subModule: "printing",
|
||||
message:
|
||||
"Printer update has been triggered to monitor progress please head to the logs.",
|
||||
data: [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -1,22 +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("");
|
||||
|
||||
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,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
277
backend/opendock/opendock.articleCheck.route.ts
Normal file
277
backend/opendock/opendock.articleCheck.route.ts
Normal 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;
|
||||
@@ -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,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -7,12 +7,17 @@ import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
export let pool: sql.ConnectionPool;
|
||||
export let connected: boolean = false;
|
||||
export let reconnecting = false;
|
||||
// start the delay out as 2 seconds
|
||||
let delayStart = 2000;
|
||||
let attempt = 0;
|
||||
const maxAttempts = 10;
|
||||
|
||||
export const connectProdSql = async () => {
|
||||
const serverUp = await checkHostnamePort(`${process.env.PROD_SERVER}:1433`);
|
||||
if (!serverUp) {
|
||||
// we will try to reconnect
|
||||
connected = false;
|
||||
reconnectToSql();
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
@@ -48,6 +53,7 @@ export const connectProdSql = async () => {
|
||||
notify: false,
|
||||
});
|
||||
} catch (error) {
|
||||
reconnectToSql();
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
@@ -104,11 +110,6 @@ export const reconnectToSql = async () => {
|
||||
//set reconnecting to true while we try to reconnect
|
||||
reconnecting = true;
|
||||
|
||||
// start the delay out as 2 seconds
|
||||
let delayStart = 2000;
|
||||
let attempt = 0;
|
||||
const maxAttempts = 10;
|
||||
|
||||
while (!connected && attempt < maxAttempts) {
|
||||
attempt++;
|
||||
log.info(
|
||||
@@ -121,7 +122,7 @@ export const reconnectToSql = async () => {
|
||||
|
||||
if (!serverUp) {
|
||||
delayStart = Math.min(delayStart * 2, 30000); // exponential backoff until up to 30000
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -133,19 +134,12 @@ export const reconnectToSql = async () => {
|
||||
);
|
||||
} catch (error) {
|
||||
delayStart = Math.min(delayStart * 2, 30000);
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "db",
|
||||
message: "Failed to reconnect to the prod sql server.",
|
||||
data: [error],
|
||||
notify: false,
|
||||
});
|
||||
delayStart = Math.min(delayStart * 2, 30000);
|
||||
log.error({ error }, "Failed to reconnect to the prod sql server.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
if (!connected && attempt >= maxAttempts) {
|
||||
log.error(
|
||||
{ notify: true },
|
||||
"Max reconnect attempts reached on the prodSql server. Stopping retries.",
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import {
|
||||
connected,
|
||||
pool,
|
||||
reconnecting,
|
||||
reconnectToSql,
|
||||
} from "./prodSqlConnection.controller.js";
|
||||
import { connected, pool } from "./prodSqlConnection.controller.js";
|
||||
|
||||
interface SqlError extends Error {
|
||||
code?: string;
|
||||
@@ -22,29 +17,15 @@ interface SqlError extends Error {
|
||||
*/
|
||||
export const prodQuery = async (queryToRun: string, name: string) => {
|
||||
if (!connected) {
|
||||
reconnectToSql();
|
||||
|
||||
if (reconnecting) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "prodSql",
|
||||
message: `The sql ${process.env.PROD_PLANT_TOKEN} is trying to reconnect already`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
} else {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "prodSql",
|
||||
message: `${process.env.PROD_PLANT_TOKEN} is not connected, and failed to connect.`,
|
||||
data: [],
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "prodSql",
|
||||
message: `${process.env.PROD_PLANT_TOKEN} is offline or attempting to reconnect`,
|
||||
data: [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
//change to the correct server
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use AlplaPROD_test1
|
||||
|
||||
SELECT V_Artikel.IdArtikelvarianten,
|
||||
SELECT V_Artikel.IdArtikelvarianten as article,
|
||||
V_Artikel.Bezeichnung,
|
||||
V_Artikel.ArtikelvariantenTypBez,
|
||||
V_Artikel.PreisEinheitBez,
|
||||
43
backend/prodSql/queries/datamart.activeArticles2.sql
Normal file
43
backend/prodSql/queries/datamart.activeArticles2.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
This will be replacing activeArticles once all data is remapped into this query.
|
||||
make a note in the docs this activeArticles will go stale sooner or later.
|
||||
**/
|
||||
use [test1_AlplaPROD2.0_Read]
|
||||
|
||||
select a.Id,
|
||||
a.HumanReadableId as av,
|
||||
a.Alias as alias,
|
||||
p.LoadingUnitsPerTruck as loadingUnitsPerTruck,
|
||||
p.LoadingUnitsPerTruck * p.LoadingUnitPieces as qtyPerTruck,
|
||||
p.LoadingUnitPieces,
|
||||
case when i.MinQuantity IS NOT NULL then round(cast(i.MinQuantity as float), 2) else 0 end as min,
|
||||
case when i.MaxQuantity IS NOT NULL then round(cast(i.MaxQuantity as float),2) else 0 end as max
|
||||
from masterData.Article (nolock) as a
|
||||
|
||||
/* sales price */
|
||||
left join
|
||||
(select *
|
||||
from (select
|
||||
id,
|
||||
PackagingId,
|
||||
ArticleId,
|
||||
DefaultCustomer,
|
||||
ROW_NUMBER() OVER (PARTITION BY ArticleId ORDER BY ValidAfter DESC) AS RowNum
|
||||
from masterData.SalesPrice (nolock)
|
||||
where DefaultCustomer = 1) as x
|
||||
where RowNum = 1
|
||||
) as s
|
||||
on a.id = s.ArticleId
|
||||
|
||||
/* pkg instructions */
|
||||
left join
|
||||
masterData.PackagingInstruction (nolock) as p
|
||||
on s.PackagingId = p.id
|
||||
|
||||
/* stock limits */
|
||||
left join
|
||||
masterData.StockLimit (nolock) as i
|
||||
on a.id = i.ArticleId
|
||||
|
||||
where a.active = 1
|
||||
and a.HumanReadableId in ([articles])
|
||||
45
backend/prodSql/queries/datamart.customerInventory.sql
Normal file
45
backend/prodSql/queries/datamart.customerInventory.sql
Normal file
@@ -0,0 +1,45 @@
|
||||
select x.idartikelVarianten as av
|
||||
,ArtikelVariantenAlias as Alias
|
||||
--x.Lfdnr as RunningNumber,
|
||||
--,round(sum(EinlagerungsMengeVPKSum),0) as Total_Pallets
|
||||
--,sum(EinlagerungsMengeSum) as Total_PalletQTY
|
||||
,round(sum(VerfuegbareMengeVPKSum),0) as Avalible_Pallets
|
||||
,sum(VerfuegbareMengeSum) as Avaliable_PalletQTY
|
||||
,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%' then GesperrteMengeVPKSum else 0 end) as Held_Pallets
|
||||
--,sum(case when c.Description NOT LIKE '%COA%' then GesperrteMengeSum else 0 end) as Held_QTY
|
||||
,IdProdPlanung as Lot
|
||||
--,IdAdressen
|
||||
--,x.AdressBez
|
||||
--,*
|
||||
from [AlplaPROD_test1].dbo.[V_LagerPositionenBarcodes] (nolock) x
|
||||
|
||||
left join
|
||||
[AlplaPROD_test1].dbo.T_EtikettenGedruckt (nolock) on
|
||||
x.Lfdnr = T_EtikettenGedruckt.Lfdnr AND T_EtikettenGedruckt.Lfdnr > 1
|
||||
|
||||
left join
|
||||
|
||||
(SELECT *
|
||||
FROM [AlplaPROD_test1].[dbo].[T_BlockingDefects] (nolock) 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
|
||||
x.IdWarenlager not in (6, 1)
|
||||
--and IdAdressen
|
||||
--and x.IdWarenlager in (0)
|
||||
|
||||
|
||||
group by x.IdArtikelVarianten
|
||||
,ArtikelVariantenAlias
|
||||
,IdProdPlanung
|
||||
--,c.Description
|
||||
,IdAdressen
|
||||
,x.AdressBez
|
||||
--, x.Lfdnr
|
||||
order by x.IdArtikelVarianten
|
||||
74
backend/prodSql/queries/datamart.deliveryByDateRange.sql
Normal file
74
backend/prodSql/queries/datamart.deliveryByDateRange.sql
Normal file
@@ -0,0 +1,74 @@
|
||||
use [test1_AlplaPROD2.0_Read]
|
||||
|
||||
DECLARE @StartDate DATE = '[startDate]' -- 2025-1-1
|
||||
DECLARE @EndDate DATE = '[endDate]' -- 2025-1-31
|
||||
SELECT
|
||||
r.[ArticleHumanReadableId]
|
||||
,[ReleaseNumber]
|
||||
,h.CustomerOrderNumber
|
||||
,x.CustomerLineItemNumber
|
||||
,[CustomerReleaseNumber]
|
||||
,[ReleaseState]
|
||||
,[DeliveryState]
|
||||
,ea.JournalNummer as BOL_Number
|
||||
,[ReleaseConfirmationState]
|
||||
,[PlanningState]
|
||||
,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
|
||||
--,r.[OrderDate]
|
||||
,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
|
||||
--,r.[DeliveryDate]
|
||||
,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
|
||||
--,r.[LoadingDate]
|
||||
,[Quantity]
|
||||
,[DeliveredQuantity]
|
||||
,r.[AdditionalInformation1]
|
||||
,r.[AdditionalInformation2]
|
||||
,[TradeUnits]
|
||||
,[LoadingUnits]
|
||||
,[Trucks]
|
||||
,[LoadingToleranceType]
|
||||
,[SalesPrice]
|
||||
,[Currency]
|
||||
,[QuantityUnit]
|
||||
,[SalesPriceRemark]
|
||||
,r.[Remark]
|
||||
,[Irradiated]
|
||||
,r.[CreatedByEdi]
|
||||
,[DeliveryAddressHumanReadableId]
|
||||
,DeliveryAddressDescription
|
||||
,[CustomerArtNo]
|
||||
,[TotalPrice]
|
||||
,r.[ArticleAlias]
|
||||
|
||||
FROM [order].[Release] (nolock) as r
|
||||
|
||||
left join
|
||||
[order].LineItem as x on
|
||||
|
||||
r.LineItemId = x.id
|
||||
|
||||
left join
|
||||
[order].Header as h on
|
||||
x.HeaderId = h.id
|
||||
|
||||
--bol stuff
|
||||
left join
|
||||
AlplaPROD_test1.dbo.V_LadePlanungenLadeAuftragAbruf (nolock) as zz
|
||||
on zz.AbrufIdAuftragsAbruf = r.ReleaseNumber
|
||||
|
||||
left join
|
||||
(select * from (SELECT
|
||||
ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
|
||||
,*
|
||||
FROM [AlplaPROD_test1].[dbo].[T_Lieferungen] (nolock)) x
|
||||
|
||||
where RowNum = 1) as ea on
|
||||
zz.IdLieferschein = ea.IdJournal
|
||||
|
||||
where
|
||||
--r.ReleaseNumber = 1452
|
||||
|
||||
r.DeliveryDate between @StartDate AND @EndDate
|
||||
and DeliveredQuantity > 0
|
||||
--and r.ArticleHumanReadableId in ([articles])
|
||||
--and Journalnummer = 169386
|
||||
29
backend/prodSql/queries/datamart.fakeEDIUpdate.sql
Normal file
29
backend/prodSql/queries/datamart.fakeEDIUpdate.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
use [test1_AlplaPROD2.0_Read]
|
||||
|
||||
select
|
||||
customerartno as CustomerArticleNumber
|
||||
,h.CustomerOrderNumber as CustomerOrderNumber
|
||||
,l.CustomerLineItemNumber as CustomerLineNumber
|
||||
,r.CustomerReleaseNumber as CustomerRealeaseNumber
|
||||
,r.Quantity
|
||||
,format(r.DeliveryDate, 'MM/dd/yyyy HH:mm') as DeliveryDate
|
||||
,h.CustomerHumanReadableId as CustomerID
|
||||
,r.Remark
|
||||
--,*
|
||||
from [order].[Release] as r (nolock)
|
||||
|
||||
left join
|
||||
[order].LineItem as l (nolock) on
|
||||
l.id = r.LineItemId
|
||||
|
||||
left join
|
||||
[order].Header as h (nolock) on
|
||||
h.id = l.HeaderId
|
||||
|
||||
WHERE releaseState not in (1, 2, 3, 4)
|
||||
AND h.CreatedByEdi = 1
|
||||
AND r.deliveryDate < getdate() + 1
|
||||
--AND h.CustomerHumanReadableId in (0)
|
||||
|
||||
|
||||
order by r.deliveryDate
|
||||
8
backend/prodSql/queries/datamart.forecast.sql
Normal file
8
backend/prodSql/queries/datamart.forecast.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
SELECT format(RequirementDate, 'yyyy-MM-dd') as requirementDate
|
||||
,ArticleHumanReadableId
|
||||
,CustomerArticleNumber
|
||||
,ArticleDescription
|
||||
,Quantity
|
||||
FROM [test1_AlplaPROD2.0_Read].[forecast].[Forecast]
|
||||
where DeliveryAddressHumanReadableId in ([customers])
|
||||
order by RequirementDate
|
||||
58
backend/prodSql/queries/datamart.inventory.sql
Normal file
58
backend/prodSql/queries/datamart.inventory.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
use [test1_AlplaPROD2.0_Read]
|
||||
|
||||
select
|
||||
ArticleHumanReadableId as article
|
||||
,ArticleAlias as alias
|
||||
,round(sum(QuantityLoadingUnits),2) total_pallets
|
||||
,round(sum(Quantity),2) as total_palletQTY
|
||||
,round(sum(case when State = 0 then QuantityLoadingUnits else 0 end),2) available_Pallets
|
||||
,round(sum(case when State = 0 then Quantity else 0 end),2) available_QTY
|
||||
,round(sum(case when b.HumanReadableId = 864 then QuantityLoadingUnits else 0 end),2) as coa_Pallets
|
||||
,round(sum(case when b.HumanReadableId = 864 then Quantity else 0 end),2) as coa_QTY
|
||||
,round(sum(case when b.HumanReadableId <> 864 then QuantityLoadingUnits else 0 end),2) as held_Pallets
|
||||
,round(sum(case when b.HumanReadableId <> 864 then Quantity else 0 end),2) as held_QTY
|
||||
,round(sum(case when w.type = 7 then QuantityLoadingUnits else 0 end),2) as consignment_Pallets
|
||||
,round(sum(case when w.type = 7 then Quantity else 0 end),2) as consignment_qty
|
||||
--,l.RunningNumber
|
||||
|
||||
/** datamart include lot number **/
|
||||
--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot
|
||||
|
||||
/** data mart include location data **/
|
||||
--,l.WarehouseDescription,l.LaneDescription
|
||||
|
||||
,articleTypeName
|
||||
|
||||
FROM [warehousing].[WarehouseUnit] as l (nolock)
|
||||
left join
|
||||
(
|
||||
SELECT [Id]
|
||||
,[HumanReadableId]
|
||||
,d.[Description]
|
||||
,[DefectGroupId]
|
||||
,[IsActive]
|
||||
FROM [blocking].[BlockingDefect] as g (nolock)
|
||||
|
||||
left join
|
||||
[AlplaPROD_test1].dbo.[T_BlockingDefects] as d (nolock) on
|
||||
d.IdGlobalBlockingDefect = g.HumanReadableId
|
||||
) as b on
|
||||
b.id = l.MainDefectId
|
||||
|
||||
left join
|
||||
[warehousing].[warehouse] as w (nolock) on
|
||||
w.id = l.warehouseid
|
||||
|
||||
where LaneHumanReadableId not in (20000,21000)
|
||||
group by ArticleHumanReadableId,
|
||||
ArticleAlias,
|
||||
ArticleTypeName
|
||||
--,l.RunningNumber
|
||||
|
||||
/** datamart include lot number **/
|
||||
--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber
|
||||
|
||||
/** data mart include location data **/
|
||||
--,l.WarehouseDescription,l.LaneDescription
|
||||
|
||||
order by ArticleHumanReadableId
|
||||
48
backend/prodSql/queries/datamart.legacy.inventory.sql
Normal file
48
backend/prodSql/queries/datamart.legacy.inventory.sql
Normal 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
|
||||
33
backend/prodSql/queries/datamart.openOrders.sql
Normal file
33
backend/prodSql/queries/datamart.openOrders.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
use [test1_AlplaPROD2.0_Read]
|
||||
|
||||
select
|
||||
customerartno
|
||||
,r.ArticleHumanReadableId as article
|
||||
,r.ArticleAlias as articleAlias
|
||||
,ReleaseNumber
|
||||
,h.CustomerOrderNumber as header
|
||||
,l.CustomerLineItemNumber as lineItem
|
||||
,r.CustomerReleaseNumber as releaseNumber
|
||||
,r.LoadingUnits
|
||||
,r.Quantity
|
||||
,r.TradeUnits
|
||||
,h.CustomerHumanReadableId
|
||||
,r.DeliveryAddressDescription
|
||||
,format(r.LoadingDate, 'MM/dd/yyyy HH:mm') as loadingDate
|
||||
,format(r.DeliveryDate, 'MM/dd/yyyy HH:mm') as deliveryDate
|
||||
,r.Remark
|
||||
--,*
|
||||
from [order].[Release] as r (nolock)
|
||||
|
||||
left join
|
||||
[order].LineItem as l (nolock) on
|
||||
l.id = r.LineItemId
|
||||
|
||||
left join
|
||||
[order].Header as h (nolock) on
|
||||
h.id = l.HeaderId
|
||||
|
||||
WHERE releasestate not in (1, 2, 4)
|
||||
AND r.deliverydate between getDate() + -[startDay] and getdate() + [endDay]
|
||||
|
||||
order by r.deliverydate
|
||||
19
backend/prodSql/queries/datamart.productionData.sql
Normal file
19
backend/prodSql/queries/datamart.productionData.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
use [test1_AlplaPROD2.0_Reporting]
|
||||
|
||||
declare @startDate nvarchar(30) = '[startDate]' --'2024-12-30'
|
||||
declare @endDate nvarchar(30) = '[endDate]' --'2025-08-09'
|
||||
|
||||
select MachineLocation,
|
||||
ArticleHumanReadableId as article,
|
||||
sum(Quantity) as Produced,
|
||||
count(Quantity) as palletsProdued,
|
||||
FORMAT(convert(date, ProductionDay), 'M/d/yyyy') as ProductionDay,
|
||||
ProductionLotHumanReadableId as productionLot
|
||||
|
||||
from [reporting_productionControlling].[ScannedUnit] (nolock)
|
||||
|
||||
where convert(date, ProductionDay) between @startDate and @endDate
|
||||
and ArticleHumanReadableId in ([articles])
|
||||
and BookedOut is null
|
||||
|
||||
group by MachineLocation, ArticleHumanReadableId,ProductionDay, ProductionLotHumanReadableId
|
||||
@@ -1,5 +1,10 @@
|
||||
use [test1_AlplaPROD2.0_Read]
|
||||
use AlplaPROD_test1
|
||||
/**
|
||||
|
||||
move this over to the delivery date range query once we have the shift data mapped over correctly.
|
||||
|
||||
update the psi stuff on this as well.
|
||||
**/
|
||||
DECLARE @StartDate DATE = '[startDate]' -- 2025-1-1
|
||||
DECLARE @EndDate DATE = '[endDate]' -- 2025-1-31
|
||||
SELECT
|
||||
@@ -66,9 +71,9 @@ ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
|
||||
zz.IdLieferschein = ea.IdJournal
|
||||
|
||||
where
|
||||
--r.ArticleHumanReadableId in ([articles])
|
||||
r.ArticleHumanReadableId in ([articles])
|
||||
--r.ReleaseNumber = 1452
|
||||
|
||||
r.DeliveryDate between @StartDate AND @EndDate
|
||||
and DeliveredQuantity > 0
|
||||
and r.DeliveryDate between @StartDate AND @EndDate
|
||||
--and DeliveredQuantity > 0
|
||||
--and Journalnummer = 169386
|
||||
32
backend/prodSql/queries/datamart.psiPlanningData.sql
Normal file
32
backend/prodSql/queries/datamart.psiPlanningData.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
use AlplaPROD_test1
|
||||
declare @start_date nvarchar(30) = '[startDate]' --'2025-01-01'
|
||||
declare @end_date nvarchar(30) = '[endDate]' --'2025-08-09'
|
||||
/*
|
||||
articles will need to be passed over as well as the date structure we want to see
|
||||
*/
|
||||
|
||||
select x.IdArtikelvarianten As Article,
|
||||
ProduktionAlias as Description,
|
||||
standort as MachineId,
|
||||
MaschinenBezeichnung as MachineName,
|
||||
--MaschZyklus as PlanningCycleTime,
|
||||
x.IdProdPlanung as LotNumber,
|
||||
FORMAT(ProdTag, 'MM/dd/yyyy') as ProductionDay,
|
||||
x.planMenge as TotalPlanned,
|
||||
ProduktionMenge as QTYPerDay,
|
||||
round(ProduktionMengeVPK, 2) PalDay,
|
||||
Status as finished
|
||||
--MaschStdAuslastung as nee
|
||||
|
||||
from dbo.V_ProdLosProduktionJeProdTag_PLANNING (nolock) as x
|
||||
|
||||
left join
|
||||
dbo.V_ProdPlanung (nolock) as p on
|
||||
x.IdProdPlanung = p.IdProdPlanung
|
||||
|
||||
where ProdTag between @start_date and @end_date
|
||||
and p.IdArtikelvarianten in ([articles])
|
||||
--and V_ProdLosProduktionJeProdTag_PLANNING.IdKunde = 10
|
||||
--and IdProdPlanung = 18442
|
||||
|
||||
order by ProdTag desc
|
||||
21
backend/prodSql/queries/disableJob.sql
Normal file
21
backend/prodSql/queries/disableJob.sql
Normal 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
|
||||
11
backend/prodSql/queries/featureCheck.sql
Normal file
11
backend/prodSql/queries/featureCheck.sql
Normal 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
|
||||
*/
|
||||
34
backend/prodSql/queries/opendock.addressLink.sql
Normal file
34
backend/prodSql/queries/opendock.addressLink.sql
Normal 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
|
||||
44
backend/prodSql/queries/qualityBlocking.sql
Normal file
44
backend/prodSql/queries/qualityBlocking.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
use [test1_AlplaPROD2.0_Read]
|
||||
|
||||
SELECT
|
||||
'Alert! new blocking order: #' + cast(bo.HumanReadableId as varchar) + ' - ' + bo.ArticleVariantDescription as subject
|
||||
,cast(bo.[HumanReadableId] as varchar) as blockingNumber
|
||||
,bo.[ArticleVariantDescription] as article
|
||||
,cast(bo.[CustomerHumanReadableId] as varchar) + ' - ' + bo.[CustomerDescription] as customer
|
||||
,convert(varchar(10), bo.[BlockingDate], 101) + ' ' + convert(varchar(5), bo.[BlockingDate], 108) as blockingDate
|
||||
,cast(ArticleVariantHumanReadableId as varchar) + ' - ' + ArticleVariantDescription as av
|
||||
,case when bo.Remark = '' or bo.Remark is NULL then 'Please reach out to quality for the reason this was placed on hold as a remark was not entered during the blocking processs' else bo.Remark end as remark
|
||||
,cast(FORMAT(TotalAmountOfPieces, '###,###') as varchar) + ' / ' + cast(LoadingUnit as varchar) as peicesAndLoadingUnits
|
||||
,bo.ProductionLotHumanReadableId as lotNumber
|
||||
,cast(osd.IdBlockingDefectsGroup as varchar) + ' - ' + osd.Description as mainDefectGroup
|
||||
,cast(df.HumanReadableId as varchar) + ' - ' + os.Description as mainDefect
|
||||
,lot.MachineLocation as line
|
||||
--,*
|
||||
FROM [blocking].[BlockingOrder] (nolock) as bo
|
||||
|
||||
|
||||
/*** get the defect details ***/
|
||||
join
|
||||
[blocking].[BlockingDefect] (nolock) AS df
|
||||
on df.id = bo.MainDefectId
|
||||
|
||||
/*** pull description from 1.0 ***/
|
||||
left join
|
||||
[AlplaPROD_test1].[dbo].[T_BlockingDefects] (nolock) as os
|
||||
on os.IdGlobalBlockingDefect = df.HumanReadableId
|
||||
|
||||
/*** join in 1.0 defect group ***/
|
||||
left join
|
||||
[AlplaPROD_test1].[dbo].[T_BlockingDefectsGroups] (nolock) as osd
|
||||
on osd.IdBlockingDefectsGroup = os.IdBlockingDefectsGroup
|
||||
|
||||
left join
|
||||
[productionControlling].[ProducedLot] (nolock) as lot
|
||||
on lot.id = bo.ProductionLotId
|
||||
|
||||
|
||||
where
|
||||
bo.[BlockingDate] between getdate() - 2 and getdate() + 3 and
|
||||
bo.BlockingTrigger = 1 -- so we only get the ir blocking and not coa
|
||||
--and HumanReadableId NOT IN ([sentBlockingOrders])
|
||||
and bo.HumanReadableId > [lastBlocking]
|
||||
@@ -21,7 +21,7 @@ SELECT
|
||||
,[MainMaterialId]
|
||||
,[MainMaterialHumanReadableId]
|
||||
,[MainMaterialDescription]
|
||||
,[AdditionalInformation1]
|
||||
,[AdditionalInformation1] -- we will use this to reference as the first check
|
||||
,[AdditionalInformation2]
|
||||
,[D365SupplierLot]
|
||||
,[TradeUnits]
|
||||
@@ -49,7 +49,7 @@ SELECT
|
||||
,[PaymentTermsDescription]
|
||||
,[Remark]
|
||||
,[DeliveryAddressId]
|
||||
,[DeliveryAddressHumanReadableId]
|
||||
,[DeliveryAddressHumanReadableId] --use this to validate with the new drop or live check
|
||||
,[DeliveryAddressDescription]
|
||||
,[DeliveryStreetName]
|
||||
,[DeliveryAddressZip]
|
||||
|
||||
@@ -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]
|
||||
|
||||
4
backend/prodSql/queries/shiftChange.sql
Normal file
4
backend/prodSql/queries/shiftChange.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
select top(1) convert(varchar(8) ,
|
||||
convert(time,startdate), 108) as shiftChange
|
||||
from [test1_AlplaPROD2.0_Read].[masterData].[ShiftDefinition]
|
||||
where teamNumber = 1
|
||||
@@ -20,8 +20,8 @@ export const gpReqCheck = async (data: GpStatus[]) => {
|
||||
module: "purchase",
|
||||
subModule: "query",
|
||||
message: `Error getting alpla purchase info`,
|
||||
data: [gpReqCheck.message],
|
||||
notify: false,
|
||||
data: gpReqCheck.message as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const gpReqCheck = async (data: GpStatus[]) => {
|
||||
const result = await gpQuery(
|
||||
gpReqCheck.query.replace(
|
||||
"[reqsToCheck]",
|
||||
data.map((r) => `'${r.req}'`).join(", ") ?? "",
|
||||
data.map((r) => `'${r.req}'`).join(", ") ?? "xo",
|
||||
),
|
||||
"Get req info",
|
||||
);
|
||||
@@ -55,7 +55,7 @@ export const gpReqCheck = async (data: GpStatus[]) => {
|
||||
[Requisition Number] as req
|
||||
,case when [Workflow Status] = 'recall' then 'returned' else [Workflow Status] end as approvedStatus
|
||||
--,*
|
||||
from [dbo].[PurchaseRequisitions] where [Requisition Number] in (${missing1Reqs.map((r) => `'${r}'`).join(", ")})`,
|
||||
from [dbo].[PurchaseRequisitions] where [Requisition Number] in (${missing1Reqs.map((r) => `'${r}'`).join(", ") ?? "xo"})`,
|
||||
"validate req is not in recall",
|
||||
);
|
||||
|
||||
@@ -76,7 +76,7 @@ export const gpReqCheck = async (data: GpStatus[]) => {
|
||||
,PONUMBER
|
||||
,reqStatus='converted'
|
||||
,*
|
||||
from alpla.dbo.sop60100 (nolock) where sopnumbe in (${missing2Reqs.map((r) => `'${r}'`).join(", ")})`,
|
||||
from alpla.dbo.sop60100 (nolock) where sopnumbe in (${missing2Reqs.map((r) => `'${r}'`).join(", ") ?? "xo"})`,
|
||||
"Get release info",
|
||||
);
|
||||
|
||||
@@ -111,7 +111,15 @@ export const gpReqCheck = async (data: GpStatus[]) => {
|
||||
}));
|
||||
|
||||
return updateData;
|
||||
} catch (error) {
|
||||
log.error({ stack: error });
|
||||
} catch (error: any) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "purchase",
|
||||
subModule: "gpChecks",
|
||||
message: error.message,
|
||||
data: error.stack as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,13 +39,13 @@ export const monitorAlplaPurchase = async () => {
|
||||
module: "purchase",
|
||||
subModule: "query",
|
||||
message: `Error getting alpla purchase info`,
|
||||
data: [sqlQuery.message],
|
||||
notify: false,
|
||||
data: sqlQuery.message as any,
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (purchaseMonitor[0]?.active) {
|
||||
createCronJob("purchaseMonitor", "0 */5 * * * *", async () => {
|
||||
createCronJob("purchaseMonitor", "0 5 * * * *", async () => {
|
||||
try {
|
||||
const result = await prodQuery(
|
||||
sqlQuery.query.replace(
|
||||
@@ -78,7 +78,7 @@ export const monitorAlplaPurchase = async () => {
|
||||
|
||||
if (error) {
|
||||
log.error(
|
||||
{ error },
|
||||
{ error, notify: true },
|
||||
"There was an error adding alpla purchase history",
|
||||
);
|
||||
}
|
||||
@@ -86,8 +86,10 @@ export const monitorAlplaPurchase = async () => {
|
||||
await delay(500);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error({ error: e }, "Error occurred while running the monitor job");
|
||||
log.error({ error: e }, "Error occurred while running the monitor job");
|
||||
log.error(
|
||||
{ error: e, notify: true },
|
||||
"Error occurred while running the monitor job",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -104,7 +106,7 @@ export const monitorAlplaPurchase = async () => {
|
||||
// if theres no reqs just end meow
|
||||
if (errorReq) {
|
||||
log.error(
|
||||
{ stack: errorReq },
|
||||
{ stack: errorReq, notify: true },
|
||||
"There was an error getting history data",
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
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 { 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";
|
||||
import { setupProdSqlRoutes } from "./prodSql/prodSql.routes.js";
|
||||
import { setupSystemRoutes } from "./system/system.routes.js";
|
||||
import { setupTCPRoutes } from "./tcpServer/tcp.routes.js";
|
||||
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);
|
||||
@@ -24,4 +28,5 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
|
||||
setupOpendockRoutes(baseUrl, app);
|
||||
setupNotificationRoutes(baseUrl, app);
|
||||
setupOCPRoutes(baseUrl, app);
|
||||
setupTCPRoutes(baseUrl, app);
|
||||
};
|
||||
|
||||
@@ -6,15 +6,26 @@ import { dbCleanup } from "./db/dbCleanup.controller.js";
|
||||
import { type Setting, settings } from "./db/schema/settings.schema.js";
|
||||
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";
|
||||
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";
|
||||
|
||||
const port = Number(process.env.PORT) || 3000;
|
||||
export let systemSettings: Setting[] = [];
|
||||
@@ -28,6 +39,7 @@ const start = async () => {
|
||||
const log = createLogger({ module: "system", subModule: "main start" });
|
||||
|
||||
// triggering long lived processes
|
||||
startTCPServer();
|
||||
connectProdSql();
|
||||
connectGPSql();
|
||||
|
||||
@@ -51,17 +63,49 @@ const start = async () => {
|
||||
monitorAlplaPurchase();
|
||||
}
|
||||
|
||||
if (systemSettings.filter((n) => n.name === "ocp")[0]?.active) {
|
||||
printerSync();
|
||||
}
|
||||
|
||||
// these jobs below are system jobs and should run no matter what.
|
||||
createCronJob("JobAuditLogCleanUp", "0 0 5 * * *", () =>
|
||||
dbCleanup("jobs", 30),
|
||||
);
|
||||
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
|
||||
historicalSchedule();
|
||||
|
||||
createCronJob("aggregateHits", "0 0 7 * * *", async () =>
|
||||
runRouteHitAnalyticsCron(),
|
||||
);
|
||||
|
||||
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) => {
|
||||
console.error("Uncaught Exception:", err);
|
||||
//await closePool();
|
||||
const emailData = {
|
||||
email: "blake.matthes@alpla.com", // should be moved to the db so it can be reused.
|
||||
subject: `${os.hostname()} has just encountered a crash.`,
|
||||
template: "serverCrash",
|
||||
context: {
|
||||
error: err,
|
||||
plant: `${os.hostname()}`,
|
||||
},
|
||||
};
|
||||
|
||||
await sendEmail(emailData);
|
||||
//process.exit(1);
|
||||
});
|
||||
|
||||
server.listen(port, async () => {
|
||||
log.info(
|
||||
`Listening on http://${os.hostname()}:${port}${baseUrl}, logging in ${process.env.LOG_LEVEL}, current ENV ${process.env.NODE_ENV ? process.env.NODE_ENV : "development"}`,
|
||||
|
||||
@@ -9,7 +9,7 @@ type RoomDefinition<T = unknown> = {
|
||||
|
||||
export const protectedRooms: any = {
|
||||
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||
admin: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||
//admin: { requiresAuth: false, role: ["admin", "systemAdmin"] },
|
||||
};
|
||||
|
||||
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||
@@ -36,4 +36,16 @@ export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
seed: async (limit) => {
|
||||
console.info(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
"admin:build": {
|
||||
seed: async (limit) => {
|
||||
console.info(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -88,14 +88,12 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
||||
});
|
||||
}
|
||||
|
||||
const roles = Array.isArray(config.role) ? config.role : [config.role];
|
||||
|
||||
console.log(roles, s.user.role);
|
||||
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", {
|
||||
room: rn,
|
||||
roomId: rn,
|
||||
message: `Not authorized to be in room: ${rn}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type RoomId = "logs" | "labels"; //| "alerts" | "metrics";
|
||||
export type RoomId = "logs" | "labels" | "admin" | "admin:build"; //| "alerts" | "metrics";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user