Compare commits

...

23 Commits

Author SHA1 Message Date
Dave Richer
b13c42b02f IO-2742-redis - Merge Release / Up deps (prior to parking)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 14:44:22 -04:00
Dave Richer
93f6a80fda Merge remote-tracking branch 'origin/release/2024-09-27' into feature/IO-2742-redis 2024-09-24 14:21:51 -04:00
Dave Richer
5e14258839 IO-2742-redis - PR Changes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 14:20:09 -04:00
Dave Richer
5c54cf6c44 feature/IO-2924-Refactor-Production-Board-For-Sockets - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-16 11:48:17 -04:00
Dave Richer
a2d95dbce3 feature/IO-2742-redis - Checkpoint - clear stage before moving to sub task
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-12 11:53:29 -04:00
Dave Richer
51c181dab7 feature/IO-2742-redis - Checkpoint, Final cleanup of server.js
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 20:52:49 -04:00
Dave Richer
4486858a86 feature/IO-2742-redis - Checkpoint, All optimizations to prevent multiple requests to redis have been applied. Things like using lists for the Log Events, to getting and setting multiple values at the same time when given the chance.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 20:27:40 -04:00
Dave Richer
f606228792 feature/IO-2742-redis - Checkpoint, Redis fully implemented.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 19:08:24 -04:00
Dave Richer
bca0a35cdd Remove express line
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 11:12:14 -04:00
Dave Richer
0ee51ed4c1 - Update
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 11:06:36 -04:00
Dave Richer
11b903f24b - Fix DMS Socket (missing reference)
- Add preview mode

Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 10:30:55 -04:00
Dave Richer
f83deb0c26 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 17:15:08 -04:00
Dave Richer
9a0d973b26 Normalize SocketIO App wide.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 16:23:38 -04:00
Dave Richer
ea0a717895 feature/IO-2742-redis - Normalize Network
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 15:17:13 -04:00
Dave Richer
40f1a2f6ed feature/IO-2742-redis - Dep Bump
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 14:34:50 -04:00
Dave Richer
3433b1a600 Merge remote-tracking branch 'origin/master-AIO' into feature/IO-2742-redis 2024-09-10 14:28:44 -04:00
Dave Richer
49a7313abb - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-09 23:27:28 -04:00
Dave Richer
319b24ce8a Merge remote-tracking branch 'origin/master-AIO' into feature/IO-2742-redis 2024-09-09 11:39:48 -04:00
Dave Richer
08d8a4f7dc - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-09 11:39:39 -04:00
Dave Richer
03e8b62e4f - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-06 13:22:17 -04:00
Dave Richer
44db8f20e9 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-06 11:53:08 -04:00
Dave Richer
f28068d0e7 Merge remote-tracking branch 'origin/release/2024-09-06' into feature/IO-2742-redis 2024-09-05 19:38:00 -04:00
Patrick Fic
5f082b9619 Basic connection to elasticache server. 2024-08-22 14:48:00 -07:00
34 changed files with 4248 additions and 3476 deletions

20
certs/cert.pem Normal file
View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDWzCCAkOgAwIBAgIUD/QBSAXy/AlJ/cS4DaPWJLpChxgwDQYJKoZIhvcNAQEL
BQAwPTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSEwHwYDVQQKDBhJbnRlcm5l
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwOTA5MTU0MjA1WhcNMjUwOTA5MTU0MjA1
WjA9MQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AKSd0l7NJCNBwvtPU+dVPQkteg0AfC3sGqRnZMQteCRVa2oIgC4NoF3A9BK/yHbF
ZF25OnXTck5vzc8yb3v73ndfTD9ASKNoiaZE84/GFBsxqlKR8cs0qVwzuAsdijMv
vlMPNlMRyE1Rb7nR6HXGkPXNyxgMko03NXPkvIje9zRudm0Lf8L4q/hPyPkS7Mrm
/uQfAAJe+xFcupkEX2XY7r0x1C+z6E8lA1UcuhK3SHdW7CWYqp1vU5/dnnUiXwCa
GiC6Y1bCJB0pDAVISzy3JUDdINZdiqGR+y8ho3pstChf2mp/76s3N9eG9KA/qaFK
BrGk2PvCoZ8/Aj1aMsRYFHECAwEAAaNTMFEwHQYDVR0OBBYEFDLJ2fbWP4VUJgOp
PSs+NGHcVgRmMB8GA1UdIwQYMBaAFDLJ2fbWP4VUJgOpPSs+NGHcVgRmMA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABfv5ut/y03atq0NMB0jeDY4
AvW4ukk0k1svyqxFZCw9o7m2lHb/IjmVrZG1Sj4JWrrSv0s02ccb26/t6vazNa5L
Powe3eyfHgfjTZJmgs8hyeMwKS0wWk/SPuu9JDhIJakiquqD+UVBGkHpP+XYvhDv
vhS2XRlW+aEjpUmr1oCyyrc6WbzrYRNadqEsn/AxwcMyUbht3Ugjkg+OpidcTIQp
5lv63waKo6I1vQofzBQ3L7JYsKo8kC0vAP7wkLxvzBii335uZJzzpFYFVOyVNezi
dJdazPbRYbXz4LjltdEn/SNfRuKX8ZRiN2OSo7OfSrZaMTS87SfCSFJGgQM8Yrk=
-----END CERTIFICATE-----

28
certs/key.pem Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkndJezSQjQcL7
T1PnVT0JLXoNAHwt7BqkZ2TELXgkVWtqCIAuDaBdwPQSv8h2xWRduTp103JOb83P
Mm97+953X0w/QEijaImmRPOPxhQbMapSkfHLNKlcM7gLHYozL75TDzZTEchNUW+5
0eh1xpD1zcsYDJKNNzVz5LyI3vc0bnZtC3/C+Kv4T8j5EuzK5v7kHwACXvsRXLqZ
BF9l2O69MdQvs+hPJQNVHLoSt0h3VuwlmKqdb1Of3Z51Il8AmhogumNWwiQdKQwF
SEs8tyVA3SDWXYqhkfsvIaN6bLQoX9pqf++rNzfXhvSgP6mhSgaxpNj7wqGfPwI9
WjLEWBRxAgMBAAECggEAUNpHYlLFxh9dokujPUMreF+Cy/IKDBAkQc2au5RNpyLh
YDIOqw/8TTAhcTgLQPLQygvZP9f8E7RsVLFD+pSJ/v2qmIJ9au1Edor1Sg+S/oxV
SLrwFMunx2aLpcH7iAqSI3+cQg7A3+D4zD7iOz6tIl3Su9wo+v073tFhHKTOrEwv
Qgr9Jf3viIiKV1ym+uQEVQndocfsj46FnFpXTQ2qs7kAF6FgAOLDGfQQwzkiqEBD
NsqsDmbYIx6foZL+DEz1ZVO2M5B+xxpbNK82KwuQilVpimW8ui4LZHCe+RIFzt9+
BK6KGlLpSEwTFliivI3nahy18JzskZsfyah0CPZlQQKBgQDVv+A0qIPGvOP3Sx+9
HyeQCV23SkvvSvw8p8pMB0gvwv63YdJ7N/rJzBGS6YUHFWWZZgEeTgkJ6VJvoe0r
8JL1el9uSUa7f0eayjmFBOGuzpktNVdIn2Tg7A9MWA4JqPNNC69RMOh86ewGD4J3
a8Hz2a1bHxAmy/AZt2ukypY6eQKBgQDFJ7kqeOPkRBz9WbALRgVIXo8YWf5di0sQ
r0HC03GAISHQ725A2IFBPHJWeqj0jaMiIZD0y+Obgp7KAskrJaLfsd7Ug775kFfw
oUI9UAl6kRuPKvm3BaVAm46SQm+56VsgxTi73YN0NUp75THHZgAJjepF9zSpVJxq
VY9DjEGruQKBgQCQCpGIatcCol/tUg69X7VFd0pULhkl1J5OMbQ9r9qRdRI5eg5h
QsQaIQ7mtb8TmvOwf/DY/zVQHI+U8sXlCmW+TwzoQTENQSR7xzMj1LpRFqBaustr
AR72A537kItFLzll/i3SxOam5uxK2UDOQSuerF4KPdCglGXkrpo3nt3F4QKBgQCa
RArPAOjQo7tLQfJN3+wiRFsTYtd1uphx5bA/EdOtvj8HjVFnzADXWsTchf3N3UXY
XwtdgGwIMpys1KEz8a8P+c2x26SDAj7NOmDqOMYx8Xju/WGHpBM6Cn30U6e4gK+d
ZLSPyzQgqdIuP5hDvbwpvbGiLVw3Ys1BJtGCuSxpgQJ/eHnRiuSi5Zq5jGg+GpA+
FEEc9NCy772rR+4uzEOqyIwqewffqzSuVWuIsY/8MP3fh+NDxl/mU6cB5QVeD54Z
JZUKwmpM26muiM6WvVnM4ExPdSGA2+l4pZjby/KKd6F/w0tgZ1jb9Pb2/0vN3qVA
Y4U4XNTMt2fxUACqiL4SHA==
-----END PRIVATE KEY-----

View File

@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4' VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000 VITE_APP_AXIOS_BASE_API_URL=/api/
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=IMEX VITE_APP_INSTANCE=IMEX

View File

@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4' VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000 VITE_APP_AXIOS_BASE_API_URL=/api/
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=PROMANAGER VITE_APP_INSTANCE=PROMANAGER

View File

@@ -9,7 +9,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo' VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000 VITE_APP_AXIOS_BASE_API_URL=/api/
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_COUNTRY=USA VITE_APP_COUNTRY=USA

1
client/.gitignore vendored
View File

@@ -1,3 +1,4 @@
# Sentry Config File # Sentry Config File
.sentryclirc .sentryclirc
/dev-dist

View File

@@ -12,6 +12,6 @@ module.exports = defineConfig({
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
return require("./cypress/plugins/index.js")(on, config); return require("./cypress/plugins/index.js")(on, config);
}, },
baseUrl: "http://localhost:3000" baseUrl: "https://localhost:3000"
} }
}); });

3321
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,38 +8,38 @@
"private": true, "private": true,
"proxy": "http://localhost:4000", "proxy": "http://localhost:4000",
"dependencies": { "dependencies": {
"@ant-design/pro-layout": "^7.19.12", "@ant-design/pro-layout": "^7.20.2",
"@apollo/client": "^3.11.4", "@apollo/client": "^3.11.8",
"@emotion/is-prop-valid": "^1.3.0", "@emotion/is-prop-valid": "^1.3.1",
"@fingerprintjs/fingerprintjs": "^4.4.3", "@fingerprintjs/fingerprintjs": "^4.5.0",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.2.7", "@reduxjs/toolkit": "^2.2.7",
"@sentry/cli": "^2.33.1", "@sentry/cli": "^2.36.2",
"@sentry/react": "^7.114.0", "@sentry/react": "^7.114.0",
"@splitsoftware/splitio-react": "^1.12.1", "@splitsoftware/splitio-react": "^1.13.0",
"@tanem/react-nprogress": "^5.0.51", "@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"antd": "^5.20.1", "antd": "^5.21.0",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0", "apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1", "autosize": "^6.0.1",
"axios": "^1.7.4", "axios": "^1.7.7",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"css-box-model": "^1.2.1", "css-box-model": "^1.2.1",
"dayjs": "^1.11.12", "dayjs": "^1.11.13",
"dayjs-business-days2": "^1.2.2", "dayjs-business-days2": "^1.2.2",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^10.12.5", "firebase": "^10.13.2",
"graphql": "^16.9.0", "graphql": "^16.9.0",
"i18next": "^23.12.3", "i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"immutability-helper": "^3.1.1", "immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.11.5", "libphonenumber-js": "^1.11.9",
"logrocket": "^8.1.2", "logrocket": "^8.1.2",
"markerjs2": "^2.32.1", "markerjs2": "^2.32.2",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"normalize-url": "^8.0.1", "normalize-url": "^8.0.1",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
@@ -47,7 +47,7 @@
"query-string": "^9.1.0", "query-string": "^9.1.0",
"raf-schd": "^4.0.3", "raf-schd": "^4.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-big-calendar": "^1.13.2", "react-big-calendar": "^1.14.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-cookie": "^7.2.0", "react-cookie": "^7.2.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@@ -58,15 +58,15 @@
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-number-format": "^5.4.0", "react-number-format": "^5.4.2",
"react-popopo": "^2.1.9", "react-popopo": "^2.1.9",
"react-product-fruits": "^2.2.6", "react-product-fruits": "^2.2.61",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-resizable": "^3.0.5", "react-resizable": "^3.0.5",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.2",
"react-sticky": "^6.0.3", "react-sticky": "^6.0.3",
"react-virtualized": "^9.22.5", "react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.1", "react-virtuoso": "^4.10.4",
"recharts": "^2.12.7", "recharts": "^2.12.7",
"redux": "^5.0.1", "redux": "^5.0.1",
"redux-actions": "^3.0.3", "redux-actions": "^3.0.3",
@@ -74,12 +74,12 @@
"redux-saga": "^1.3.0", "redux-saga": "^1.3.0",
"redux-state-sync": "^3.1.4", "redux-state-sync": "^3.1.4",
"reselect": "^5.1.1", "reselect": "^5.1.1",
"sass": "^1.77.8", "sass": "^1.79.3",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.8.0",
"styled-components": "^6.1.12", "styled-components": "^6.1.13",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
"use-memo-one": "^1.1.3", "use-memo-one": "^1.1.3",
"userpilot": "^1.3.5", "userpilot": "^1.3.6",
"vite-plugin-ejs": "^1.7.0", "vite-plugin-ejs": "^1.7.0",
"web-vitals": "^3.5.2" "web-vitals": "^3.5.2"
}, },
@@ -91,6 +91,9 @@
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite", "start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite", "start:rome": "dotenvx run --env-file=.env.development.rome -- vite",
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite", "start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite",
"preview:imex": "dotenvx run --env-file=.env.development.imex -- vite preview",
"preview:rome": "dotenvx run --env-file=.env.development.rome -- vite preview",
"preview:promanager": "dotenvx run --env-file=.env.development.promanager -- vite preview",
"build:test:imex": "env-cmd -f .env.test.imex npm run build", "build:test:imex": "env-cmd -f .env.test.imex npm run build",
"build:test:rome": "env-cmd -f .env.test.rome npm run build", "build:test:rome": "env-cmd -f .env.test.rome npm run build",
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build", "build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
@@ -131,29 +134,29 @@
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-react": "^7.24.7", "@babel/preset-react": "^7.24.7",
"@dotenvx/dotenvx": "^1.7.0", "@dotenvx/dotenvx": "^1.14.1",
"@emotion/babel-plugin": "^11.12.0", "@emotion/babel-plugin": "^11.12.0",
"@emotion/react": "^11.13.0", "@emotion/react": "^11.13.3",
"@sentry/webpack-plugin": "^2.22.2", "@sentry/webpack-plugin": "^2.22.4",
"@testing-library/cypress": "^10.0.2", "@testing-library/cypress": "^10.0.2",
"browserslist": "^4.23.3", "browserslist": "^4.23.3",
"browserslist-to-esbuild": "^2.1.1", "browserslist-to-esbuild": "^2.1.1",
"chalk": "^5.3.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cypress": "^13.13.3", "cypress": "^13.14.2",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.15.1", "eslint-plugin-cypress": "^2.15.1",
"memfs": "^4.11.1", "memfs": "^4.12.0",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"react-error-overlay": "6.0.11", "react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3", "source-map-explorer": "^2.5.3",
"vite": "^5.4.0", "vite": "^5.4.7",
"vite-plugin-babel": "^1.2.0", "vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-legacy": "^2.1.0",
"vite-plugin-node-polyfills": "^0.22.0", "vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-pwa": "^0.20.1", "vite-plugin-pwa": "^0.20.5",
"vite-plugin-style-import": "^2.0.0", "vite-plugin-style-import": "^2.0.0",
"workbox-window": "^7.1.0" "workbox-window": "^7.1.0"
} }

View File

@@ -21,6 +21,7 @@ import "./App.styles.scss";
import Eula from "../components/eula/eula.component"; import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr"; import InstanceRenderMgr from "../utils/instanceRenderMgr";
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx"; import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component")); const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
@@ -201,7 +202,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
path="/manage/*" path="/manage/*"
element={ element={
<ErrorBoundary> <ErrorBoundary>
<PrivateRoute isAuthorized={currentUser.authorized} /> <SocketProvider bodyshop={bodyshop}>
<PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider>
</ErrorBoundary> </ErrorBoundary>
} }
> >
@@ -211,7 +214,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
path="/tech/*" path="/tech/*"
element={ element={
<ErrorBoundary> <ErrorBoundary>
<PrivateRoute isAuthorized={currentUser.authorized} /> <SocketProvider bodyshop={bodyshop}>
<PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider>
</ErrorBoundary> </ErrorBoundary>
} }
> >

View File

@@ -1,62 +1,66 @@
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Form, Input, Table } from "antd"; import { Button, Card, Form, Input, Table } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState, useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { pageLimit } from "../../utils/config"; import { pageLimit } from "../../utils/config";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; // Import SocketContext
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ export default connect(mapStateToProps)(DmsAllocationsSummaryAp);
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummaryAp); export function DmsAllocationsSummaryAp({ bodyshop, billids, title }) {
export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]); const [allocationsSummary, setAllocationsSummary] = useState([]);
const { socket } = useContext(SocketContext);
useEffect(() => { useEffect(() => {
socket.on("ap-export-success", (billid) => { if (!socket || !socket.connected) return;
const handleSuccess = async (billid) => {
setAllocationsSummary((allocationsSummary) => setAllocationsSummary((allocationsSummary) =>
allocationsSummary.map((a) => { allocationsSummary.map((a) => {
if (a.billid !== billid) return a; if (a.billid !== billid) return a;
return { ...a, status: "Successful" }; return { ...a, status: "Successful" };
}) })
); );
});
socket.on("ap-export-failure", ({ billid, error }) => {
allocationsSummary.map((a) => {
if (a.billid !== billid) return a;
return { ...a, status: error };
});
});
if (socket.disconnected) socket.connect(); try {
return () => { await new Promise((resolve, reject) => {
socket.removeListener("ap-export-success"); socket.emit("clear-dms-session", (response) => {
socket.removeListener("ap-export-failure"); if (response && response.status === "ok") {
//socket.disconnect(); resolve();
} else {
reject(new Error("Failed to clear DMS session"));
}
});
});
} catch (error) {
console.error("Failed to clear DMS session", error);
}
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); socket.on("ap-export-success", handleSuccess);
return () => {
socket.off("ap-export-success", handleSuccess);
};
}, [socket]);
useEffect(() => { useEffect(() => {
if (socket.connected) { if (socket && socket.connected) {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => { socket.emit("pbs-calculate-allocations-ap", billids, (ack) => {
setAllocationsSummary(ack); setAllocationsSummary(ack);
socket.allocationsSummary = ack; socket.allocationsSummary = ack;
}); });
} }
}, [socket, socket.connected, billids]); }, [socket, socket.connected, billids]);
console.log(allocationsSummary);
const columns = [ const columns = [
{ {
title: t("general.labels.status"), title: t("general.labels.status"),
@@ -68,35 +72,40 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
dataIndex: ["Posting", "Reference"], dataIndex: ["Posting", "Reference"],
key: "reference" key: "reference"
}, },
{ {
title: t("jobs.fields.dms.lines"), title: t("jobs.fields.dms.lines"),
dataIndex: "Lines", dataIndex: "Lines",
key: "Lines", key: "Lines",
render: (text, record) => ( render: (text, record) => (
<table style={{ tableLayout: "auto", width: "100%" }}> <table style={{ tableLayout: "auto", width: "100%" }}>
<tr> <thead>
<th>{t("bills.fields.invoice_number")}</th> <tr>
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th> <th>{t("bills.fields.invoice_number")}</th>
<th>{t("jobs.fields.dms.amount")}</th> <th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
</tr> <th>{t("jobs.fields.dms.amount")}</th>
{record.Posting.Lines.map((l, idx) => (
<tr key={idx}>
<td>{l.InvoiceNumber}</td>
<td>{l.Account}</td>
<td>{l.Amount}</td>
</tr> </tr>
))} </thead>
<tbody>
{record.Posting.Lines.map((l, idx) => (
<tr key={idx}>
<td>{l.InvoiceNumber}</td>
<td>{l.Account}</td>
<td>{l.Amount}</td>
</tr>
))}
</tbody>
</table> </table>
) )
} }
]; ];
const handleFinish = async (values) => { const handleFinish = async (values) => {
socket.emit(`pbs-export-ap`, { if (socket) {
billids, socket.emit("pbs-export-ap", {
txEnvelope: values billids,
}); txEnvelope: values
});
}
}; };
return ( return (
@@ -105,7 +114,9 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
extra={ extra={
<Button <Button
onClick={() => { onClick={() => {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack)); if (socket) {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack));
}
}} }}
> >
<SyncOutlined /> <SyncOutlined />
@@ -124,12 +135,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
name="journal" name="journal"
label={t("jobs.fields.dms.journal")} label={t("jobs.fields.dms.journal")}
initialValue={bodyshop.cdk_configuration && bodyshop.cdk_configuration.default_journal} initialValue={bodyshop.cdk_configuration && bodyshop.cdk_configuration.default_journal}
rules={[ rules={[{ required: true }]}
{
required: true
//message: t("general.validation.required"),
}
]}
> >
<Input /> <Input />
</Form.Item> </Form.Item>

View File

@@ -24,7 +24,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
const [allocationsSummary, setAllocationsSummary] = useState([]); const [allocationsSummary, setAllocationsSummary] = useState([]);
useEffect(() => { useEffect(() => {
if (socket.connected) { if (socket && socket.connected) {
socket.emit("cdk-calculate-allocations", jobId, (ack) => { socket.emit("cdk-calculate-allocations", jobId, (ack) => {
setAllocationsSummary(ack); setAllocationsSummary(ack);
socket.allocationsSummary = ack; socket.allocationsSummary = ack;

View File

@@ -1,37 +1,51 @@
import { Button, Checkbox, Col, Table } from "antd"; import { Button, Checkbox, Col, Table } from "antd";
import React, { useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { socket } from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import SocketContext from "../../contexts/SocketIO/socketContext"; // Import Socket Context
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({});
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector); export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector);
export function DmsCustomerSelector({ bodyshop }) { export function DmsCustomerSelector({ bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [customerList, setcustomerList] = useState([]); const { socket } = useContext(SocketContext); // Use Socket Context
const [customerList, setCustomerList] = useState([]);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null); const [selectedCustomer, setSelectedCustomer] = useState(null);
const [dmsType, setDmsType] = useState("cdk"); const [dmsType, setDmsType] = useState("cdk");
socket.on("cdk-select-customer", (customerList, callback) => { useEffect(() => {
setOpen(true); if (socket) {
setDmsType("cdk"); const handleCdkSelectCustomer = (customerList) => {
setcustomerList(customerList); setOpen(true);
}); setDmsType("cdk");
socket.on("pbs-select-customer", (customerList, callback) => { setCustomerList(customerList);
setOpen(true); };
setDmsType("pbs");
setcustomerList(customerList); const handlePbsSelectCustomer = (customerList) => {
}); setOpen(true);
setDmsType("pbs");
setCustomerList(customerList);
};
socket.on("cdk-select-customer", handleCdkSelectCustomer);
socket.on("pbs-select-customer", handlePbsSelectCustomer);
// Clean up listeners on unmount
return () => {
socket.off("cdk-select-customer", handleCdkSelectCustomer);
socket.off("pbs-select-customer", handlePbsSelectCustomer);
};
}
}, [socket]);
const onUseSelected = () => { const onUseSelected = () => {
setOpen(false); setOpen(false);
@@ -69,17 +83,11 @@ export function DmsCustomerSelector({ bodyshop }) {
key: "name1", key: "name1",
sorter: (a, b) => alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName) sorter: (a, b) => alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName)
}, },
{ {
title: t("jobs.fields.dms.address"), title: t("jobs.fields.dms.address"),
//dataIndex: ["name2", "fullName"],
key: "address", key: "address",
render: (record, value) => render: (record) =>
`${ `${record.address?.addressLine?.[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`
record.address && record.address.addressLine && record.address.addressLine[0]
}, ${record.address && record.address.city} ${
record.address && record.address.stateOrProvince
} ${record.address && record.address.postalCode}`
} }
]; ];
@@ -95,15 +103,15 @@ export function DmsCustomerSelector({ bodyshop }) {
sorter: (a, b) => alphaSort(a.LastName, b.LastName), sorter: (a, b) => alphaSort(a.LastName, b.LastName),
render: (text, record) => `${record.FirstName || ""} ${record.LastName || ""}` render: (text, record) => `${record.FirstName || ""} ${record.LastName || ""}`
}, },
{ {
title: t("jobs.fields.dms.address"), title: t("jobs.fields.dms.address"),
key: "address", key: "address",
render: (record, value) => `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}` render: (record) => `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`
} }
]; ];
if (!open) return null; if (!open) return null;
return ( return (
<Col span={24}> <Col span={24}>
<Table <Table
@@ -125,7 +133,6 @@ export function DmsCustomerSelector({ bodyshop }) {
columns={dmsType === "cdk" ? cdkColumns : pbsColumns} columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)} rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)}
dataSource={customerList} dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{ rowSelection={{
onSelect: (record) => { onSelect: (record) => {
setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId); setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId);

View File

@@ -4,11 +4,8 @@ import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({});
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -17,7 +14,7 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents); export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
export function DmsLogEvents({ socket, logs, bodyshop }) { export function DmsLogEvents({ logs }) {
return ( return (
<Timeline <Timeline
pending pending

View File

@@ -46,6 +46,7 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`) onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`)
}); });
// This provides us the current version of the Lanes from the Redux store
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {})); // const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,13 @@
import React, { createContext } from "react";
import useSocket from "./useSocket"; // Import the custom hook
// Create the SocketContext
const SocketContext = createContext(null);
export const SocketProvider = ({ children, bodyshop }) => {
const { socket, clientId } = useSocket(bodyshop);
return <SocketContext.Provider value={{ socket, clientId }}> {children}</SocketContext.Provider>;
};
export default SocketContext;

View File

@@ -0,0 +1,54 @@
import { useEffect, useState } from "react";
import SocketIO from "socket.io-client";
import { auth } from "../../firebase/firebase.utils";
const useSocket = (bodyshop) => {
const [socket, setSocket] = useState(null);
const [clientId, setClientId] = useState(null); // State to store unique identifier
useEffect(() => {
if (bodyshop && bodyshop.id) {
const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "https://localhost:3000";
const socketInstance = SocketIO(endpoint, {
path: "/ws", // Ensure this matches the Vite proxy and backend path
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
},
reconnectionAttempts: Infinity, // Try reconnecting forever
reconnectionDelay: 2000, // How long to wait between reconnection attempts
reconnectionDelayMax: 10000 // Maximum delay between attempts
});
setSocket(socketInstance);
socketInstance.on("connect", () => {
console.log("Socket connected:", socketInstance.id);
setClientId(socketInstance.id);
});
socketInstance.on("reconnect", (attempt) => {
console.log(`Socket reconnected after ${attempt} attempts`);
});
socketInstance.on("connect_error", (err) => {
console.error("Socket connection error:", err);
});
socketInstance.on("disconnect", () => {
console.log("Socket disconnected");
});
return () => {
socketInstance.disconnect();
};
}
}, [bodyshop]);
// Return both socket and clientId
return { socket, clientId };
};
export default useSocket;

View File

@@ -1,20 +1,16 @@
import { Button, Card, Col, notification, Row, Select, Space } from "antd"; import { Button, Card, Col, notification, Row, Select, Space } from "antd";
import React, { useEffect, useRef, useState } from "react"; import React, { useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client";
import DmsAllocationsSummaryApComponent from "../../components/dms-allocations-summary-ap/dms-allocations-summary-ap.component"; import DmsAllocationsSummaryApComponent from "../../components/dms-allocations-summary-ap/dms-allocations-summary-ap.component";
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component"; import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import { auth } from "../../firebase/firebase.utils";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import SocketContext from "../../contexts/SocketIO/socketContext";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({});
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -23,20 +19,9 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO( export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : window.location.origin,
{
path: "/ws",
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
}
}
);
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { socket } = useContext(SocketContext);
const [logLevel, setLogLevel] = useState("DEBUG"); const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate(); const history = useNavigate();
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
@@ -67,40 +52,43 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
}, [t, setBreadcrumbs, setSelectedHeader]); }, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => { useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel)); if (socket) {
socket.on("reconnect", () => { const handleConnect = () => socket.emit("set-log-level", logLevel);
setLogs((logs) => { const handleReconnect = () => {
return [ setLogs((logs) => [
...logs, ...logs,
{ {
timestamp: new Date(), timestamp: new Date(),
level: "WARNING", level: "WARNING",
message: "Reconnected to CDK Export Service" message: "Reconnected to DMS Export Service"
} }
]; ]);
}); };
}); const handleLogEvent = (payload) => {
setLogs((logs) => [...logs, payload]);
};
const handleExportComplete = () => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported")
});
};
socket.on("log-event", (payload) => { socket.on("connect", handleConnect);
setLogs((logs) => { socket.on("reconnect", handleReconnect);
return [...logs, payload]; socket.on("log-event", handleLogEvent);
}); socket.on("ap-export-complete", handleExportComplete);
});
socket.on("ap-export-complete", (payload) => { if (socket.disconnected) socket.connect();
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported")
});
});
if (socket.disconnected) socket.connect(); return () => {
return () => { socket.off("connect", handleConnect);
socket.removeAllListeners(); socket.off("reconnect", handleReconnect);
socket.disconnect(); socket.off("log-event", handleLogEvent);
}; socket.off("ap-export-complete", handleExportComplete);
// eslint-disable-next-line react-hooks/exhaustive-deps };
}, []); }
}, [socket, logLevel, t]);
if (!state?.billids) { if (!state?.billids) {
history(`/manage/accounting/payables`); history(`/manage/accounting/payables`);
@@ -136,27 +124,20 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
<Button <Button
onClick={() => { onClick={() => {
setLogs([]); setLogs([]);
if (socket) {
socket.disconnect(); socket.emit("clear-dms-session");
socket.connect(); }
}} }}
> >
Reconnect Clear Session
</Button> </Button>
</Space> </Space>
} }
> >
<DmsLogEvents socket={socket} logs={logs} /> <DmsLogEvents logs={logs} />
</Card> </Card>
</div> </div>
</Col> </Col>
</Row> </Row>
); );
} }
export const determineDmsType = (bodyshop) => {
if (bodyshop.cdk_dealerid) return "cdk";
else {
return "pbs";
}
};

View File

@@ -1,12 +1,11 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Card, Col, notification, Result, Row, Select, Space } from "antd"; import { Button, Card, Col, notification, Result, Row, Select, Space } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React, { useEffect, useRef, useState } from "react"; import React, { useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component"; import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component"; import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
@@ -14,12 +13,12 @@ import DmsLogEvents from "../../components/dms-log-events/dms-log-events.compone
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component"; import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component"; import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
import { auth } from "../../firebase/firebase.utils";
import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries"; import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
import { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import SocketContext from "../../contexts/SocketIO/socketContext";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -28,25 +27,21 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
)
}); });
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO(
import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "http://localhost:4000", // for dev testing,
{
path: "/ws",
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
}
}
);
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) { export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { socket } = useContext(SocketContext);
const [logLevel, setLogLevel] = useState("DEBUG"); const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate(); const history = useNavigate();
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
@@ -59,6 +54,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only" nextFetchPolicy: "network-only"
}); });
const logsRef = useRef(null); const logsRef = useRef(null);
useEffect(() => { useEffect(() => {
@@ -83,47 +79,73 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
}, [t, setBreadcrumbs, setSelectedHeader]); }, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => { useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel)); if (socket) {
socket.on("reconnect", () => { const handleConnect = () => {
setLogs((logs) => { socket.emit("set-log-level", logLevel);
return [ };
const handleReconnect = () => {
setLogs((logs) => [
...logs, ...logs,
{ {
timestamp: new Date(), timestamp: new Date(),
level: "WARNING", level: "WARNING",
message: "Reconnected to CDK Export Service" message: "Reconnected to DMS Export Service"
} }
]; ]);
}); };
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({ message: err.message });
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("export-success", (payload) => {
notification.success({
message: t("jobs.successes.exported")
});
insertAuditTrail({
jobid: payload,
operation: AuditTrailMapping.jobexported(),
type: "jobexported"
});
history("/manage/accounting/receivables");
});
if (socket.disconnected) socket.connect(); const handleConnectError = (err) => {
return () => { console.log(`connect_error due to ${err}`, err);
socket.removeAllListeners(); notification.error({ message: err.message });
socket.disconnect(); };
};
// eslint-disable-next-line react-hooks/exhaustive-deps const handleLogEvent = (payload) => {
}, []); setLogs((logs) => [...logs, payload]);
};
const handleExportSuccess = async (payload) => {
notification.success({
message: t("jobs.successes.exported")
});
insertAuditTrail({
jobid: payload,
operation: AuditTrailMapping.jobexported(),
type: "jobexported"
});
try {
await new Promise((resolve, reject) => {
socket.emit("clear-dms-session", (response) => {
if (response && response.status === "ok") {
resolve();
} else {
reject(new Error("Failed to clear DMS session"));
}
});
});
} catch (error) {
console.error("Failed to clear DMS session", error);
}
history("/manage/accounting/receivables");
};
socket.on("connect", handleConnect);
socket.on("reconnect", handleReconnect);
socket.on("connect_error", handleConnectError);
socket.on("log-event", handleLogEvent);
socket.on("export-success", handleExportSuccess);
return () => {
socket.off("connect", handleConnect);
socket.off("reconnect", handleReconnect);
socket.off("connect_error", handleConnectError);
socket.off("log-event", handleLogEvent);
socket.off("export-success", handleExportSuccess);
};
}
}, [socket, logLevel, t, insertAuditTrail, history]);
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
@@ -183,16 +205,17 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
<Button <Button
onClick={() => { onClick={() => {
setLogs([]); setLogs([]);
socket.disconnect(); if (socket) {
socket.connect(); socket.emit("clear-dms-session");
}
}} }}
> >
Reconnect Clear Session
</Button> </Button>
</Space> </Space>
} }
> >
<DmsLogEvents socket={socket} logs={logs} /> <DmsLogEvents logs={logs} />
</Card> </Card>
</div> </div>
</Col> </Col>

View File

@@ -1,6 +1,6 @@
import { FloatButton, Layout, Spin } from "antd"; import { FloatButton, Layout, Spin } from "antd";
// import preval from "preval.macro"; // import preval from "preval.macro";
import React, { lazy, Suspense, useEffect, useState } from "react"; import React, { lazy, Suspense, useContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, Route, Routes } from "react-router-dom"; import { Link, Route, Routes } from "react-router-dom";
@@ -18,12 +18,13 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component"; import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import { requestForToken } from "../../firebase/firebase.utils";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors"; import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component"; import UpdateAlert from "../../components/update-alert/update-alert.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { requestForToken } from "../../firebase/firebase.utils.js";
const JobsPage = lazy(() => import("../jobs/jobs.page")); const JobsPage = lazy(() => import("../jobs/jobs.page"));
@@ -110,6 +111,7 @@ const mapDispatchToProps = (dispatch) => ({});
export function Manage({ conflict, bodyshop }) { export function Manage({ conflict, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [chatVisible] = useState(false); const [chatVisible] = useState(false);
const { socket, clientId } = useContext(SocketContext);
useEffect(() => { useEffect(() => {
const widgetId = InstanceRenderManager({ const widgetId = InstanceRenderManager({
@@ -129,6 +131,28 @@ export function Manage({ conflict, bodyshop }) {
promanager: t("titles.promanager") promanager: t("titles.promanager")
}); });
}, [t]); }, [t]);
useEffect(() => {
if (socket && bodyshop && bodyshop.id) {
const handleConnect = () => {
socket.emit("join-bodyshop-room", bodyshop.id);
};
const handleBodyshopMessage = (message) => {
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
};
socket.on("connect", handleConnect);
socket.on("bodyshop-message", handleBodyshopMessage);
return () => {
socket.emit("leave-bodyshop-room", bodyshop.id);
socket.off("connect", handleConnect);
socket.off("bodyshop-message", handleBodyshopMessage);
};
}
}, [socket, bodyshop]);
const AppRouteTable = ( const AppRouteTable = (
<Suspense <Suspense
fallback={ fallback={
@@ -569,6 +593,13 @@ export function Manage({ conflict, bodyshop }) {
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />; else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
else PageContent = AppRouteTable; else PageContent = AppRouteTable;
const broadcastMessage = () => {
if (socket && bodyshop && bodyshop.id) {
console.log(`Broadcasting message to bodyshop ${bodyshop.id}:`);
socket.emit("broadcast-to-bodyshop", bodyshop.id, `Hello from ${clientId}`);
}
};
return ( return (
<> <>
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />} {import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
@@ -603,6 +634,8 @@ export function Manage({ conflict, bodyshop }) {
</div> </div>
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} /> <div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
</div> </div>
<button onClick={broadcastMessage}>Broadcast Message</button>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}> <Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices Disclaimer & Notices
</Link> </Link>

View File

@@ -2,9 +2,9 @@ import axios from "axios";
import { auth } from "../firebase/firebase.utils"; import { auth } from "../firebase/firebase.utils";
import InstanceRenderManager from "./instanceRenderMgr"; import InstanceRenderManager from "./instanceRenderMgr";
axios.defaults.baseURL = axios.defaults.baseURL = import.meta.env.DEV
import.meta.env.VITE_APP_AXIOS_BASE_API_URL || ? "/api/"
(import.meta.env.MODE === "production" ? "https://api.imex.online/" : "http://localhost:4000/"); : import.meta.env.VITE_APP_AXIOS_BASE_API_URL || "https://api.imex.online/";
export const axiosAuthInterceptorId = axios.interceptors.request.use( export const axiosAuthInterceptorId = axios.interceptors.request.use(
async (config) => { async (config) => {

View File

@@ -3,16 +3,21 @@ import { promises as fsPromises } from "fs";
import { createRequire } from "module"; import { createRequire } from "module";
import * as path from "path"; import * as path from "path";
import * as url from "url"; import * as url from "url";
import { defineConfig } from "vite"; import { createLogger, defineConfig } from "vite";
import { ViteEjsPlugin } from "vite-plugin-ejs"; import { ViteEjsPlugin } from "vite-plugin-ejs";
import eslint from "vite-plugin-eslint"; import eslint from "vite-plugin-eslint";
import { VitePWA } from "vite-plugin-pwa"; import { VitePWA } from "vite-plugin-pwa";
import InstanceRenderManager from "./src/utils/instanceRenderMgr"; import InstanceRenderManager from "./src/utils/instanceRenderMgr";
import chalk from "chalk";
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", { process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
timeZone: "America/Los_Angeles" timeZone: "America/Los_Angeles"
}); });
const getFormattedTimestamp = () =>
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
/** This is a hack around react-virtualized, should be removed when switching to react-virtuoso */
const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`; const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`;
function reactVirtualizedFix() { function reactVirtualizedFix() {
@@ -32,6 +37,11 @@ function reactVirtualizedFix() {
} }
}; };
} }
/** End of hack */
export const logger = createLogger("info", {
allowClearScreen: false
});
export default defineConfig({ export default defineConfig({
base: "/", base: "/",
@@ -99,7 +109,6 @@ export default defineConfig({
reactVirtualizedFix(), reactVirtualizedFix(),
react(), react(),
eslint() eslint()
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
], ],
define: { define: {
APP_VERSION: JSON.stringify(process.env.npm_package_version) APP_VERSION: JSON.stringify(process.env.npm_package_version)
@@ -107,7 +116,57 @@ export default defineConfig({
server: { server: {
host: true, host: true,
port: 3000, port: 3000,
open: true open: true,
proxy: {
"/ws": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
secure: false,
ws: false,
rewrite: (path) => {
const replacedValue = path.replace(/^\/api/, "");
logger.info(
`${chalk.grey.bold(getFormattedTimestamp())} ${chalk.cyan.bold("[vite]")} ${chalk.green.bold("[API]")} ${chalk.blue(replacedValue)}`
);
return replacedValue;
}
}
},
https: {
key: await fsPromises.readFile("../certs/key.pem"),
cert: await fsPromises.readFile("../certs/cert.pem"),
allowHTTP1: false // Force HTTP/2
}
},
preview: {
port: 6000,
host: true,
open: true,
https: {
key: await fsPromises.readFile("../certs/key.pem"),
cert: await fsPromises.readFile("../certs/cert.pem"),
allowHTTP1: false // Force HTTP/2
},
proxy: {
"/ws": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
secure: false,
ws: false
}
}
}, },
build: { build: {
rollupOptions: { rollupOptions: {
@@ -121,11 +180,30 @@ export default defineConfig({
} }
}, },
optimizeDeps: { optimizeDeps: {
include: ["react", "react-dom", "antd", "@apollo/client", "@reduxjs/toolkit", "axios"], include: [
"react",
"react-dom",
"antd",
"@apollo/client",
"@reduxjs/toolkit",
"axios",
"react-router-dom",
"dayjs",
"redux",
"react-redux"
],
esbuildOptions: { esbuildOptions: {
loader: { loader: {
".js": "jsx" ".js": "jsx"
} }
} }
},
css: {
preprocessorOptions: {
scss: {
api: "modern-compiler",
quietDeps: true // Quite Deprecation Warnings, should be disabled occasionally before major upgrades
}
}
} }
}); });

View File

@@ -1,4 +1,4 @@
Must set the environment variables using: Must set the environment variables using:
firebase functions:config:set auth.graphql_endpoint="https://db.dev.bodyshop.app/v1/graphql" firebase functions:config:set auth.graphql_endpoint="https://db.dev.imex.online/v1/graphql"
auth.hasura_secret_admin_key="Dev-BodyShopApp!" auth.hasura_secret_admin_key="Dev-BodyShopApp!"

1558
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,26 +19,27 @@
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.629.0", "@aws-sdk/client-secrets-manager": "^3.654.0",
"@aws-sdk/client-ses": "^3.629.0", "@aws-sdk/client-ses": "^3.654.0",
"@aws-sdk/credential-provider-node": "^3.629.0", "@aws-sdk/credential-provider-node": "^3.654.0",
"@opensearch-project/opensearch": "^2.11.0", "@opensearch-project/opensearch": "^2.12.0",
"aws4": "^1.13.1", "@socket.io/redis-adapter": "^8.3.0",
"axios": "^1.7.4", "aws4": "^1.13.2",
"axios": "^1.7.7",
"better-queue": "^3.8.12", "better-queue": "^3.8.12",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.2", "body-parser": "^1.20.3",
"canvas": "^2.11.2", "canvas": "^2.11.2",
"chart.js": "^4.4.3", "chart.js": "^4.4.4",
"cloudinary": "^2.4.0", "cloudinary": "^2.5.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"csrf": "^3.1.0", "csrf": "^3.1.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.21.0",
"firebase-admin": "^12.3.1", "firebase-admin": "^12.5.0",
"graphql": "^16.9.0", "graphql": "^16.9.0",
"graphql-request": "^6.1.0", "graphql-request": "^6.1.0",
"graylog2": "^0.2.1", "graylog2": "^0.2.1",
@@ -49,14 +50,16 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.5.45", "moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-mailjet": "^6.0.5", "node-mailjet": "^6.0.6",
"node-persist": "^4.0.3", "node-persist": "^4.0.3",
"nodemailer": "^6.9.14", "nodemailer": "^6.9.15",
"phone": "^3.1.49", "phone": "^3.1.50",
"recursive-diff": "^1.0.9", "recursive-diff": "^1.0.9",
"redis": "^4.7.0",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"soap": "^1.1.1", "soap": "^1.1.4",
"socket.io": "^4.7.5", "socket.io": "^4.8.0",
"socket.io-adapter": "^2.5.5",
"ssh2-sftp-client": "^10.0.3", "ssh2-sftp-client": "^10.0.3",
"twilio": "^4.23.0", "twilio": "^4.23.0",
"uuid": "^10.0.0", "uuid": "^10.0.0",

383
server.js
View File

@@ -1,4 +1,3 @@
// Import core modules
const express = require("express"); const express = require("express");
const cors = require("cors"); const cors = require("cors");
const bodyParser = require("body-parser"); const bodyParser = require("body-parser");
@@ -7,104 +6,320 @@ const compression = require("compression");
const cookieParser = require("cookie-parser"); const cookieParser = require("cookie-parser");
const http = require("http"); const http = require("http");
const { Server } = require("socket.io"); const { Server } = require("socket.io");
const { createClient } = require("redis");
const { createAdapter } = require("@socket.io/redis-adapter");
const logger = require("./server/utils/logger");
// Load environment variables // Load environment variables
require("dotenv").config({ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
}); });
// Import custom utilities and handlers /**
const logger = require("./server/utils/logger"); * CORS Origin for Socket.IO
* @type {string[][]}
*/
const SOCKETIO_CORS_ORIGIN = [
"https://test.imex.online",
"https://www.test.imex.online",
"http://localhost:3000",
"https://imex.online",
"https://www.imex.online",
"https://romeonline.io",
"https://www.romeonline.io",
"https://beta.test.romeonline.io",
"https://www.beta.test.romeonline.io",
"https://beta.romeonline.io",
"https://www.beta.romeonline.io",
"https://beta.test.imex.online",
"https://www.beta.test.imex.online",
"https://beta.imex.online",
"https://www.beta.imex.online",
"https://www.test.promanager.web-est.com",
"https://test.promanager.web-est.com",
"https://www.promanager.web-est.com",
"https://www.promanager.web-est.com",
"https://old.imex.online",
"https://www.old.imex.online"
];
// Express app and server setup /**
const app = express(); * Middleware for Express app
const port = process.env.PORT || 5000; * @param app
const server = http.createServer(app); */
const io = new Server(server, { const applyMiddleware = (app) => {
path: "/ws", app.use(compression());
cors: { app.use(cookieParser());
origin: [ app.use(bodyParser.json({ limit: "50mb" }));
"https://test.imex.online", app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
"https://www.test.imex.online", app.use(cors({ credentials: true, exposedHeaders: ["set-cookie"] }));
"http://localhost:3000",
"https://imex.online", // Helper middleware
"https://www.imex.online", app.use((req, res, next) => {
"https://romeonline.io", //Added in all RO and PM routes to simplyify setup. req.logger = logger;
"https://www.romeonline.io", next();
"https://beta.test.romeonline.io", });
"https://www.beta.test.romeonline.io", };
"https://beta.romeonline.io",
"https://www.beta.romeonline.io", /**
"https://beta.test.imex.online", * Route groupings for Express app
"https://www.beta.test.imex.online", * @param app
"https://beta.imex.online", */
"https://www.beta.imex.online", const applyRoutes = (app) => {
"https://www.test.promanager.web-est.com", app.use("/", require("./server/routes/miscellaneousRoutes"));
"https://test.promanager.web-est.com", app.use("/notifications", require("./server/routes/notificationsRoutes"));
"https://www.promanager.web-est.com", app.use("/render", require("./server/routes/renderRoutes"));
"https://www.promanager.web-est.com" app.use("/mixdata", require("./server/routes/mixDataRoutes"));
], app.use("/accounting", require("./server/routes/accountingRoutes"));
methods: ["GET", "POST"], app.use("/qbo", require("./server/routes/qboRoutes"));
credentials: true, app.use("/media", require("./server/routes/mediaRoutes"));
exposedHeaders: ["set-cookie"] app.use("/sms", require("./server/routes/smsRoutes"));
app.use("/job", require("./server/routes/jobRoutes"));
app.use("/scheduling", require("./server/routes/schedulingRoutes"));
app.use("/utils", require("./server/routes/utilRoutes"));
app.use("/data", require("./server/routes/dataRoutes"));
app.use("/adm", require("./server/routes/adminRoutes"));
app.use("/tech", require("./server/routes/techRoutes"));
app.use("/intellipay", require("./server/routes/intellipayRoutes"));
app.use("/cdk", require("./server/routes/cdkRoutes"));
app.use("/csi", require("./server/routes/csiRoutes"));
app.use("/payroll", require("./server/routes/payrollRoutes"));
// Default route for forbidden access
app.get("/", (req, res) => {
res.status(200).send("Access Forbidden.");
});
};
/**
* Apply Redis to the server
* @param server
* @param app
*/
const applySocketIO = async (server, app) => {
// Redis client setup for Pub/Sub and Key-Value Store
const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" });
const subClient = pubClient.duplicate();
pubClient.on("error", (err) => logger.log(`Redis pubClient error: ${err}`, "ERROR", "redis"));
subClient.on("error", (err) => logger.log(`Redis subClient error: ${err}`, "ERROR", "redis"));
try {
await Promise.all([pubClient.connect(), subClient.connect()]);
logger.log(`[${process.env.NODE_ENV}] Connected to Redis`, "INFO", "redis", "api");
} catch (redisError) {
logger.log("Failed to connect to Redis", "ERROR", "redis", redisError);
} }
});
exports.io = io;
require("./server/web-sockets/web-socket"); process.on("SIGINT", async () => {
logger.log("Closing Redis connections...", "INFO", "redis", "api");
await Promise.all([pubClient.disconnect(), subClient.disconnect()]);
process.exit(0);
});
// Middleware const io = new Server(server, {
app.use(compression()); path: "/ws",
app.use(cookieParser()); adapter: createAdapter(pubClient, subClient),
app.use(bodyParser.json({ limit: "50mb" })); cors: {
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); origin: SOCKETIO_CORS_ORIGIN,
app.use(cors({ credentials: true, exposedHeaders: ["set-cookie"] })); methods: ["GET", "POST"],
credentials: true,
exposedHeaders: ["set-cookie"]
}
});
// Helper middleware app.use((req, res, next) => {
app.use((req, res, next) => { req.pubClient = pubClient;
req.logger = logger; req.io = io;
next(); next();
}); });
// Route groupings Object.assign(module.exports, { io, pubClient });
app.use("/", require("./server/routes/miscellaneousRoutes"));
app.use("/notifications", require("./server/routes/notificationsRoutes"));
app.use("/render", require("./server/routes/renderRoutes"));
app.use("/mixdata", require("./server/routes/mixDataRoutes"));
app.use("/accounting", require("./server/routes/accountingRoutes"));
app.use("/qbo", require("./server/routes/qboRoutes"));
app.use("/media", require("./server/routes/mediaRoutes"));
app.use("/sms", require("./server/routes/smsRoutes"));
app.use("/job", require("./server/routes/jobRoutes"));
app.use("/scheduling", require("./server/routes/schedulingRoutes"));
app.use("/utils", require("./server/routes/utilRoutes"));
app.use("/data", require("./server/routes/dataRoutes"));
app.use("/adm", require("./server/routes/adminRoutes"));
app.use("/tech", require("./server/routes/techRoutes"));
app.use("/intellipay", require("./server/routes/intellipayRoutes"));
app.use("/cdk", require("./server/routes/cdkRoutes"));
app.use("/csi", require("./server/routes/csiRoutes"));
app.use("/payroll", require("./server/routes/payrollRoutes"));
// Default route for forbidden access return { pubClient, io };
app.get("/", (req, res) => { };
res.status(200).send("Access Forbidden.");
});
/**
* Apply Redis helper functions
* @param pubClient
* @param app
*/
const applyRedisHelpers = (pubClient, app) => {
// Store session data in Redis
const setSessionData = async (socketId, key, value) => {
await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
};
// Retrieve session data from Redis
const getSessionData = async (socketId, key) => {
const data = await pubClient.hGet(`socket:${socketId}`, key);
return data ? JSON.parse(data) : null;
};
// Clear session data from Redis
const clearSessionData = async (socketId) => {
await pubClient.del(`socket:${socketId}`);
};
// Store multiple session data in Redis
const setMultipleSessionData = async (socketId, keyValues) => {
// keyValues is expected to be an object { key1: value1, key2: value2, ... }
const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]);
await pubClient.hSet(`socket:${socketId}`, ...entries.flat());
};
// Retrieve multiple session data from Redis
const getMultipleSessionData = async (socketId, keys) => {
const data = await pubClient.hmGet(`socket:${socketId}`, keys);
// Redis returns an object with null values for missing keys, so we parse the non-null ones
return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null]));
};
const setMultipleFromArraySessionData = async (socketId, keyValueArray) => {
// Use Redis multi/pipeline to batch the commands
const multi = pubClient.multi();
keyValueArray.forEach(([key, value]) => {
multi.hSet(`socket:${socketId}`, key, JSON.stringify(value));
});
await multi.exec(); // Execute all queued commands
};
// Helper function to add an item to the end of the Redis list
const addItemToEndOfList = async (socketId, key, newItem) => {
try {
await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) {
console.error(`Error adding item to the end of the list for socket ${socketId}:`, error);
}
};
// Helper function to add an item to the beginning of the Redis list
const addItemToBeginningOfList = async (socketId, key, newItem) => {
try {
await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) {
console.error(`Error adding item to the beginning of the list for socket ${socketId}:`, error);
}
};
// Helper function to clear a list in Redis
const clearList = async (socketId, key) => {
try {
await pubClient.del(`socket:${socketId}:${key}`);
} catch (error) {
console.error(`Error clearing list for socket ${socketId}:`, error);
}
};
const api = {
setSessionData,
getSessionData,
clearSessionData,
setMultipleSessionData,
getMultipleSessionData,
setMultipleFromArraySessionData,
addItemToEndOfList,
addItemToBeginningOfList,
clearList
};
Object.assign(module.exports, api);
app.use((req, res, next) => {
req.sessionUtils = api;
next();
});
// // Demo to show how all the helper functions work
// const demoSessionData = async () => {
// const socketId = "testSocketId";
//
// // Store session data using setSessionData
// await exports.setSessionData(socketId, "field1", "Hello, Redis!");
//
// // Retrieve session data using getSessionData
// const field1Value = await exports.getSessionData(socketId, "field1");
// console.log("Retrieved single field value:", field1Value);
//
// // Store multiple session data using setMultipleSessionData
// await exports.setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" });
//
// // Retrieve multiple session data using getMultipleSessionData
// const multipleFields = await exports.getMultipleSessionData(socketId, ["field2", "field3"]);
// console.log("Retrieved multiple field values:", multipleFields);
//
// // Store multiple session data using setMultipleFromArraySessionData
// await exports.setMultipleFromArraySessionData(socketId, [
// ["field4", "Fourth Value"],
// ["field5", "Fifth Value"]
// ]);
//
// // Retrieve and log all fields
// const allFields = await exports.getMultipleSessionData(socketId, [
// "field1",
// "field2",
// "field3",
// "field4",
// "field5"
// ]);
// console.log("Retrieved all field values:", allFields);
//
// // Add item to the end of a Redis list
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() });
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() });
//
// // Add item to the beginning of a Redis list
// await exports.addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() });
//
// // Retrieve the entire list (using lRange)
// const logEvents = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List:", logEvents.map(JSON.parse));
//
// // **Add the new code below to test clearList**
//
// // Clear the list using clearList
// await exports.clearList(socketId, "logEvents");
// console.log("Log Events List cleared.");
//
// // Retrieve the list after clearing to confirm it's empty
// const logEventsAfterClear = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List after clearing:", logEventsAfterClear); // Should be an empty array
//
// // Clear session data
// await exports.clearSessionData(socketId);
// console.log("Session data cleared.");
// };
//
// if (process.env.NODE_ENV === "development") {
// demoSessionData();
// }
};
/**
* Main function to start the server
* @returns {Promise<void>}
*/
const main = async () => { const main = async () => {
await server.listen(port); const app = express();
const port = process.env.PORT || 5000;
const server = http.createServer(app);
const { pubClient } = await applySocketIO(server, app);
applyRedisHelpers(pubClient, app);
require("./server/web-sockets/web-socket");
applyMiddleware(app);
applyRoutes(app);
try {
await server.listen(port);
logger.log(`[${process.env.NODE_ENV}] Server started on port ${port}`, "INFO", "api");
} catch (error) {
logger.log(`[${process.env.NODE_ENV}] Server failed to start on port ${port}`, "ERROR", "api", error);
}
}; };
// Start server // Start server
main() main();
.then(() => {
logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server started on port ${port}`, "INFO", "api");
})
.catch((error) => {
logger.log(
`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server failed to start on port ${port}`,
"ERROR",
"api",
error
);
});

View File

@@ -12,67 +12,92 @@ const AxiosLib = require("axios").default;
const axios = AxiosLib.create(); const axios = AxiosLib.create();
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants"); const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const { CheckForErrors } = require("./pbs-job-export"); const { CheckForErrors } = require("./pbs-job-export");
const { getSessionData, getMultipleSessionData, setMultipleSessionData } = require("../../../server");
const uuid = require("uuid").v4; const uuid = require("uuid").v4;
axios.interceptors.request.use((x) => {
const socket = x.socket;
const headers = { axios.interceptors.request.use(
...x.headers.common, async (x) => {
...x.headers[x.method], const socket = x.socket;
...x.headers
};
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data); const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
return x; const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
}); x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
axios.interceptors.response.use((x) => { console.log(printable);
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`; // Use await properly here for the async operation
console.log(printable); await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
return x; return x; // Return the modified request
}); },
(error) => {
return Promise.reject(error); // Proper error handling
}
);
axios.interceptors.response.use(
async (x) => {
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
// Use await properly here for the async operation
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
return x; // Return the modified response
},
(error) => {
return Promise.reject(error); // Proper error handling
}
);
async function PbsCalculateAllocationsAp(socket, billids) { async function PbsCalculateAllocationsAp(socket, billids) {
try { try {
CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`); await CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
const { bills, bodyshops } = await QueryBillData(socket, billids); const { bills, bodyshops } = await QueryBillData(socket, billids);
const bodyshop = bodyshops[0]; const bodyshop = bodyshops[0];
socket.bodyshop = bodyshop;
socket.bills = bills; await setMultipleSessionData(socket.id, {
bills,
bodyshop
});
const txEnvelope = await getSessionData(socket.id, "txEnvelope");
//Each bill will enter it's own top level transaction. //Each bill will enter it's own top level transaction.
const transactionlist = []; const transactionlist = [];
if (bills.length === 0) { if (bills.length === 0) {
CdkBase.createLogEvent( await CdkBase.createLogEvent(
socket, socket,
"ERROR", "ERROR",
`No bills found for export. Ensure they have not already been exported and try again.` `No bills found for export. Ensure they have not already been exported and try again.`
); );
} }
bills.forEach((bill) => { bills.forEach((bill) => {
//Keep the allocations at the bill level. //Keep the allocations at the bill level.
const transactionObject = { const transactionObject = {
SerialNumber: socket.bodyshop.pbs_serialnumber, SerialNumber: bodyshop.pbs_serialnumber,
billid: bill.id, billid: bill.id,
Posting: { Posting: {
Reference: bill.invoice_number,
JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null,
TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
//Description: "Bulk AP posting.", //Description: "Bulk AP posting.",
//AdditionalInfo: "String", //AdditionalInfo: "String",
Source: "ImEX Online", //TODO:AIO Resolve this for rome online. Reference: bill.invoice_number,
Lines: [] //socket.apAllocations, JournalCode: txEnvelope?.journal,
TransactionDate: moment().tz(bodyshop.timezone).toISOString(),
Source: "ImEX Online", // TODO: Resolve this for Rome Online.
Lines: [] // Will be populated with allocation data,
} }
}; };
@@ -117,13 +142,13 @@ async function PbsCalculateAllocationsAp(socket, billids) {
}; };
} }
//Add the line amount. // Add the line amount.
billHash[cc.name] = { billHash[cc.name] = {
...billHash[cc.name], ...billHash[cc.name],
Amount: billHash[cc.name].Amount.add(lineDinero) Amount: billHash[cc.name].Amount.add(lineDinero)
}; };
//Does the line have taxes? // Does the line have taxes?
if (bl.applicable_taxes.federal) { if (bl.applicable_taxes.federal) {
billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] = { billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] = {
...billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name], ...billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name],
@@ -145,7 +170,7 @@ async function PbsCalculateAllocationsAp(socket, billids) {
let APAmount = Dinero(); let APAmount = Dinero();
Object.keys(billHash).map((key) => { Object.keys(billHash).map((key) => {
if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) { if (billHash[key].Amount.getAmount() !== 0) {
transactionObject.Posting.Lines.push({ transactionObject.Posting.Lines.push({
...billHash[key], ...billHash[key],
Amount: billHash[key].Amount.toFormat("0.00") Amount: billHash[key].Amount.toFormat("0.00")
@@ -169,19 +194,19 @@ async function PbsCalculateAllocationsAp(socket, billids) {
return transactionlist; return transactionlist;
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
} }
} }
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp; exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
async function QueryBillData(socket, billids) { async function QueryBillData(socket, billids) {
CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`); await CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids }); .request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });
CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`); await CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
return result; return result;
} }
@@ -196,40 +221,49 @@ function getCostAccount(billline, respcenters) {
} }
exports.PbsExportAp = async function (socket, { billids, txEnvelope }) { exports.PbsExportAp = async function (socket, { billids, txEnvelope }) {
CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`); await CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
//apAllocations has the same shap as the lines key for the accounting posting to PBS. const apAllocations = await PbsCalculateAllocationsAp(socket, billids);
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
socket.txEnvelope = txEnvelope; await setMultipleSessionData(socket.id, {
for (const allocation of socket.apAllocations) { apAllocations,
txEnvelope
});
for (const allocation of apAllocations) {
const { billid, ...restAllocation } = allocation; const { billid, ...restAllocation } = allocation;
const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, { const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
auth: PBS_CREDENTIALS, auth: PBS_CREDENTIALS,
socket socket
}); });
CheckForErrors(socket, AccountPostingChange); CheckForErrors(socket, AccountPostingChange).catch((err) =>
console.error(`Error running CheckingForErrors in pbs-ap-allocations`)
);
if (AccountPostingChange.WasSuccessful) { if (AccountPostingChange.WasSuccessful) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`); await CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
await MarkApExported(socket, [billid]); await MarkApExported(socket, [billid]);
socket.emit("ap-export-success", billid); socket.emit("ap-export-success", billid);
} else { } else {
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`); await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
socket.emit("ap-export-failure", { socket.emit("ap-export-failure", {
billid, billid,
error: AccountPostingChange.Message error: AccountPostingChange.Message
}); });
} }
} }
socket.emit("ap-export-complete"); socket.emit("ap-export-complete");
}; };
async function MarkApExported(socket, billids) { async function MarkApExported(socket, billids) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`); const { bills, bodyshop } = await getMultipleSessionData(socket.id, ["bills", "bodyshop"]);
await CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client return await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_BILLS_EXPORTED, { .request(queries.MARK_BILLS_EXPORTED, {
billids, billids,
@@ -237,13 +271,11 @@ async function MarkApExported(socket, billids) {
exported: true, exported: true,
exported_at: new Date() exported_at: new Date()
}, },
logs: socket.bills.map((bill) => ({ logs: bills.map((bill) => ({
bodyshopid: socket.bodyshop.id, bodyshopid: bodyshop.id,
billid: bill.id, billid: bill.id,
successful: true, successful: true,
useremail: socket.user.email useremail: socket.user.email
})) }))
}); });
return result;
} }

View File

@@ -12,136 +12,169 @@ const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").defa
const CdkBase = require("../../web-sockets/web-socket"); const CdkBase = require("../../web-sockets/web-socket");
const moment = require("moment-timezone"); const moment = require("moment-timezone");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const { setSessionData, getSessionData, getMultipleSessionData, setMultipleSessionData } = require("../../../server");
const InstanceManager = require("../../utils/instanceMgr").default; const InstanceManager = require("../../utils/instanceMgr").default;
const axios = AxiosLib.create(); const axios = AxiosLib.create();
axios.interceptors.request.use((x) => { axios.interceptors.request.use(
const socket = x.socket; async (x) => {
const socket = x.socket;
const headers = { const headers = {
...x.headers.common, ...x.headers.common,
...x.headers[x.method], ...x.headers[x.method],
...x.headers ...x.headers
}; };
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data); const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
return x; console.log(printable);
});
axios.interceptors.response.use((x) => { await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`; return x; // Make sure to return the request object
console.log(printable); },
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data); (error) => {
return Promise.reject(error);
}
);
return x; axios.interceptors.response.use(
}); async (x) => {
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
return x; // Make sure to return the response object
},
(error) => {
return Promise.reject(error);
}
);
exports.default = async function (socket, { txEnvelope, jobid }) { exports.default = async function (socket, { txEnvelope, jobid }) {
socket.logEvents = [];
socket.recordid = jobid;
socket.txEnvelope = txEnvelope;
try { try {
CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`); await setMultipleSessionData(socket.id, {
recordid: jobid,
txEnvelope
});
await CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);
const JobData = await QueryJobData(socket, jobid); const JobData = await QueryJobData(socket, jobid);
socket.JobData = JobData; await setSessionData(socket.id, "JobData", JobData);
CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`);
//Query for the Vehicle record to get the associated customer. await CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`);
socket.DmsVeh = await QueryVehicleFromDms(socket);
// Query for the Vehicle record to get the associated customer
const DmsVeh = await QueryVehicleFromDms(socket);
await setSessionData(socket.id, "DmsVeh", DmsVeh);
let DMSVehCustomer;
//Todo: Need to validate the lines and methods below. //Todo: Need to validate the lines and methods below.
if (socket.DmsVeh && socket.DmsVeh.CustomerRef) { if (DmsVeh?.CustomerRef) {
//Get the associated customer from the Vehicle Record. // Get the associated customer from the Vehicle Record
socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef); DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, DmsVeh.CustomerRef);
await setSessionData(socket.id, "DMSVehCustomer", DMSVehCustomer);
} }
socket.DMSCustList = await QueryCustomersFromDms(socket);
const DMSCustList = await QueryCustomersFromDms(socket);
await setSessionData(socket.id, "DMSCustList", DMSCustList);
socket.emit("pbs-select-customer", [ socket.emit("pbs-select-customer", [
...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []), ...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []),
...socket.DMSCustList ...DMSCustList
]); ]);
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
} }
}; };
exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) { exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) {
try { try {
if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) { const JobData = await getSessionData(socket.id, "JobData");
CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`);
//Upsert the contact information as per Wafaa's Email. if (JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) {
CdkBase.createLogEvent( await CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`);
// Upsert the contact information as per Wafaa's Email
await CdkBase.createLogEvent(
socket, socket,
"DEBUG", "DEBUG",
`Upserting contact information to DMS for ${ `Upserting contact information to DMS for ${
socket.JobData.ownr_fn || "" JobData.ownr_fn || ""
} ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}` } ${JobData.ownr_ln || ""} ${JobData.ownr_co_nm || ""}`
); );
const ownerRef = await UpsertContactData(socket, selectedCustomerId); const ownerRef = await UpsertContactData(socket, selectedCustomerId);
CdkBase.createLogEvent(socket, "DEBUG", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`); await CdkBase.createLogEvent(socket, "DEBUG", `Upserting vehicle information to DMS for ${JobData.v_vin}`);
await UpsertVehicleData(socket, ownerRef.ReferenceId); await UpsertVehicleData(socket, ownerRef.ReferenceId);
} else { } else {
CdkBase.createLogEvent( await CdkBase.createLogEvent(
socket, socket,
"DEBUG", "DEBUG",
`Contact and Vehicle updates disabled. Skipping to accounting data insert.` `Contact and Vehicle updates disabled. Skipping to accounting data insert.`
); );
} }
CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`);
CdkBase.createLogEvent(socket, "DEBUG", `Inserting accounting posting data..`); await CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`);
await CdkBase.createLogEvent(socket, "DEBUG", `Inserting accounting posting data..`);
const insertResponse = await InsertAccountPostingData(socket); const insertResponse = await InsertAccountPostingData(socket);
// TODO: Insert Clear session
if (insertResponse.WasSuccessful) { if (insertResponse.WasSuccessful) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`); await CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
await MarkJobExported(socket, socket.JobData.id); await MarkJobExported(socket, JobData.id);
socket.emit("export-success", socket.JobData.id); socket.emit("export-success", JobData.id);
} else { } else {
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`); await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
} }
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
await InsertFailedExportLog(socket, error); await InsertFailedExportLog(socket, error);
} }
}; };
async function CheckForErrors(socket, response) { async function CheckForErrors(socket, response) {
if (response.WasSuccessful === undefined || response.WasSuccessful === true) { if (response.WasSuccessful === undefined || response.WasSuccessful === true) {
CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`); await CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`);
} else { } else {
CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`); await CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`); await CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`);
} }
} }
exports.CheckForErrors = CheckForErrors; exports.CheckForErrors = CheckForErrors;
async function QueryJobData(socket, jobid) { async function QueryJobData(socket, jobid) {
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); await CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid }); .request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid });
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`); await CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk; return result.jobs_by_pk;
} }
async function QueryVehicleFromDms(socket) { async function QueryVehicleFromDms(socket) {
try { try {
if (!socket.JobData.v_vin) return null; const JobData = await getSessionData(socket.id, "JobData");
if (!JobData.v_vin) return null;
const { data: VehicleGetResponse, request } = await axios.post( const { data: VehicleGetResponse, request } = await axios.post(
PBS_ENDPOINTS.VehicleGet, PBS_ENDPOINTS.VehicleGet,
{ {
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, SerialNumber: JobData.bodyshop.pbs_serialnumber,
// VehicleId: "00000000000000000000000000000000", // VehicleId: "00000000000000000000000000000000",
// Year: "String", // Year: "String",
// Make: "String", // Make: "String",
@@ -149,7 +182,7 @@ async function QueryVehicleFromDms(socket) {
// Trim: "String", // Trim: "String",
// ModelNumber: "String", // ModelNumber: "String",
// StockNumber: "String", // StockNumber: "String",
VIN: socket.JobData.v_vin VIN: JobData.v_vin
// LicenseNumber: "String", // LicenseNumber: "String",
// Lot: "String", // Lot: "String",
// Status: "String", // Status: "String",
@@ -166,26 +199,31 @@ async function QueryVehicleFromDms(socket) {
{ auth: PBS_CREDENTIALS, socket } { auth: PBS_CREDENTIALS, socket }
); );
CheckForErrors(socket, VehicleGetResponse); await CheckForErrors(socket, VehicleGetResponse);
return VehicleGetResponse; return VehicleGetResponse;
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
throw new Error(error); throw new Error(error);
} }
} }
async function QueryCustomersFromDms(socket) { async function QueryCustomersFromDms(socket) {
try { try {
// Retrieve JobData from session storage
const JobData = await getSessionData(socket.id, "JobData");
// Make an API call to PBS to query customer details
const { data: CustomerGetResponse } = await axios.post( const { data: CustomerGetResponse } = await axios.post(
PBS_ENDPOINTS.ContactGet, PBS_ENDPOINTS.ContactGet,
{ {
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, SerialNumber: JobData.bodyshop.pbs_serialnumber,
//ContactId: "00000000000000000000000000000000", //ContactId: "00000000000000000000000000000000",
// ContactCode: socket.JobData.owner.accountingid, // ContactCode: JobData.owner.accountingid,
FirstName: socket.JobData.ownr_fn, FirstName: JobData.ownr_fn,
LastName: socket.JobData.ownr_co_nm ? socket.JobData.ownr_co_nm : socket.JobData.ownr_ln, LastName: JobData.ownr_co_nm ? JobData.ownr_co_nm : JobData.ownr_ln,
PhoneNumber: socket.JobData.ownr_ph1, PhoneNumber: JobData.ownr_ph1,
EmailAddress: socket.JobData.ownr_ea EmailAddress: JobData.ownr_ea
// ModifiedSince: "0001-01-01T00:00:00.0000000Z", // ModifiedSince: "0001-01-01T00:00:00.0000000Z",
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z", // ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
// ContactIdList: ["00000000000000000000000000000000"], // ContactIdList: ["00000000000000000000000000000000"],
@@ -197,27 +235,36 @@ async function QueryCustomersFromDms(socket) {
}, },
{ auth: PBS_CREDENTIALS, socket } { auth: PBS_CREDENTIALS, socket }
); );
CheckForErrors(socket, CustomerGetResponse);
// Check for errors in the PBS response
await CheckForErrors(socket, CustomerGetResponse);
// Return the list of contacts from the PBS response
return CustomerGetResponse && CustomerGetResponse.Contacts; return CustomerGetResponse && CustomerGetResponse.Contacts;
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`); // Log any errors encountered during the API call
await CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
throw new Error(error); throw new Error(error);
} }
} }
async function QueryCustomerBycodeFromDms(socket, CustomerRef) { async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
try { try {
// Retrieve JobData from session storage
const JobData = await getSessionData(socket.id, "JobData");
// Make an API call to PBS to query customer by ContactId
const { data: CustomerGetResponse } = await axios.post( const { data: CustomerGetResponse } = await axios.post(
PBS_ENDPOINTS.ContactGet, PBS_ENDPOINTS.ContactGet,
{ {
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, SerialNumber: JobData.bodyshop.pbs_serialnumber,
ContactId: CustomerRef ContactId: CustomerRef
//ContactCode: socket.JobData.owner.accountingid, //ContactCode: JobData.owner.accountingid,
//FirstName: socket.JobData.ownr_co_nm //FirstName: JobData.ownr_co_nm
// ? socket.JobData.ownr_co_nm // ? JobData.ownr_co_nm
// : socket.JobData.ownr_fn, // : JobData.ownr_fn,
//LastName: socket.JobData.ownr_ln, //LastName: JobData.ownr_ln,
//PhoneNumber: socket.JobData.ownr_ph1, //PhoneNumber: JobData.ownr_ph1,
// EmailAddress: "String", // EmailAddress: "String",
// ModifiedSince: "0001-01-01T00:00:00.0000000Z", // ModifiedSince: "0001-01-01T00:00:00.0000000Z",
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z", // ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
@@ -230,33 +277,42 @@ async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
}, },
{ auth: PBS_CREDENTIALS, socket } { auth: PBS_CREDENTIALS, socket }
); );
CheckForErrors(socket, CustomerGetResponse);
// Check for errors in the PBS response
await CheckForErrors(socket, CustomerGetResponse);
// Return the list of contacts from the PBS response
return CustomerGetResponse && CustomerGetResponse.Contacts; return CustomerGetResponse && CustomerGetResponse.Contacts;
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`); // Log any errors encountered during the API call
await CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomerBycodeFromDms - ${error}`);
throw new Error(error); throw new Error(error);
} }
} }
async function UpsertContactData(socket, selectedCustomerId) { async function UpsertContactData(socket, selectedCustomerId) {
try { try {
// Retrieve JobData from session storage
const JobData = await getSessionData(socket.id, "JobData");
// Make an API call to PBS to upsert contact data
const { data: ContactChangeResponse } = await axios.post( const { data: ContactChangeResponse } = await axios.post(
PBS_ENDPOINTS.ContactChange, PBS_ENDPOINTS.ContactChange,
{ {
ContactInfo: { ContactInfo: {
// Id: socket.JobData.owner.id, // Id: JobData.owner.id,
...(selectedCustomerId ? { ContactId: selectedCustomerId } : {}), ...(selectedCustomerId ? { ContactId: selectedCustomerId } : {}),
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, SerialNumber: JobData.bodyshop.pbs_serialnumber,
Code: socket.JobData.owner.accountingid, Code: JobData.owner.accountingid,
...(socket.JobData.ownr_co_nm ...(JobData.ownr_co_nm
? { ? {
//LastName: socket.JobData.ownr_ln, //LastName: JobData.ownr_ln,
FirstName: socket.JobData.ownr_co_nm, FirstName: JobData.ownr_co_nm,
IsBusiness: true IsBusiness: true
} }
: { : {
LastName: socket.JobData.ownr_ln, LastName: JobData.ownr_ln,
FirstName: socket.JobData.ownr_fn, FirstName: JobData.ownr_fn,
IsBusiness: false IsBusiness: false
}), }),
@@ -266,20 +322,20 @@ async function UpsertContactData(socket, selectedCustomerId) {
IsInactive: false, IsInactive: false,
//ApartmentNumber: "String", //ApartmentNumber: "String",
Address: socket.JobData.ownr_addr1, Address: JobData.ownr_addr1,
City: socket.JobData.ownr_city, City: JobData.ownr_city,
//County: socket.JobData.ownr_addr1, //County: JobData.ownr_addr1,
State: socket.JobData.ownr_st, State: JobData.ownr_st,
ZipCode: socket.JobData.ownr_zip, ZipCode: JobData.ownr_zip,
//BusinessPhone: "String", //BusinessPhone: "String",
//BusinessPhoneExt: "String", //BusinessPhoneExt: "String",
HomePhone: socket.JobData.ownr_ph2, HomePhone: JobData.ownr_ph2,
CellPhone: socket.JobData.ownr_ph1, CellPhone: JobData.ownr_ph1,
//BusinessPhoneRawReverse: "String", //BusinessPhoneRawReverse: "String",
//HomePhoneRawReverse: "String", //HomePhoneRawReverse: "String",
//CellPhoneRawReverse: "String", //CellPhoneRawReverse: "String",
//FaxNumber: "String", //FaxNumber: "String",
EmailAddress: socket.JobData.ownr_ea EmailAddress: JobData.ownr_ea
//Notes: "String", //Notes: "String",
//CriticalMemo: "String", //CriticalMemo: "String",
//BirthDate: "0001-01-01T00:00:00.0000000Z", //BirthDate: "0001-01-01T00:00:00.0000000Z",
@@ -312,39 +368,43 @@ async function UpsertContactData(socket, selectedCustomerId) {
}, },
{ auth: PBS_CREDENTIALS, socket } { auth: PBS_CREDENTIALS, socket }
); );
CheckForErrors(socket, ContactChangeResponse);
await CheckForErrors(socket, ContactChangeResponse);
return ContactChangeResponse; return ContactChangeResponse;
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
throw new Error(error); throw new Error(error);
} }
} }
async function UpsertVehicleData(socket, ownerRef) { async function UpsertVehicleData(socket, ownerRef) {
try { try {
const JobData = await getSessionData(socket.id, "JobData");
const { data: VehicleChangeResponse } = await axios.post( const { data: VehicleChangeResponse } = await axios.post(
PBS_ENDPOINTS.VehicleChange, PBS_ENDPOINTS.VehicleChange,
{ {
VehicleInfo: { VehicleInfo: {
//Id: "string/00000000-0000-0000-0000-000000000000", //Id: "string/00000000-0000-0000-0000-000000000000",
//VehicleId: "00000000000000000000000000000000", //VehicleId: "00000000000000000000000000000000",
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, SerialNumber: JobData.bodyshop.pbs_serialnumber,
//StockNumber: "String", //StockNumber: "String",
VIN: socket.JobData.v_vin, VIN: JobData.v_vin,
LicenseNumber: socket.JobData.plate_no, LicenseNumber: JobData.plate_no,
//FleetNumber: "String", //FleetNumber: "String",
//Status: "String", //Status: "String",
OwnerRef: ownerRef, // "00000000000000000000000000000000", OwnerRef: ownerRef, // "00000000000000000000000000000000",
ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode, ModelNumber: JobData.vehicle && JobData.vehicle.v_makecode,
Make: socket.JobData.v_make_desc, Make: JobData.v_make_desc,
Model: socket.JobData.v_model_desc, Model: JobData.v_model_desc,
Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode, Trim: JobData.vehicle && JobData.vehicle.v_trimcode,
//VehicleType: "String", //VehicleType: "String",
Year: socket.JobData.v_model_yr, Year: JobData.v_model_yr,
Odometer: socket.JobData.kmout, Odometer: JobData.kmout,
ExteriorColor: { ExteriorColor: {
Code: socket.JobData.v_color, Code: JobData.v_color,
Description: socket.JobData.v_color Description: JobData.v_color
} }
// InteriorColor: { Code: "String", Description: "String" }, // InteriorColor: { Code: "String", Description: "String" },
//Engine: "String", //Engine: "String",
@@ -465,100 +525,112 @@ async function UpsertVehicleData(socket, ownerRef) {
}, },
{ auth: PBS_CREDENTIALS, socket } { auth: PBS_CREDENTIALS, socket }
); );
CheckForErrors(socket, VehicleChangeResponse);
await CheckForErrors(socket, VehicleChangeResponse);
return VehicleChangeResponse; return VehicleChangeResponse;
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
throw new Error(error); throw new Error(error);
} }
} }
async function InsertAccountPostingData(socket) { async function InsertAccountPostingData(socket) {
try { try {
const allocations = await CalculateAllocations(socket, socket.JobData.id); const { JobData, txEnvelope } = await getMultipleSessionData(socket.id, ["JobData", "txEnvelope"]);
const allocations = await CalculateAllocations(socket, JobData.id);
const wips = []; const wips = [];
allocations.forEach((alloc) => { allocations.forEach((alloc) => {
//Add the sale item from each allocation. // Add the sale item from each allocation if the amount is greater than 0 and not a tax
if (alloc.sale.getAmount() > 0 && !alloc.tax) { if (alloc.sale.getAmount() > 0 && !alloc.tax) {
const item = { const item = {
Account: alloc.profitCenter.dms_acctnumber, Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number, ControlNumber: JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.00"), Amount: alloc.sale.multiply(-1).toFormat("0.00"),
//Comment: "String", // Comment: "String",
//AdditionalInfo: "String", // AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number, InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
}; };
wips.push(item); wips.push(item);
} }
//Add the cost Item. // Add the cost item if the cost amount is greater than 0 and not a tax
if (alloc.cost.getAmount() > 0 && !alloc.tax) { if (alloc.cost.getAmount() > 0 && !alloc.tax) {
const item = { const item = {
Account: alloc.costCenter.dms_acctnumber, Account: alloc.costCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number, ControlNumber: JobData.ro_number,
Amount: alloc.cost.toFormat("0.00"), Amount: alloc.cost.toFormat("0.00"),
//Comment: "String", // Comment: "String",
//AdditionalInfo: "String", // AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number, InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
}; };
wips.push(item); wips.push(item);
const itemWip = { const itemWip = {
Account: alloc.costCenter.dms_wip_acctnumber, Account: alloc.costCenter.dms_wip_acctnumber,
ControlNumber: socket.JobData.ro_number, ControlNumber: JobData.ro_number,
Amount: alloc.cost.multiply(-1).toFormat("0.00"), Amount: alloc.cost.multiply(-1).toFormat("0.00"),
//Comment: "String", // Comment: "String",
//AdditionalInfo: "String", // AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number, InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
}; };
wips.push(itemWip); wips.push(itemWip);
//Add to the WIP account. // Add to the WIP account.
} }
// Add tax-related entries if applicable
if (alloc.tax) { if (alloc.tax) {
if (alloc.sale.getAmount() > 0) { if (alloc.sale.getAmount() > 0) {
const item2 = { const item2 = {
Account: alloc.profitCenter.dms_acctnumber, Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number, ControlNumber: JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.00"), Amount: alloc.sale.multiply(-1).toFormat("0.00"),
//Comment: "String", // Comment: "String",
//AdditionalInfo: "String", // AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number, InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
}; };
wips.push(item2); wips.push(item2);
} }
} }
}); });
socket.txEnvelope.payers.forEach((payer) => {
// Add payer information
txEnvelope.payers.forEach((payer) => {
const item = { const item = {
Account: payer.dms_acctnumber, Account: payer.dms_acctnumber,
ControlNumber: payer.controlnumber, ControlNumber: payer.controlnumber,
Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat("0.0"), Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat("0.0"),
//Comment: "String", // Comment: "String",
//AdditionalInfo: "String", // AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number, InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString() InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
}; };
wips.push(item); wips.push(item);
}); });
socket.transWips = wips;
await setSessionData(socket.id, "transWips", wips);
const { data: AccountPostingChange } = await axios.post( const { data: AccountPostingChange } = await axios.post(
PBS_ENDPOINTS.AccountingPostingChange, PBS_ENDPOINTS.AccountingPostingChange,
{ {
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber, SerialNumber: JobData.bodyshop.pbs_serialnumber,
Posting: { Posting: {
Reference: socket.JobData.ro_number, Reference: JobData.ro_number,
JournalCode: socket.txEnvelope.journal, JournalCode: txEnvelope.journal,
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z", TransactionDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString(), // "0001-01-01T00:00:00.0000000Z",
Description: socket.txEnvelope.story, Description: txEnvelope.story,
//AdditionalInfo: "String", // AdditionalInfo: "String",
Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }), Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
Lines: wips Lines: wips
} }
@@ -566,54 +638,56 @@ async function InsertAccountPostingData(socket) {
{ auth: PBS_CREDENTIALS, socket } { auth: PBS_CREDENTIALS, socket }
); );
CheckForErrors(socket, AccountPostingChange); await CheckForErrors(socket, AccountPostingChange);
return AccountPostingChange; return AccountPostingChange;
} catch (error) { } catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
throw new Error(error); throw new Error(error);
} }
} }
async function MarkJobExported(socket, jobid) { async function MarkJobExported(socket, jobid) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`); await CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
const { JobData, transWips } = await getMultipleSessionData(socket.id, ["JobData", "transWips"]);
return await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_JOB_EXPORTED, { .request(queries.MARK_JOB_EXPORTED, {
jobId: jobid, jobId: jobid,
job: { job: {
status: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*", status: JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
date_exported: new Date() date_exported: new Date()
}, },
log: { log: {
bodyshopid: socket.JobData.bodyshop.id, bodyshopid: JobData.bodyshop.id,
jobid: jobid, jobid: jobid,
successful: true, successful: true,
useremail: socket.user.email, useremail: socket.user.email,
metadata: socket.transWips metadata: transWips
}, },
bill: { bill: {
exported: true, exported: true,
exported_at: new Date() exported_at: new Date()
} }
}); });
return result;
} }
async function InsertFailedExportLog(socket, error) { async function InsertFailedExportLog(socket, error) {
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client const JobData = await getSessionData(socket.id, "JobData");
return await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.INSERT_EXPORT_LOG, { .request(queries.INSERT_EXPORT_LOG, {
log: { log: {
bodyshopid: socket.JobData.bodyshop.id, bodyshopid: JobData.bodyshop.id,
jobid: socket.JobData.id, jobid: JobData.id,
successful: false, successful: false,
message: [error], message: [error],
useremail: socket.user.email useremail: socket.user.email
} }
}); });
return result;
} }

View File

@@ -18,12 +18,12 @@ const { DiscountNotAlreadyCounted } = InstanceManager({
exports.defaultRoute = async function (req, res) { exports.defaultRoute = async function (req, res) {
try { try {
CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`); await CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid); const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
return res.status(200).json({ data: calculateAllocations(req, jobData) }); return res.status(200).json({ data: await calculateAllocations(req, jobData) });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`); await CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` }); res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
} }
}; };
@@ -31,22 +31,22 @@ exports.defaultRoute = async function (req, res) {
exports.default = async function (socket, jobid) { exports.default = async function (socket, jobid) {
try { try {
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid); const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid);
return calculateAllocations(socket, jobData); return await calculateAllocations(socket, jobData);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`); await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
} }
}; };
async function QueryJobData(connectionData, token, jobid) { async function QueryJobData(connectionData, token, jobid) {
CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`); await CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid }); const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`); await CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk; return result.jobs_by_pk;
} }
function calculateAllocations(connectionData, job) { async function calculateAllocations(connectionData, job) {
const { bodyshop } = job; const { bodyshop } = job;
const taxAllocations = InstanceManager({ const taxAllocations = InstanceManager({
@@ -164,7 +164,7 @@ function calculateAllocations(connectionData, job) {
const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find( const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find(
(d) => d.name === job.dms_allocation (d) => d.name === job.dms_allocation
); );
CdkBase.createLogEvent( await CdkBase.createLogEvent(
connectionData, connectionData,
"DEBUG", "DEBUG",
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.` `Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
@@ -354,9 +354,10 @@ function calculateAllocations(connectionData, job) {
} }
if (InstanceManager({ rome: true })) { if (InstanceManager({ rome: true })) {
//profile level adjustments for parts //profile level adjustments for parts
Object.keys(job.job_totals.parts.adjustments).forEach((key) => { for (const key of Object.keys(job.job_totals.parts.adjustments)) {
const accountName = selectedDmsAllocationConfig.profits[key]; const accountName = selectedDmsAllocationConfig.profits[key];
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName); const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
if (otherAccount) { if (otherAccount) {
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero(); if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
@@ -364,31 +365,41 @@ function calculateAllocations(connectionData, job) {
Dinero(job.job_totals.parts.adjustments[key]) Dinero(job.job_totals.parts.adjustments[key])
); );
} else { } else {
CdkBase.createLogEvent( // Use await correctly here
await CdkBase.createLogEvent(
connectionData, connectionData,
"ERROR", "ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}` `Error encountered in CdkCalculateAllocations. Unable to find adjustment account for key ${key}.`
); );
} }
}); }
//profile level adjustments for labor and materials //profile level adjustments for labor and materials
Object.keys(job.job_totals.rates).forEach((key) => { for (const key of Object.keys(job.job_totals.rates)) {
if (job.job_totals.rates[key] && job.job_totals.rates[key].adjustment && Dinero(job.job_totals.rates[key].adjustment).isZero() === false) { if (
job.job_totals.rates[key] &&
job.job_totals.rates[key].adjustment &&
Dinero(job.job_totals.rates[key].adjustment).isZero() === false
) {
const accountName = selectedDmsAllocationConfig.profits[key.toUpperCase()]; const accountName = selectedDmsAllocationConfig.profits[key.toUpperCase()];
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName); const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
if (otherAccount) { if (otherAccount) {
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero(); if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
profitCenterHash[accountName] = profitCenterHash[accountName].add(Dinero(job.job_totals.rates[key].adjustments)); profitCenterHash[accountName] = profitCenterHash[accountName].add(
Dinero(job.job_totals.rates[key].adjustments)
);
} else { } else {
CdkBase.createLogEvent( // Await the log event creation here
await CdkBase.createLogEvent(
connectionData, connectionData,
"ERROR", "ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}` `Error encountered in CdkCalculateAllocations. Unable to find adjustment account for key ${key}.`
); );
} }
} }
}); }
} }
const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => { const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => {

View File

@@ -86,7 +86,7 @@ async function GetCdkMakes(req, cdk_dealerid) {
{} {}
); );
CheckCdkResponseForError(null, soapResponseVehicleSearch); await CheckCdkResponseForError(null, soapResponseVehicleSearch);
const [result, rawResponse, , rawRequest] = soapResponseVehicleSearch; const [result, rawResponse, , rawRequest] = soapResponseVehicleSearch;
logger.log("cdk-replace-makes-models-request", "ERROR", req.user.email, null, { logger.log("cdk-replace-makes-models-request", "ERROR", req.user.email, null, {
cdk_dealerid, cdk_dealerid,

File diff suppressed because it is too large Load Diff

View File

@@ -15,10 +15,10 @@ exports.CDK_CREDENTIALS = CDK_CREDENTIALS;
const cdkDomain = const cdkDomain =
process.env.NODE_ENV === "production" ? "https://3pa.dmotorworks.com" : "https://uat-3pa.dmotorworks.com"; process.env.NODE_ENV === "production" ? "https://3pa.dmotorworks.com" : "https://uat-3pa.dmotorworks.com";
function CheckCdkResponseForError(socket, soapResponse) { async function CheckCdkResponseForError(socket, soapResponse) {
if (!soapResponse[0]) { if (!soapResponse[0]) {
//The response was null, this might be ok, it might not. //The response was null, this might be ok, it might not.
CdkBase.createLogEvent( await CdkBase.createLogEvent(
socket, socket,
"WARNING", "WARNING",
`Warning detected in CDK Response - it appears to be null. Stack: ${new Error().stack}` `Warning detected in CDK Response - it appears to be null. Stack: ${new Error().stack}`
@@ -31,23 +31,22 @@ function CheckCdkResponseForError(socket, soapResponse) {
if (Array.isArray(ResultToCheck)) { if (Array.isArray(ResultToCheck)) {
ResultToCheck.forEach((result) => checkIndividualResult(socket, result)); ResultToCheck.forEach((result) => checkIndividualResult(socket, result));
} else { } else {
checkIndividualResult(socket, ResultToCheck); await checkIndividualResult(socket, ResultToCheck);
} }
} }
exports.CheckCdkResponseForError = CheckCdkResponseForError; exports.CheckCdkResponseForError = CheckCdkResponseForError;
function checkIndividualResult(socket, ResultToCheck) { async function checkIndividualResult(socket, ResultToCheck) {
if ( if (
ResultToCheck.errorLevel === 0 || ResultToCheck.errorLevel === 0 ||
ResultToCheck.errorLevel === "0" || ResultToCheck.errorLevel === "0" ||
ResultToCheck.code === "success" || ResultToCheck.code === "success" ||
(!ResultToCheck.code && !ResultToCheck.errorLevel) (!ResultToCheck.code && !ResultToCheck.errorLevel)
) ) {
//TODO: Verify that this is the best way to detect errors. //TODO: Verify that this is the best way to detect errors.
return; } else {
else { await CdkBase.createLogEvent(
CdkBase.createLogEvent(
socket, socket,
"ERROR", "ERROR",
`Error detected in CDK Response - ${JSON.stringify(ResultToCheck, null, 2)}` `Error detected in CDK Response - ${JSON.stringify(ResultToCheck, null, 2)}`

View File

@@ -41,9 +41,11 @@ const tasksEmailQueueCleanup = async () => {
} }
}; };
// TODO: Consolidate into a group that can be run (multiple events with the same name)
if (process.env.NODE_ENV !== "development") { if (process.env.NODE_ENV !== "development") {
// Handling SIGINT (e.g., Ctrl+C) // Handling SIGINT (e.g., Ctrl+C)
process.on("SIGINT", async () => { process.on("SIGINT", async () => {
logger.log("Clearing Tasks Email Queue...", "INFO", "redis", "api");
await tasksEmailQueueCleanup(); await tasksEmailQueueCleanup();
process.exit(0); process.exit(0);
}); });

View File

@@ -3,64 +3,68 @@ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
}); });
const { io } = require("../../server"); const { io, setSessionData, clearSessionData, getMultipleSessionData, addItemToEndOfList } = require("../../server");
const { admin } = require("../firebase/firebase-handler"); const { admin } = require("../firebase/firebase-handler");
const logger = require("../utils/logger");
const { default: CdkJobExport, CdkSelectedCustomer } = require("../cdk/cdk-job-export"); const { default: CdkJobExport, CdkSelectedCustomer } = require("../cdk/cdk-job-export");
const CdkGetMakes = require("../cdk/cdk-get-makes").default; const CdkGetMakes = require("../cdk/cdk-get-makes").default;
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default; const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
const { isArray } = require("lodash");
const logger = require("../utils/logger");
const { default: PbsExportJob, PbsSelectedCustomer } = require("../accounting/pbs/pbs-job-export"); const { default: PbsExportJob, PbsSelectedCustomer } = require("../accounting/pbs/pbs-job-export");
const { PbsCalculateAllocationsAp, PbsExportAp } = require("../accounting/pbs/pbs-ap-allocations"); const { PbsCalculateAllocationsAp, PbsExportAp } = require("../accounting/pbs/pbs-ap-allocations");
io.use(function (socket, next) { // Middleware for verifying tokens via Firebase authentication
try { function socketAuthMiddleware(socket, next) {
if (socket.handshake.auth.token) { const token = socket.handshake.auth.token;
admin
.auth() if (!token) return next(new Error("Authentication error - no authorization token."));
.verifyIdToken(socket.handshake.auth.token)
.then((user) => { admin
socket.user = user; .auth()
next(); .verifyIdToken(token)
}) .then((user) => {
.catch((error) => { socket.user = user;
next(new Error("Authentication error", JSON.stringify(error))); next();
}); })
} else { .catch((error) => {
next(new Error("Authentication error - no authorization token.")); next(new Error("Authentication error", JSON.stringify(error)));
}
} catch (error) {
console.log("Uncaught connection error:::", error);
logger.log("websocket-connection-error", "error", null, null, {
token: socket.handshake.auth.token,
...error
}); });
next(new Error(`Authentication error ${error}`)); }
}
});
io.on("connection", (socket) => { // Register all socket events for a given socket connection
socket.log_level = "TRACE"; async function registerSocketEvents(socket) {
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`); await setSessionData(socket.id, "log_level", "TRACE");
await createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
socket.on("set-log-level", (level) => { // Register CDK-related socket events
socket.log_level = level; registerCdkEvents(socket);
socket.emit("log-event", {
timestamp: new Date(), // Register PBS AR-related socket events
level: "INFO", registerPbsArEvents(socket);
message: `Updated log level to ${level}`
}); // Register PBS AP-related socket events
registerPbsApEvents(socket);
// Register room and broadcasting events
registerRoomAndBroadcastEvents(socket);
// Register event to clear DMS session
registerDmsClearSessionEvent(socket);
// Handle socket disconnection
socket.on("disconnect", async () => {
await createLogEvent(socket, "DEBUG", `User disconnected.`);
}); });
}
///CDK // CDK-specific socket events
socket.on("cdk-export-job", (jobid) => { function registerCdkEvents(socket) {
CdkJobExport(socket, jobid); socket.on("cdk-export-job", (jobid) => CdkJobExport(socket, jobid));
});
socket.on("cdk-selected-customer", (selectedCustomerId) => { socket.on("cdk-selected-customer", async (selectedCustomerId) => {
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`); await createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
socket.selectedCustomerId = selectedCustomerId;
CdkSelectedCustomer(socket, selectedCustomerId); CdkSelectedCustomer(socket, selectedCustomerId);
await setSessionData(socket.id, "selectedCustomerId ", selectedCustomerId);
}); });
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => { socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
@@ -68,159 +72,160 @@ io.on("connection", (socket) => {
const makes = await CdkGetMakes(socket, cdk_dealerid); const makes = await CdkGetMakes(socket, cdk_dealerid);
callback(makes); callback(makes);
} catch (error) { } catch (error) {
createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`); await createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`);
} }
}); });
socket.on("cdk-calculate-allocations", async (jobid, callback) => { socket.on("cdk-calculate-allocations", async (jobid, callback) => {
const allocations = await CdkCalculateAllocations(socket, jobid); const allocations = await CdkCalculateAllocations(socket, jobid);
createLogEvent(socket, "DEBUG", `Allocations calculated.`); await createLogEvent(socket, "DEBUG", `Allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`); await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
callback(allocations); callback(allocations);
}); });
//END CDK }
//PBS AR // PBS AR-specific socket events
function registerPbsArEvents(socket) {
socket.on("pbs-calculate-allocations", async (jobid, callback) => { socket.on("pbs-calculate-allocations", async (jobid, callback) => {
const allocations = await CdkCalculateAllocations(socket, jobid); const allocations = await CdkCalculateAllocations(socket, jobid);
createLogEvent(socket, "DEBUG", `Allocations calculated.`); await createLogEvent(socket, "DEBUG", `PBS AR allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`); await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
callback(allocations); callback(allocations);
}); });
socket.on("pbs-export-job", (jobid) => {
PbsExportJob(socket, jobid);
});
socket.on("pbs-selected-customer", (selectedCustomerId) => {
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
socket.selectedCustomerId = selectedCustomerId;
PbsSelectedCustomer(socket, selectedCustomerId);
});
//End PBS AR
//PBS AP socket.on("pbs-export-job", (jobid) => PbsExportJob(socket, jobid));
socket.on("pbs-selected-customer", async (selectedCustomerId) => {
await createLogEvent(socket, "DEBUG", `PBS AR selected customer ID ${selectedCustomerId}`);
PbsSelectedCustomer(socket, selectedCustomerId).catch((err) =>
console.error(`Error in pbs-selected-customer: ${err}`)
);
await setSessionData(socket.id, "selectedCustomerId", selectedCustomerId);
});
}
// PBS AP-specific socket events
function registerPbsApEvents(socket) {
socket.on("pbs-calculate-allocations-ap", async (billids, callback) => { socket.on("pbs-calculate-allocations-ap", async (billids, callback) => {
const allocations = await PbsCalculateAllocationsAp(socket, billids); const allocations = await PbsCalculateAllocationsAp(socket, billids);
createLogEvent(socket, "DEBUG", `AP Allocations calculated.`); await createLogEvent(socket, "DEBUG", `PBS AP allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`); await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
socket.apAllocations = allocations;
callback(allocations); callback(allocations);
await setSessionData(socket.id, "pbs_ap_allocations", allocations);
}); });
socket.on("pbs-export-ap", ({ billids, txEnvelope }) => { socket.on("pbs-export-ap", async ({ billids, txEnvelope }) => {
socket.txEnvelope = txEnvelope; await setSessionData(socket.id, "txEnvelope", txEnvelope);
PbsExportAp(socket, { billids, txEnvelope }); PbsExportAp(socket, {
billids,
txEnvelope
}).catch((err) => console.error(`Error in pbs-export-ap: ${err}`));
});
}
// Room management and broadcasting events
function registerRoomAndBroadcastEvents(socket) {
socket.on("join-bodyshop-room", async (bodyshopUUID) => {
socket.join(bodyshopUUID);
await createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`);
}); });
//END PBS AP socket.on("leave-bodyshop-room", async (bodyshopUUID) => {
socket.leave(bodyshopUUID);
socket.on("disconnect", () => { await createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${bodyshopUUID}`);
createLogEvent(socket, "DEBUG", `User disconnected.`);
}); });
});
function createLogEvent(socket, level, message) { socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => {
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) { io.to(bodyshopUUID).emit("bodyshop-message", message);
console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`); await createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${bodyshopUUID}`);
socket.emit("log-event", { });
timestamp: new Date(), }
level,
message
});
logger.log("ws-log-event", level, socket.user.email, socket.recordid, { // DMS session clearing event
wsmessage: message function registerDmsClearSessionEvent(socket) {
}); socket.on("clear-dms-session", async () => {
await clearSessionData(socket.id); // Clear all session data in Redis
await createLogEvent(socket, "INFO", `DMS session data cleared for socket ${socket.id}`);
});
}
if (socket.logEvents && isArray(socket.logEvents)) { // Logging helper functions
socket.logEvents.push({ async function createLogEvent(socket, level, message) {
timestamp: new Date(), const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
level,
message if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) {
}); const logMessage = { timestamp: new Date(), level, message };
}
// if (level === "ERROR") { // Log the message to the console and emit it via the socket
// throw new Error(message); console.log(`[WS LOG EVENT] ${level} - ${logMessage.timestamp} - ${socket.user.email} - ${socket.id} - ${message}`);
// } socket.emit("log-event", logMessage);
// Log the event via the logger
logger.log("ws-log-event", level, socket.user.email, recordid, { wsmessage: message });
// Add the log message to the Redis list using the helper function
await addItemToEndOfList(socket.id, "logEvents", logMessage);
} }
} }
function createJsonEvent(socket, level, message, json) { async function createJsonEvent(socket, level, message, json) {
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) { const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`);
socket.emit("log-event", {
timestamp: new Date(),
level,
message
});
}
logger.log("ws-log-event-json", level, socket.user.email, socket.recordid, {
wsmessage: message,
json
});
if (socket.logEvents && isArray(socket.logEvents)) { if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) {
socket.logEvents.push({ const logMessage = { timestamp: new Date(), level, message };
timestamp: new Date(),
level, // Emit the log message via the socket
message socket.emit("log-event", logMessage);
// Log the JSON event via the logger
logger.log("ws-log-event-json", level, socket.user.email, recordid, {
wsmessage: message,
json
}); });
// Use the helper function to append the log event to the Redis list
await addItemToEndOfList(socket.id, "logEvents", logMessage);
} }
// if (level === "ERROR") {
// throw new Error(message);
// }
} }
function createXmlEvent(socket, xml, message, isError = false) { async function createXmlEvent(socket, xml, message, isError = false) {
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy("TRACE")) { const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
socket.emit("log-event", {
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy("TRACE")) {
const logMessage = {
timestamp: new Date(), timestamp: new Date(),
level: isError ? "ERROR" : "TRACE", level: isError ? "ERROR" : "TRACE",
message: `${message}: ${xml}` message: `${message}: ${xml}`
}); };
}
logger.log( // Emit the log message via the socket
isError ? "ws-log-event-xml-error" : "ws-log-event-xml", socket.emit("log-event", logMessage);
isError ? "ERROR" : "TRACE",
socket.user.email,
socket.recordid,
{
wsmessage: message,
xml
}
);
if (socket.logEvents && isArray(socket.logEvents)) { // Log the XML event via the logger
socket.logEvents.push({ logger.log(
timestamp: new Date(), isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
level: isError ? "ERROR" : "TRACE", isError ? "ERROR" : "TRACE",
message, socket.user.email,
xml recordid,
}); { wsmessage: message, xml }
);
// Use the helper function to append the log event to the Redis list
await addItemToEndOfList(socket.id, "logEvents", { ...logMessage, xml });
} }
} }
// Log level hierarchy
function LogLevelHierarchy(level) { function LogLevelHierarchy(level) {
switch (level) { const levels = { XML: 5, TRACE: 5, DEBUG: 4, INFO: 3, WARNING: 2, ERROR: 1 };
case "XML": return levels[level] || 3;
return 5;
case "TRACE":
return 5;
case "DEBUG":
return 4;
case "INFO":
return 3;
case "WARNING":
return 2;
case "ERROR":
return 1;
default:
return 3;
}
} }
// Socket.IO Middleware and Connection
io.use(socketAuthMiddleware);
io.on("connection", registerSocketEvents);
// Export logging helpers
exports.createLogEvent = createLogEvent; exports.createLogEvent = createLogEvent;
exports.createXmlEvent = createXmlEvent; exports.createXmlEvent = createXmlEvent;
exports.createJsonEvent = createJsonEvent; exports.createJsonEvent = createJsonEvent;