Compare commits
203 Commits
v0.0.1-alp
...
f635415b75
| Author | SHA1 | Date | |
|---|---|---|---|
| f635415b75 | |||
| 40edbc3eae | |||
| 2558b2e5bb | |||
| 45a0dee9ca | |||
| bb7931d6c8 | |||
| 4f848bb649 | |||
| f8335f5217 | |||
| 2a35381fe4 | |||
| e6b92aeb10 | |||
| da87e2e1d3 | |||
| 3ef0f230dd | |||
| 78f7b8a179 | |||
| 1a3d8a7ddc | |||
| 2a648f6306 | |||
| 69a9a81a88 | |||
| 188fdd0eb7 | |||
| db28635c8c | |||
| bcdf9566bc | |||
| c15ee070e7 | |||
| 347edb7078 | |||
| fe0b1573f3 | |||
| 9c0ef1f5df | |||
| 8b076949a7 | |||
| 6d0fb8aee4 | |||
| 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 | |||
| d05a0ce930 | |||
| 995b1dda7c | |||
| 97f93a1830 | |||
| 635635b356 | |||
| a691dc276e | |||
| 8dfcbc5720 | |||
| 103ae77e9f | |||
| beeccc6e8d | |||
| 0880298cf5 | |||
| 34b0abac36 | |||
| 28c226ddbc | |||
| 42861cc69e | |||
| 5f3d683a13 | |||
| a17787e852 | |||
| 5865ac3b99 | |||
| 637de857f9 | |||
| 3ecf5fb916 | |||
| 92ba3ef512 | |||
| 7d6c2db89c | |||
| 74262beb65 | |||
| f3b8dd94e5 | |||
| 0059b9b850 | |||
| 1ad789b2b9 | |||
| 079478f932 | |||
| d6d5b451cd | |||
| 76747cf917 | |||
| 6e85991062 | |||
| 98e408cb85 | |||
| ed052dff3c | |||
| 8f59bba614 | |||
| fb2c5609aa | |||
| 17aed6cb89 | |||
| b02b93b83f | |||
| 9ceba8b5bb | |||
| 2c0dbf95c7 | |||
| 860207a60b | |||
| 5c6460012a | |||
| be1d4081e0 | |||
| 83a94cacf3 | |||
| 0ce3790675 | |||
| 5854889eb5 | |||
| 4caaf74569 | |||
| fe889ca757 | |||
| 699c124b0e | |||
| 7d55c5f431 | |||
| c4fd74fc93 | |||
| 3775760734 | |||
| 643d12ff18 |
@@ -1,8 +0,0 @@
|
|||||||
# Changesets
|
|
||||||
|
|
||||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
||||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
||||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
||||||
|
|
||||||
We have a quick list of common questions to get you started engaging with this project in
|
|
||||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://unpkg.com/@changesets/config/schema.json",
|
|
||||||
"changelog": "@changesets/cli/changelog",
|
|
||||||
"commit": false,
|
|
||||||
"fixed": [],
|
|
||||||
"linked": [],
|
|
||||||
"access": "restricted",
|
|
||||||
"baseBranch": "main",
|
|
||||||
"updateInternalDependencies": "patch",
|
|
||||||
"ignore": []
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"lst_v3": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
build stuff
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"mode": "pre",
|
|
||||||
"tag": "alpha",
|
|
||||||
"initialVersions": {
|
|
||||||
"lst_v3": "1.0.1"
|
|
||||||
},
|
|
||||||
"changesets": [
|
|
||||||
"neat-years-unite",
|
|
||||||
"soft-onions-appear"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"lst_v3": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
external url added for docker
|
|
||||||
@@ -9,4 +9,4 @@ builds
|
|||||||
testFiles
|
testFiles
|
||||||
nssm.exe
|
nssm.exe
|
||||||
postgresql-17.9-2-windows-x64.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
|
# Server
|
||||||
PORT=3000
|
PORT=3000
|
||||||
URL=http://localhost: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
|
RESET_EXPIRY_SECONDS=3600
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=
|
||||||
|
|
||||||
# prodServer
|
# SMTP password
|
||||||
PROD_SERVER=usmcd1vms036
|
SMTP_PASSWORD=
|
||||||
PROD_PLANT_TOKEN=test3
|
|
||||||
PROD_USER=alplaprod
|
# opendock
|
||||||
PROD_PASSWORD=password
|
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
|
# postgres connection
|
||||||
DATABASE_HOST=localhost
|
DATABASE_HOST=localhost
|
||||||
DATABASE_PORT=5433
|
DATABASE_PORT=5432
|
||||||
DATABASE_USER=user
|
DATABASE_USER=
|
||||||
DATABASE_PASSWORD=password
|
DATABASE_PASSWORD=
|
||||||
DATABASE_DB=lst_dev
|
DATABASE_DB=
|
||||||
|
|
||||||
# how is the app running server or client when in client mode you must provide the server
|
# Gp connection
|
||||||
APP_RUNNING_IN=server
|
GP_USER=
|
||||||
SERVER_NAME=localhost
|
GP_PASSWORD=
|
||||||
|
|
||||||
#dev stuff
|
# how often to check for new/updated queries in min
|
||||||
GITEA_TOKEN=""
|
QUERY_TIME_TYPE=m #valid options are m, h
|
||||||
EMAIL_USER=""
|
QUERY_CHECK=1
|
||||||
EMAIL_PASSWORD=""
|
|
||||||
|
|
||||||
|
# Oauth setup
|
||||||
|
PROVIDER=""
|
||||||
|
CLIENT_ID=""
|
||||||
|
CLIENT_SECRET=""
|
||||||
|
CLIENT_SCOPES="openid profile email groups"
|
||||||
|
DISCOVERY_URL=""
|
||||||
|
|||||||
66
.gitea/ISSUE_TEMPLATE/bug_report_frontend.md
Normal file
66
.gitea/ISSUE_TEMPLATE/bug_report_frontend.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report - Frontend
|
||||||
|
about: Report something that is broken or not working correctly
|
||||||
|
title: "[BUG - Frontend] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- bug
|
||||||
|
- frontend
|
||||||
|
---
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
Briefly explain the issue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Steps To Reproduce
|
||||||
|
|
||||||
|
1. Go to ...
|
||||||
|
2. Click ...
|
||||||
|
3. Scan ...
|
||||||
|
4. Error occurs ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Behavior
|
||||||
|
|
||||||
|
What should have happened?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Actual Behavior
|
||||||
|
|
||||||
|
What actually happened instead?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Severity
|
||||||
|
|
||||||
|
- [ ] Low
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] High
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Production
|
||||||
|
- Development
|
||||||
|
- Zebra Scanner
|
||||||
|
- Mobile Device
|
||||||
|
- Windows Server
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Logs / Screenshots
|
||||||
|
|
||||||
|
Paste logs or upload screenshots here.
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Paste logs here
|
||||||
66
.gitea/ISSUE_TEMPLATE/bug_report_mobile.md
Normal file
66
.gitea/ISSUE_TEMPLATE/bug_report_mobile.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report - Mobile
|
||||||
|
about: Report something that is broken or not working correctly
|
||||||
|
title: "[BUG - Mobile] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- bug
|
||||||
|
- mobile
|
||||||
|
---
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
Briefly explain the issue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Steps To Reproduce
|
||||||
|
|
||||||
|
1. Go to ...
|
||||||
|
2. Click ...
|
||||||
|
3. Scan ...
|
||||||
|
4. Error occurs ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Behavior
|
||||||
|
|
||||||
|
What should have happened?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Actual Behavior
|
||||||
|
|
||||||
|
What actually happened instead?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Severity
|
||||||
|
|
||||||
|
- [ ] Low
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] High
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Production
|
||||||
|
- Development
|
||||||
|
- Zebra Scanner
|
||||||
|
- Mobile Device
|
||||||
|
- Windows Server
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Logs / Screenshots
|
||||||
|
|
||||||
|
Paste logs or upload screenshots here.
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Paste logs here
|
||||||
67
.gitea/ISSUE_TEMPLATE/bug_report_server.md
Normal file
67
.gitea/ISSUE_TEMPLATE/bug_report_server.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report - Server
|
||||||
|
about: Report something that is broken or not working correctly
|
||||||
|
title: "[BUG - Server] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- bug
|
||||||
|
- server
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
Briefly explain the issue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Steps To Reproduce
|
||||||
|
|
||||||
|
1. Go to ...
|
||||||
|
2. Click ...
|
||||||
|
3. Scan ...
|
||||||
|
4. Error occurs ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Behavior
|
||||||
|
|
||||||
|
What should have happened?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Actual Behavior
|
||||||
|
|
||||||
|
What actually happened instead?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Severity
|
||||||
|
|
||||||
|
- [ ] Low
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] High
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Production
|
||||||
|
- Development
|
||||||
|
- Zebra Scanner
|
||||||
|
- Mobile Device
|
||||||
|
- Windows Server
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Logs / Screenshots
|
||||||
|
|
||||||
|
Paste logs or upload screenshots here.
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Paste logs here
|
||||||
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
48
.gitea/ISSUE_TEMPLATE/enhancement_frontend.md
Normal file
48
.gitea/ISSUE_TEMPLATE/enhancement_frontend.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement - Frontend
|
||||||
|
about: Improve or refine an existing feature
|
||||||
|
title: "[ENHANCEMENT - Frontend] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- enhancement
|
||||||
|
- frontend
|
||||||
|
---
|
||||||
|
|
||||||
|
# Existing Feature
|
||||||
|
|
||||||
|
What current feature or workflow is being improved?
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Notifications
|
||||||
|
- Scanner Login
|
||||||
|
- Release Monitor
|
||||||
|
- Printing
|
||||||
|
- Auth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Improvement
|
||||||
|
|
||||||
|
Describe the improvement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Benefit
|
||||||
|
|
||||||
|
Why would this improvement help?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Impact
|
||||||
|
|
||||||
|
- [ ] Small
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] Large
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Notes
|
||||||
|
|
||||||
|
Anything else worth mentioning.
|
||||||
48
.gitea/ISSUE_TEMPLATE/enhancement_mobile.md
Normal file
48
.gitea/ISSUE_TEMPLATE/enhancement_mobile.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement - Mobile
|
||||||
|
about: Improve or refine an existing feature
|
||||||
|
title: "[ENHANCEMENT - Mobile] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- enhancement
|
||||||
|
- mobile
|
||||||
|
---
|
||||||
|
|
||||||
|
# Existing Feature
|
||||||
|
|
||||||
|
What current feature or workflow is being improved?
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Notifications
|
||||||
|
- Scanner Login
|
||||||
|
- Release Monitor
|
||||||
|
- Printing
|
||||||
|
- Auth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Improvement
|
||||||
|
|
||||||
|
Describe the improvement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Benefit
|
||||||
|
|
||||||
|
Why would this improvement help?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Impact
|
||||||
|
|
||||||
|
- [ ] Small
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] Large
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Notes
|
||||||
|
|
||||||
|
Anything else worth mentioning.
|
||||||
48
.gitea/ISSUE_TEMPLATE/enhancement_server.md
Normal file
48
.gitea/ISSUE_TEMPLATE/enhancement_server.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement - Server
|
||||||
|
about: Improve or refine an existing feature
|
||||||
|
title: "[ENHANCEMENT - Server] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- enhancement
|
||||||
|
- server
|
||||||
|
---
|
||||||
|
|
||||||
|
# Existing Feature
|
||||||
|
|
||||||
|
What current feature or workflow is being improved?
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Notifications
|
||||||
|
- Scanner Login
|
||||||
|
- Release Monitor
|
||||||
|
- Printing
|
||||||
|
- Auth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Improvement
|
||||||
|
|
||||||
|
Describe the improvement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Benefit
|
||||||
|
|
||||||
|
Why would this improvement help?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Impact
|
||||||
|
|
||||||
|
- [ ] Small
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] Large
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Notes
|
||||||
|
|
||||||
|
Anything else worth mentioning.
|
||||||
41
.gitea/ISSUE_TEMPLATE/feature_request_frontend.md
Normal file
41
.gitea/ISSUE_TEMPLATE/feature_request_frontend.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request - Frontend
|
||||||
|
about: Suggest a brand new feature or module
|
||||||
|
title: "[FEATURE - Frontend] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- feature
|
||||||
|
- frontend
|
||||||
|
---
|
||||||
|
|
||||||
|
# Problem Statement
|
||||||
|
|
||||||
|
What problem are you trying to solve?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Solution
|
||||||
|
|
||||||
|
Describe the feature you would like added.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Alternatives Considered
|
||||||
|
|
||||||
|
Any other ideas, workarounds, or approaches?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Priority
|
||||||
|
|
||||||
|
- [ ] Nice to Have
|
||||||
|
- [ ] Medium Priority
|
||||||
|
- [ ] High Priority
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Context
|
||||||
|
|
||||||
|
Add mockups, screenshots, examples, or notes here.
|
||||||
41
.gitea/ISSUE_TEMPLATE/feature_request_mobile.md
Normal file
41
.gitea/ISSUE_TEMPLATE/feature_request_mobile.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request - Mobile
|
||||||
|
about: Suggest a brand new feature or module
|
||||||
|
title: "[FEATURE - Mobile] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- feature
|
||||||
|
- mobile
|
||||||
|
---
|
||||||
|
|
||||||
|
# Problem Statement
|
||||||
|
|
||||||
|
What problem are you trying to solve?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Solution
|
||||||
|
|
||||||
|
Describe the feature you would like added.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Alternatives Considered
|
||||||
|
|
||||||
|
Any other ideas, workarounds, or approaches?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Priority
|
||||||
|
|
||||||
|
- [ ] Nice to Have
|
||||||
|
- [ ] Medium Priority
|
||||||
|
- [ ] High Priority
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Context
|
||||||
|
|
||||||
|
Add mockups, screenshots, examples, or notes here.
|
||||||
41
.gitea/ISSUE_TEMPLATE/feature_request_server.md
Normal file
41
.gitea/ISSUE_TEMPLATE/feature_request_server.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request - Server
|
||||||
|
about: Suggest a brand new feature or module
|
||||||
|
title: "[FEATURE - Server] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- feature
|
||||||
|
- server
|
||||||
|
---
|
||||||
|
|
||||||
|
# Problem Statement
|
||||||
|
|
||||||
|
What problem are you trying to solve?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Solution
|
||||||
|
|
||||||
|
Describe the feature you would like added.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Alternatives Considered
|
||||||
|
|
||||||
|
Any other ideas, workarounds, or approaches?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Priority
|
||||||
|
|
||||||
|
- [ ] Nice to Have
|
||||||
|
- [ ] Medium Priority
|
||||||
|
- [ ] High Priority
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Context
|
||||||
|
|
||||||
|
Add mockups, screenshots, examples, or notes here.
|
||||||
@@ -12,20 +12,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout (local)
|
- name: Checkout (local)
|
||||||
run: |
|
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 }}
|
git checkout ${{ gitea.sha }}
|
||||||
|
|
||||||
- name: Login to registry
|
- 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
|
- name: Build image
|
||||||
run: |
|
run: |
|
||||||
docker build \
|
docker build \
|
||||||
-t git.tuffraid.net/cowch/lst_v3:latest \
|
-t 10.75.9.150:3100/cowch/lst_v3:latest \
|
||||||
-t git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }} \
|
-t 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }} \
|
||||||
.
|
.
|
||||||
|
|
||||||
- name: Push
|
- name: Push
|
||||||
run: |
|
run: |
|
||||||
docker push git.tuffraid.net/cowch/lst_v3:latest
|
docker push 10.75.9.150:3100/cowch/lst_v3:latest
|
||||||
docker push git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }}
|
docker push 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }}
|
||||||
229
.gitea/workflows/release.yml
Normal file
229
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
name: Release and Build Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Internal/origin Gitea URL. Do NOT use the Cloudflare fronted URL here.
|
||||||
|
# Examples:
|
||||||
|
# http://gitea.internal.lan:3000
|
||||||
|
# https://gitea-origin.yourdomain.local
|
||||||
|
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: "10.75.9.150:3100" #"git.tuffraid.net"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare release metadata
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TAG="${GITHUB_REF_NAME:-${GITHUB_REF##refs/tags/}}"
|
||||||
|
VERSION="${TAG#v}"
|
||||||
|
IMAGE_NAME="${REGISTRY_HOST}/${{ gitea.repository }}"
|
||||||
|
|
||||||
|
echo "TAG=$TAG" >> "$GITHUB_ENV"
|
||||||
|
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||||
|
echo "IMAGE_NAME=$IMAGE_NAME" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
if [[ "$TAG" == *-* ]]; then
|
||||||
|
echo "PRERELEASE=true" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "PRERELEASE=false" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Resolved TAG=$TAG"
|
||||||
|
echo "Resolved VERSION=$VERSION"
|
||||||
|
echo "Resolved IMAGE_NAME=$IMAGE_NAME"
|
||||||
|
|
||||||
|
- name: Log in to Gitea container registry
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
REGISTRY_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USERNAME" --password-stdin
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker build \
|
||||||
|
-t "$IMAGE_NAME:$TAG" \
|
||||||
|
-t "$IMAGE_NAME:latest" \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Push version tag
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker push "$IMAGE_NAME:$TAG"
|
||||||
|
|
||||||
|
- name: Push latest tag
|
||||||
|
if: ${{ !contains(env.TAG, '-') }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker push "$IMAGE_NAME:latest"
|
||||||
|
|
||||||
|
- name: Push prerelease channel tag
|
||||||
|
if: ${{ contains(env.TAG, '-') }}
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAG: ${{ env.TAG }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
CHANNEL="${TAG#*-}"
|
||||||
|
CHANNEL="${CHANNEL%%.*}"
|
||||||
|
|
||||||
|
echo "Resolved prerelease channel: $CHANNEL"
|
||||||
|
|
||||||
|
docker tag "$IMAGE_NAME:$TAG" "$IMAGE_NAME:$CHANNEL"
|
||||||
|
docker push "$IMAGE_NAME:$CHANNEL"
|
||||||
|
|
||||||
|
- name: Extract matching CHANGELOG section
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
VERSION: ${{ env.VERSION }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
version = os.environ["VERSION"]
|
||||||
|
changelog_path = Path("CHANGELOG.md")
|
||||||
|
|
||||||
|
if not changelog_path.exists():
|
||||||
|
Path("release_body.md").write_text(f"Release {version}\n", encoding="utf-8")
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
text = changelog_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# Matches headings like:
|
||||||
|
# ## [0.1.0]
|
||||||
|
# ## 0.1.0
|
||||||
|
# ## [0.1.0-alpha.1]
|
||||||
|
pattern = re.compile(
|
||||||
|
rf"^##\s+\[?{re.escape(version)}\]?[^\n]*\n(.*?)(?=^##\s+\[?[^\n]+|\Z)",
|
||||||
|
re.MULTILINE | re.DOTALL,
|
||||||
|
)
|
||||||
|
|
||||||
|
match = pattern.search(text)
|
||||||
|
if match:
|
||||||
|
body = match.group(1).strip()
|
||||||
|
else:
|
||||||
|
body = f"Release {version}"
|
||||||
|
|
||||||
|
if not body:
|
||||||
|
body = f"Release {version}"
|
||||||
|
|
||||||
|
Path("release_body.md").write_text(body + "\n", encoding="utf-8")
|
||||||
|
print("----- release_body.md -----")
|
||||||
|
print(body)
|
||||||
|
print("---------------------------")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Create Gitea release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
|
GITEA_INTERNAL_URL: ${{ env.GITEA_INTERNAL_URL }}
|
||||||
|
TAG: ${{ env.TAG }}
|
||||||
|
PRERELEASE: ${{ env.PRERELEASE }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
tag = os.environ["TAG"]
|
||||||
|
prerelease = os.environ["PRERELEASE"].lower() == "true"
|
||||||
|
server_url = os.environ["GITEA_INTERNAL_URL"].rstrip("/")
|
||||||
|
repo = os.environ["GITEA_REPOSITORY"]
|
||||||
|
token = os.environ["RELEASE_TOKEN"]
|
||||||
|
|
||||||
|
body = Path("release_body.md").read_text(encoding="utf-8").strip()
|
||||||
|
|
||||||
|
# Check if the release already exists for this tag
|
||||||
|
get_url = f"{server_url}/api/v1/repos/{repo}/releases/tags/{tag}"
|
||||||
|
get_req = urllib.request.Request(
|
||||||
|
get_url,
|
||||||
|
method="GET",
|
||||||
|
headers={
|
||||||
|
"Authorization": f"token {token}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "lst-release-workflow/1.0",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_release = None
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(get_req) as resp:
|
||||||
|
existing_release = json.loads(resp.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if e.code != 404:
|
||||||
|
details = e.read().decode("utf-8", errors="replace")
|
||||||
|
print("Failed checking existing release:")
|
||||||
|
print(details)
|
||||||
|
raise
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"tag_name": tag,
|
||||||
|
"name": tag,
|
||||||
|
"body": body,
|
||||||
|
"draft": False,
|
||||||
|
"prerelease": prerelease,
|
||||||
|
}
|
||||||
|
|
||||||
|
data = json.dumps(payload).encode("utf-8")
|
||||||
|
|
||||||
|
if existing_release:
|
||||||
|
release_id = existing_release["id"]
|
||||||
|
url = f"{server_url}/api/v1/repos/{repo}/releases/{release_id}"
|
||||||
|
method = "PATCH"
|
||||||
|
print(f"Release already exists for tag {tag}, updating release id {release_id}")
|
||||||
|
else:
|
||||||
|
url = f"{server_url}/api/v1/repos/{repo}/releases"
|
||||||
|
method = "POST"
|
||||||
|
print(f"No release exists for tag {tag}, creating a new one")
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=data,
|
||||||
|
method=method,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"token {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "lst-release-workflow/1.0",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as resp:
|
||||||
|
print(resp.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
details = e.read().decode("utf-8", errors="replace")
|
||||||
|
print("Release create/update failed:")
|
||||||
|
print(details)
|
||||||
|
raise
|
||||||
|
PY
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -4,11 +4,14 @@ builds
|
|||||||
.includes
|
.includes
|
||||||
.buildNumber
|
.buildNumber
|
||||||
temp
|
temp
|
||||||
|
brunoApi
|
||||||
|
downloads
|
||||||
.scriptCreds
|
.scriptCreds
|
||||||
node-v24.14.0-x64.msi
|
node-v24.14.0-x64.msi
|
||||||
postgresql-17.9-2-windows-x64.exe
|
postgresql-17.9-2-windows-x64.exe
|
||||||
VSCodeUserSetup-x64-1.112.0.exe
|
VSCodeSetup-x64-1.120.0.exe
|
||||||
nssm.exe
|
nssm.exe
|
||||||
|
frontend/.tanstack
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
@@ -147,3 +150,4 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
frontend/.tanstack/tmp/2249110e-da91fb0b1b87b6c4cc3e2c2cd25037fd
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{ "type": "ci", "hidden": false, "section": "📈 Project changes" },
|
{ "type": "ci", "hidden": false, "section": "📈 Project changes" },
|
||||||
{ "type": "build", "hidden": false, "section": "📈 Project Builds" }
|
{ "type": "build", "hidden": false, "section": "📈 Project Builds" }
|
||||||
],
|
],
|
||||||
"commitUrlFormat": "https://git.tuffraid.net/cowch/lst/commits/{{hash}}",
|
"commitUrlFormat": "https://git.tuffraid.net/cowch/lst_v3/commits/{{hash}}",
|
||||||
"compareUrlFormat": "https://git.tuffraid.net/cowch/lst/compare/{{previousTag}}...{{currentTag}}",
|
"compareUrlFormat": "https://git.tuffraid.net/cowch/lst_v3/compare/{{previousTag}}...{{currentTag}}",
|
||||||
"header": "# All Changes to LST can be found below.\n"
|
"header": "# All Changes to LST can be found below.\n"
|
||||||
}
|
}
|
||||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"editor.defaultFormatter": "biomejs.biome",
|
"editor.defaultFormatter": "biomejs.biome",
|
||||||
"workbench.colorTheme": "Default Dark+",
|
"workbench.colorTheme": "Dark+",
|
||||||
"terminal.integrated.env.windows": {},
|
"terminal.integrated.env.windows": {},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"typescript.preferences.importModuleSpecifier": "relative",
|
"typescript.preferences.importModuleSpecifier": "relative",
|
||||||
@@ -54,8 +54,10 @@
|
|||||||
"alpla",
|
"alpla",
|
||||||
"alplamart",
|
"alplamart",
|
||||||
"alplaprod",
|
"alplaprod",
|
||||||
|
"alplapurchase",
|
||||||
"bookin",
|
"bookin",
|
||||||
"Datamart",
|
"Datamart",
|
||||||
|
"dotenvx",
|
||||||
"dyco",
|
"dyco",
|
||||||
"intiallally",
|
"intiallally",
|
||||||
"manadatory",
|
"manadatory",
|
||||||
@@ -63,12 +65,14 @@
|
|||||||
"onnotice",
|
"onnotice",
|
||||||
"opendock",
|
"opendock",
|
||||||
"opendocks",
|
"opendocks",
|
||||||
|
"palletizer",
|
||||||
"ppoo",
|
"ppoo",
|
||||||
"preseed",
|
"preseed",
|
||||||
"prodlabels",
|
"prodlabels",
|
||||||
"prolink",
|
"prolink",
|
||||||
"Skelly",
|
"Skelly",
|
||||||
"trycatch"
|
"trycatch",
|
||||||
|
"whse"
|
||||||
],
|
],
|
||||||
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
|
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
|
||||||
"gitea.instanceURL": "https://git.tuffraid.net",
|
"gitea.instanceURL": "https://git.tuffraid.net",
|
||||||
|
|||||||
401
CHANGELOG.md
401
CHANGELOG.md
@@ -1,14 +1,399 @@
|
|||||||
# lst_v3
|
# All Changes to LST can be found below.
|
||||||
|
|
||||||
## 1.0.2-alpha.0
|
## [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)
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- build stuff
|
### ⚠ BREAKING CHANGES
|
||||||
- external url added for docker
|
|
||||||
|
|
||||||
## 1.0.1
|
* **build:** gives a rabbit hole error
|
||||||
|
|
||||||
### Patch Changes
|
### 🌟 Enhancements
|
||||||
|
|
||||||
- cf18e94: core stuff
|
* **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)
|
||||||
|
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **puchase hist:** finished up purhcase historical / gp updates ([a691dc2](https://git.tuffraid.net/cowch/lst_v3/commits/a691dc276e8650c669409241f73d7b2d7a1f9176))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **gp connect:** gp connect as was added to long live services ([635635b](https://git.tuffraid.net/cowch/lst_v3/commits/635635b356e1262e1c0b063408fe2209e6a8d4ec))
|
||||||
|
* **reprints:** changes the module and submodule around to be more accurate ([97f93a1](https://git.tuffraid.net/cowch/lst_v3/commits/97f93a1830761437118863372108df810ce9977a))
|
||||||
|
* **send email:** changes the error message to show the true message in the error ([995b1dd](https://git.tuffraid.net/cowch/lst_v3/commits/995b1dda7cdfebf4367d301ccac38fd339fab6dd))
|
||||||
|
|
||||||
|
## [0.0.1-alpha.2](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.1...v0.0.1-alpha.2) (2026-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project Builds
|
||||||
|
|
||||||
|
* **release:** docker and release corrections ([103ae77](https://git.tuffraid.net/cowch/lst_v3/commits/103ae77e9f82fc008a8ae143b6feccc3ce802f8c))
|
||||||
|
|
||||||
|
## [0.0.1-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.0...v0.0.1-alpha.1) (2026-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
* **notifcaion:** style changes to the notificaion card and started the table ([7d6c2db](https://git.tuffraid.net/cowch/lst_v3/commits/7d6c2db89cae1f137f126f5814dccd373f7ccb76))
|
||||||
|
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **notification:** base notifcaiton sub and admin compelted ([5865ac3](https://git.tuffraid.net/cowch/lst_v3/commits/5865ac3b99d60005c4245740369b0e0789c8fbbd))
|
||||||
|
* **notification:** reprint added ([a17787e](https://git.tuffraid.net/cowch/lst_v3/commits/a17787e85217f1fa4a5e5389e29c33ec09c286c5))
|
||||||
|
* **puchase history:** purhcase history changed to long running no notification ([34b0aba](https://git.tuffraid.net/cowch/lst_v3/commits/34b0abac36f645d0fe5f508881ddbef81ff04b7c))
|
||||||
|
* **purchase:** historical data capture for alpla purchase ([42861cc](https://git.tuffraid.net/cowch/lst_v3/commits/42861cc69e8d4aba5a9670aaed55417efda2b505))
|
||||||
|
* **user notifications:** added the ability for users to sub to notifications and add multi email ([637de85](https://git.tuffraid.net/cowch/lst_v3/commits/637de857f99499a41f7175181523f5d809d95d7e))
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **build:** issue with how i wrote the release token ([fe889ca](https://git.tuffraid.net/cowch/lst_v3/commits/fe889ca75731af08c42ec714b7f2abf17cd1ee40))
|
||||||
|
* **build:** type in how we pushed the header over ([83a94ca](https://git.tuffraid.net/cowch/lst_v3/commits/83a94cacf3fc87287cdc0c0cc861b339e72e4b83))
|
||||||
|
* **build:** typo ([860207a](https://git.tuffraid.net/cowch/lst_v3/commits/860207a60b6e04b15736cba631be6c7eab74d020))
|
||||||
|
* **i suck:** more learning experance ([9ceba8b](https://git.tuffraid.net/cowch/lst_v3/commits/9ceba8b5bba17959f27b16b28f50a83c044863fb))
|
||||||
|
* **lala:** something here ([17aed6c](https://git.tuffraid.net/cowch/lst_v3/commits/17aed6cb89f8220570f6c66f78dba6bb202c1aaa))
|
||||||
|
* **release:** typo that caused errors ([76747cf](https://git.tuffraid.net/cowch/lst_v3/commits/76747cf91738bd0d0530afcf7b4f51f0db11ca98))
|
||||||
|
* **typo:** more dam typos ([079478f](https://git.tuffraid.net/cowch/lst_v3/commits/079478f93217dea31c9a1e8ffed85d2381a6977d))
|
||||||
|
* **wrelease:** forgot to save ([3775760](https://git.tuffraid.net/cowch/lst_v3/commits/377576073449e95d315defb913dc317759cc3f43))
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Chore
|
||||||
|
|
||||||
|
* **release:** 0.1.0-alpha.10 ([98e408c](https://git.tuffraid.net/cowch/lst_v3/commits/98e408cb8577da18e24821b55474198439434f3e))
|
||||||
|
* **release:** 0.1.0-alpha.11 ([d6d5b45](https://git.tuffraid.net/cowch/lst_v3/commits/d6d5b451cd9aeba642ef94654ca20f4acd0b827c))
|
||||||
|
* **release:** 0.1.0-alpha.12 ([1ad789b](https://git.tuffraid.net/cowch/lst_v3/commits/1ad789b2b91a20a2f5a8dc9e6f39af2e19ec9cdc))
|
||||||
|
* **release:** 0.1.0-alpha.9 ([8f59bba](https://git.tuffraid.net/cowch/lst_v3/commits/8f59bba614a8eaa3105bb56f0db36013d5e68485))
|
||||||
|
* **release:** version packages ([fb2c560](https://git.tuffraid.net/cowch/lst_v3/commits/fb2c5609aa12ea7823783c364d5bd029c48a64bd))
|
||||||
|
* **release:** version packages ([b02b93b](https://git.tuffraid.net/cowch/lst_v3/commits/b02b93b83f488fbcee6d24db080ad0d1fe1c5f59))
|
||||||
|
* **release:** version packages ([2c0dbf9](https://git.tuffraid.net/cowch/lst_v3/commits/2c0dbf95c7b8dfd2c98b476d3f44bc8929668c88))
|
||||||
|
* **release:** version packages ([5c64600](https://git.tuffraid.net/cowch/lst_v3/commits/5c6460012aa70d336fbc9702240b4f19262a6b41))
|
||||||
|
* **release:** version packages ([0ce3790](https://git.tuffraid.net/cowch/lst_v3/commits/0ce3790675bc408762eafe76cbd5ab496fd06e73))
|
||||||
|
* **release:** version packages ([4caaf74](https://git.tuffraid.net/cowch/lst_v3/commits/4caaf745693d4df847aefd3721ac5d0ae792114a))
|
||||||
|
* **release:** version packages ([699c124](https://git.tuffraid.net/cowch/lst_v3/commits/699c124b0efba8282e436210619504bda8878e90))
|
||||||
|
* **release:** version packages ([c4fd74f](https://git.tuffraid.net/cowch/lst_v3/commits/c4fd74fc93226cffd9e39602f507a05cd8ea628b))
|
||||||
|
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
* **readme:** updated progress data ([92ba3ef](https://git.tuffraid.net/cowch/lst_v3/commits/92ba3ef5121afd0d82d4f40a5a985e1fdc081011))
|
||||||
|
* **sop:** added more info ([be1d408](https://git.tuffraid.net/cowch/lst_v3/commits/be1d4081e07b0982b355a270b7850a852a4398f5))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **build:** added in more info to the relase section ([5854889](https://git.tuffraid.net/cowch/lst_v3/commits/5854889eb5398feebda50a5d256ce7aec39ce112))
|
||||||
|
* **build:** changes to auto release when we cahnge version ([643d12f](https://git.tuffraid.net/cowch/lst_v3/commits/643d12ff182827e724e1569a583bd625a0d1dd0c))
|
||||||
|
* **build:** changes to the way we do release so it builds as well ([7d55c5f](https://git.tuffraid.net/cowch/lst_v3/commits/7d55c5f43173edb48d8709adcb972b7d8fbc3ebd))
|
||||||
|
* **changelog:** reverted back to commit-chagnelog, like more than changeset for solo dev ([ed052df](https://git.tuffraid.net/cowch/lst_v3/commits/ed052dff3c81a7064660a7d25685e0505065252c))
|
||||||
|
* **notification:** reprint - removed a console log as it shouldnt bc there ([5f3d683](https://git.tuffraid.net/cowch/lst_v3/commits/5f3d683a13c831229674166cced699e373131316))
|
||||||
|
* **notification:** select menu looks propper now ([74262be](https://git.tuffraid.net/cowch/lst_v3/commits/74262beb6596ddc971971cc9214a2688accf3a8e))
|
||||||
|
* **opendock refactor on how releases are posted:** this was a bug maybe just a better refactory ([0880298](https://git.tuffraid.net/cowch/lst_v3/commits/0880298cf53d83e487c706e73854e0874ae2d9da))
|
||||||
|
* **queries:** changed dev version to be 1500ms vs 5000ms ([f3b8dd9](https://git.tuffraid.net/cowch/lst_v3/commits/f3b8dd94e5ebae0cc4dd0a2689a19051942e94b8))
|
||||||
|
* **release:** changes to only have the changelog in the release ([6e85991](https://git.tuffraid.net/cowch/lst_v3/commits/6e8599106298ed13febd069d6fda8b354efb5b7b))
|
||||||
|
* **userprofile:** changes to have the table be blank and say nothing subscribed ([3ecf5fb](https://git.tuffraid.net/cowch/lst_v3/commits/3ecf5fb916d5dc1b1ffb224e2142d94f7a9cb126))
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project Builds
|
||||||
|
|
||||||
|
* **agent:** added westbend into the flow ([28c226d](https://git.tuffraid.net/cowch/lst_v3/commits/28c226ddbc37ab85cd6a9a6aec091def3e5623d6))
|
||||||
|
* **changelog:** reset the change log after all crap testing ([0059b9b](https://git.tuffraid.net/cowch/lst_v3/commits/0059b9b850c9647695a3fecaf5927c2e3ee7b192))
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -7,7 +7,7 @@
|
|||||||
Quick summary of current rewrite/migration goal.
|
Quick summary of current rewrite/migration goal.
|
||||||
|
|
||||||
- **Phase:** Backend rewrite
|
- **Phase:** Backend rewrite
|
||||||
- **Last updated:** 2024-05-01
|
- **Last updated:** 2026-05-27
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,21 +16,21 @@ Quick summary of current rewrite/migration goal.
|
|||||||
| Feature | Description | Status |
|
| Feature | Description | Status |
|
||||||
|----------|--------------|--------|
|
|----------|--------------|--------|
|
||||||
| User Authentication | ~~Login~~, ~~Signup~~, API Key | 🟨 In Progress |
|
| User Authentication | ~~Login~~, ~~Signup~~, API Key | 🟨 In Progress |
|
||||||
| User Profile | Edit profile, upload avatar | ⏳ Not Started |
|
| User Profile | ~~Edit profile~~, upload avatar | 🟨 In Progress |
|
||||||
| User Admin | Edit user, create user, remove user, alplaprod user integration | ⏳ Not Started |
|
| User Admin | ~~Edit user~~, ~~create user~~, remove user, alplaprod user integration | ⏳ Not Started |
|
||||||
| Notifications | Subscribe, Create, Update, Remove, Manual Trigger | ⏳ 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 |
|
| Frontend | Analytics and charts | ⏳ Not Started |
|
||||||
| Docs | Instructions and trouble shooting | ⏳ Not Started |
|
| Docs | Instructions and trouble shooting | ⏳ Not Started |
|
||||||
| One Click Print | Get printers, monitor printers, label process, material process, Special processes | ⏳ Not Started |
|
| One Click Print | ~~Get printers~~, monitor printers, label process, material process, Special processes | 🟨 In Progress |
|
||||||
| Silo Adjustments | Create, History, Comments | ⏳ Not Started |
|
| Silo Adjustments | Create, History, Comments | ⏳ Not Started |
|
||||||
| Demand Management | Orders, Forecast, Special Mappings, Create trucks, Load Trucks (tablet scanning) | ⏳ Not Started |
|
| Demand Management | Orders, Forecast, Special Mappings, Create trucks, Load Trucks (tablet scanning) | ⏳ Not Started |
|
||||||
| Open Docks | Integrations | ⏳ Not Started |
|
| Open Docks | Integrations | 🟨 In Progress |
|
||||||
| Transport Insight | Integrations | ⏳ Not Started |
|
| Transport Insight | Integrations | ⏳ Not Started |
|
||||||
| Quality Request Tool | Add Pallet, Monitor for moved, status changes, alerts | ⏳ Not Started |
|
| Quality Request Tool | Add Pallet, Monitor for moved, status changes, alerts | ⏳ Not Started |
|
||||||
| Logistics | Consume material, return and print, label info, relocate | ⏳ Not Started |
|
| Logistics | Consume material, return and print, label info, relocate | ⏳ Not Started |
|
||||||
| EOM | Endpoints, Report Pull for finance | ⏳ Not Started |
|
| EOM | Endpoints, Report Pull for finance | ⏳ Not Started |
|
||||||
| OCME | Custom integration | ⏳ Not Started |
|
| ~~OCME~~ | ~~Custom integration~~ | Canceled |
|
||||||
| API Migration | Moving to new REST endpoints | 🔧 In Progress |
|
| API Migration | Moving to new REST endpoints | 🔧 In Progress |
|
||||||
| System | Tests,Builds, Updates, Remote Logging, DB Backups, Alerting | ⏳ Not Started |
|
| System | Tests,Builds, Updates, Remote Logging, DB Backups, Alerting | ⏳ Not Started |
|
||||||
|
|
||||||
@@ -44,7 +44,16 @@ _Status legend:_
|
|||||||
How to run the current version of the app.
|
How to run the current version of the app.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/youruser/yourrepo.git
|
git clone https://git.tuffraid.net/cowch/lst_v3.git
|
||||||
cd yourrepo
|
cd lst_v3
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
```
|
||||||
|
|
||||||
|
Rename the .env-example to .env
|
||||||
|
|
||||||
|
Update all the fields
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:db:migrate
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
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 { toNodeHandler } from "better-auth/node";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
|
import { umamiConfig } from "./configs/umami.config.js";
|
||||||
import { createLogger } from "./logger/logger.controller.js";
|
import { createLogger } from "./logger/logger.controller.js";
|
||||||
|
import { routeHitMiddleware } from "./middleware/routeHit.middleware.js";
|
||||||
import { setupRoutes } from "./routeHandler.routes.js";
|
import { setupRoutes } from "./routeHandler.routes.js";
|
||||||
import { auth } from "./utils/auth.utils.js";
|
import { auth } from "./utils/auth.utils.js";
|
||||||
import { lstCors } from "./utils/cors.utils.js";
|
import { lstCors } from "./utils/cors.utils.js";
|
||||||
@@ -26,19 +28,38 @@ const createApp = async () => {
|
|||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
// well leave this active so we can monitor it to validate
|
// well leave this active so we can monitor it to validate
|
||||||
app.use(morgan("tiny"));
|
app.use(morgan("dev"));
|
||||||
app.set("trust proxy", true);
|
app.set("trust proxy", true);
|
||||||
app.use(lstCors());
|
app.use(lstCors());
|
||||||
|
app.use(routeHitMiddleware);
|
||||||
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
||||||
app.use(express.json());
|
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 ?? "")},
|
||||||
|
timezone: ${JSON.stringify(process.env.TIMEZONE ?? "America/Chicago")}
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
setupRoutes(baseUrl, app);
|
setupRoutes(baseUrl, app);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
baseUrl + "/app",
|
`${baseUrl}/app`,
|
||||||
express.static(join(__dirname, "../frontend/dist")),
|
express.static(join(__dirname, "../frontend/dist")),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(baseUrl + "/app/*splat", (_, res) => {
|
app.get(`${baseUrl}/app/*splat`, (_, res) => {
|
||||||
res.sendFile(join(__dirname, "../frontend/dist/index.html"));
|
res.sendFile(join(__dirname, "../frontend/dist/index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
|
|
||||||
import login from "./login.route.js";
|
import login from "./login.route.js";
|
||||||
import register from "./register.route.js";
|
import register from "./register.route.js";
|
||||||
|
|
||||||
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
|
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
|
||||||
//setup all the routes
|
//setup all the routes
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/authentication/login`, login);
|
app.use(`${baseUrl}/api/authentication/login`, login);
|
||||||
app.use(`${baseUrl}/api/authentication/register`, register);
|
app.use(`${baseUrl}/api/authentication/register`, register);
|
||||||
};
|
};
|
||||||
|
|||||||
29
backend/configs/gpSql.config.ts
Normal file
29
backend/configs/gpSql.config.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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: `${process.env.GP_SERVER ?? "USMCD1VMS011"}`,
|
||||||
|
port: port,
|
||||||
|
database: `ALPLA`,
|
||||||
|
user: username,
|
||||||
|
password: password,
|
||||||
|
options: {
|
||||||
|
encrypt: true,
|
||||||
|
trustServerCertificate: true,
|
||||||
|
},
|
||||||
|
requestTimeout: 90000, // how long until we kill the query and fail it
|
||||||
|
pool: {
|
||||||
|
max: 20, // Maximum number of connections in the pool
|
||||||
|
min: 0, // Minimum number of connections in the pool
|
||||||
|
idleTimeoutMillis: 10000, // How long a connection is allowed to be idle before being released
|
||||||
|
reapIntervalMillis: 1000, // how often to check for idle resources to destroy
|
||||||
|
acquireTimeoutMillis: 100000, // How long until a complete timeout happens
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
import type sql from "mssql";
|
import type sql from "mssql";
|
||||||
|
|
||||||
|
const port = process.env.SQL_PORT
|
||||||
|
? Number.parseInt(process.env.SQL_PORT, 10)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export const prodSqlConfig: sql.config = {
|
export const prodSqlConfig: sql.config = {
|
||||||
server: `${process.env.PROD_SERVER}`,
|
server: `${process.env.PROD_SERVER}`,
|
||||||
database: `AlplaPROD_${process.env.PROD_PLANT_TOKEN}_cus`,
|
database: `AlplaPROD_${process.env.PROD_PLANT_TOKEN}_cus`,
|
||||||
|
port: port,
|
||||||
user: process.env.PROD_USER,
|
user: process.env.PROD_USER,
|
||||||
password: process.env.PROD_PASSWORD,
|
password: process.env.PROD_PASSWORD,
|
||||||
options: {
|
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
|
* 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 { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
import {
|
import {
|
||||||
type SqlQuery,
|
type SqlQuery,
|
||||||
@@ -22,37 +26,125 @@ import { returnFunc } from "../utils/returnHelper.utils.js";
|
|||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
import { datamartData } from "./datamartData.utlis.js";
|
import { datamartData } from "./datamartData.utlis.js";
|
||||||
|
|
||||||
type Options = {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
type Data = {
|
type Data = {
|
||||||
name: string;
|
name: string;
|
||||||
options: Options;
|
options: any;
|
||||||
optionsRequired?: boolean;
|
optionsRequired?: boolean;
|
||||||
howManyOptionsRequired?: number;
|
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) => {
|
export const runDatamartQuery = async (data: Data) => {
|
||||||
// search the query db for the query by name
|
// 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 getDataMartInfo = datamartData.filter((x) => x.endpoint === data.name);
|
||||||
|
|
||||||
// const optionsMissing =
|
// const optionsMissing =
|
||||||
// !data.options || Object.keys(data.options).length === 0;
|
// !data.options || Object.keys(data.options).length === 0;
|
||||||
|
|
||||||
const optionCount =
|
const isValid =
|
||||||
Object.keys(data.options).length ===
|
Object.keys(data.options ?? {}).length >=
|
||||||
getDataMartInfo[0]?.howManyOptionsRequired;
|
(getDataMartInfo[0]?.howManyOptionsRequired ?? 0);
|
||||||
|
|
||||||
if (getDataMartInfo[0]?.optionsRequired && !optionCount) {
|
if (getDataMartInfo[0]?.optionsRequired && !isValid) {
|
||||||
return returnFunc({
|
return returnFunc({
|
||||||
success: false,
|
success: false,
|
||||||
level: "error",
|
level: "error",
|
||||||
module: "datamart",
|
module: "datamart",
|
||||||
subModule: "query",
|
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],
|
data: [getDataMartInfo[0].options],
|
||||||
notify: false,
|
notify: false,
|
||||||
});
|
});
|
||||||
@@ -75,10 +167,130 @@ export const runDatamartQuery = async (data: Data) => {
|
|||||||
|
|
||||||
// split the criteria by "," then and then update the query
|
// split the criteria by "," then and then update the query
|
||||||
if (data.options) {
|
if (data.options) {
|
||||||
Object.entries(data.options ?? {}).forEach(([key, value]) => {
|
switch (data.name) {
|
||||||
const pattern = new RegExp(`\\[${key.trim()}\\]`, "g");
|
case "activeArticles":
|
||||||
datamartQuery = datamartQuery.replace(pattern, String(value).trim());
|
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(
|
const { data: queryRun, error } = await tryCatch(
|
||||||
|
|||||||
@@ -10,14 +10,50 @@ export const datamartData = [
|
|||||||
name: "Active articles",
|
name: "Active articles",
|
||||||
endpoint: "activeArticles",
|
endpoint: "activeArticles",
|
||||||
description: "returns all active articles for the server with custom data",
|
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,
|
optionsRequired: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Delivery by date range",
|
name: "Delivery by date range",
|
||||||
endpoint: "deliveryByDateRange",
|
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()}`,
|
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", // set as a string and each item will be seperated by a , this way we can split it later in the excel file.
|
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,
|
optionsRequired: true,
|
||||||
howManyOptionsRequired: 2,
|
howManyOptionsRequired: 2,
|
||||||
},
|
},
|
||||||
|
|||||||
68
backend/datamart/getDatamart.route.test.ts
Normal file
68
backend/datamart/getDatamart.route.test.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import express from "express";
|
||||||
|
import request from "supertest";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("../db/db.controller.js", () => ({
|
||||||
|
db: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../logger/logger.controller.js", () => ({
|
||||||
|
createLogger: () => ({
|
||||||
|
info: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
debug: vi.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./datamart.controller.js", () => ({
|
||||||
|
runDatamartQuery: vi.fn(async ({ name, options }) => ({
|
||||||
|
success: true,
|
||||||
|
message: `Ran ${name}`,
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { runDatamartQuery } from "./datamart.controller.js";
|
||||||
|
import getDatamartRoute from "./getDatamart.route.js";
|
||||||
|
|
||||||
|
function createTestApp() {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
app.use("/datamart", getDatamartRoute);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("GET /datamart/:name", () => {
|
||||||
|
it("runs a datamart query by name and returns api response", async () => {
|
||||||
|
const app = createTestApp();
|
||||||
|
|
||||||
|
const res = await request(app).get("/datamart/orders").query({
|
||||||
|
value: "123",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
|
||||||
|
expect(runDatamartQuery).toHaveBeenCalledWith({
|
||||||
|
name: "orders",
|
||||||
|
options: {
|
||||||
|
value: "123",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.body.success).toBe(true);
|
||||||
|
expect(res.body.module).toBe("datamart");
|
||||||
|
expect(res.body.subModule).toBe("query");
|
||||||
|
expect(res.body.data).toEqual({
|
||||||
|
name: "orders",
|
||||||
|
options: {
|
||||||
|
value: "123",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
import postgres from "postgres";
|
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}`;
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
39
backend/db/schema/alplapurchase.schema.ts
Normal file
39
backend/db/schema/alplapurchase.schema.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
integer,
|
||||||
|
jsonb,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
export const alplaPurchaseHistory = pgTable("alpla_purchase_history", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
apo: integer("apo"),
|
||||||
|
revision: integer("revision"),
|
||||||
|
confirmed: integer("confirmed"),
|
||||||
|
status: integer("status"),
|
||||||
|
statusText: text("status_text"),
|
||||||
|
journalNum: integer("journal_num"),
|
||||||
|
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||||
|
add_user: text("add_user"),
|
||||||
|
upd_user: text("upd_user"),
|
||||||
|
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||||
|
remark: text("remark"),
|
||||||
|
approvedStatus: text("approved_status").default("new"),
|
||||||
|
position: jsonb("position").default([]),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const alplaPurchaseHistorySchema =
|
||||||
|
createSelectSchema(alplaPurchaseHistory);
|
||||||
|
export const newAlplaPurchaseHistorySchema =
|
||||||
|
createInsertSchema(alplaPurchaseHistory);
|
||||||
|
|
||||||
|
export type AlplaPurchaseHistory = z.infer<typeof alplaPurchaseHistorySchema>;
|
||||||
|
export type NewAlplaPurchaseHistory = z.infer<
|
||||||
|
typeof newAlplaPurchaseHistorySchema
|
||||||
|
>;
|
||||||
23
backend/db/schema/analytics.schema.ts
Normal file
23
backend/db/schema/analytics.schema.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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", { withTimezone: true })
|
||||||
|
.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"),
|
||||||
|
});
|
||||||
@@ -16,13 +16,13 @@ export const jobAuditLog = pgTable(
|
|||||||
id: uuid("id").defaultRandom().primaryKey(),
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
jobName: text("job_name"),
|
jobName: text("job_name"),
|
||||||
startedAt: timestamp("start_at"),
|
startedAt: timestamp("start_at"),
|
||||||
finishedAt: timestamp("finished_at"),
|
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
||||||
durationMs: integer("duration_ms"),
|
durationMs: integer("duration_ms"),
|
||||||
status: text("status"), //success | error
|
status: text("status"), //success | error
|
||||||
errorMessage: text("error_message"),
|
errorMessage: text("error_message"),
|
||||||
errorStack: text("error_stack"),
|
errorStack: text("error_stack"),
|
||||||
metadata: jsonb("meta_data"),
|
metadata: jsonb("meta_data"),
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||||
},
|
},
|
||||||
(table) => {
|
(table) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const user = pgTable("user", {
|
|||||||
emailVerified: boolean("email_verified").default(false).notNull(),
|
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||||
image: text("image"),
|
image: text("image"),
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updated_at")
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
||||||
.defaultNow()
|
.defaultNow()
|
||||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
.notNull(),
|
.notNull(),
|
||||||
|
|||||||
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", { withTimezone: true }).defaultNow(),
|
||||||
|
});
|
||||||
49
backend/db/schema/dailyAnalytics.schema.ts
Normal file
49
backend/db/schema/dailyAnalytics.schema.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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", { withTimezone: true }).notNull(),
|
||||||
|
lastHitAt: timestamp("last_hit_at", { withTimezone: true }).notNull(),
|
||||||
|
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
updatedAt: timestamp("updated_at", { withTimezone: true })
|
||||||
|
.defaultNow()
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
unique("analytics_daily_business_route_unique").on(
|
||||||
|
table.businessDate,
|
||||||
|
table.method,
|
||||||
|
table.routePattern,
|
||||||
|
table.module,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
@@ -18,9 +18,9 @@ export const datamart = pgTable("datamart", {
|
|||||||
active: boolean("active").default(true),
|
active: boolean("active").default(true),
|
||||||
options: text("options").default(""),
|
options: text("options").default(""),
|
||||||
public: boolean("public_access").default(false),
|
public: boolean("public_access").default(false),
|
||||||
add_date: timestamp("add_date").defaultNow(),
|
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||||
add_user: text("add_user").default("lst-system"),
|
add_user: text("add_user").default("lst-system"),
|
||||||
upd_date: timestamp("upd_date").defaultNow(),
|
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||||
upd_user: text("upd_user").default("lst-system"),
|
upd_user: text("upd_user").default("lst-system"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
22
backend/db/schema/dockdoor.schema.ts
Normal file
22
backend/db/schema/dockdoor.schema.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
export const dockDoorScanners = pgTable("dock_door_scanners", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
ip: text("ip").notNull(),
|
||||||
|
name: text("name").unique(),
|
||||||
|
dockId: text("dock_id"),
|
||||||
|
active: boolean("active").default(true),
|
||||||
|
currentLoadingOrder: text("current_loading_order").default(""),
|
||||||
|
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||||
|
add_user: text("add_user").default("lst-system"),
|
||||||
|
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||||
|
upd_user: text("upd_user").default("lst-system"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const dockDoorScannersSchema = createSelectSchema(dockDoorScanners);
|
||||||
|
export const newDockDoorScannersSchema = createInsertSchema(dockDoorScanners);
|
||||||
|
|
||||||
|
export type DockDoorScanners = z.infer<typeof dockDoorScannersSchema>;
|
||||||
|
export type NewDockDoorScanners = z.infer<typeof newDockDoorScannersSchema>;
|
||||||
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", { withTimezone: true }).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>;
|
||||||
@@ -18,7 +18,7 @@ export const logs = pgTable("logs", {
|
|||||||
stack: jsonb("stack").default([]),
|
stack: jsonb("stack").default([]),
|
||||||
checked: boolean("checked").default(false),
|
checked: boolean("checked").default(false),
|
||||||
hostname: text("hostname"),
|
hostname: text("hostname"),
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const logSchema = createSelectSchema(logs);
|
export const logSchema = createSelectSchema(logs);
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import {
|
|
||||||
integer,
|
|
||||||
jsonb,
|
|
||||||
pgTable,
|
|
||||||
text,
|
|
||||||
timestamp,
|
|
||||||
uuid,
|
|
||||||
} from "drizzle-orm/pg-core";
|
|
||||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
|
||||||
import type { z } from "zod";
|
|
||||||
|
|
||||||
export const opendockApt = pgTable("opendock_apt", {
|
|
||||||
id: uuid("id").defaultRandom().primaryKey(),
|
|
||||||
release: integer("release").unique(),
|
|
||||||
openDockAptId: text("open_dock_apt_id").notNull(),
|
|
||||||
appointment: jsonb("appointment").default([]),
|
|
||||||
upd_date: timestamp("upd_date").defaultNow(),
|
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const opendockAptSchema = createSelectSchema(opendockApt);
|
|
||||||
export const newOpendockAptSchema = createInsertSchema(opendockApt);
|
|
||||||
|
|
||||||
export type OpendockApt = z.infer<typeof opendockAptSchema>;
|
|
||||||
export type NewOpendockApt = z.infer<typeof newOpendockAptSchema>;
|
|
||||||
38
backend/db/schema/opendock_apt.schema.ts
Normal file
38
backend/db/schema/opendock_apt.schema.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
index,
|
||||||
|
integer,
|
||||||
|
jsonb,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
export const opendockApt = pgTable(
|
||||||
|
"opendock_apt",
|
||||||
|
{
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
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", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
},
|
||||||
|
(table) => ({
|
||||||
|
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
|
||||||
|
table.openDockAptId,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const opendockAptSchema = createSelectSchema(opendockApt);
|
||||||
|
export const newOpendockAptSchema = createInsertSchema(opendockApt);
|
||||||
|
|
||||||
|
export type OpendockApt = z.infer<typeof opendockAptSchema>;
|
||||||
|
export type NewOpendockApt = z.infer<typeof newOpendockAptSchema>;
|
||||||
50
backend/db/schema/opendock_articleSetup.ts
Normal file
50
backend/db/schema/opendock_articleSetup.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
upd_user: text("upd_user").notNull().default("lst-system"),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true })
|
||||||
|
.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
|
||||||
|
>;
|
||||||
25
backend/db/schema/opendock_docks.ts
Normal file
25
backend/db/schema/opendock_docks.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
upd_user: text("upd_user").notNull().default("lst-system"),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true })
|
||||||
|
.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(),
|
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", { withTimezone: true }).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", { withTimezone: true }).defaultNow(),
|
||||||
|
upd_date: timestamp("upd_date", { withTimezone: true }).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", { withTimezone: true }).defaultNow(),
|
||||||
|
upd_date: timestamp("upd_date", { withTimezone: true }).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", { withTimezone: true }).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", { withTimezone: true }).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>;
|
||||||
@@ -32,13 +32,13 @@ export const settings = pgTable(
|
|||||||
settingType: settingType(),
|
settingType: settingType(),
|
||||||
seedVersion: integer("seed_version").default(1), // this is intended for if we want to update the settings.
|
seedVersion: integer("seed_version").default(1), // this is intended for if we want to update the settings.
|
||||||
add_User: text("add_User").default("LST_System").notNull(),
|
add_User: text("add_User").default("LST_System").notNull(),
|
||||||
add_Date: timestamp("add_Date").defaultNow(),
|
add_Date: timestamp("add_Date", { withTimezone: true }).defaultNow(),
|
||||||
upd_user: text("upd_User").default("LST_System").notNull(),
|
upd_user: text("upd_User").default("LST_System").notNull(),
|
||||||
upd_date: timestamp("upd_date").defaultNow(),
|
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||||
},
|
},
|
||||||
(table) => [
|
(table) => [
|
||||||
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
// 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 {
|
||||||
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
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", {
|
export const appStats = pgTable("app_stats", {
|
||||||
id: text("id").primaryKey().default("serverStats"),
|
id: text("id").primaryKey().default("primary"),
|
||||||
build: integer("build").notNull().default(1),
|
currentBuild: integer("current_build").notNull().default(1),
|
||||||
lastUpdate: timestamp("last_update").defaultNow(),
|
lastBuildAt: timestamp("last_build_at", { withTimezone: true }),
|
||||||
|
lastDeployAt: timestamp("last_deploy_at", { withTimezone: true }),
|
||||||
|
building: boolean("building").notNull().default(false),
|
||||||
|
updating: boolean("updating").notNull().default(false),
|
||||||
|
lastUpdated: timestamp("last_updated", { withTimezone: true }).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>;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { addDays, subDays } from "date-fns";
|
||||||
|
import { format } from "date-fns-tz";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.get("/", async (_, res) => {
|
||||||
|
const orders = await runProdApi({
|
||||||
|
method: "post",
|
||||||
|
endpoint: "/public/v1.0/OutboundDeliveries/LoadingOrders/Search",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
loadingDateFrom: format(subDays(new Date(Date.now()), 3), "yyyy-MM-dd"),
|
||||||
|
loadingDateTo: format(addDays(new Date(Date.now()), 3), "yyyy-MM-dd"),
|
||||||
|
states: [
|
||||||
|
1, // planned
|
||||||
|
],
|
||||||
|
//isCommissioned: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "current Active loading orders",
|
||||||
|
message: `Current active loading loaders.`,
|
||||||
|
data: orders?.data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
70
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
70
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import z from "zod";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
const endLoading = z.object({
|
||||||
|
loadingOrder: z.string(),
|
||||||
|
dockId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
r.post("/", async (req, res) => {
|
||||||
|
// close the loading order
|
||||||
|
|
||||||
|
// clear the loading order off the dock
|
||||||
|
|
||||||
|
try {
|
||||||
|
const validated = endLoading.parse(req.body);
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(dockDoorScanners)
|
||||||
|
.set({
|
||||||
|
currentLoadingOrder: "",
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
upd_user: req.user?.username,
|
||||||
|
})
|
||||||
|
.where(eq(dockDoorScanners.dockId, validated.dockId))
|
||||||
|
.returning(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingOrder",
|
||||||
|
message: `Failed to updating the dock.`,
|
||||||
|
data: (error as any) ?? [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingOrder",
|
||||||
|
message: `Loading order ${validated.loadingOrder} was just closed.`,
|
||||||
|
data: data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingOrder",
|
||||||
|
message: `Failed to start loading order.`,
|
||||||
|
data: (error as any) ?? [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
25
backend/dockdoorScanning/dockdoor.loadUnits.route.ts
Normal file
25
backend/dockdoorScanning/dockdoor.loadUnits.route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import loadUnit from "./dockdoor.loadUnits.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.post("/", async (req, res) => {
|
||||||
|
const unit = await loadUnit({
|
||||||
|
dockId: req.body.dockId,
|
||||||
|
runningNo: req.body.runningNo,
|
||||||
|
});
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: unit.success,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingUnit",
|
||||||
|
message: unit.message,
|
||||||
|
data: unit?.data ?? [],
|
||||||
|
status: unit.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
143
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
143
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// sends the units from the dock door scanner here.
|
||||||
|
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||||
|
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||||
|
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||||
|
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
// validate we are active
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
dockId?: string;
|
||||||
|
sscc?: string;
|
||||||
|
runningNo?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadUnit = async (data: Data) => {
|
||||||
|
// are we even active at this time?
|
||||||
|
const dockDoorActive = await db.query.settings.findFirst({
|
||||||
|
where: (u, { eq }) => eq(u.name, "dockDoorScanning"),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dockDoorActive?.active) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadunit",
|
||||||
|
message: "Dock door scanning feature is not active.",
|
||||||
|
data: [],
|
||||||
|
notify: false,
|
||||||
|
room: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// check if its a valids an sscc
|
||||||
|
|
||||||
|
if (data.sscc === "noread") {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadUnit",
|
||||||
|
message:
|
||||||
|
"Failed to load the unit to the truck, there was no pallet read.",
|
||||||
|
data: [],
|
||||||
|
notify: false,
|
||||||
|
room: `dockDoorLoading:${data.dockId}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we currently have a loading order attached to the dock door.
|
||||||
|
const dock = await db
|
||||||
|
.select()
|
||||||
|
.from(dockDoorScanners)
|
||||||
|
.where(eq(dockDoorScanners.dockId, data.dockId as string));
|
||||||
|
|
||||||
|
if (dock[0]?.currentLoadingOrder === "") {
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingOrders",
|
||||||
|
message:
|
||||||
|
"There are know current active loading orders please start one and try again.",
|
||||||
|
data: [],
|
||||||
|
notify: false,
|
||||||
|
room: `dockDoorLoading:${data.dockId}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: pallet validation, check if we are on hold, then check if we have been in the staging warehouse for more than x time.
|
||||||
|
|
||||||
|
// if on hold stop the scan and send a bad read with the reason its on hold and what its on hold for, including coa.
|
||||||
|
|
||||||
|
// if precheck is active then check if we have a warehouse, then check if the pallet was in the warehouse for greater than the define min, all fails send a warning and still do the scan
|
||||||
|
|
||||||
|
// add the loading units
|
||||||
|
try {
|
||||||
|
const unitToScan = data.sscc
|
||||||
|
? { sscc: data.sscc?.slice(2) }
|
||||||
|
: { runningNo: Number(data.runningNo) };
|
||||||
|
|
||||||
|
const prod = (await runProdApi({
|
||||||
|
method: "post",
|
||||||
|
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`,
|
||||||
|
data: [unitToScan],
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
//emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data ?? []);
|
||||||
|
|
||||||
|
if (!prod?.success) {
|
||||||
|
emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data.errors[0]);
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadUnit",
|
||||||
|
message: `Unit encountered an error while loading`,
|
||||||
|
data: prod?.data.errors[0] as any,
|
||||||
|
notify: false,
|
||||||
|
//room: `dockDoorLoading:${data.dockId}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const emitData = {
|
||||||
|
message: `The unit ${prod.data.message.messageParams.runningNo} was loaded.`,
|
||||||
|
data: prod.data,
|
||||||
|
code: 0,
|
||||||
|
};
|
||||||
|
emitToRoom(`dockDoorLoading:${data.dockId}`, emitData as any);
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadUnit",
|
||||||
|
message: `Unit added to loading order`,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
message: `The unit ${prod.data.message.messageParams.runningNo} was loaded.`,
|
||||||
|
data: prod.data,
|
||||||
|
code: 0,
|
||||||
|
},
|
||||||
|
] as any,
|
||||||
|
notify: false,
|
||||||
|
//room: `dockDoorLoading:${data.dockId}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadUnit",
|
||||||
|
message: `Failed to load unit`,
|
||||||
|
data: error as any,
|
||||||
|
notify: false,
|
||||||
|
room: `dockDoorLoading:${data.dockId}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default loadUnit;
|
||||||
48
backend/dockdoorScanning/dockdoor.routes.ts
Normal file
48
backend/dockdoorScanning/dockdoor.routes.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import type { Express } from "express";
|
||||||
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
|
import activeLoadingOrders from "./dockdoor.activeLoadingOrders.route.js";
|
||||||
|
import closeLoadingOrder from "./dockdoor.closeLoadingOrder.route.js";
|
||||||
|
import load from "./dockdoor.loadUnits.route.js";
|
||||||
|
import startLoad from "./dockdoor.startLoad.route.js";
|
||||||
|
import prodDocks from "./dockdoors.docks.route.js";
|
||||||
|
import docks from "./dockdoors.route.js";
|
||||||
|
|
||||||
|
export const setupDockDoorRoutes = (baseUrl: string, app: Express) => {
|
||||||
|
//stats will be like this as we dont need to change this
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/dockDoor/scanners`,
|
||||||
|
featureCheck("dockDoorScanning"),
|
||||||
|
|
||||||
|
docks,
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/dockDoor/finishOrder`,
|
||||||
|
featureCheck("dockDoorScanning"),
|
||||||
|
closeLoadingOrder,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/dockDoor/activeLoadingOrders`,
|
||||||
|
featureCheck("dockDoorScanning"),
|
||||||
|
activeLoadingOrders,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/dockDoor/startLoad`,
|
||||||
|
featureCheck("dockDoorScanning"),
|
||||||
|
startLoad,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/dockDoor/docks`,
|
||||||
|
featureCheck("dockDoorScanning"),
|
||||||
|
prodDocks,
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/dockDoor/loadUnit`,
|
||||||
|
featureCheck("dockDoorScanning"),
|
||||||
|
load,
|
||||||
|
);
|
||||||
|
|
||||||
|
// all other system should be under /api/system/*
|
||||||
|
};
|
||||||
66
backend/dockdoorScanning/dockdoor.startLoad.route.ts
Normal file
66
backend/dockdoorScanning/dockdoor.startLoad.route.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import z from "zod";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
const startLoading = z.object({
|
||||||
|
loadingOrder: z.string(),
|
||||||
|
dockId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
r.post("/", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const validated = startLoading.parse(req.body);
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(dockDoorScanners)
|
||||||
|
.set({
|
||||||
|
currentLoadingOrder: validated.loadingOrder,
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
upd_user: req.user?.username,
|
||||||
|
})
|
||||||
|
.where(eq(dockDoorScanners.dockId, validated.dockId))
|
||||||
|
.returning(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingOrder",
|
||||||
|
message: `Failed to updating the dock.`,
|
||||||
|
data: (error as any) ?? [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingOrder",
|
||||||
|
message: `Loading order ${validated.loadingOrder} was just added to dockId ${validated.dockId}.`,
|
||||||
|
data: data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "loadingOrder",
|
||||||
|
message: `Failed to start loading order.`,
|
||||||
|
data: (error as any) ?? [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
54
backend/dockdoorScanning/dockdoors.docks.route.ts
Normal file
54
backend/dockdoorScanning/dockdoors.docks.route.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.get("/", async (_, res) => {
|
||||||
|
const activeDocks = sqlQuerySelector(`outbound.docks`) as SqlQuery;
|
||||||
|
|
||||||
|
if (!activeDocks.success) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "docks",
|
||||||
|
message: `There was an error getting the docks query.`,
|
||||||
|
data: [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
prodQuery(activeDocks.query, "Current Active Docks"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "newDock",
|
||||||
|
message: `There was an error getting the docks.`,
|
||||||
|
data: (error as any) ?? ([] as any),
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "docks",
|
||||||
|
message: `Current active docks.`,
|
||||||
|
data: (data.data as any) ?? ([] as any),
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
76
backend/dockdoorScanning/dockdoors.route.ts
Normal file
76
backend/dockdoorScanning/dockdoors.route.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import z from "zod";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
const newDockScanner = z.object({
|
||||||
|
ip: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
dockId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
r.get("/", async (_, res) => {
|
||||||
|
try {
|
||||||
|
const docks = await db
|
||||||
|
.select()
|
||||||
|
.from(dockDoorScanners)
|
||||||
|
.orderBy(dockDoorScanners.name);
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "lane check",
|
||||||
|
message: `All dock Doors.`,
|
||||||
|
data: docks ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "newDock",
|
||||||
|
message: `There was an error adding in the new dock.`,
|
||||||
|
data: error ?? ([] as any),
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
r.post("/", requireAuth, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const validated = newDockScanner.parse(req.body);
|
||||||
|
|
||||||
|
const newDock = await db
|
||||||
|
.insert(dockDoorScanners)
|
||||||
|
.values(validated)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "lane check",
|
||||||
|
message: `${validated.name} was just added.`,
|
||||||
|
data: newDock ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "dockdoor",
|
||||||
|
subModule: "newDock",
|
||||||
|
message: `There was an error adding in the new dock.`,
|
||||||
|
data: error ?? ([] as any),
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
17
backend/gpSql/gpSql.routes.ts
Normal file
17
backend/gpSql/gpSql.routes.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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";
|
||||||
|
export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
|
||||||
|
//setup all the routes
|
||||||
|
// Apply auth to entire router
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use(start);
|
||||||
|
router.use(stop);
|
||||||
|
router.use(restart);
|
||||||
|
|
||||||
|
app.use(`${baseUrl}/api/system/gpSql`, requireAuth, router);
|
||||||
|
};
|
||||||
153
backend/gpSql/gpSqlConnection.controller.ts
Normal file
153
backend/gpSql/gpSqlConnection.controller.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import sql from "mssql";
|
||||||
|
import { gpSqlConfig } from "../configs/gpSql.config.js";
|
||||||
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { checkHostnamePort } from "../utils/checkHost.utils.js";
|
||||||
|
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(
|
||||||
|
`${process.env.GP_SERVER ?? "usmcd1vms011"}:1433`,
|
||||||
|
);
|
||||||
|
if (!serverUp) {
|
||||||
|
// we will try to reconnect
|
||||||
|
connected = false;
|
||||||
|
reconnectToSql;
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
message: "GP server is offline or unreachable.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are trying to click restart from the api for some reason we want to kick back and say no
|
||||||
|
if (connected) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
message: "The Sql server is already connected.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to connect to the sql server
|
||||||
|
try {
|
||||||
|
pool2 = new sql.ConnectionPool(gpSqlConfig);
|
||||||
|
await pool2.connect();
|
||||||
|
connected = true;
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
message: `${gpSqlConfig.server} is connected to ${gpSqlConfig.database}`,
|
||||||
|
data: [],
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
reconnectToSql;
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
message: "Failed to connect to the gp sql server.",
|
||||||
|
data: [error],
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closePool = async () => {
|
||||||
|
if (!connected) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
message: "There is no connection to the prod server currently.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool2.close();
|
||||||
|
connected = false;
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
message: "The sql connection has been closed.",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
connected = false;
|
||||||
|
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
message: "There was an error closing the sql connection",
|
||||||
|
data: [error],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const reconnectToSql = async () => {
|
||||||
|
const log = createLogger({
|
||||||
|
module: "system",
|
||||||
|
subModule: "db",
|
||||||
|
});
|
||||||
|
if (reconnecting) return;
|
||||||
|
|
||||||
|
//set reconnecting to true while we try to reconnect
|
||||||
|
reconnecting = true;
|
||||||
|
|
||||||
|
while (!connected && attempt < maxAttempts) {
|
||||||
|
attempt++;
|
||||||
|
log.info(
|
||||||
|
`Reconnect attempt ${attempt}/${maxAttempts} in ${delayStart / 1000}s ...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await new Promise((res) => setTimeout(res, delayStart));
|
||||||
|
|
||||||
|
const serverUp = await checkHostnamePort(
|
||||||
|
`${process.env.GP_SERVER ?? "usmcd1vms011"}:1433`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!serverUp) {
|
||||||
|
delayStart = Math.min(delayStart * 2, 30000); // exponential backoff until up to 30000
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pool2 = await sql.connect(gpSqlConfig);
|
||||||
|
reconnecting = false;
|
||||||
|
connected = true;
|
||||||
|
log.info(`${gpSqlConfig.server} is connected to ${gpSqlConfig.database}`);
|
||||||
|
} catch (error) {
|
||||||
|
delayStart = Math.min(delayStart * 2, 30000);
|
||||||
|
log.error({ error }, "Failed to reconnect to the prod sql server.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected && attempt >= maxAttempts) {
|
||||||
|
log.error(
|
||||||
|
{ notify: true },
|
||||||
|
"Max reconnect attempts reached on the prodSql server. Stopping retries.",
|
||||||
|
);
|
||||||
|
|
||||||
|
reconnecting = false;
|
||||||
|
// TODO: exit alert someone here
|
||||||
|
}
|
||||||
|
};
|
||||||
78
backend/gpSql/gpSqlQuery.controller.ts
Normal file
78
backend/gpSql/gpSqlQuery.controller.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
import { connected, pool2 } from "./gpSqlConnection.controller.js";
|
||||||
|
|
||||||
|
interface SqlError extends Error {
|
||||||
|
code?: string;
|
||||||
|
originalError?: {
|
||||||
|
info?: { message?: string };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a prod query
|
||||||
|
* just pass over the query as a string and the name of the query.
|
||||||
|
* Query should be like below.
|
||||||
|
* * select * from AlplaPROD_test1.dbo.table
|
||||||
|
* You must use test1 always as it will be changed via query
|
||||||
|
*/
|
||||||
|
export const gpQuery = async (queryToRun: string, name: string) => {
|
||||||
|
if (!connected) {
|
||||||
|
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
|
||||||
|
const query = queryToRun.replaceAll(
|
||||||
|
"test1",
|
||||||
|
`${process.env.PROD_PLANT_TOKEN}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await pool2.request().query(query);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Query results for: ${name}`,
|
||||||
|
data: result.recordset ?? [],
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const err = error as SqlError;
|
||||||
|
if (err.code === "ETIMEOUT") {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
module: "system",
|
||||||
|
subModule: "gpSql",
|
||||||
|
level: "error",
|
||||||
|
message: `${name} did not run due to a timeout.`,
|
||||||
|
notify: false,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === "EREQUEST") {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
module: "system",
|
||||||
|
subModule: "gpSql",
|
||||||
|
level: "error",
|
||||||
|
message: `${name} encountered an error ${err.originalError?.info?.message || "undefined error"}`,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
module: "system",
|
||||||
|
subModule: "gpSql",
|
||||||
|
level: "error",
|
||||||
|
message: `${name} encountered an unknown error.`,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
29
backend/gpSql/gpSqlQuerySelector.utils.ts
Normal file
29
backend/gpSql/gpSqlQuerySelector.utils.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
|
||||||
|
export type SqlGPQuery = {
|
||||||
|
query: string;
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sqlGpQuerySelector = (name: string) => {
|
||||||
|
try {
|
||||||
|
const queryFile = readFileSync(
|
||||||
|
new URL(`../gpSql/queries/${name}.sql`, import.meta.url),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Query for: ${name}`,
|
||||||
|
query: queryFile,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message:
|
||||||
|
"Error getting the query file, please make sure you have the correct name.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
23
backend/gpSql/gpSqlRestart.route.ts
Normal file
23
backend/gpSql/gpSqlRestart.route.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { closePool, connectGPSql } from "./gpSqlConnection.controller.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.post("/restart", async (_, res) => {
|
||||||
|
await closePool();
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
const connect = await connectGPSql();
|
||||||
|
apiReturn(res, {
|
||||||
|
success: connect.success,
|
||||||
|
level: connect.success ? "info" : "error",
|
||||||
|
module: "routes",
|
||||||
|
subModule: "prodSql",
|
||||||
|
message: "Sql Server has been restarted",
|
||||||
|
data: connect.data,
|
||||||
|
status: connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
export default r;
|
||||||
20
backend/gpSql/gpSqlStart.route.ts
Normal file
20
backend/gpSql/gpSqlStart.route.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { connectGPSql } from "./gpSqlConnection.controller.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.post("/start", async (_, res) => {
|
||||||
|
const connect = await connectGPSql();
|
||||||
|
apiReturn(res, {
|
||||||
|
success: connect.success,
|
||||||
|
level: connect.success ? "info" : "error",
|
||||||
|
module: "routes",
|
||||||
|
subModule: "prodSql",
|
||||||
|
message: connect.message,
|
||||||
|
data: connect.data,
|
||||||
|
status: connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
20
backend/gpSql/gpSqlStop.route.ts
Normal file
20
backend/gpSql/gpSqlStop.route.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { closePool } from "./gpSqlConnection.controller.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.post("/stop", async (_, res) => {
|
||||||
|
const connect = await closePool();
|
||||||
|
apiReturn(res, {
|
||||||
|
success: connect.success,
|
||||||
|
level: connect.success ? "info" : "error",
|
||||||
|
module: "routes",
|
||||||
|
subModule: "prodSql",
|
||||||
|
message: connect.message,
|
||||||
|
data: connect.data,
|
||||||
|
status: connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
39
backend/gpSql/queries/reqCheck.sql
Normal file
39
backend/gpSql/queries/reqCheck.sql
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
USE [ALPLA]
|
||||||
|
|
||||||
|
SELECT Distinct r.[POPRequisitionNumber] as req,
|
||||||
|
r.[ApprovalStatus] as approvalStatus,
|
||||||
|
r.[Requested By] requestedBy,
|
||||||
|
format(t.[Created Date], 'yyyy-MM-dd') as createdAt,
|
||||||
|
format(r.[Requisition Date], 'MM/dd/yyyy') as expectedDate,
|
||||||
|
r.[Requisition Amount] as glAccount,
|
||||||
|
case when r.[Account Segment 2] is null or r.[Account Segment 2] = '' then '999' else cast(r.[Account Segment 2] as varchar) end as plant
|
||||||
|
,t.Status as status
|
||||||
|
,t.[Document Status] as docStatus
|
||||||
|
,t.[Workflow Status] as reqState
|
||||||
|
,CASE
|
||||||
|
WHEN [Workflow Status] = 'Completed'
|
||||||
|
THEN 'Pending APO convertion'
|
||||||
|
WHEN [Workflow Status] = 'Pending User Action'
|
||||||
|
AND r.[ApprovalStatus] = 'Pending Approval'
|
||||||
|
THEN 'Pending plant approver'
|
||||||
|
WHEN [Workflow Status] = ''
|
||||||
|
AND r.[ApprovalStatus] = 'Not Submitted'
|
||||||
|
THEN 'Req not submited'
|
||||||
|
ELSE 'Unknown reason'
|
||||||
|
END AS approvedStatus
|
||||||
|
|
||||||
|
FROM [dbo].[PORequisitions] r (nolock)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
left join
|
||||||
|
[dbo].[PurchaseRequisitions] as t (nolock) on
|
||||||
|
t.[Requisition Number] = r.[POPRequisitionNumber]
|
||||||
|
|
||||||
|
|
||||||
|
--where ApprovalStatus = 'Pending Approval'
|
||||||
|
--and [Account Segment 2] = 80
|
||||||
|
|
||||||
|
where r.POPRequisitionNumber in ([reqsToCheck])
|
||||||
|
|
||||||
|
Order By r.POPRequisitionNumber
|
||||||
@@ -5,6 +5,7 @@ import { db } from "../db/db.controller.js";
|
|||||||
import { logs } from "../db/schema/logs.schema.js";
|
import { logs } from "../db/schema/logs.schema.js";
|
||||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
import { notifySystemIssue } from "./logger.notify.js";
|
||||||
//import build from "pino-abstract-transport";
|
//import build from "pino-abstract-transport";
|
||||||
|
|
||||||
export const logLevel = process.env.LOG_LEVEL || "info";
|
export const logLevel = process.env.LOG_LEVEL || "info";
|
||||||
@@ -36,7 +37,7 @@ const dbStream = new Writable({
|
|||||||
subModule: obj?.subModule?.toLowerCase(),
|
subModule: obj?.subModule?.toLowerCase(),
|
||||||
hostname: obj?.hostname?.toLowerCase(),
|
hostname: obj?.hostname?.toLowerCase(),
|
||||||
message: obj.msg,
|
message: obj.msg,
|
||||||
stack: obj?.stack,
|
stack: obj?.stack || obj?.error, // this will add in the error or stack depending on how we pass it.
|
||||||
})
|
})
|
||||||
.returning(),
|
.returning(),
|
||||||
);
|
);
|
||||||
@@ -45,6 +46,10 @@ const dbStream = new Writable({
|
|||||||
console.error(res.error);
|
console.error(res.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (obj.notify) {
|
||||||
|
notifySystemIssue(obj);
|
||||||
|
}
|
||||||
|
|
||||||
if (obj.room) {
|
if (obj.room) {
|
||||||
emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
||||||
}
|
}
|
||||||
|
|||||||
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: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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.user));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
const newLog = await db
|
||||||
|
.insert(scanLog)
|
||||||
|
.values({
|
||||||
|
scannerId: body.scannerId ?? "",
|
||||||
|
message: body.message ?? "",
|
||||||
|
prompt: body.prompt ?? "",
|
||||||
|
commandDescription: body.commandDescription ?? "",
|
||||||
|
status: body.status ?? "",
|
||||||
|
lines: body.lines ?? "",
|
||||||
|
user: body.user ?? "",
|
||||||
|
runningNumber: body.runningNumber ?? "",
|
||||||
|
scannerVersion: body.scannerVersion ?? "0",
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "scan logs",
|
||||||
|
message: `New log from ${body.scannerId}`,
|
||||||
|
data: newLog,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
113
backend/notification/notification.alplapurchase.ts
Normal file
113
backend/notification/notification.alplapurchase.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;
|
||||||
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;
|
||||||
@@ -1,10 +1,113 @@
|
|||||||
const reprint = (data: any, emails: string) => {
|
import { eq } from "drizzle-orm";
|
||||||
// TODO: do the actual logic for the notification.
|
import { db } from "../db/db.controller.js";
|
||||||
console.log(data);
|
import { notifications } from "../db/schema/notifications.schema.js";
|
||||||
console.log(emails);
|
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";
|
||||||
|
import { v2QueryRun } from "../utils/pgConnectToLst.utils.js";
|
||||||
|
|
||||||
// TODO send the error to systemAdmin users so they do not always need to be on the notifications.
|
let shutoffv1 = false
|
||||||
// these errors are defined per notification.
|
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
|
||||||
|
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 as any,
|
||||||
|
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 as any,
|
||||||
|
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: "notification",
|
||||||
|
subModule: "email",
|
||||||
|
message: `${l[0].name} failed to send the email`,
|
||||||
|
data: sentEmail?.data as any,
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reprint;
|
export default func;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
|
||||||
|
import manual from "./notification.manualTrigger.js";
|
||||||
import getNotifications from "./notification.route.js";
|
import getNotifications from "./notification.route.js";
|
||||||
import updateNote from "./notification.update.route.js";
|
import updateNote from "./notification.update.route.js";
|
||||||
import deleteSub from "./notificationSub.delete.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) => {
|
export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
|
||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
app.use(`${baseUrl}/api/notification`, requireAuth, getNotifications);
|
app.use(
|
||||||
app.use(`${baseUrl}/api/notification`, requireAuth, updateNote);
|
`${baseUrl}/api/notification`,
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, subs);
|
requireAuth,
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, newSub);
|
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, updateSub);
|
getNotifications,
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, deleteSub);
|
);
|
||||||
|
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/*
|
// all other system should be under /api/system/*
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { type Response, Router } from "express";
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||||
|
import { auth } from "../utils/auth.utils.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
import { modifiedNotification } from "./notification.controller.js";
|
import { modifiedNotification } from "./notification.controller.js";
|
||||||
|
|
||||||
const newSubscribe = z.object({
|
const newSubscribe = z.object({
|
||||||
emails: z.email().array().describe("An array of emails"),
|
|
||||||
userId: z.string().describe("User id."),
|
userId: z.string().describe("User id."),
|
||||||
notificationId: z.string().describe("Notification id"),
|
notificationId: z.string().describe("Notification id"),
|
||||||
});
|
});
|
||||||
@@ -16,14 +16,29 @@ const newSubscribe = z.object({
|
|||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.delete("/", async (req, res: Response) => {
|
r.delete("/", 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
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const validated = newSubscribe.parse(req.body);
|
const validated = newSubscribe.parse(req.body);
|
||||||
|
|
||||||
const { data, error } = await tryCatch(
|
const { data, error } = await tryCatch(
|
||||||
db
|
db
|
||||||
.delete(notificationSub)
|
.delete(notificationSub)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(notificationSub.userId, validated.userId),
|
eq(
|
||||||
|
notificationSub.userId,
|
||||||
|
hasPermissions ? validated.userId : (req?.user?.id ?? ""),
|
||||||
|
), // allows the admin to delete this
|
||||||
|
//eq(notificationSub.userId, req?.user?.id ?? ""),
|
||||||
eq(notificationSub.notificationId, validated.notificationId),
|
eq(notificationSub.notificationId, validated.notificationId),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -44,6 +59,18 @@ r.delete("/", async (req, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.length <= 0) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "info",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "post",
|
||||||
|
message: `Subscription was not deleted invalid data sent over`,
|
||||||
|
data: data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return apiReturn(res, {
|
return apiReturn(res, {
|
||||||
success: true,
|
success: true,
|
||||||
level: "info",
|
level: "info",
|
||||||
|
|||||||
@@ -21,12 +21,16 @@ r.get("/", async (req, res: Response) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
hasPermissions.success = false;
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await tryCatch(
|
const { data, error } = await tryCatch(
|
||||||
db
|
db
|
||||||
.select()
|
.select()
|
||||||
.from(notificationSub)
|
.from(notificationSub)
|
||||||
.where(
|
.where(
|
||||||
userId || !hasPermissions.success
|
!hasPermissions.success
|
||||||
? eq(notificationSub.userId, `${req?.user?.id ?? ""}`)
|
? eq(notificationSub.userId, `${req?.user?.id ?? ""}`)
|
||||||
: undefined,
|
: undefined,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -25,8 +25,25 @@ r.post("/", async (req, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
const validated = newSubscribe.parse(req.body);
|
const validated = newSubscribe.parse(req.body);
|
||||||
|
|
||||||
|
const emails = validated.emails
|
||||||
|
.map((e) => e.trim().toLowerCase())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const uniqueEmails = [...new Set(emails)];
|
||||||
|
|
||||||
const { data, error } = await tryCatch(
|
const { data, error } = await tryCatch(
|
||||||
db.insert(notificationSub).values(validated).returning(),
|
db
|
||||||
|
.insert(notificationSub)
|
||||||
|
.values({
|
||||||
|
userId: req?.user?.id ?? "",
|
||||||
|
notificationId: validated.notificationId,
|
||||||
|
emails: uniqueEmails,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: [notificationSub.userId, notificationSub.notificationId],
|
||||||
|
set: { emails: uniqueEmails },
|
||||||
|
})
|
||||||
|
.returning(),
|
||||||
);
|
);
|
||||||
|
|
||||||
await modifiedNotification(validated.notificationId);
|
await modifiedNotification(validated.notificationId);
|
||||||
|
|||||||
@@ -14,7 +14,27 @@ const note: NewNotification[] = [
|
|||||||
"Monitors the labels that are printed and returns a there data, if one falls withing the time frame.",
|
"Monitors the labels that are printed and returns a there data, if one falls withing the time frame.",
|
||||||
active: false,
|
active: false,
|
||||||
interval: "10",
|
interval: "10",
|
||||||
options: [{ prodID: 1 }],
|
options: [{ auditId: [0] }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "qualityBlocking",
|
||||||
|
description:
|
||||||
|
"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: [{ lastBlockingOrderIdSent: 1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "alplaPurchaseHistory",
|
||||||
|
description:
|
||||||
|
"Will check the alpla purchase data for any changes, if the req has not been sent already then we will send this, for a po or fresh order we will ignore. ",
|
||||||
|
active: false,
|
||||||
|
interval: "5",
|
||||||
|
options: [
|
||||||
|
{ sentReqs: [{ timeStamp: "0", req: 1, approved: false }] },
|
||||||
|
{ sentAPOs: [{ timeStamp: "0", apo: 1 }] },
|
||||||
|
{ sentRCT: [{ timeStamp: "0", rct: 1 }] },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,82 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "express";
|
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 { 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 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;
|
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, {
|
return apiReturn(res, {
|
||||||
success: true,
|
success: true,
|
||||||
level: "info",
|
level: "info",
|
||||||
module: "ocp",
|
module: "ocp",
|
||||||
subModule: "printing",
|
subModule: "printing",
|
||||||
message: `${printerName} just passed over a message`,
|
message: `${printerName} just sent a message`,
|
||||||
data: req.body ?? [],
|
data: req.body ?? [],
|
||||||
status: 200,
|
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...
|
* 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 printerManager = async () => {};
|
||||||
|
|
||||||
export const printerHeartBeat = 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 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;
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user