Compare commits
131 Commits
v0.0.2-alp
...
2ebf695526
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ebf695526 | |||
| 24af3ca403 | |||
| 6fbe3a9eed | |||
| 7dbc31c046 | |||
| ba09a77f29 | |||
| 3f04609f82 | |||
| 22a7b612e1 | |||
| 39db142db4 | |||
| d85f08cb19 | |||
| 15c939ebe8 | |||
| 9ff428f5ea | |||
| 2e460c7f8a | |||
| 7c4c5f980a | |||
| 86e1237509 | |||
| 706ab8b448 | |||
| 9440b44f3b | |||
| a2d9a6c127 | |||
| c0a7d4a125 | |||
| 8fcb2c66ed | |||
| 8fc3129f7d | |||
| 05c553e927 | |||
| 6eaae0f537 | |||
| 3ad84dab71 | |||
| 5a7046bacd | |||
| cfc497c1f2 | |||
| 7bbdd4e555 | |||
| e909e8deec | |||
| 4249e90307 | |||
| 2f495739e6 | |||
| 7d722c4aac | |||
| 5f2148f5f0 | |||
| 7671172d97 | |||
| 6dd1a5b332 | |||
| 865f280cfa | |||
| a717260b8d | |||
| 4464cea022 | |||
| 55418e2f09 | |||
| 5281118596 | |||
| 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 |
@@ -9,4 +9,4 @@ builds
|
||||
testFiles
|
||||
nssm.exe
|
||||
postgresql-17.9-2-windows-x64.exe
|
||||
VSCodeUserSetup-x64-1.112.0.msi
|
||||
VSCodeSetup-x64-1.120.0.msi
|
||||
66
.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:
|
||||
- name: Checkout (local)
|
||||
run: |
|
||||
git clone https://git.tuffraid.net/cowch/lst_v3.git .
|
||||
git clone http://10.75.9.150:3100/cowch/lst_v3.git .
|
||||
git checkout ${{ gitea.sha }}
|
||||
|
||||
- name: Login to registry
|
||||
run: echo "${{ secrets.PASSWORD }}" | docker login git.tuffraid.net -u "cowch" --password-stdin
|
||||
run: echo "${{ secrets.PASSWORD }}" | docker login 10.75.9.150:3100 -u "cowch" --password-stdin
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
docker build \
|
||||
-t git.tuffraid.net/cowch/lst_v3:latest \
|
||||
-t git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }} \
|
||||
-t 10.75.9.150:3100/cowch/lst_v3:latest \
|
||||
-t 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }} \
|
||||
.
|
||||
|
||||
- name: Push
|
||||
run: |
|
||||
docker push git.tuffraid.net/cowch/lst_v3:latest
|
||||
docker push git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }}
|
||||
docker push 10.75.9.150:3100/cowch/lst_v3:latest
|
||||
docker push 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }}
|
||||
@@ -14,12 +14,12 @@ jobs:
|
||||
# Examples:
|
||||
# http://gitea.internal.lan:3000
|
||||
# https://gitea-origin.yourdomain.local
|
||||
GITEA_INTERNAL_URL: "https://git.tuffraid.net"
|
||||
GITEA_INTERNAL_URL: "http://10.75.9.150:3100" #"https://git.tuffraid.net"
|
||||
|
||||
# Internal/origin registry host. Usually same host as above, but without protocol.
|
||||
# Example:
|
||||
# gitea.internal:3000
|
||||
REGISTRY_HOST: "git.tuffraid.net"
|
||||
REGISTRY_HOST: "10.75.9.150:3100" #"git.tuffraid.net"
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,8 +9,9 @@ downloads
|
||||
.scriptCreds
|
||||
node-v24.14.0-x64.msi
|
||||
postgresql-17.9-2-windows-x64.exe
|
||||
VSCodeUserSetup-x64-1.112.0.exe
|
||||
VSCodeSetup-x64-1.120.0.exe
|
||||
nssm.exe
|
||||
frontend/.tanstack
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
||||
259
CHANGELOG.md
259
CHANGELOG.md
@@ -1,5 +1,264 @@
|
||||
# All Changes to LST can be found below.
|
||||
|
||||
## [0.1.0-alpha.3](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) (2026-06-10)
|
||||
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **eom:** migrated eom endpoints from old version validated working ([e909e8d](https://git.tuffraid.net/cowch/lst_v3/commits/e909e8deecb54a3e4c39789609b0aa7435b9e08a))
|
||||
* **mobile:** added auto download of latest ([6d0fb8a](https://git.tuffraid.net/cowch/lst_v3/commits/6d0fb8aee45c8b5c56ccd7d8a010e1dc803408bf))
|
||||
* **mobile:** dock door scanning backend added ([fe0b157](https://git.tuffraid.net/cowch/lst_v3/commits/fe0b1573f3ba6fd220f181088b994588c52af139)), closes [#12](https://git.tuffraid.net/cowch/lst_v3/issues/12)
|
||||
* **opendock:** added delete button in the article tab ([4464cea](https://git.tuffraid.net/cowch/lst_v3/commits/4464cea022ba48744b884b83fef0fc3f3421dea5)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
* **opendock:** added in a new article link system ([bb7931d](https://git.tuffraid.net/cowch/lst_v3/commits/bb7931d6c8d0f5ce4065955491e9ee1247b5e92d))
|
||||
* **warehousing:** ppoo monitoring added ([8b07694](https://git.tuffraid.net/cowch/lst_v3/commits/8b076949a7f8e723bc87619f729082d2c1991b2d)), closes [#13](https://git.tuffraid.net/cowch/lst_v3/issues/13)
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **app:** type in the templates... they all looked the same ([2a648f6](https://git.tuffraid.net/cowch/lst_v3/commits/2a648f63064a300b0a2888bae3322b857af6a238))
|
||||
* **dock door scanning:** correction to how the data is posted ([2f49573](https://git.tuffraid.net/cowch/lst_v3/commits/2f495739e653c462eff7f10ff6343d9572f25563))
|
||||
* **dockscanner:** removed console log ([4249e90](https://git.tuffraid.net/cowch/lst_v3/commits/4249e90307ba1a1992753803e1dc3ab7dd7ac95e))
|
||||
* **eom:** removed un needed imports ([05c553e](https://git.tuffraid.net/cowch/lst_v3/commits/05c553e9279c6e8384d61073781bf915733b0ab5))
|
||||
* **logistics:** historical checks for no data errors when feature is activeed ([4f848bb](https://git.tuffraid.net/cowch/lst_v3/commits/4f848bb649f350c9d370daa09c6fc48f7b76e2e2))
|
||||
* **mobile users:** corrected and endpoint that prevented us from change the pin number ([347edb7](https://git.tuffraid.net/cowch/lst_v3/commits/347edb7078fb4ce959975cd968a6f026bacc98bf))
|
||||
* **mobile:** scan log incorrect user ref ([9c0ef1f](https://git.tuffraid.net/cowch/lst_v3/commits/9c0ef1f5dfa13e8f7e1f72d47d0d5842b3da3c87))
|
||||
* **mobile:** temp removed the autodownload as its causing issues ([3ef0f23](https://git.tuffraid.net/cowch/lst_v3/commits/3ef0f230ddbaef4c3d738cc531e7afee25b210dd))
|
||||
* **mobile:** ui over lapping ([db28635](https://git.tuffraid.net/cowch/lst_v3/commits/db28635c8c260d0f378e109755d32201acdb2328)), closes [#25](https://git.tuffraid.net/cowch/lst_v3/issues/25)
|
||||
* **notifications:** missed api converstion in the front end for updating ([5281118](https://git.tuffraid.net/cowch/lst_v3/commits/52811185965cb0fe4c9b42e73c447b67e917a5ca))
|
||||
* **opendock:** changed the header of delete to be properly named ([865f280](https://git.tuffraid.net/cowch/lst_v3/commits/865f280cfaf82a7126ca4b57d658aed50cf19a73)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
* **opendock:** correction to article link success on delete ([a717260](https://git.tuffraid.net/cowch/lst_v3/commits/a717260b8d5cf72534b055721de2b79901506875)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
* **opendock:** ref wrong field oops ([f635415](https://git.tuffraid.net/cowch/lst_v3/commits/f635415b751e11d0e7beb557d18b83915155428a))
|
||||
* **scanner:** corrected teh endpoint to delete the user if needed ([6dd1a5b](https://git.tuffraid.net/cowch/lst_v3/commits/6dd1a5b332a853f0e2c7226ac1f64e403ef752d7))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* **app:** updated last updated to the readme to show current progress ([78f7b8a](https://git.tuffraid.net/cowch/lst_v3/commits/78f7b8a179d078fc1b2740fd2bf4af71a3df8292))
|
||||
* **app:** updated readme ([1a3d8a7](https://git.tuffraid.net/cowch/lst_v3/commits/1a3d8a7ddcb7a2e51da4b77968ffdefb7644a0b1))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **backend:** dock door scanning socket and perms ([f8335f5](https://git.tuffraid.net/cowch/lst_v3/commits/f8335f5217c339a7dc09883fa8ba9c60aa4c35b1))
|
||||
* **datamart:** if added to query will include plant token now ([7d722c4](https://git.tuffraid.net/cowch/lst_v3/commits/7d722c4aac16bc1d2cfbfbc70539783f34003f77))
|
||||
* **db:** added in notifications vs pulling from the db makes it easier on the system ([706ab8b](https://git.tuffraid.net/cowch/lst_v3/commits/706ab8b448aafb81da94a76fbf8d3d400cba616b))
|
||||
* **db:** added timezone check in so it comes over correct based on the backend timezone ([2558b2e](https://git.tuffraid.net/cowch/lst_v3/commits/2558b2e5bb68be7a3f46de09bb509c99423adeb6))
|
||||
* **dock door scanning:** fixes and final writes for the intial trial went smooth ([86e1237](https://git.tuffraid.net/cowch/lst_v3/commits/86e1237509b81722dee7b42762d0bfced8d26fa3))
|
||||
* **dock scanner:** more work on dock scanner and semi finished ([6eaae0f](https://git.tuffraid.net/cowch/lst_v3/commits/6eaae0f5378e39c4002dadd8325833698dd960e7))
|
||||
* **dockscanning:** more work on the dock door scanning ([7671172](https://git.tuffraid.net/cowch/lst_v3/commits/7671172d975355b5d245a482b481726b49153578)), closes [#12](https://git.tuffraid.net/cowch/lst_v3/issues/12)
|
||||
* **logger:** included error in the stack version so we dont have to remove it all ([da87e2e](https://git.tuffraid.net/cowch/lst_v3/commits/da87e2e1d322a45ca9a7b500d77499fe4c7b999e))
|
||||
* **logs:** refactored to show the submodule and stack as well to make it more easy to watch ([45a0dee](https://git.tuffraid.net/cowch/lst_v3/commits/45a0dee9caae14df639439b905fa81b340791197))
|
||||
* **logs:** socket io setup to be properly logging ([9ff428f](https://git.tuffraid.net/cowch/lst_v3/commits/9ff428f5ea1051e62521632947092f74eb93944d))
|
||||
* **mobile:** intial addin of dockdoor scanning on mobile ([2a35381](https://git.tuffraid.net/cowch/lst_v3/commits/2a35381fe400fc46e6f10c4e72c3ab9ac435e0e5))
|
||||
* **mobile:** moved logout to the tab bar ([bcdf956](https://git.tuffraid.net/cowch/lst_v3/commits/bcdf9566bcf721a085f46ce3befe783eb7b91949))
|
||||
* **mobile:** new error found and added in ([7bbdd4e](https://git.tuffraid.net/cowch/lst_v3/commits/7bbdd4e5552889434aff81ed10ea7c64174f7d34))
|
||||
* **mobile:** setup - added button to go home as it caused confustion ([c15ee07](https://git.tuffraid.net/cowch/lst_v3/commits/c15ee070e7057ad8a5e3d42f51c230d680db9e21))
|
||||
* **new role:** added in warehouse role ([55418e2](https://git.tuffraid.net/cowch/lst_v3/commits/55418e2f098193b0891129a19532608dce2abd9c))
|
||||
* **opend dock:** added in default dock so it uses the default setup as a backup ([8fc3129](https://git.tuffraid.net/cowch/lst_v3/commits/8fc3129f7d687d45dc242aca9a6e71f43d0352ab))
|
||||
* **opendock:** added in check for really using the article link ([cfc497c](https://git.tuffraid.net/cowch/lst_v3/commits/cfc497c1f2254dc12a6e7bb15cfc0dce2a64c1e6))
|
||||
* **opendock:** added in proper complete and ignore of picksheets ([3ad84da](https://git.tuffraid.net/cowch/lst_v3/commits/3ad84dab71ceea081efc824e97d075c6f33807ad))
|
||||
* **opendock:** added some new goodies to the app to help manage releases ([c0a7d4a](https://git.tuffraid.net/cowch/lst_v3/commits/c0a7d4a1252e3f14240d14288a3ac4add44a6463))
|
||||
* **opendock:** changed the article to look at the label to match all plants ([a2d9a6c](https://git.tuffraid.net/cowch/lst_v3/commits/a2d9a6c127cae3b6dc5beb1a996558ab1383fa8c))
|
||||
* **opendock:** changed the subModule for better logging ([7c4c5f9](https://git.tuffraid.net/cowch/lst_v3/commits/7c4c5f980a15e13e2a43a8583a652622370ec1dd))
|
||||
* **opendock:** if load type is drop we will not do anything unless its already scanned ([5a7046b](https://git.tuffraid.net/cowch/lst_v3/commits/5a7046bacd9323627a37c89d7a9a329383e8b1be))
|
||||
* **server builds:** added in proper logging will still need fine tuned though ([15c939e](https://git.tuffraid.net/cowch/lst_v3/commits/15c939ebe8b808b3a9a46a4c1e62fb0488d3649b))
|
||||
* **servers:** all remaining servers added ([5f2148f](https://git.tuffraid.net/cowch/lst_v3/commits/5f2148f5f0f7688cf71e1dad99d4a5f6c34241dd))
|
||||
* **socket.io:** complete rewrite to manage dynamic rooms and seeding better ([9440b44](https://git.tuffraid.net/cowch/lst_v3/commits/9440b44f3bb1842d0e6ffc479d340ae9c5b84656))
|
||||
* **socketio:** changes that if we are in dock room to constant reseed the room ([8fcb2c6](https://git.tuffraid.net/cowch/lst_v3/commits/8fcb2c66ed25c746dc1d63c5290e11fe6a56328e))
|
||||
* **times:** added a better view for times as we save all db as there respective timezone ([40edbc3](https://git.tuffraid.net/cowch/lst_v3/commits/40edbc3eae9000afea447f81ec127884712b38da))
|
||||
* **warehouse:** fixed ppoo check to match the new changes ([2e460c7](https://git.tuffraid.net/cowch/lst_v3/commits/2e460c7f8aeb7de1d641c42357001704a4d8d8b2))
|
||||
|
||||
|
||||
### 📝 Testing Code
|
||||
|
||||
* **test:** added in vitest to start doing more testing before deploying ([e6b92ae](https://git.tuffraid.net/cowch/lst_v3/commits/e6b92aeb109394691dd2a7d548ddadd02769a159))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **app:** added more labels to the templates ([69a9a81](https://git.tuffraid.net/cowch/lst_v3/commits/69a9a81a889aca010ec8835ffab7084f7cd4a9b0))
|
||||
* **app:** changed the issues templates to be easier to add to the part of the app needed ([188fdd0](https://git.tuffraid.net/cowch/lst_v3/commits/188fdd0eb7c6bedf52ae1dcc9109a0fdbe969a21))
|
||||
|
||||
## [0.1.0-alpha.2](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) (2026-05-23)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **build:** gives a rabbit hole error
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **opendock:** added in new article link setup for fine tuning how od works ([3892111](https://git.tuffraid.net/cowch/lst_v3/commits/389211186f00cb8a6fdd5de092a944fa7e5898aa))
|
||||
* **opendock:** scheduing updates ([1840ac5](https://git.tuffraid.net/cowch/lst_v3/commits/1840ac5e580c726c452216480b6e14e7c52a0f35)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **build:** crashes when files changed :( ([1f6637c](https://git.tuffraid.net/cowch/lst_v3/commits/1f6637c512dcd465c5000f8d1baaa8e76766edc1)), closes [#24](https://git.tuffraid.net/cowch/lst_v3/issues/24)
|
||||
* **docs:** wrong location for images ([057a570](https://git.tuffraid.net/cowch/lst_v3/commits/057a570e43a8e1763652d98244c90999c3fccd42))
|
||||
* **mobile:** correction to axios helper ([ecfbda9](https://git.tuffraid.net/cowch/lst_v3/commits/ecfbda9036f3d68c93e9c1d81021efa8093f18e2))
|
||||
* **sql queries:** disable job would error so now we will check if it exists before trying to kill it ([636daae](https://git.tuffraid.net/cowch/lst_v3/commits/636daaed0adeda908e7e850a4f5bb20d7bbef861))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* **mobile:** updated imgs to be a little smaller ([3a27fd8](https://git.tuffraid.net/cowch/lst_v3/commits/3a27fd8542c3fa4ad5520532c2f10c6e3eaa951c))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **mobile:** added missing error to the scanner ([52974aa](https://git.tuffraid.net/cowch/lst_v3/commits/52974aa0b4f21431777b773200a57f185b4babd2))
|
||||
* **opendock:** changes to how we do the intergration scheduling ([cd67c4d](https://git.tuffraid.net/cowch/lst_v3/commits/cd67c4de80b6f0244afc639a7360e9dc2ba97a21)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **docker:** changes to the ignore file ([71c8306](https://git.tuffraid.net/cowch/lst_v3/commits/71c83062cb644796ebbfd845084ac6c019206faa))
|
||||
|
||||
## [0.1.0-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.0...v0.1.0-alpha.1) (2026-05-19)
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **notifications:** reprinting ([c8931c7](https://git.tuffraid.net/cowch/lst_v3/commits/c8931c7249b8f532b5dd37df3271da98f14ee710)), closes [#20](https://git.tuffraid.net/cowch/lst_v3/issues/20)
|
||||
* **settings:** failed build due it dormant import ([a7bb364](https://git.tuffraid.net/cowch/lst_v3/commits/a7bb364a2fd49d96b6195aca0cd58ba57c58f3a6))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **servers:** changed activeity around and trying to make use of it ([514a44b](https://git.tuffraid.net/cowch/lst_v3/commits/514a44b6de3efe8dd8b308d98bdbc82e31ed8427))
|
||||
* **users:** lots of auth stuff added to make it more easy to manage users ([047cc7c](https://git.tuffraid.net/cowch/lst_v3/commits/047cc7cdf036c39a89a0b87ab59dda8328efe0c0))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **app:** added in chokidar to monitor folders ([8dc4d70](https://git.tuffraid.net/cowch/lst_v3/commits/8dc4d70e2827f0a40d2f54886fd757c8a2dc5ac4))
|
||||
|
||||
## [0.1.0-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.10...v0.1.0-alpha.0) (2026-05-14)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **app:** moved teh middleware to call the api hits to the main app and removed from
|
||||
everywhere else
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **notification:** migrated sql cleanup ([3e66c39](https://git.tuffraid.net/cowch/lst_v3/commits/3e66c3920d65cee7a0a788f3910c1ddf09a07805))
|
||||
* **scan users:** added in the place to add the new scanner users in ([ce9d8ea](https://git.tuffraid.net/cowch/lst_v3/commits/ce9d8eaaf5bcb8f53ea4bdc191347df8d589fdfa))
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **anaylistics:** changes to the daily section so it populates correctly now ([f5bae2c](https://git.tuffraid.net/cowch/lst_v3/commits/f5bae2c0c24b85423c5c421164d94d58159ff70a))
|
||||
* **anaylitics:** unique values were missing causing a weird crash ([13718fe](https://git.tuffraid.net/cowch/lst_v3/commits/13718fe70293c039bd1d9bf8cf395852e6ea6c21))
|
||||
* **app:** emit.maxlistener issue ([7c31b43](https://git.tuffraid.net/cowch/lst_v3/commits/7c31b43a4a313237fa63c0c9bbc3690b74f63a6f)), closes [#18](https://git.tuffraid.net/cowch/lst_v3/issues/18)
|
||||
* **app:** required auth was in wrong spot caused entire app to want you logged in ([d2a9e1d](https://git.tuffraid.net/cowch/lst_v3/commits/d2a9e1d1107ea05f13725e9528bc6ab1566c8efb))
|
||||
* **notification subs:** made it so only acitve show ([2616acf](https://git.tuffraid.net/cowch/lst_v3/commits/2616acf106530f5c5ee04d1b79033795cf06b42d)), closes [#14](https://git.tuffraid.net/cowch/lst_v3/issues/14)
|
||||
* **scanner:** changed to not crash on logging ([0de2579](https://git.tuffraid.net/cowch/lst_v3/commits/0de25799420f38a293ee9acc70eb36e3287145c4)), closes [#19](https://git.tuffraid.net/cowch/lst_v3/issues/19)
|
||||
* **scanner:** fixes to be more clear that you need to scan a command to start ([0575879](https://git.tuffraid.net/cowch/lst_v3/commits/05758791be7a50e90b5da05d4977e618c311f654)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
|
||||
* **scanner:** logut out corrections ([85e96f5](https://git.tuffraid.net/cowch/lst_v3/commits/85e96f5ed13a81fd466c6bbff31c539244750838)), closes [#17](https://git.tuffraid.net/cowch/lst_v3/issues/17)
|
||||
* **table:** skelly table causing hydration error ([1bbf5c2](https://git.tuffraid.net/cowch/lst_v3/commits/1bbf5c2a4955107a36ace05595886d19cc8e64f4))
|
||||
|
||||
|
||||
### 📝 Chore
|
||||
|
||||
* **mobile:** removed console log that shouldnt be there ([9631736](https://git.tuffraid.net/cowch/lst_v3/commits/9631736e263ed00189f8118f686690cab25f09d3))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* **scanner:** added in instructions on how to update the scanner ([b0c7277](https://git.tuffraid.net/cowch/lst_v3/commits/b0c7277a6cdb5becec3a994ea1d5cc2d7b0326aa))
|
||||
* **scanner:** added in westbend and dayton commands to scan for updates ([eb9d77c](https://git.tuffraid.net/cowch/lst_v3/commits/eb9d77c3d4767fd961759662ef44c3e09e00946b))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **api:** changes to call a helper api to quit and redirect if needed ([c64392f](https://git.tuffraid.net/cowch/lst_v3/commits/c64392f45769108aa4134c7fd865f3d4bc664179))
|
||||
* **app:** changed ways we get data so we can have better reasons why app no worky ([30ff7b7](https://git.tuffraid.net/cowch/lst_v3/commits/30ff7b71d9d159ced263a5330d70d53b97393157))
|
||||
* **mobile:** scanner response ([a9c6925](https://git.tuffraid.net/cowch/lst_v3/commits/a9c69250bd3272ad682751e41b671c119cb678f1)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
|
||||
* **scanner:** logging - version of app ([d61be61](https://git.tuffraid.net/cowch/lst_v3/commits/d61be61f4433a2be2678d724f4724301931614c9))
|
||||
* **scanner:** more scanner admin stuff ([eb950d2](https://git.tuffraid.net/cowch/lst_v3/commits/eb950d2c29f692b806d5cc4ab7014bd59a726a8d))
|
||||
* **scanner:** removed 69 as an option lol ([e7af3d1](https://git.tuffraid.net/cowch/lst_v3/commits/e7af3d11824b42915cf6789f9c508a727511d678))
|
||||
* **servers:** server name now links to the actual server:port ([ebf1060](https://git.tuffraid.net/cowch/lst_v3/commits/ebf1060475d37627b371bc6c79507cdde411600b))
|
||||
* **users:** some user refactoring and configuring ([342a97f](https://git.tuffraid.net/cowch/lst_v3/commits/342a97f6b1054443b9126186d2c7872fbd8586da))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **mobile:** added in ehs config to make it more easy for users to update the scanner app on the fly ([dc95e50](https://git.tuffraid.net/cowch/lst_v3/commits/dc95e50a8412b4fbc629fd44fcb5c77295583ca8))
|
||||
* **notification:** removal of more console logs that shouldnt be here ([51026e3](https://git.tuffraid.net/cowch/lst_v3/commits/51026e3e2cce4d7f696d26aae305b3fd221f5bb1))
|
||||
* **servives:** helpers moved around ([e9e73c8](https://git.tuffraid.net/cowch/lst_v3/commits/e9e73c829c2e5726650c0ac7ffa6a9055dbc982b))
|
||||
* **updateserver:** changes to actually add the new env stuff ([bcb7773](https://git.tuffraid.net/cowch/lst_v3/commits/bcb7773007894ac2f85fe2a0b47faf14c7b474ad))
|
||||
|
||||
## [0.0.2-alpha.10](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.9...v0.0.2-alpha.10) (2026-05-08)
|
||||
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **analytics:** added in backend anaylitics ([9edafc9](https://git.tuffraid.net/cowch/lst_v3/commits/9edafc9d2810f339d197c10dfc6a037b3352d81f))
|
||||
* **api hits:** added in api hits for monitoring ([2721bb2](https://git.tuffraid.net/cowch/lst_v3/commits/2721bb2a3bf1f829591d26a0716f74c4f7fc0c79))
|
||||
* **scanner:** added in lanechecks ([87803ee](https://git.tuffraid.net/cowch/lst_v3/commits/87803eed43069b73de3f66e6524bb45da9c46334))
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **scan user:** typo ([d8869b1](https://git.tuffraid.net/cowch/lst_v3/commits/d8869b103b80e4208b3928a370a9524ef33d25cd))
|
||||
* **schema:** typo in add_date ([7d7d991](https://git.tuffraid.net/cowch/lst_v3/commits/7d7d9911223905d6767b87d2471b6607a90f1ea7))
|
||||
* **spelling:** corrected the spelling on the file ([0459cd7](https://git.tuffraid.net/cowch/lst_v3/commits/0459cd788aaad6ac54a67e23f798ce5e5a437394))
|
||||
|
||||
|
||||
### 📝 Chore
|
||||
|
||||
* **file:** name changes.. spelled wrong ([5fa70da](https://git.tuffraid.net/cowch/lst_v3/commits/5fa70da90ca290ee45088e9c8eb06ba48a6677af))
|
||||
* **server:** removed a console log that shouldnt be there ([1dba774](https://git.tuffraid.net/cowch/lst_v3/commits/1dba774abc54bf20850c3f26d49926e86d59712d))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **analyitics:** finished analyitics as a base ([4424c74](https://git.tuffraid.net/cowch/lst_v3/commits/4424c742d24dc230b2bc1782e33535184c378cf0))
|
||||
* **scan:** bump in build and style update ([505d7ce](https://git.tuffraid.net/cowch/lst_v3/commits/505d7cea5d2f52fc4a3ec1edff1878be703c4034))
|
||||
* **scanner:** added toasts in to make it look better ([edb3668](https://git.tuffraid.net/cowch/lst_v3/commits/edb366854825f4c24ab5d77cf88759465d067f00))
|
||||
|
||||
|
||||
### 📝 Testing Code
|
||||
|
||||
* **scanusers:** added in scan users as test ([1ff5e50](https://git.tuffraid.net/cowch/lst_v3/commits/1ff5e5032f9c8bf81f972dc99d6c86ba8d3936c6))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **template:** bug in getting the template to work correctly ([e9b0101](https://git.tuffraid.net/cowch/lst_v3/commits/e9b01010954624aed738cd6e4b82fccbba195cc4))
|
||||
* **templates:** added in templates for the repo to make it more easy to manage and add in new ideas ([ca885fb](https://git.tuffraid.net/cowch/lst_v3/commits/ca885fb01a3c8bc22694c2e05269c43fcd4de70e))
|
||||
* **templates:** force useage ([6d8499b](https://git.tuffraid.net/cowch/lst_v3/commits/6d8499bfb85f7b9131b1ec7b31a17c4256d0f0cf))
|
||||
|
||||
## [0.0.2-alpha.9](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.8...v0.0.2-alpha.9) (2026-05-06)
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **mobile:** valildation of server after each scan ([4ca74de](https://git.tuffraid.net/cowch/lst_v3/commits/4ca74de2795cea7244e38697d16afe2822164ed6))
|
||||
* **scanner:** added in running number ([a38e2e0](https://git.tuffraid.net/cowch/lst_v3/commits/a38e2e033977b725538e9a9046098d94194d549e))
|
||||
* **scanner:** finished login stuff for current routes ([1241253](https://git.tuffraid.net/cowch/lst_v3/commits/12412536d10981013053c39d156c6c9cb0babd11))
|
||||
|
||||
|
||||
### 📝 Testing Code
|
||||
|
||||
* **scanner:** lane check ([d99449d](https://git.tuffraid.net/cowch/lst_v3/commits/d99449ddc4e2777c1b0fe9189ba0a7c01fe1dd8f))
|
||||
|
||||
|
||||
### 📈 Project Builds
|
||||
|
||||
* **builds:** changed to ip as its on the same server ([3552ca3](https://git.tuffraid.net/cowch/lst_v3/commits/3552ca31f9f7b3bcbe557a145e7eb154bfdae79c))
|
||||
* **release:** bypass cloudflare upload limit ([b578f05](https://git.tuffraid.net/cowch/lst_v3/commits/b578f05d6482f9b6f30febeee6ab0b708a70f68b))
|
||||
|
||||
## [0.0.2-alpha.8](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.7...v0.0.2-alpha.8) (2026-05-06)
|
||||
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **mobile:** auth added in ([ba30281](https://git.tuffraid.net/cowch/lst_v3/commits/ba30281e59040513a036fb7413e372457d04a7c8))
|
||||
|
||||
## [0.0.2-alpha.7](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.6...v0.0.2-alpha.7) (2026-05-06)
|
||||
|
||||
|
||||
|
||||
23
README.md
23
README.md
@@ -7,7 +7,7 @@
|
||||
Quick summary of current rewrite/migration goal.
|
||||
|
||||
- **Phase:** Backend rewrite
|
||||
- **Last updated:** 2026-04-06
|
||||
- **Last updated:** 2026-05-27
|
||||
|
||||
---
|
||||
|
||||
@@ -17,20 +17,20 @@ Quick summary of current rewrite/migration goal.
|
||||
|----------|--------------|--------|
|
||||
| User Authentication | ~~Login~~, ~~Signup~~, API Key | 🟨 In Progress |
|
||||
| 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 | 🟨 In Progress |
|
||||
| Datamart | ~~Create~~, ~~Update~~, ~~Run~~, Deactivate | 🟨 In Progress |
|
||||
| Frontend | Analytics and charts | ⏳ Not Started |
|
||||
| Docs | Instructions and trouble shooting | ⏳ Not Started |
|
||||
| One Click Print | Get printers, monitor printers, label process, material process, Special processes | ⏳ Not Started |
|
||||
| One Click Print | ~~Get printers~~, monitor printers, label process, material process, Special processes | 🟨 In Progress |
|
||||
| Silo Adjustments | Create, History, Comments | ⏳ 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 |
|
||||
| Quality Request Tool | Add Pallet, Monitor for moved, status changes, alerts | ⏳ Not Started |
|
||||
| Logistics | Consume material, return and print, label info, relocate | ⏳ Not Started |
|
||||
| EOM | Endpoints, Report Pull for finance | ⏳ Not Started |
|
||||
| OCME | Custom integration | ⏳ Not Started |
|
||||
| EOM | ~~Endpoints~~, Report Pull for finance, SSRS report | 🟨 In Progress |
|
||||
| ~~OCME~~ | ~~Custom integration~~ | Canceled |
|
||||
| API Migration | Moving to new REST endpoints | 🔧 In Progress |
|
||||
| System | Tests,Builds, Updates, Remote Logging, DB Backups, Alerting | ⏳ Not Started |
|
||||
|
||||
@@ -47,4 +47,13 @@ How to run the current version of the app.
|
||||
git clone https://git.tuffraid.net/cowch/lst_v3.git
|
||||
cd lst_v3
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Rename the .env-example to .env
|
||||
|
||||
Update all the fields
|
||||
|
||||
```bash
|
||||
npm run dev:db:migrate
|
||||
npm run dev
|
||||
```
|
||||
@@ -1,12 +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/*
|
||||
};
|
||||
|
||||
46
backend/admin/admin.users.ts
Normal file
46
backend/admin/admin.users.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* To be able to run this we need to set our dev pc in the .env.
|
||||
* if its empty just ignore it. this will just be the double catch
|
||||
*/
|
||||
|
||||
import { fromNodeHeaders } from "better-auth/node";
|
||||
import { Router } from "express";
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { users } = await auth.api.listUsers({
|
||||
query: {
|
||||
limit: 50,
|
||||
},
|
||||
headers: fromNodeHeaders(req.headers),
|
||||
});
|
||||
|
||||
// console.log(error);
|
||||
|
||||
// if (error) {
|
||||
// return apiReturn(res, {
|
||||
// success: false,
|
||||
// level: "info",
|
||||
// module: "admin",
|
||||
// subModule: "user",
|
||||
// message: `There was an error getting the users.`,
|
||||
// data: users,
|
||||
// status: 400,
|
||||
// });
|
||||
// }
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "admin",
|
||||
subModule: "users",
|
||||
message: `Current active users.`,
|
||||
data: users,
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -3,7 +3,9 @@ import { fileURLToPath } from "node:url";
|
||||
import { toNodeHandler } from "better-auth/node";
|
||||
import express from "express";
|
||||
import morgan from "morgan";
|
||||
import { umamiConfig } from "./configs/umami.config.js";
|
||||
import { createLogger } from "./logger/logger.controller.js";
|
||||
import { routeHitMiddleware } from "./middleware/routeHit.middleware.js";
|
||||
import { setupRoutes } from "./routeHandler.routes.js";
|
||||
import { auth } from "./utils/auth.utils.js";
|
||||
import { lstCors } from "./utils/cors.utils.js";
|
||||
@@ -29,8 +31,27 @@ const createApp = async () => {
|
||||
app.use(morgan("dev"));
|
||||
app.set("trust proxy", true);
|
||||
app.use(lstCors());
|
||||
app.use(routeHitMiddleware);
|
||||
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
||||
app.use(express.json());
|
||||
|
||||
app.get(`${baseUrl}/api/lst-config.js`, (_, res) => {
|
||||
res.type("application/javascript");
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
|
||||
res.send(`
|
||||
window.LST_CONFIG = {
|
||||
appName: ${JSON.stringify(umamiConfig.appName ?? "LST")},
|
||||
site: ${JSON.stringify(umamiConfig.site ?? "unknown")},
|
||||
server: ${JSON.stringify(umamiConfig.server ?? "unknown")},
|
||||
appVersion: ${JSON.stringify(umamiConfig.appVersion ?? "dev")},
|
||||
umamiHost: ${JSON.stringify(umamiConfig.umamiHost ?? "")},
|
||||
umamiWebsiteId: ${JSON.stringify(umamiConfig.umamiWebsiteId ?? "")},
|
||||
timezone: ${JSON.stringify(process.env.TIMEZONE ?? "America/Chicago")}
|
||||
};
|
||||
`);
|
||||
});
|
||||
|
||||
setupRoutes(baseUrl, app);
|
||||
|
||||
app.use(
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Express } from "express";
|
||||
|
||||
import login from "./login.route.js";
|
||||
import register from "./register.route.js";
|
||||
|
||||
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
|
||||
//setup all the routes
|
||||
|
||||
app.use(`${baseUrl}/api/authentication/login`, login);
|
||||
app.use(`${baseUrl}/api/authentication/register`, register);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
0
backend/datamart/datamart.controller.test.ts
Normal file
0
backend/datamart/datamart.controller.test.ts
Normal file
@@ -37,6 +37,7 @@ 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(",")
|
||||
@@ -326,8 +327,13 @@ export const runDatamartQuery = async (data: Data) => {
|
||||
level: "info",
|
||||
module: "datamart",
|
||||
subModule: "query",
|
||||
message: `Data for: ${data.name}`,
|
||||
data: queryRun.data,
|
||||
message: `Data for: ${data.name} ${data.options.includePlantToken ? "including plant token" : ""}`,
|
||||
// if includePlantToken was passed we should map this into the data
|
||||
data: data.options.includePlantToken
|
||||
? queryRun.data.map((i) => {
|
||||
return { plantToken: process.env.PROD_PLANT_TOKEN, ...i };
|
||||
})
|
||||
: queryRun.data,
|
||||
notify: false,
|
||||
});
|
||||
};
|
||||
|
||||
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,4 +1,5 @@
|
||||
import { Router } from "express";
|
||||
import * as XLSX from "xlsx";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { runDatamartQuery } from "./datamart.controller.js";
|
||||
|
||||
@@ -7,13 +8,73 @@ const r = Router();
|
||||
type Options = {
|
||||
name: string;
|
||||
value: string;
|
||||
format: string;
|
||||
};
|
||||
|
||||
r.get("/:name", async (req, res) => {
|
||||
const { name } = req.params;
|
||||
const options = req.query as Options;
|
||||
|
||||
const options = { ...req.query } as Options;
|
||||
const dataRan = await runDatamartQuery({ name, options });
|
||||
|
||||
if (!dataRan.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "datamart",
|
||||
subModule: "query",
|
||||
message: dataRan.message,
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
// XLSX Export
|
||||
if (options.format?.toLowerCase() === "xlsx") {
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
const ws = XLSX.utils.json_to_sheet(dataRan.data);
|
||||
|
||||
XLSX.utils.book_append_sheet(wb, ws, name);
|
||||
|
||||
const buffer = XLSX.write(wb, {
|
||||
type: "buffer",
|
||||
bookType: "xlsx",
|
||||
});
|
||||
|
||||
res.setHeader(
|
||||
"Content-Type",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
);
|
||||
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${name}.xlsx"`);
|
||||
|
||||
return res.send(buffer);
|
||||
}
|
||||
|
||||
// CSV Export
|
||||
if (options.format?.toLowerCase() === "csv") {
|
||||
const rows = dataRan.data as any;
|
||||
|
||||
if (!rows.length) {
|
||||
return res.status(200).send("");
|
||||
}
|
||||
|
||||
const headers = Object.keys(rows[0]);
|
||||
|
||||
const csv = [
|
||||
headers.join(","),
|
||||
...rows.map((row: any) =>
|
||||
headers
|
||||
.map((h) => `"${String(row[h] ?? "").replace(/"/g, '""')}"`)
|
||||
.join(","),
|
||||
),
|
||||
].join("\r\n");
|
||||
|
||||
res.setHeader("Content-Type", "text/csv");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${name}.csv"`);
|
||||
|
||||
return res.send(csv);
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: dataRan.success,
|
||||
level: "info",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
|
||||
import * as dockScans from "./schema/dockdoor.scans.schema.js";
|
||||
import * as logs from "./schema/logs.schema.js";
|
||||
import * as opendockAVCheck from "./schema/opendock_articleSetup.js";
|
||||
import * as 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}`;
|
||||
|
||||
@@ -20,5 +23,9 @@ const queryClient = postgres(dbURL, {
|
||||
export const db = drizzle(queryClient, {
|
||||
schema: {
|
||||
...scanUserSchema,
|
||||
...settingsSchema,
|
||||
...opendockAVCheck,
|
||||
...logs,
|
||||
...dockScans,
|
||||
},
|
||||
});
|
||||
|
||||
59
backend/db/db.listener.ts
Normal file
59
backend/db/db.listener.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import postgres from "postgres";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { handleDbNotification } from "./db.router.js";
|
||||
|
||||
const log = createLogger({
|
||||
module: "db",
|
||||
subModule: "notifications",
|
||||
});
|
||||
|
||||
const CHANNELS = [
|
||||
"logs_inserted",
|
||||
// "labels_inserted",
|
||||
// "dock_scans_inserted",
|
||||
] as const;
|
||||
|
||||
type DbNotificationChannel = (typeof CHANNELS)[number];
|
||||
|
||||
type DbNotificationPayload = {
|
||||
table: string;
|
||||
action: "INSERT" | "UPDATE" | "DELETE";
|
||||
id: string;
|
||||
};
|
||||
|
||||
export async function startDbNotificationListener() {
|
||||
const sql = postgres({
|
||||
host: `${process.env.DATABASE_HOST}`,
|
||||
port: Number(process.env.DATABASE_PORT),
|
||||
database: `${process.env.DATABASE_DB}`,
|
||||
username: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
});
|
||||
|
||||
for (const channel of CHANNELS) {
|
||||
await sql.listen(channel, async (rawPayload) => {
|
||||
await processNotification(channel, rawPayload);
|
||||
});
|
||||
|
||||
log.info({ stack: { channel } }, `Listening for ${channel}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function processNotification(
|
||||
channel: DbNotificationChannel,
|
||||
rawPayload: string,
|
||||
) {
|
||||
try {
|
||||
const payload = JSON.parse(rawPayload) as DbNotificationPayload;
|
||||
|
||||
await handleDbNotification({
|
||||
channel,
|
||||
payload,
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(
|
||||
{ stack: { channel, rawPayload, e }, notify: true },
|
||||
"Failed processing DB notification",
|
||||
);
|
||||
}
|
||||
}
|
||||
41
backend/db/db.router.ts
Normal file
41
backend/db/db.router.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { handleDockScanInsertedNotification } from "../dockdoorScanning/dockdoor.socket.notifications.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { handleLogInsertedNotification } from "../logger/logger.socket.notifications.js";
|
||||
|
||||
const log = createLogger({
|
||||
module: "db",
|
||||
subModule: "notifications-router",
|
||||
});
|
||||
|
||||
type DbNotification = {
|
||||
channel: string;
|
||||
payload: {
|
||||
table: string;
|
||||
action: "INSERT" | "UPDATE" | "DELETE";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export async function handleDbNotification(notification: DbNotification) {
|
||||
const { channel, payload } = notification;
|
||||
|
||||
switch (channel) {
|
||||
case "logs_inserted":
|
||||
await handleLogInsertedNotification(payload.id);
|
||||
return;
|
||||
|
||||
// case "labels_inserted":
|
||||
// await handleLabelInsertedNotification(payload.id);
|
||||
// return;
|
||||
|
||||
case "dock_scan_inserted":
|
||||
await handleDockScanInsertedNotification(payload.id);
|
||||
return;
|
||||
|
||||
default:
|
||||
log.warn(
|
||||
{ stack: notification },
|
||||
`Unhandled DB notification channel: ${channel}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
99
backend/db/db.setupNotifications.ts
Normal file
99
backend/db/db.setupNotifications.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
|
||||
const log = createLogger({
|
||||
module: "db",
|
||||
subModule: "notifications",
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates/updates Postgres notification functions + triggers.
|
||||
*
|
||||
* Safe to run on every app startup.
|
||||
* CREATE OR REPLACE updates the function.
|
||||
* DROP TRIGGER IF EXISTS prevents duplicate triggers.
|
||||
*/
|
||||
export async function setupDbNotifications() {
|
||||
log.info({}, "Setting up DB notifications");
|
||||
|
||||
await setupLogsNotifications();
|
||||
await setupDockScansNotifications();
|
||||
|
||||
log.info({}, "DB notifications setup complete");
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs notification setup.
|
||||
*
|
||||
* Flow:
|
||||
* 1. app inserts into logs table
|
||||
* 2. trigger runs after insert
|
||||
* 3. Postgres sends NOTIFY logs_inserted with the new log id
|
||||
* 4. Node listener receives id and fetches/emits full row
|
||||
*/
|
||||
async function setupLogsNotifications() {
|
||||
await db.execute(sql`
|
||||
CREATE OR REPLACE FUNCTION notify_logs_inserted()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify(
|
||||
'logs_inserted',
|
||||
json_build_object(
|
||||
'table', TG_TABLE_NAME,
|
||||
'action', TG_OP,
|
||||
'id', NEW.id
|
||||
)::text
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
DROP TRIGGER IF EXISTS logs_inserted_notify_trigger ON logs;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TRIGGER logs_inserted_notify_trigger
|
||||
AFTER INSERT ON logs
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_logs_inserted();
|
||||
`);
|
||||
|
||||
log.info({}, "Logs DB notification trigger ready");
|
||||
}
|
||||
|
||||
async function setupDockScansNotifications() {
|
||||
await db.execute(sql`
|
||||
CREATE OR REPLACE FUNCTION notify_dock_scan_inserted()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify(
|
||||
'dock_scan_inserted',
|
||||
json_build_object(
|
||||
'table', TG_TABLE_NAME,
|
||||
'action', TG_OP,
|
||||
'id', NEW.id
|
||||
)::text
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
DROP TRIGGER IF EXISTS dock_scan_inserted_notify_trigger ON dock_door_scans;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TRIGGER dock_scan_inserted_notify_trigger
|
||||
AFTER INSERT ON dock_door_scans
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_dock_scan_inserted();
|
||||
`);
|
||||
|
||||
log.info({}, "Dock scan DB notification trigger ready");
|
||||
}
|
||||
21
backend/db/db.socketSeed.ts
Normal file
21
backend/db/db.socketSeed.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { db } from "./db.controller.js";
|
||||
|
||||
export const getRecentLogs = ({
|
||||
module,
|
||||
submodule,
|
||||
limit = 200,
|
||||
}: {
|
||||
module?: string | undefined;
|
||||
submodule?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
}) => {
|
||||
return db.query.logs.findMany({
|
||||
where: (logs, { and, eq }) =>
|
||||
and(
|
||||
module ? eq(logs.module, module) : undefined,
|
||||
submodule ? eq(logs.subModule, submodule) : undefined,
|
||||
),
|
||||
orderBy: (logs, { desc }) => [desc(logs.createdAt)],
|
||||
limit,
|
||||
});
|
||||
};
|
||||
@@ -17,15 +17,15 @@ export const alplaPurchaseHistory = pgTable("alpla_purchase_history", {
|
||||
status: integer("status"),
|
||||
statusText: text("status_text"),
|
||||
journalNum: integer("journal_num"),
|
||||
add_date: timestamp("add_date").defaultNow(),
|
||||
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||
add_user: text("add_user"),
|
||||
upd_user: text("upd_user"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
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").defaultNow(),
|
||||
updatedAt: timestamp("updated_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const alplaPurchaseHistorySchema =
|
||||
|
||||
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(),
|
||||
jobName: text("job_name"),
|
||||
startedAt: timestamp("start_at"),
|
||||
finishedAt: timestamp("finished_at"),
|
||||
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
||||
durationMs: integer("duration_ms"),
|
||||
status: text("status"), //success | error
|
||||
errorMessage: text("error_message"),
|
||||
errorStack: text("error_stack"),
|
||||
metadata: jsonb("meta_data"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
|
||||
@@ -15,7 +15,7 @@ export const user = pgTable("user", {
|
||||
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true })
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
|
||||
@@ -6,5 +6,5 @@ export const deploymentHistory = pgTable("deployment_history", {
|
||||
buildNumber: integer("build_number").notNull(),
|
||||
status: text("status").notNull(), // started, success, failed
|
||||
message: text("message"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
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),
|
||||
options: text("options").default(""),
|
||||
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"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
upd_user: text("upd_user").default("lst-system"),
|
||||
});
|
||||
|
||||
|
||||
23
backend/db/schema/dockdoor.scans.schema.ts
Normal file
23
backend/db/schema/dockdoor.scans.schema.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const dockDoorScans = pgTable("dock_door_scans", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
dockId: text("dock_id").notNull(),
|
||||
loadingOrder: text("loading_order").notNull(),
|
||||
loadingUnit: text("loading_Unit"), // can be running number or sscc depending on where it came from
|
||||
loadingUnitStatus: text("loading_unit_status").default("loaded"), // TODO: add enums on the status of each load.
|
||||
message: text("message"), // the response it gave when scanning
|
||||
status: text("status").default("active"), // TODO: add in enums for this
|
||||
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||
add_user: text("add_user").default("lst-system"),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
upd_user: text("upd_user").default("lst-system"),
|
||||
});
|
||||
|
||||
export const dockDoorScansSchema = createSelectSchema(dockDoorScans);
|
||||
export const newDockDoorScansSchema = createInsertSchema(dockDoorScans);
|
||||
|
||||
export type DockDoorScans = z.infer<typeof dockDoorScansSchema>;
|
||||
export type NewDockDoorScans = z.infer<typeof newDockDoorScansSchema>;
|
||||
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>;
|
||||
22
backend/db/schema/forecastImports.schema.ts
Normal file
22
backend/db/schema/forecastImports.schema.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const forecastImport = pgTable("forecast_import", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
receivingPlantId: text("receiving_plant_id").notNull(),
|
||||
documentName: text("documentName"),
|
||||
sender: text("sender"),
|
||||
customerId: text("customer_id"),
|
||||
rawData: jsonb("raw_data").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 forecastImportSchema = createSelectSchema(forecastImport);
|
||||
export const newForecastImportSchema = createInsertSchema(forecastImport);
|
||||
|
||||
export type ForecastImport = z.infer<typeof forecastImportSchema>;
|
||||
export type NeworecastImport = z.infer<typeof newForecastImportSchema>;
|
||||
@@ -20,7 +20,7 @@ export const invHistoricalData = pgTable("inv_historical_data", {
|
||||
whseId: text("whse_id").default(""),
|
||||
whseName: text("whse_name").default("missing whseName"),
|
||||
upd_user: text("upd_user").default("lst-system"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const invHistoricalDataSchema = createSelectSchema(invHistoricalData);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const logs = pgTable("logs", {
|
||||
stack: jsonb("stack").default([]),
|
||||
checked: boolean("checked").default(false),
|
||||
hostname: text("hostname"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const logSchema = createSelectSchema(logs);
|
||||
|
||||
@@ -14,14 +14,18 @@ export const opendockApt = pgTable(
|
||||
"opendock_apt",
|
||||
{
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
release: integer("release").notNull().unique(),
|
||||
release: integer("release").notNull().unique("opendock_apt_release_unique"),
|
||||
openDockAptId: text("open_dock_apt_id").notNull(),
|
||||
status: text("status").default("active"),
|
||||
appointment: jsonb("appointment").notNull().default([]),
|
||||
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
releaseIdx: index("opendock_apt_release_idx").on(table.release),
|
||||
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
|
||||
table.openDockAptId,
|
||||
),
|
||||
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
|
||||
>;
|
||||
@@ -7,5 +7,5 @@ export const printerLog = pgTable("printer_log", {
|
||||
printerSN: text("printer_sn"),
|
||||
condition: text("condition").notNull(),
|
||||
message: text("message"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
@@ -28,8 +28,8 @@ export const printerData = pgTable(
|
||||
printDelay: integer("printDelay").default(90),
|
||||
processes: jsonb("processes").default([]),
|
||||
printDelayOverride: boolean("print_delay_override").default(false), // this will be more for if we have the lot time active but want to over ride this single line for some reason
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
add_Date: timestamp("add_Date", { withTimezone: true }).defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
//uniqueIndex("emailUniqueIndex").on(sql`lower(${table.email})`),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
boolean,
|
||||
jsonb,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
text,
|
||||
@@ -25,12 +26,12 @@ export const scanUser = pgTable(
|
||||
scannerId: text("scanner_id").unique().notNull(),
|
||||
pinNumber: text("pin_number").unique().notNull(),
|
||||
pinHash: text("pin_hash").notNull(),
|
||||
excludedCommand: text("excluded_commands").default(""),
|
||||
excludedCommand: jsonb("excluded_commands").default([]),
|
||||
role: mobileRoleEnum("role").notNull().default("user"),
|
||||
active: boolean("active").default(true),
|
||||
lastScan: timestamp("last_scan").defaultNow(),
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
add_Date: timestamp("add_Date", { withTimezone: true }).defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
userNotificationUnique: unique("scan_user_unique").on(
|
||||
|
||||
@@ -4,13 +4,16 @@ import type z from "zod";
|
||||
|
||||
export const scanLog = pgTable("scan_log", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
user: text("user"),
|
||||
scannerId: text("scanner_id"),
|
||||
message: text("message").notNull(),
|
||||
prompt: text("prompt"),
|
||||
commandDescription: text("command_description"),
|
||||
runningNumber: text("running_number").default("0"),
|
||||
status: text("status"),
|
||||
scannerVersion: text("scanner_version").default("0"),
|
||||
lines: jsonb("lines").default([]),
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
add_Date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const scanLogSchema = createSelectSchema(scanLog);
|
||||
|
||||
@@ -22,7 +22,7 @@ export const serverData = pgTable(
|
||||
contactPhone: text("contact_phone"),
|
||||
active: boolean("active").default(true),
|
||||
serverLoc: text("server_loc"),
|
||||
lastUpdated: timestamp("last_updated").defaultNow(),
|
||||
lastUpdated: timestamp("last_updated", { withTimezone: true }).defaultNow(),
|
||||
buildNumber: integer("build_number"),
|
||||
isUpgrading: boolean("is_upgrading").default(false),
|
||||
},
|
||||
|
||||
@@ -32,13 +32,13 @@ export const settings = pgTable(
|
||||
settingType: settingType(),
|
||||
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_Date: timestamp("add_Date").defaultNow(),
|
||||
add_Date: timestamp("add_Date", { withTimezone: true }).defaultNow(),
|
||||
upd_user: text("upd_User").default("LST_System").notNull(),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
||||
uniqueIndex("name").on(table.name),
|
||||
uniqueIndex("settings_name_unique").on(table.name),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ import type z from "zod";
|
||||
export const appStats = pgTable("app_stats", {
|
||||
id: text("id").primaryKey().default("primary"),
|
||||
currentBuild: integer("current_build").notNull().default(1),
|
||||
lastBuildAt: timestamp("last_build_at"),
|
||||
lastDeployAt: timestamp("last_deploy_at"),
|
||||
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").defaultNow(),
|
||||
lastUpdated: timestamp("last_updated", { withTimezone: true }).defaultNow(),
|
||||
meta: jsonb("meta").$type<Record<string, unknown>>().default({}),
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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
|
||||
2, // loading
|
||||
],
|
||||
//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;
|
||||
181
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
181
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScans } from "../db/schema/dockdoor.scans.schema.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.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) => {
|
||||
if (req.body.clear) {
|
||||
// just clear the loading order and clear out all the pallets to keep it clean.
|
||||
await tryCatch(
|
||||
db
|
||||
.update(dockDoorScans)
|
||||
.set({
|
||||
status: "completed",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username ?? "lst-dock-system",
|
||||
})
|
||||
.where(
|
||||
req.body.loadingOrder
|
||||
? eq(dockDoorScanners.currentLoadingOrder, req.body.loadingOrder)
|
||||
: undefined,
|
||||
)
|
||||
.returning(),
|
||||
);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(dockDoorScanners)
|
||||
.set({
|
||||
currentLoadingOrder: "",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username ?? "lst-dock-system",
|
||||
})
|
||||
.where(eq(dockDoorScanners.dockId, req.body.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: ${req.body.loadingOrder} was just cleared out do to the process being completed in some other means. \nThis includes any scanned pallets as well.`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const validated = endLoading.parse(req.body);
|
||||
|
||||
const orders = (await runProdApi({
|
||||
method: "post",
|
||||
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${req.body.loadingOrder}/Finish`,
|
||||
data: [
|
||||
{
|
||||
printDeliveryDocuments: true,
|
||||
},
|
||||
],
|
||||
})) as any;
|
||||
|
||||
if (orders?.data.errors) {
|
||||
//console.log(orders.data.errors);
|
||||
|
||||
// if (orders.data.errors[0].code === 20) {
|
||||
// await db
|
||||
// .update(dockDoorScanners)
|
||||
// .set({
|
||||
// currentLoadingOrder: "",
|
||||
// upd_date: sql`NOW()`,
|
||||
// upd_user: req.user?.username ?? "lst-dock-system",
|
||||
// })
|
||||
// .where(eq(dockDoorScanners.dockId, validated.dockId));
|
||||
|
||||
// return apiReturn(res, {
|
||||
// success: false,
|
||||
// level: "warn",
|
||||
// module: "dockdoor",
|
||||
// subModule: "loadingOrder",
|
||||
// message: `Loading order: ${validated.loadingOrder} cleared, It was probable completed by a scanner.. or user...`,
|
||||
// data: (orders.data.errors as any) ?? [],
|
||||
// status: 200,
|
||||
// });
|
||||
// }
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Failed to finish the order: ${orders.data.errors[0].message}`,
|
||||
data: (orders.data.errors as any) ?? [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
await tryCatch(
|
||||
db
|
||||
.update(dockDoorScans)
|
||||
.set({
|
||||
status: "completed",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username ?? "lst-dock-system",
|
||||
})
|
||||
.where(
|
||||
eq(dockDoorScans.loadingOrder, validated.loadingOrder.toString()),
|
||||
)
|
||||
.returning(),
|
||||
);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(dockDoorScanners)
|
||||
.set({
|
||||
currentLoadingOrder: "",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username ?? "lst-dock-system",
|
||||
})
|
||||
.where(eq(dockDoorScanners.dockId, validated.dockId))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Failed to updating the dock.`,
|
||||
data: (error as any) ?? [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: orders.data.errors ? false : true,
|
||||
level: orders.data.errors ? "error" : "info",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: orders.data.errors
|
||||
? `Loading order was cleared but encountered an error: \n${orders.data.errors[0].message} \nPossible reason for this is the loading order was completed via scanner or other means.`
|
||||
: `Loading order ${validated.loadingOrder} was just closed.`,
|
||||
data: data ?? [],
|
||||
status: orders.data.errors ? 400 : 200,
|
||||
});
|
||||
} catch (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Failed to Close 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;
|
||||
201
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
201
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
// sends the units from the dock door scanner here.
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScans } from "../db/schema/dockdoor.scans.schema.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { createLogger } from "../logger/logger.controller.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 postScan = async (data: any) => {
|
||||
try {
|
||||
const newScan = (await db
|
||||
.insert(dockDoorScans)
|
||||
.values({
|
||||
dockId: data.dockId,
|
||||
loadingOrder: data.loadingOrder,
|
||||
loadingUnit: data.loadingUnit.sscc ?? data.loadingUnit.runningNo, // can be running number or sscc depending on where it came from
|
||||
loadingUnitStatus: data.loadingUnitStatus, // TODO: add enums on the status of each load.
|
||||
message: data.message, // the response it gave when scanning
|
||||
status: data.status ?? undefined,
|
||||
})
|
||||
.returning()) as any;
|
||||
|
||||
emitToRoom(`dockDoorLoading:${data.dockId}`, newScan[0]);
|
||||
} catch (error) {
|
||||
console.log("Error: ", error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadUnit = async (data: Data) => {
|
||||
const log = createLogger({ module: "dockdoor", subModule: "loadunit" });
|
||||
log.info({ stack: data }, "Data Passed over from the scanner.");
|
||||
// are we even active at this time?
|
||||
const dockDoorActive = await db.query.settings.findFirst({
|
||||
where: (u, { eq }) => eq(u.name, "dockDoorScanning"),
|
||||
});
|
||||
|
||||
const unitToScan = data.sscc
|
||||
? { sscc: data.sscc !== "noread" ? data.sscc?.slice(2) : data.sscc }
|
||||
: { runningNo: Number(data.runningNo) };
|
||||
|
||||
const dock = await db
|
||||
.select()
|
||||
.from(dockDoorScanners)
|
||||
.where(eq(dockDoorScanners.dockId, data.dockId as string));
|
||||
|
||||
if (!dockDoorActive?.active) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadunit",
|
||||
message: "Dock door scanning feature is not active.",
|
||||
data: [],
|
||||
notify: false,
|
||||
room: "",
|
||||
});
|
||||
}
|
||||
|
||||
// check if we currently have a loading order attached to the dock door.
|
||||
|
||||
if (dock[0]?.currentLoadingOrder === "") {
|
||||
postScan({
|
||||
dockId: data.dockId,
|
||||
loadingOrder: dock[0]?.currentLoadingOrder,
|
||||
loadingUnit: unitToScan,
|
||||
loadingUnitStatus: "notLoaded",
|
||||
message:
|
||||
"There are know current active loading orders please start one and try again.",
|
||||
status: "error",
|
||||
});
|
||||
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,
|
||||
});
|
||||
}
|
||||
// check if its a valids an sscc
|
||||
|
||||
if (data.sscc === "noread") {
|
||||
postScan({
|
||||
dockId: data.dockId,
|
||||
loadingOrder: dock[0]?.currentLoadingOrder,
|
||||
loadingUnit: unitToScan,
|
||||
loadingUnitStatus: "noread",
|
||||
message:
|
||||
"Failed to load the unit to the truck, there was no pallet read.",
|
||||
status: "error",
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: pallet validation, check if we are on hold, then check if we have been in the staging warehouse for more than x time.
|
||||
|
||||
// if on hold stop the scan and send a bad read with the reason its on hold and what its on hold for, including coa.
|
||||
|
||||
// if precheck is active then check if we have a warehouse, then check if the pallet was in the warehouse for greater than the define min, all fails send a warning and still do the scan
|
||||
|
||||
// add the loading units
|
||||
try {
|
||||
const prod = (await runProdApi({
|
||||
method: "post",
|
||||
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`,
|
||||
data: [unitToScan],
|
||||
})) as any;
|
||||
|
||||
//emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data ?? []);
|
||||
|
||||
if (!prod?.success) {
|
||||
postScan({
|
||||
dockId: data.dockId,
|
||||
loadingOrder: dock[0]?.currentLoadingOrder,
|
||||
loadingUnit: unitToScan,
|
||||
loadingUnitStatus: "notLoaded",
|
||||
message: prod?.data.errors[0].message,
|
||||
status: "error",
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
postScan({
|
||||
dockId: data.dockId,
|
||||
loadingOrder: dock[0]?.currentLoadingOrder,
|
||||
loadingUnit: unitToScan,
|
||||
loadingUnitStatus: "loaded",
|
||||
message: emitData.message,
|
||||
});
|
||||
|
||||
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/*
|
||||
};
|
||||
17
backend/dockdoorScanning/dockdoor.socket.notifications.ts
Normal file
17
backend/dockdoorScanning/dockdoor.socket.notifications.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
|
||||
export async function handleDockScanInsertedNotification(id: string) {
|
||||
const row = await db.query.dockDoorScans.findFirst({
|
||||
where: eq(logs.id, id),
|
||||
});
|
||||
|
||||
if (!row) return;
|
||||
|
||||
// send only to the current dock door
|
||||
if (row.dockId) {
|
||||
emitToRoom(`dockDoorLoading:${row.dockId}`, row);
|
||||
}
|
||||
}
|
||||
22
backend/dockdoorScanning/dockdoor.socket.seed.ts
Normal file
22
backend/dockdoorScanning/dockdoor.socket.seed.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { db } from "../db/db.controller.js";
|
||||
|
||||
export const getRecentDockScans = ({
|
||||
loadingOrder,
|
||||
limit = 200,
|
||||
}: {
|
||||
loadingOrder: string;
|
||||
limit?: number | undefined;
|
||||
}) => {
|
||||
return db.query.dockDoorScans.findMany({
|
||||
//where: (scans, { eq }) => eq(scans.status, "active"),
|
||||
where: (scans, { and, eq }) =>
|
||||
and(
|
||||
eq(scans.status, "active"),
|
||||
loadingOrder
|
||||
? eq(scans.loadingOrder, loadingOrder)
|
||||
: eq(scans.loadingOrder, "0"),
|
||||
),
|
||||
orderBy: (scans, { desc }) => [desc(scans.upd_date)],
|
||||
limit,
|
||||
});
|
||||
};
|
||||
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;
|
||||
107
backend/eom/eom.gpdata.route.ts
Normal file
107
backend/eom/eom.gpdata.route.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { Router } from "express";
|
||||
import { gpQuery } from "../gpSql/gpSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlGPQuery,
|
||||
sqlGpQuerySelector,
|
||||
} from "../gpSql/gpSqlQuerySelector.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { startDate, endDate, glCode, includePlantToken } = req.query;
|
||||
|
||||
if (
|
||||
!startDate ||
|
||||
startDate === "" ||
|
||||
!endDate ||
|
||||
endDate === "" ||
|
||||
!glCode ||
|
||||
glCode === ""
|
||||
) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "GpData",
|
||||
message:
|
||||
"The start date, end date, and gl code are required to run this query.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const sqlQuery = sqlGpQuerySelector(`gp.eom.data`) as SqlGPQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "GpData",
|
||||
message:
|
||||
"Failed to get GpData sql file please, please contact support if this continues.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
gpQuery(
|
||||
sqlQuery.query
|
||||
.replace("[startDate]", startDate as string)
|
||||
.replace("[endDate]", endDate as string)
|
||||
.replace("[gpCode]", glCode as string),
|
||||
"Eom GpData data",
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "GpData",
|
||||
message: `Error getting GpData data info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: data.success,
|
||||
level: data.success ? "info" : "error",
|
||||
module: "eom",
|
||||
subModule: "GpData",
|
||||
message: data.message,
|
||||
data:
|
||||
includePlantToken === "true" && data.success
|
||||
? data.data.map((i) => {
|
||||
return {
|
||||
plantToken: process.env.PROD_PLANT_TOKEN,
|
||||
...i,
|
||||
Date_Received: formatInTimeZone(
|
||||
i.Date_Received,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
})
|
||||
: data.data.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
Date_Received: formatInTimeZone(
|
||||
i.Date_Received,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
}),
|
||||
notify: false,
|
||||
status: data.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
9
backend/eom/eom.history.controller.ts
Normal file
9
backend/eom/eom.history.controller.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* The flow that will trigger all the history functions to run and store the data each day and clean up as needed
|
||||
*
|
||||
* if we are on usmcd1vms036 we will run a get request to all servers in the db so we can store that data as well.
|
||||
*
|
||||
* this will be stored in both. the vms036 functions will store in a bigger server so it can be pulled faster and in ssrs
|
||||
*/
|
||||
|
||||
export const eomHistory = async () => {};
|
||||
61
backend/eom/eom.historyInv.route.ts
Normal file
61
backend/eom/eom.historyInv.route.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { format } from "date-fns";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { invHistoricalData } from "../db/schema/historicalInv.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
// the params we are wanting to add in. min required will be the month so we dont pass everything over
|
||||
const { date } = req.query;
|
||||
|
||||
if (!date || date === "") {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "historical inv",
|
||||
message:
|
||||
"The day of the month is required to be included in order to pass.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// get the date passed over.
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(invHistoricalData)
|
||||
.where(
|
||||
eq(invHistoricalData.histDate, format(date as string, "yyyy-MM-dd")),
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "historical inv",
|
||||
message:
|
||||
"There was an error getting the historical data from the server.",
|
||||
data: error as any,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "eom",
|
||||
subModule: "historical inv",
|
||||
message: "Eom Historical Inv Data",
|
||||
data: data,
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
export default r;
|
||||
77
backend/eom/eom.lastPurchasePrice.route.ts
Normal file
77
backend/eom/eom.lastPurchasePrice.route.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Router } from "express";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { includePlantToken } = req.query;
|
||||
|
||||
const sqlQuery = sqlQuerySelector(`eom.lastPurchasePrice`) as SqlQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "lastPurchasePrice",
|
||||
message:
|
||||
"Failed to get last sales price sql file please, please contact support if this continues.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(
|
||||
sqlQuery.query,
|
||||
|
||||
"Eom last purchase price data",
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "lastPurchasePrice",
|
||||
message: `Error getting last purchase Price data info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: data.success,
|
||||
level: data.success ? "info" : "error",
|
||||
module: "eom",
|
||||
subModule: "lastPurchasePrice",
|
||||
message: data.message,
|
||||
data:
|
||||
includePlantToken === "true" && data.success
|
||||
? data.data.map((i) => {
|
||||
return {
|
||||
plantToken: process.env.PROD_PLANT_TOKEN,
|
||||
...i,
|
||||
//validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
|
||||
};
|
||||
})
|
||||
: data.data.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
//validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
|
||||
};
|
||||
}),
|
||||
notify: false,
|
||||
status: data.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
89
backend/eom/eom.lastSalesPrice.route.ts
Normal file
89
backend/eom/eom.lastSalesPrice.route.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { Router } from "express";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { date, includePlantToken } = req.query;
|
||||
|
||||
if (!date || date === "") {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "lastSalesPrice",
|
||||
message: "A date is required to run this query.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const sqlQuery = sqlQuerySelector(`eom.lastSalesPrice`) as SqlQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "lastSalesPrice",
|
||||
message:
|
||||
"Failed to get last sales price sql file please, please contact support if this continues.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(
|
||||
sqlQuery.query.replace("[date]", date as string),
|
||||
|
||||
"Eom last sales price data",
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "lastSalesPrice",
|
||||
message: `Error getting last Sales Price data info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: data.success,
|
||||
level: data.success ? "info" : "error",
|
||||
module: "eom",
|
||||
subModule: "lastSalesPrice",
|
||||
message: data.message,
|
||||
data:
|
||||
includePlantToken === "true" && data.success
|
||||
? data.data.map((i) => {
|
||||
return {
|
||||
plantToken: process.env.PROD_PLANT_TOKEN,
|
||||
...i,
|
||||
validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
|
||||
};
|
||||
})
|
||||
: data.data.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
validDate: formatInTimeZone(i.validDate, "etc/utc", "M/d/yyyy"),
|
||||
};
|
||||
}),
|
||||
notify: false,
|
||||
status: data.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
97
backend/eom/eom.productionConsumption.route.ts
Normal file
97
backend/eom/eom.productionConsumption.route.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Router } from "express";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { startDate, endDate, includePlantToken } = req.query;
|
||||
|
||||
if (!startDate || startDate === "" || !endDate || endDate === "") {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "productionConsumption",
|
||||
message: "The start date and end date are required to run this query.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const sqlQuery = sqlQuerySelector(`eom.productionConsumption`) as SqlQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "productionConsumption",
|
||||
message:
|
||||
"Failed to get production consumption sql file please, please contact support if this continues.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(
|
||||
sqlQuery.query
|
||||
.replace("[startDate]", startDate as string)
|
||||
.replace("[endDate]", endDate as string),
|
||||
"Eom production consumption data",
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "productionConsumption",
|
||||
message: `Error getting production consumption data info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: data.success,
|
||||
level: data.success ? "info" : "error",
|
||||
module: "eom",
|
||||
subModule: "productionConsumption",
|
||||
message: data.message,
|
||||
data:
|
||||
includePlantToken === "true" && data.success
|
||||
? data.data.map((i) => {
|
||||
return {
|
||||
plantToken: process.env.PROD_PLANT_TOKEN,
|
||||
...i,
|
||||
// Prod_Date: formatInTimeZone(
|
||||
// i.Prod_Date,
|
||||
// "etc/utc",
|
||||
// "M/d/yyyy",
|
||||
// ),
|
||||
};
|
||||
})
|
||||
: data.data.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
// Prod_Date: formatInTimeZone(
|
||||
// i.Prod_Date,
|
||||
// "etc/utc",
|
||||
// "M/d/yyyy",
|
||||
// ),
|
||||
};
|
||||
}),
|
||||
notify: false,
|
||||
status: data.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
98
backend/eom/eom.purchased.route.ts
Normal file
98
backend/eom/eom.purchased.route.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { Router } from "express";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { startDate, endDate, includePlantToken } = req.query;
|
||||
|
||||
if (!startDate || startDate === "" || !endDate || endDate === "") {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "purchased",
|
||||
message: "The start date and end date are required to run this query.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const sqlQuery = sqlQuerySelector(`eom.purchased`) as SqlQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "purchased",
|
||||
message:
|
||||
"Failed to get purchased sql file please, please contact support if this continues.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(
|
||||
sqlQuery.query
|
||||
.replace("[startDate]", startDate as string)
|
||||
.replace("[endDate]", endDate as string),
|
||||
"Eom purchased data",
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "purchased",
|
||||
message: `Error getting purchased data info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: data.success,
|
||||
level: data.success ? "info" : "error",
|
||||
module: "eom",
|
||||
subModule: "purchased",
|
||||
message: data.message,
|
||||
data:
|
||||
includePlantToken === "true" && data.success
|
||||
? data.data.map((i) => {
|
||||
return {
|
||||
plantToken: process.env.PROD_PLANT_TOKEN,
|
||||
...i,
|
||||
Received_Date: formatInTimeZone(
|
||||
i.Received_Date,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
})
|
||||
: data.data.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
Received_Date: formatInTimeZone(
|
||||
i.Received_Date,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
}),
|
||||
notify: false,
|
||||
status: data.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
98
backend/eom/eom.regrind.route.ts
Normal file
98
backend/eom/eom.regrind.route.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { Router } from "express";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { startDate, endDate, includePlantToken } = req.query;
|
||||
|
||||
if (!startDate || startDate === "" || !endDate || endDate === "") {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "regrind",
|
||||
message: "The start date and end date are required to run this query.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const sqlQuery = sqlQuerySelector(`eom.regrind`) as SqlQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "regrind",
|
||||
message:
|
||||
"Failed to get regrind sql file please, please contact support if this continues.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(
|
||||
sqlQuery.query
|
||||
.replace("[startDate]", startDate as string)
|
||||
.replace("[endDate]", endDate as string),
|
||||
"Eom regrind data",
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "regrind",
|
||||
message: `Error getting regrind data info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: data.success,
|
||||
level: data.success ? "info" : "error",
|
||||
module: "eom",
|
||||
subModule: "regrind",
|
||||
message: data.message,
|
||||
data:
|
||||
includePlantToken === "true" && data.success
|
||||
? data.data.map((i) => {
|
||||
return {
|
||||
plantToken: process.env.PROD_PLANT_TOKEN,
|
||||
...i,
|
||||
Buchungsdatum: formatInTimeZone(
|
||||
i.Buchungsdatum,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
})
|
||||
: data.data.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
Buchungsdatum: formatInTimeZone(
|
||||
i.Buchungsdatum,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
}),
|
||||
notify: false,
|
||||
status: data.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
35
backend/eom/eom.routes.ts
Normal file
35
backend/eom/eom.routes.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { Express } from "express";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import gpData from "./eom.gpdata.route.js";
|
||||
import historyInv from "./eom.historyInv.route.js";
|
||||
import lastPurchasePrice from "./eom.lastPurchasePrice.route.js";
|
||||
import lastSalesPrice from "./eom.lastSalesPrice.route.js";
|
||||
import productionConsumption from "./eom.productionConsumption.route.js";
|
||||
import purchased from "./eom.purchased.route.js";
|
||||
import regrind from "./eom.regrind.route.js";
|
||||
import soldItems from "./eom.soldItems.route.js";
|
||||
|
||||
export const setupEomRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
|
||||
app.use(`${baseUrl}/api/eom/historyInv`, featureCheck("eom"), historyInv);
|
||||
app.use(`${baseUrl}/api/eom/purchased`, featureCheck("eom"), purchased);
|
||||
app.use(
|
||||
`${baseUrl}/api/eom/lastSalesPrice`,
|
||||
featureCheck("eom"),
|
||||
lastSalesPrice,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/eom/lastPurchasePrice`,
|
||||
featureCheck("eom"),
|
||||
lastPurchasePrice,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/eom/productionConsumption`,
|
||||
featureCheck("eom"),
|
||||
productionConsumption,
|
||||
);
|
||||
app.use(`${baseUrl}/api/eom/regrind`, featureCheck("eom"), regrind);
|
||||
app.use(`${baseUrl}/api/eom/soldItems`, featureCheck("eom"), soldItems);
|
||||
app.use(`${baseUrl}/api/eom/gpData`, featureCheck("eom"), gpData);
|
||||
};
|
||||
98
backend/eom/eom.soldItems.route.ts
Normal file
98
backend/eom/eom.soldItems.route.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { Router } from "express";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
const { startDate, endDate, includePlantToken } = req.query;
|
||||
|
||||
if (!startDate || startDate === "" || !endDate || endDate === "") {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "soldItems",
|
||||
message: "The start date and end date are required to run this query.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const sqlQuery = sqlQuerySelector(`eom.soldItems`) as SqlQuery;
|
||||
|
||||
if (!sqlQuery.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "soldItems",
|
||||
message:
|
||||
"Failed to get soldItems sql file please, please contact support if this continues.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(
|
||||
sqlQuery.query
|
||||
.replace("[startDate]", startDate as string)
|
||||
.replace("[endDate]", endDate as string),
|
||||
"Eom soldItems data",
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "eom",
|
||||
subModule: "soldItems",
|
||||
message: `Error getting soldItems data info`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: data.success,
|
||||
level: data.success ? "info" : "error",
|
||||
module: "eom",
|
||||
subModule: "soldItems",
|
||||
message: data.message,
|
||||
data:
|
||||
includePlantToken === "true" && data.success
|
||||
? data.data.map((i) => {
|
||||
return {
|
||||
plantToken: process.env.PROD_PLANT_TOKEN,
|
||||
...i,
|
||||
DeliveryDate: formatInTimeZone(
|
||||
i.DeliveryDate,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
})
|
||||
: data.data.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
DeliveryDate: formatInTimeZone(
|
||||
i.DeliveryDate,
|
||||
"etc/utc",
|
||||
"M/d/yyyy",
|
||||
),
|
||||
};
|
||||
}),
|
||||
notify: false,
|
||||
status: data.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type Express, Router } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
|
||||
import restart from "./gpSqlRestart.route.js";
|
||||
import start from "./gpSqlStart.route.js";
|
||||
import stop from "./gpSqlStop.route.js";
|
||||
@@ -7,11 +8,10 @@ export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
|
||||
//setup all the routes
|
||||
// Apply auth to entire router
|
||||
const router = Router();
|
||||
router.use(requireAuth);
|
||||
|
||||
router.use(start);
|
||||
router.use(stop);
|
||||
router.use(restart);
|
||||
|
||||
app.use(`${baseUrl}/api/system/gpSql`, router);
|
||||
app.use(`${baseUrl}/api/system/gpSql`, requireAuth, router);
|
||||
};
|
||||
|
||||
16
backend/gpSql/queries/gp.eom.data.sql
Normal file
16
backend/gpSql/queries/gp.eom.data.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
select * from (
|
||||
select
|
||||
case when x.POPRCTNM is null then p.POPRCTNM else p.POPRCTNM end as RCT_Num,
|
||||
PONUMBER PO,
|
||||
p.VENDORID Supplier,
|
||||
ITEMNMBR Item,
|
||||
QTYSHPPD shipped,
|
||||
UOFM Type,
|
||||
TRXLOCTN Location,
|
||||
case when CONVERT(DATE, x.receiptdate) is null then convert(date, p.DATERECD) else CONVERT(DATE, x.receiptdate) end as Date_Received
|
||||
from ALPLA.dbo.pop10500 (nolock) as p
|
||||
left join
|
||||
ALPLA.dbo.POP10300 as x on p.POPRCTNM = x.POPRCTNM
|
||||
WHERE TRXLOCTN LIKE '[gpCode]%' and p.POPTYPE = 1) a
|
||||
|
||||
where Date_Received BETWEEN '[startDate]' AND '[endDate]'
|
||||
@@ -3,7 +3,6 @@ import { Writable } from "node:stream";
|
||||
import pino, { type Logger } from "pino";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { notifySystemIssue } from "./logger.notify.js";
|
||||
//import build from "pino-abstract-transport";
|
||||
@@ -37,7 +36,7 @@ const dbStream = new Writable({
|
||||
subModule: obj?.subModule?.toLowerCase(),
|
||||
hostname: obj?.hostname?.toLowerCase(),
|
||||
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(),
|
||||
);
|
||||
@@ -50,10 +49,10 @@ const dbStream = new Writable({
|
||||
notifySystemIssue(obj);
|
||||
}
|
||||
|
||||
if (obj.room) {
|
||||
emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
||||
}
|
||||
emitToRoom("logs", res.data ? res.data[0] : obj);
|
||||
// if (obj.room) {
|
||||
// emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
||||
// }
|
||||
// emitToRoom("logs", res.data ? res.data[0] : obj);
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.error("DB log insert error:", err);
|
||||
|
||||
24
backend/logger/logger.socket.notifications.ts
Normal file
24
backend/logger/logger.socket.notifications.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
|
||||
export async function handleLogInsertedNotification(id: string) {
|
||||
const row = await db.query.logs.findFirst({
|
||||
where: eq(logs.id, id),
|
||||
});
|
||||
|
||||
if (!row) return;
|
||||
|
||||
// More targeted rooms.
|
||||
if (row.module) {
|
||||
emitToRoom(`logs:${row.module}`, row);
|
||||
}
|
||||
|
||||
if (row.subModule) {
|
||||
emitToRoom(`logs:${row.subModule}`, row);
|
||||
}
|
||||
|
||||
// Everyone listening to all logs.
|
||||
emitToRoom("logs", row);
|
||||
}
|
||||
92
backend/logistics/logistics.dm.forecast.map.abbott.ts
Normal file
92
backend/logistics/logistics.dm.forecast.map.abbott.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import XLSX from "xlsx";
|
||||
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||
import { postData } from "./logistics.dm.postData.js";
|
||||
|
||||
export const abbottForecast = async (sheet: any, user: any) => {
|
||||
/*
|
||||
This is the forecast but will only be triggered when the actual sheet is passed over from the orders in. this is being done this way due to the truck list being sent over as well.
|
||||
*/
|
||||
const customerId = 8;
|
||||
const posting: any = [];
|
||||
|
||||
const customHeaders = [
|
||||
"date",
|
||||
"time",
|
||||
"newton8oz",
|
||||
"newton10oz",
|
||||
"E",
|
||||
"F",
|
||||
"fDate",
|
||||
"f8ozqty",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"f10ozqty",
|
||||
];
|
||||
const forecastData = XLSX.utils.sheet_to_json(sheet, {
|
||||
range: 5, // Start at row 5 (index 4)
|
||||
header: customHeaders,
|
||||
defval: "", // Default value for empty cells
|
||||
});
|
||||
|
||||
for (let i = 1; i < forecastData.length; i++) {
|
||||
const row: any = forecastData[i];
|
||||
//console.log(row);
|
||||
//if (row.fDate == undefined) continue;
|
||||
|
||||
if (row.fDate !== "") {
|
||||
const date = isNaN(row.fDate)
|
||||
? new Date(row.fDate)
|
||||
: excelDateStuff(row.fDate);
|
||||
// for 8oz do
|
||||
if (row.f8ozqty > 0) {
|
||||
posting.push({
|
||||
customerArticleNo: "45300DA",
|
||||
quantity: row.f8ozqty,
|
||||
requirementDate: date,
|
||||
});
|
||||
}
|
||||
|
||||
if (row.f10ozqty > 0) {
|
||||
posting.push({
|
||||
customerArticleNo: "43836DA",
|
||||
quantity: row.f10ozqty,
|
||||
requirementDate: date,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// the predefined data that will never change
|
||||
const predefinedObject = {
|
||||
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||
"en-US",
|
||||
)}`,
|
||||
sender: user.username || "lst-system",
|
||||
customerId: customerId,
|
||||
positions: [],
|
||||
};
|
||||
|
||||
// add the new forecast to the predefined data
|
||||
const updatedPredefinedObject = {
|
||||
...predefinedObject,
|
||||
positions: [...predefinedObject.positions, ...posting],
|
||||
};
|
||||
|
||||
const forecast: any = await postData(
|
||||
{
|
||||
type: "forecast",
|
||||
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||
data: updatedPredefinedObject as any,
|
||||
},
|
||||
user,
|
||||
);
|
||||
|
||||
return {
|
||||
success: forecast.success,
|
||||
message: forecast.message,
|
||||
data: forecast.data,
|
||||
};
|
||||
};
|
||||
86
backend/logistics/logistics.dm.forecast.map.energizer.ts
Normal file
86
backend/logistics/logistics.dm.forecast.map.energizer.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import XLSX from "xlsx";
|
||||
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||
import { postData } from "./logistics.dm.postData.js";
|
||||
|
||||
export const energizerForecast = async (data: any, user: any) => {
|
||||
/**
|
||||
* Post a standard forecast based on the standard template.
|
||||
*/
|
||||
const buffer = Buffer.from(data.buffer);
|
||||
|
||||
const workbook = XLSX.read(buffer, { type: "buffer" });
|
||||
|
||||
const sheet: any = workbook.Sheets.Sheet1;
|
||||
// const range = XLSX.utils.decode_range(sheet["!ref"]);
|
||||
|
||||
// const headers = [
|
||||
// "CustomerArticleNumber",
|
||||
// "Quantity",
|
||||
// "RequirementDate",
|
||||
// "CustomerID",
|
||||
// ];
|
||||
|
||||
// formatting the data
|
||||
const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 }) as any;
|
||||
|
||||
const posting: any = [];
|
||||
const customerId = 44;
|
||||
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
const row: any = rows[i];
|
||||
const material = row[0];
|
||||
|
||||
if (material === undefined) continue;
|
||||
for (let j = 1; j < row.length; j++) {
|
||||
const qty = row[j];
|
||||
|
||||
if (qty && qty > 0) {
|
||||
const requirementDate = rows[0][j]; // first row is dates
|
||||
const date = Number.isNaN(requirementDate)
|
||||
? new Date(requirementDate)
|
||||
: excelDateStuff(requirementDate);
|
||||
|
||||
posting.push({
|
||||
customerArticleNo: material,
|
||||
quantity: qty,
|
||||
requirementDate: date,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(posting);
|
||||
|
||||
// the predefined data that will never change
|
||||
const predefinedObject = {
|
||||
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||
"en-US",
|
||||
)}`,
|
||||
sender: user.username || "lst-system",
|
||||
customerId: customerId,
|
||||
positions: [],
|
||||
};
|
||||
|
||||
// add the new forecast to the predefined data
|
||||
const updatedPredefinedObject = {
|
||||
...predefinedObject,
|
||||
positions: [...predefinedObject.positions, ...posting],
|
||||
};
|
||||
|
||||
//post it
|
||||
const forecastData: any = await postData(
|
||||
{
|
||||
type: "forecast",
|
||||
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||
data: updatedPredefinedObject as any,
|
||||
},
|
||||
user,
|
||||
);
|
||||
|
||||
return {
|
||||
success: forecastData.success,
|
||||
message: forecastData.message,
|
||||
data: forecastData.data,
|
||||
};
|
||||
};
|
||||
245
backend/logistics/logistics.dm.forecast.map.loreal.ts
Normal file
245
backend/logistics/logistics.dm.forecast.map.loreal.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { addDays } from "date-fns";
|
||||
import XLSX from "xlsx";
|
||||
import { runDatamartQuery } from "../datamart/datamart.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 { postData } from "./logistics.dm.postData.js";
|
||||
|
||||
const customerID = 4;
|
||||
export const lorealForecast = async (data: any, user: any) => {
|
||||
/**
|
||||
* Post a standard forecast based on the standard template.
|
||||
*/
|
||||
|
||||
const buffer = Buffer.from(data.buffer);
|
||||
|
||||
const workbook = XLSX.read(buffer, { type: "buffer" });
|
||||
|
||||
const sheet: any = workbook.Sheets["Alpla HDPE"];
|
||||
const range = XLSX.utils.decode_range(sheet["!ref"]);
|
||||
|
||||
const psheet: any = workbook.Sheets["Alpla PET"];
|
||||
const prange = XLSX.utils.decode_range(psheet["!ref"]);
|
||||
|
||||
const headers = [];
|
||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||
const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1
|
||||
const cell = sheet[cellAddress];
|
||||
headers.push(cell ? cell.v : undefined);
|
||||
}
|
||||
|
||||
const pheaders = [];
|
||||
for (let C = prange.s.c; C <= prange.e.c; ++C) {
|
||||
const cellAddress = XLSX.utils.encode_cell({ r: 1, c: C }); // row 0 = Excel row 1
|
||||
const cell = psheet[cellAddress];
|
||||
pheaders.push(cell ? cell.v : undefined);
|
||||
}
|
||||
|
||||
const ebmForeCastData: any = XLSX.utils.sheet_to_json(sheet, {
|
||||
defval: "",
|
||||
header: headers,
|
||||
range: 3,
|
||||
});
|
||||
|
||||
const petForeCastData: any = XLSX.utils.sheet_to_json(psheet, {
|
||||
defval: "",
|
||||
header: pheaders,
|
||||
range: 3,
|
||||
});
|
||||
|
||||
const ebmForecastData: any = [];
|
||||
const missingSku: any = [];
|
||||
|
||||
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||
|
||||
if (!avSQLQuery.success) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "forecast",
|
||||
message: `Error getting Article info`,
|
||||
data: [avSQLQuery.message],
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
const { data: a, error: ae } = await tryCatch(
|
||||
runDatamartQuery({ name: "activeArticles", options: {} }),
|
||||
);
|
||||
|
||||
if (ae) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Error getting active av",
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
const article: any = a?.data;
|
||||
|
||||
//console.log(article);
|
||||
|
||||
// process the ebm forcast
|
||||
for (let i = 0; i < ebmForeCastData.length; i++) {
|
||||
// bottle code
|
||||
const sku = ebmForeCastData[i]["HDPE Bottle Code"];
|
||||
|
||||
// ignore the blanks
|
||||
if (sku === "") continue;
|
||||
|
||||
// ignore zero qty
|
||||
// if (ebmForeCastData[i][`Day ${i}`]) continue;
|
||||
|
||||
for (let f = 0; f <= 90; f++) {
|
||||
const day = `Day ${f + 1}`;
|
||||
// if (ebmForeCastData[i][day] === 0) continue;
|
||||
|
||||
const forcast = {
|
||||
customerArticleNo: sku,
|
||||
requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)),
|
||||
quantity: ebmForeCastData[i][day] ?? 0,
|
||||
};
|
||||
|
||||
if (forcast.quantity === 0) continue;
|
||||
|
||||
// checking to make sure there is a real av to add to.
|
||||
const activeAV = article.filter(
|
||||
(c: any) =>
|
||||
c?.CustomerArticleNumber === forcast.customerArticleNo.toString(),
|
||||
);
|
||||
|
||||
if (activeAV.length === 0) {
|
||||
if (typeof forcast.customerArticleNo === "number") {
|
||||
missingSku.push(forcast);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ebmForecastData.push(forcast);
|
||||
}
|
||||
|
||||
//console.log(ebmForeCastData.length);
|
||||
}
|
||||
|
||||
// pet forecast
|
||||
for (let i = 0; i < petForeCastData.length; i++) {
|
||||
// bottle code
|
||||
const sku = petForeCastData[i]["South PET Bottle Code"];
|
||||
|
||||
// ignore the blanks
|
||||
if (sku === "") continue;
|
||||
|
||||
// ignore zero qty
|
||||
// if (ebmForeCastData[i][`Day ${i}`]) continue;
|
||||
|
||||
for (let f = 0; f <= 90; f++) {
|
||||
const day = `Day ${f + 1}`;
|
||||
// if (ebmForeCastData[i][day] === 0) continue;
|
||||
|
||||
const forcast = {
|
||||
customerArticleNo: sku,
|
||||
requirementDate: addDays(new Date(Date.now()), f), //excelDateStuff(parseInt(date)),
|
||||
quantity: petForeCastData[i][day] ?? 0,
|
||||
};
|
||||
|
||||
if (forcast.quantity === 0 || forcast.quantity === "") continue;
|
||||
|
||||
if (forcast.customerArticleNo < 99999) {
|
||||
//console.log(`Sku a normal av ${forcast.customerArticleNo}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// checking to make sure there is a real av to add to.
|
||||
const activeAV = article.filter(
|
||||
(c: any) =>
|
||||
c?.CustomerArticleNumber === forcast.customerArticleNo.toString(),
|
||||
);
|
||||
|
||||
if (activeAV.length === 0) {
|
||||
if (typeof forcast.customerArticleNo === "number") {
|
||||
missingSku.push(forcast);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ebmForecastData.push(forcast);
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(comForecast);
|
||||
|
||||
// email the for the missing ones
|
||||
// const missedGrouped = Object.values(
|
||||
// missingSku.reduce((acc: any, item: any) => {
|
||||
// const key = item.customerArticleNo;
|
||||
|
||||
// if (!acc[key]) {
|
||||
// // first time we see this customer
|
||||
// acc[key] = item;
|
||||
// } else {
|
||||
// // compare dates and keep the earliest
|
||||
// if (
|
||||
// new Date(item.requirementDate) < new Date(acc[key].requirementDate)
|
||||
// ) {
|
||||
// acc[key] = item;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return acc;
|
||||
// }, {}),
|
||||
// );
|
||||
|
||||
// TODO: change this to a flagged notification so that he users can subscribe or leave it. this removes the hardcody shit.
|
||||
// const emailSetup = {
|
||||
// email:
|
||||
// "Blake.matthes@alpla.com; Stuart.Gladney@alpla.com; Harold.Mccalister@alpla.com; Jenn.Osbourn@alpla.com",
|
||||
// subject:
|
||||
// missedGrouped.length > 0
|
||||
// ? `Alert! There are ${missedGrouped.length}, missing skus.`
|
||||
// : `Alert! There is a missing SKU.`,
|
||||
// template: "missingLorealSkus",
|
||||
// context: {
|
||||
// items: missedGrouped,
|
||||
// },
|
||||
// };
|
||||
// sendEmail(emailSetup);
|
||||
|
||||
// if the customerarticle number is not matching just ignore it
|
||||
const predefinedObject = {
|
||||
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||
"en-US",
|
||||
)}`,
|
||||
sender: user.username || "lst-system",
|
||||
customerId: customerID,
|
||||
positions: [],
|
||||
};
|
||||
|
||||
const updatedPredefinedObject = {
|
||||
...predefinedObject,
|
||||
positions: [...predefinedObject.positions, ...ebmForecastData],
|
||||
};
|
||||
// console.log(updatedPredefinedObject);
|
||||
|
||||
// posting the data to the new backend so we can store the data.
|
||||
const posting: any = await postData(
|
||||
{
|
||||
type: "forecast",
|
||||
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||
data: updatedPredefinedObject as any,
|
||||
},
|
||||
user,
|
||||
);
|
||||
|
||||
return {
|
||||
success: posting.success,
|
||||
message: posting.message,
|
||||
data: posting.data === "" ? ebmForecastData : posting.data,
|
||||
};
|
||||
};
|
||||
182
backend/logistics/logistics.dm.forecast.map.pNg.ts
Normal file
182
backend/logistics/logistics.dm.forecast.map.pNg.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import XLSX from "xlsx";
|
||||
import { runDatamartQuery } from "../datamart/datamart.controller.js";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { settings } from "../db/schema/settings.schema.js";
|
||||
import {
|
||||
type SqlQuery,
|
||||
sqlQuerySelector,
|
||||
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { postData } from "./logistics.dm.postData.js";
|
||||
|
||||
export const pNgForecast = async (data: any, user: any) => {
|
||||
/**
|
||||
* Post a standard forecast based on the standard template.
|
||||
*/
|
||||
|
||||
const { data: s, error: e } = await tryCatch(db.select().from(settings));
|
||||
|
||||
if (e) {
|
||||
return {
|
||||
sucess: false,
|
||||
message: `Error getting settings`,
|
||||
data: e,
|
||||
};
|
||||
}
|
||||
|
||||
const pNg = s.filter((n: any) => n.name === "pNgAddress");
|
||||
|
||||
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||
|
||||
if (!avSQLQuery.success) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "logistics",
|
||||
subModule: "forecast",
|
||||
message: `Error getting Article info`,
|
||||
data: [avSQLQuery.message],
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
|
||||
const { data: a, error: ae } = await tryCatch(
|
||||
runDatamartQuery({ name: "activeArticles", options: {} }),
|
||||
);
|
||||
|
||||
if (ae) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Error getting active av",
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
const article: any = a?.data;
|
||||
|
||||
const buffer = Buffer.from(data.buffer);
|
||||
|
||||
const workbook = XLSX.read(buffer, { type: "buffer" });
|
||||
|
||||
//const sheet: any = workbook.Sheets[sheetName];
|
||||
const sheet: any = workbook.Sheets["SchedAgreementUIConfigSpreadshe"];
|
||||
const range = XLSX.utils.decode_range(sheet["!ref"]);
|
||||
|
||||
const headers = [];
|
||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: C }); // row 0 = Excel row 1
|
||||
const cell = sheet[cellAddress];
|
||||
headers.push(cell ? cell.v : undefined);
|
||||
}
|
||||
|
||||
//console.log(headers);
|
||||
const forecastData: any = XLSX.utils.sheet_to_json(sheet, {
|
||||
defval: "",
|
||||
header: headers,
|
||||
range: 1,
|
||||
});
|
||||
|
||||
const groupedByCustomer: any = forecastData.reduce((acc: any, item: any) => {
|
||||
const id = item.CustomerID;
|
||||
if (!acc[id]) {
|
||||
acc[id] = [];
|
||||
}
|
||||
acc[id].push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const foreCastData: any = [];
|
||||
|
||||
for (const [customerID, forecast] of Object.entries(groupedByCustomer)) {
|
||||
//console.log(`Running for Customer ID: ${customerID}`);
|
||||
const newForecast: any = forecast;
|
||||
|
||||
const predefinedObject = {
|
||||
receivingPlantId: process.env.PROD_PLANT_TOKEN ?? "test1",
|
||||
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||
"en-US",
|
||||
)}`,
|
||||
sender: user.username || "lst-system",
|
||||
customerId: pNg[0]?.value,
|
||||
positions: [],
|
||||
};
|
||||
|
||||
// map everything out for each order
|
||||
const nForecast = newForecast.map((o: any) => {
|
||||
// const invoice = i.filter(
|
||||
// (i: any) => i.deliveryAddress === parseInt(customerID)
|
||||
// );
|
||||
// if (!invoice) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
return {
|
||||
customerArticleNo: parseInt(o["Customer Item No."] ?? "0", 10),
|
||||
requirementDate: excelDateStuff(parseInt(o["Request Date"] ?? "0", 10)),
|
||||
quantity: o["Remaining Qty to be Shipped"],
|
||||
};
|
||||
});
|
||||
|
||||
// check to make sure the av belongs in this plant.
|
||||
const onlyNumbers = nForecast.filter((n: any) => n.quantity > 0);
|
||||
const filteredForecast: any = [];
|
||||
|
||||
for (let i = 0; i < nForecast.length; i++) {
|
||||
//console.log(nForecast[i].customerArticleNo);
|
||||
const activeAV = article.filter(
|
||||
(c: any) =>
|
||||
c?.CustomerArticleNumber ===
|
||||
nForecast[i]?.customerArticleNo.toString() &&
|
||||
// validate it works via the default address
|
||||
c?.IdAdresse === parseInt(pNg[0]?.value ?? "139", 10),
|
||||
);
|
||||
|
||||
if (activeAV.length > 0) {
|
||||
filteredForecast.push(onlyNumbers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredForecast.length === 0) {
|
||||
console.log("Nothing to post");
|
||||
return {
|
||||
success: true,
|
||||
message: "No forecast to be posted",
|
||||
data: foreCastData,
|
||||
};
|
||||
}
|
||||
|
||||
// do that fun combining thing
|
||||
const updatedPredefinedObject = {
|
||||
...predefinedObject,
|
||||
positions: [...predefinedObject.positions, ...filteredForecast],
|
||||
};
|
||||
|
||||
//console.log(updatedPredefinedObject);
|
||||
|
||||
// post the orders to the server
|
||||
const posting: any = await postData(
|
||||
{
|
||||
type: "forecast",
|
||||
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||
data: updatedPredefinedObject as any,
|
||||
},
|
||||
user,
|
||||
);
|
||||
|
||||
foreCastData.push({
|
||||
customer: customerID,
|
||||
//totalOrders: orders?.length(),
|
||||
success: posting.success,
|
||||
message: posting.message,
|
||||
data: posting.data,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: foreCastData[0].success,
|
||||
message: foreCastData[0].message,
|
||||
data: foreCastData,
|
||||
};
|
||||
};
|
||||
104
backend/logistics/logistics.dm.forecast.map.standard.ts
Normal file
104
backend/logistics/logistics.dm.forecast.map.standard.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import * as XLSX from "xlsx";
|
||||
import { excelDateStuff } from "../utils/excelToDate.utils.js";
|
||||
import { postData } from "./logistics.dm.postData.js";
|
||||
export const standardForecast = async (data: any, user: any) => {
|
||||
/**
|
||||
* Post a standard forecast based on the standard template.
|
||||
*/
|
||||
|
||||
const plantToken = process.env.PROD_PLANT_TOKEN;
|
||||
|
||||
//const arrayBuffer = await data.arrayBuffer();
|
||||
const buffer = Buffer.from(data.buffer);
|
||||
|
||||
const workbook = XLSX.read(buffer, { type: "buffer" });
|
||||
|
||||
const sheetName = workbook.SheetNames[0] as string;
|
||||
const sheet = workbook.Sheets[sheetName] as any;
|
||||
|
||||
const headers = [
|
||||
"CustomerArticleNumber",
|
||||
"Quantity",
|
||||
"RequirementDate",
|
||||
"CustomerID",
|
||||
];
|
||||
|
||||
const forecastData: any = XLSX.utils.sheet_to_json(sheet, {
|
||||
defval: "",
|
||||
header: headers,
|
||||
range: 1,
|
||||
});
|
||||
|
||||
const groupedByCustomer: any = forecastData.reduce((acc: any, item: any) => {
|
||||
const id = item.CustomerID;
|
||||
if (!acc[id]) {
|
||||
acc[id] = [];
|
||||
}
|
||||
acc[id].push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const foreCastData: any = [];
|
||||
|
||||
for (const [customerID, forecast] of Object.entries(groupedByCustomer)) {
|
||||
//console.log(`Running for Customer ID: ${customerID}`);
|
||||
const newForecast: any = forecast;
|
||||
|
||||
const predefinedObject = {
|
||||
receivingPlantId: plantToken,
|
||||
documentName: `ForecastFromLST-${new Date(Date.now()).toLocaleString(
|
||||
"en-US",
|
||||
)}`,
|
||||
sender: user.username || "lst-system",
|
||||
customerId: customerID,
|
||||
positions: [],
|
||||
};
|
||||
|
||||
// map everything out for each order
|
||||
const nForecast = newForecast.map((o: any) => {
|
||||
// const invoice = i.filter(
|
||||
// (i: any) => i.deliveryAddress === parseInt(customerID)
|
||||
// );
|
||||
// if (!invoice) {
|
||||
// return;
|
||||
// }
|
||||
return {
|
||||
customerArticleNo: o.CustomerArticleNumber,
|
||||
requirementDate: excelDateStuff(parseInt(o.RequirementDate)),
|
||||
quantity: o.Quantity,
|
||||
};
|
||||
});
|
||||
|
||||
// do that fun combining thing
|
||||
const updatedPredefinedObject = {
|
||||
...predefinedObject,
|
||||
positions: [...predefinedObject.positions, ...nForecast],
|
||||
};
|
||||
|
||||
//console.log(updatedPredefinedObject);
|
||||
|
||||
// post the orders to the server
|
||||
const posting: any = await postData(
|
||||
{
|
||||
type: "forecast",
|
||||
endpoint: "/public/v1.0/DemandManagement/DELFOR",
|
||||
data: updatedPredefinedObject as any,
|
||||
},
|
||||
user,
|
||||
);
|
||||
|
||||
foreCastData.push({
|
||||
customer: customerID,
|
||||
//totalOrders: orders?.length(),
|
||||
success: posting.success,
|
||||
message: posting.message,
|
||||
data: posting.data,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: foreCastData[0].success,
|
||||
message: foreCastData[0].message,
|
||||
data: foreCastData,
|
||||
};
|
||||
};
|
||||
95
backend/logistics/logistics.dm.forecast.route.ts
Normal file
95
backend/logistics/logistics.dm.forecast.route.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Router } from "express";
|
||||
import multer from "multer";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { energizerForecast } from "./logistics.dm.forecast.map.energizer.js";
|
||||
import { lorealForecast } from "./logistics.dm.forecast.map.loreal.js";
|
||||
import { pNgForecast } from "./logistics.dm.forecast.map.pNg.js";
|
||||
import { standardForecast } from "./logistics.dm.forecast.map.standard.js";
|
||||
|
||||
type ForecastResult = {
|
||||
success?: boolean;
|
||||
message?: string;
|
||||
data?: unknown;
|
||||
};
|
||||
|
||||
const r = Router();
|
||||
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
});
|
||||
|
||||
r.post("/", requireAuth, upload.single("file"), async (req, res) => {
|
||||
if (!req.file) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dm",
|
||||
subModule: "forecast",
|
||||
message: "A file must be added to be able to run the forecast.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { fileType } = req.body;
|
||||
|
||||
if (typeof fileType !== "string") {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dm",
|
||||
subModule: "forecast",
|
||||
message: "A fileType must be provided.",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
//console.log("fileType:", req.body.fileType);
|
||||
|
||||
let result: ForecastResult;
|
||||
|
||||
switch (fileType) {
|
||||
case "standard":
|
||||
result = await standardForecast(req.file, req.user);
|
||||
break;
|
||||
|
||||
case "loreal":
|
||||
result = await lorealForecast(req.file, req.user);
|
||||
break;
|
||||
|
||||
case "pg":
|
||||
result = await pNgForecast(req.file, req.user);
|
||||
break;
|
||||
|
||||
case "energizer":
|
||||
result = await energizerForecast(req.file, req.user);
|
||||
break;
|
||||
|
||||
default:
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dm",
|
||||
subModule: "forecast",
|
||||
message: `Invalid fileType: ${fileType}`,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: result.success ?? false,
|
||||
level: result.success ? "info" : "error",
|
||||
module: "dm",
|
||||
subModule: "forecast",
|
||||
message: result.success
|
||||
? "The forecast was accepted by Alplaprod 2.0 please check to make sure everything processed properly."
|
||||
: (result.message as string),
|
||||
data: result.data ?? ([] as any),
|
||||
status: result.success ? 200 : 500,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
65
backend/logistics/logistics.dm.postData.ts
Normal file
65
backend/logistics/logistics.dm.postData.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { forecastImport } from "../db/schema/forecastImports.schema.js";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
|
||||
type PostData = {
|
||||
receivingPlantId: string;
|
||||
documentName: string;
|
||||
sender: string;
|
||||
customerId: string;
|
||||
positions: unknown[];
|
||||
};
|
||||
type Data = {
|
||||
type: "orders" | "forecast";
|
||||
endpoint: string;
|
||||
data: PostData;
|
||||
};
|
||||
export const postData = async (data: Data, user: any) => {
|
||||
const posting = await runProdApi(
|
||||
{
|
||||
method: "post",
|
||||
endpoint: data.endpoint,
|
||||
data: [data.data],
|
||||
},
|
||||
"Forecast post",
|
||||
);
|
||||
|
||||
if (!posting?.success) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dm",
|
||||
subModule: data.type === "orders" ? "orders" : "forecast",
|
||||
message:
|
||||
posting?.message ??
|
||||
`Error in posting the ${data.type === "orders" ? "orders" : "forecast"} data`,
|
||||
data: posting?.data ?? [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (posting.success) {
|
||||
if (data.type === "forecast") {
|
||||
await db.insert(forecastImport).values({
|
||||
receivingPlantId: data.data.receivingPlantId ?? "test1",
|
||||
documentName: data.data.documentName ?? "forecast-data-missing",
|
||||
sender: data.data.sender ?? "lst-user",
|
||||
customerId: data.data.customerId ?? "0",
|
||||
rawData: data ?? [],
|
||||
add_user: user.username ?? undefined,
|
||||
upd_user: user.username ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dm",
|
||||
subModule: data.type === "orders" ? "orders" : "forecast",
|
||||
message: posting?.message ?? "",
|
||||
data: (data.data as any) ?? [],
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
79
backend/logistics/logistics.dm.template.route.ts
Normal file
79
backend/logistics/logistics.dm.template.route.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { format } from "date-fns";
|
||||
import { Router } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import { excelTemplate, type Template } from "../utils/excelTemplates.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", requireAuth, async (req, res) => {
|
||||
const { filename } = req.query;
|
||||
|
||||
const templateNames = ["orders", "forecast"];
|
||||
if (typeof filename !== "string" || !templateNames.includes(filename)) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dm",
|
||||
subModule: "template",
|
||||
message: "You are required to pass over the template name.",
|
||||
data: [],
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const name = `${filename}-Template-${format(
|
||||
new Date(Date.now()),
|
||||
"M-d-yyyy",
|
||||
)}.xlsx`;
|
||||
|
||||
const standardHeaders = [
|
||||
"CustomerArticleNumber",
|
||||
"CustomerOrderNumber",
|
||||
"CustomerLineNumber",
|
||||
"CustomerRealeaseNumber",
|
||||
"Quantity",
|
||||
"DeliveryDate",
|
||||
"CustomerID",
|
||||
"Remark",
|
||||
// "InvoiceID",
|
||||
];
|
||||
|
||||
const forecastHeaders = [
|
||||
"CustomerArticleNumber",
|
||||
"Quantity",
|
||||
"RequirementDate",
|
||||
"CustomerID",
|
||||
];
|
||||
|
||||
const template = {
|
||||
name,
|
||||
headers: filename === "orders" ? standardHeaders : forecastHeaders,
|
||||
} as Template;
|
||||
|
||||
//console.log(template);
|
||||
const { data, error } = await tryCatch(excelTemplate(template));
|
||||
|
||||
if (error || !data) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dm",
|
||||
subModule: "template",
|
||||
message: "There was an error creating the Excel template.",
|
||||
data: [],
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
res.set({
|
||||
"Content-Type":
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"Content-Disposition": `attachment; filename="${name}"`,
|
||||
});
|
||||
|
||||
return res.status(200).send(data);
|
||||
});
|
||||
|
||||
export default r;
|
||||
21
backend/logistics/logistics.routes.ts
Normal file
21
backend/logistics/logistics.routes.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { Express } from "express";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import forecast from "./logistics.dm.forecast.route.js";
|
||||
import createTemplate from "./logistics.dm.template.route.js";
|
||||
|
||||
export const setupLogisticsRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
|
||||
app.use(
|
||||
`${baseUrl}/api/logistics/dm/template`,
|
||||
featureCheck("demandManagement"),
|
||||
createTemplate,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/logistics/dm/forecast`,
|
||||
featureCheck("demandManagement"),
|
||||
forecast,
|
||||
);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
@@ -49,7 +49,7 @@ const historicalInvImport = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (data?.length === 0) {
|
||||
if (data.length === 0) {
|
||||
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||
|
||||
if (!avSQLQuery.success) {
|
||||
@@ -139,7 +139,7 @@ const historicalInvImport = async () => {
|
||||
subModule: "inv",
|
||||
message: `Error adding historical data to lst db`,
|
||||
data: errorImport as any,
|
||||
notify: true,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1,46 +0,0 @@
|
||||
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);
|
||||
});
|
||||
|
||||
export default router;
|
||||
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;
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { Express } from "express";
|
||||
import downloads from "./donwloadApps.route.js";
|
||||
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";
|
||||
@@ -7,11 +10,14 @@ 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`, version);
|
||||
app.use(`${baseUrl}/api/mobile/apk`, downloads);
|
||||
app.use(`${baseUrl}/api/mobile/logs`, logs);
|
||||
app.use(`${baseUrl}/api/mobile/auth`, authPin);
|
||||
app.use(`${baseUrl}/api/mobile/pin`, newPin);
|
||||
|
||||
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/*
|
||||
};
|
||||
|
||||
@@ -162,6 +162,14 @@ r.post("/user", async (req, res) => {
|
||||
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,
|
||||
@@ -263,6 +271,10 @@ r.patch("/user/:id", requireAuth, async (req, res) => {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
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,
|
||||
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();
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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();
|
||||
|
||||
@@ -10,6 +12,15 @@ 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);
|
||||
@@ -22,6 +33,7 @@ router.get("/", async (req, res) => {
|
||||
versionCode: exp.android?.versionCode,
|
||||
minSupportedVersionCode: exp?.android?.minSupportedVersionCode ?? 0,
|
||||
downloadUrl: `${baseUrl}/lst/api/mobile/apk/latest`,
|
||||
settings: mobileSettings,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Express } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
|
||||
import manual from "./notification.manualTrigger.js";
|
||||
import getNotifications from "./notification.route.js";
|
||||
import updateNote from "./notification.update.route.js";
|
||||
@@ -10,13 +11,48 @@ import updateSub from "./notificationSub.update.route.js";
|
||||
|
||||
export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
app.use(`${baseUrl}/api/notification`, requireAuth, getNotifications);
|
||||
app.use(`${baseUrl}/api/notification`, requireAuth, updateNote);
|
||||
app.use(`${baseUrl}/api/notification/manual`, requireAuth, manual);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, subs);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, newSub);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, updateSub);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, deleteSub);
|
||||
app.use(
|
||||
`${baseUrl}/api/notification`,
|
||||
requireAuth,
|
||||
|
||||
getNotifications,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/notification`,
|
||||
requireAuth,
|
||||
|
||||
updateNote,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/notification/manual`,
|
||||
requireAuth,
|
||||
|
||||
manual,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/notification/sub`,
|
||||
requireAuth,
|
||||
|
||||
subs,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/notification/sub`,
|
||||
requireAuth,
|
||||
|
||||
newSub,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/notification/sub`,
|
||||
requireAuth,
|
||||
|
||||
updateSub,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/notification/sub`,
|
||||
requireAuth,
|
||||
|
||||
deleteSub,
|
||||
);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
|
||||
@@ -22,7 +22,6 @@ r.patch(
|
||||
requirePermission({ notifications: ["update"] }),
|
||||
async (req, res: Response) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const validated = updateNote.parse(req.body);
|
||||
|
||||
@@ -37,6 +36,7 @@ r.patch(
|
||||
await modifiedNotification(id as string);
|
||||
|
||||
if (nError) {
|
||||
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
@@ -58,6 +58,7 @@ r.patch(
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
if (err instanceof z.ZodError) {
|
||||
const flattened = z.flattenError(err);
|
||||
// return res.status(400).json({
|
||||
|
||||
@@ -43,7 +43,7 @@ const parseZebraAlert = (body: any): PrinterEvent => {
|
||||
};
|
||||
};
|
||||
|
||||
r.post("/printer/listener/:printer", upload.any(), async (req, res) => {
|
||||
r.post("/:printer", upload.any(), async (req, res) => {
|
||||
const { printer: printerName } = req.params;
|
||||
const event: PrinterEvent = parseZebraAlert(req.body);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { printerSync } from "./ocp.printer.manage.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/printer/update", async (_, res) => {
|
||||
r.post("/update", async (_, res) => {
|
||||
printerSync();
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user