Compare commits
24 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62185196a8 | ||
|
|
88b313b9d8 | ||
|
|
08e1cf80c2 | ||
|
|
2f3056b49b | ||
|
|
a08125df54 | ||
|
|
a2d95dbce3 | ||
|
|
51c181dab7 | ||
|
|
4486858a86 | ||
|
|
f606228792 | ||
|
|
bca0a35cdd | ||
|
|
0ee51ed4c1 | ||
|
|
11b903f24b | ||
|
|
f83deb0c26 | ||
|
|
9a0d973b26 | ||
|
|
ea0a717895 | ||
|
|
40f1a2f6ed | ||
|
|
3433b1a600 | ||
|
|
49a7313abb | ||
|
|
319b24ce8a | ||
|
|
08d8a4f7dc | ||
|
|
03e8b62e4f | ||
|
|
44db8f20e9 | ||
|
|
f28068d0e7 | ||
|
|
5f082b9619 |
20
certs/cert.pem
Normal file
20
certs/cert.pem
Normal 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
28
certs/key.pem
Normal 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-----
|
||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
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_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
|
||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
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_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=PROMANAGER
|
||||
|
||||
@@ -9,7 +9,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
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_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_COUNTRY=USA
|
||||
|
||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
/dev-dist
|
||||
|
||||
@@ -12,6 +12,6 @@ module.exports = defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
return require("./cypress/plugins/index.js")(on, config);
|
||||
},
|
||||
baseUrl: "http://localhost:3000"
|
||||
baseUrl: "https://localhost:3000"
|
||||
}
|
||||
});
|
||||
|
||||
2648
client/package-lock.json
generated
2648
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,22 +8,22 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.19.12",
|
||||
"@apollo/client": "^3.11.4",
|
||||
"@ant-design/pro-layout": "^7.20.0",
|
||||
"@apollo/client": "^3.11.8",
|
||||
"@emotion/is-prop-valid": "^1.3.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.4.3",
|
||||
"@fingerprintjs/fingerprintjs": "^4.5.0",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@sentry/cli": "^2.33.1",
|
||||
"@sentry/cli": "^2.36.1",
|
||||
"@sentry/react": "^7.114.0",
|
||||
"@splitsoftware/splitio-react": "^1.12.1",
|
||||
"@splitsoftware/splitio-react": "^1.13.0",
|
||||
"@tanem/react-nprogress": "^5.0.51",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"antd": "^5.20.1",
|
||||
"antd": "^5.20.6",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^3.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.7.7",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"dayjs": "^1.11.12",
|
||||
@@ -32,12 +32,12 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^10.12.5",
|
||||
"firebase": "^10.13.1",
|
||||
"graphql": "^16.9.0",
|
||||
"i18next": "^23.12.3",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.11.5",
|
||||
"libphonenumber-js": "^1.11.8",
|
||||
"logrocket": "^8.1.2",
|
||||
"markerjs2": "^2.32.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"query-string": "^9.1.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.13.2",
|
||||
"react-big-calendar": "^1.13.4",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^7.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -58,15 +58,15 @@
|
||||
"react-icons": "^5.3.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-number-format": "^5.4.0",
|
||||
"react-number-format": "^5.4.2",
|
||||
"react-popopo": "^2.1.9",
|
||||
"react-product-fruits": "^2.2.6",
|
||||
"react-product-fruits": "^2.2.61",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"react-virtuoso": "^4.10.1",
|
||||
"react-virtuoso": "^4.10.3",
|
||||
"recharts": "^2.12.7",
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
@@ -74,9 +74,9 @@
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.77.8",
|
||||
"sass": "^1.78.0",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"styled-components": "^6.1.12",
|
||||
"styled-components": "^6.1.13",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"use-memo-one": "^1.1.3",
|
||||
"userpilot": "^1.3.5",
|
||||
@@ -90,6 +90,9 @@
|
||||
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
|
||||
"start:rome": "dotenvx run --env-file=.env.development.rome -- 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:rome": "env-cmd -f .env.test.rome npm run build",
|
||||
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
|
||||
@@ -130,15 +133,16 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@dotenvx/dotenvx": "^1.7.0",
|
||||
"@dotenvx/dotenvx": "^1.14.0",
|
||||
"@emotion/babel-plugin": "^11.12.0",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@sentry/webpack-plugin": "^2.22.2",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@sentry/webpack-plugin": "^2.22.4",
|
||||
"@testing-library/cypress": "^10.0.2",
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.13.3",
|
||||
"cypress": "^13.14.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
@@ -147,10 +151,9 @@
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^5.4.0",
|
||||
"vite": "^5.4.3",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
|
||||
@@ -21,6 +21,7 @@ import "./App.styles.scss";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||
@@ -201,7 +202,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/manage/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
<SocketProvider bodyshop={bodyshop}>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
@@ -211,7 +214,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/tech/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
<SocketProvider bodyshop={bodyshop}>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,62 +1,77 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
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 { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; // Import SocketContext
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps)(DmsAllocationsSummaryAp);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummaryAp);
|
||||
|
||||
export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
export function DmsAllocationsSummaryAp({ bodyshop, billids, title }) {
|
||||
const { t } = useTranslation();
|
||||
const [allocationsSummary, setAllocationsSummary] = useState([]);
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("ap-export-success", (billid) => {
|
||||
if (!socket) return;
|
||||
|
||||
const handleSuccess = async (billid) => {
|
||||
setAllocationsSummary((allocationsSummary) =>
|
||||
allocationsSummary.map((a) => {
|
||||
if (a.billid !== billid) return a;
|
||||
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();
|
||||
return () => {
|
||||
socket.removeListener("ap-export-success");
|
||||
socket.removeListener("ap-export-failure");
|
||||
//socket.disconnect();
|
||||
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);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleFailure = ({ billid, error }) => {
|
||||
setAllocationsSummary((allocationsSummary) =>
|
||||
allocationsSummary.map((a) => {
|
||||
if (a.billid !== billid) return a;
|
||||
return { ...a, status: error };
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
socket.on("ap-export-success", handleSuccess);
|
||||
socket.on("ap-export-failure", handleFailure);
|
||||
|
||||
return () => {
|
||||
socket.off("ap-export-success", handleSuccess);
|
||||
socket.off("ap-export-failure", handleFailure);
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (socket.connected) {
|
||||
if (socket && socket.connected) {
|
||||
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => {
|
||||
setAllocationsSummary(ack);
|
||||
|
||||
socket.allocationsSummary = ack;
|
||||
});
|
||||
}
|
||||
}, [socket, socket.connected, billids]);
|
||||
console.log(allocationsSummary);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("general.labels.status"),
|
||||
@@ -68,35 +83,40 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
dataIndex: ["Posting", "Reference"],
|
||||
key: "reference"
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.dms.lines"),
|
||||
dataIndex: "Lines",
|
||||
key: "Lines",
|
||||
render: (text, record) => (
|
||||
<table style={{ tableLayout: "auto", width: "100%" }}>
|
||||
<tr>
|
||||
<th>{t("bills.fields.invoice_number")}</th>
|
||||
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
|
||||
<th>{t("jobs.fields.dms.amount")}</th>
|
||||
</tr>
|
||||
{record.Posting.Lines.map((l, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>{l.InvoiceNumber}</td>
|
||||
<td>{l.Account}</td>
|
||||
<td>{l.Amount}</td>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("bills.fields.invoice_number")}</th>
|
||||
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
|
||||
<th>{t("jobs.fields.dms.amount")}</th>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
socket.emit(`pbs-export-ap`, {
|
||||
billids,
|
||||
txEnvelope: values
|
||||
});
|
||||
if (socket) {
|
||||
socket.emit("pbs-export-ap", {
|
||||
billids,
|
||||
txEnvelope: values
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -105,7 +125,9 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
extra={
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack));
|
||||
if (socket) {
|
||||
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SyncOutlined />
|
||||
@@ -124,12 +146,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
name="journal"
|
||||
label={t("jobs.fields.dms.journal")}
|
||||
initialValue={bodyshop.cdk_configuration && bodyshop.cdk_configuration.default_journal}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,37 +1,51 @@
|
||||
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 { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { socket } from "../../pages/dms/dms.container";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext"; // Import Socket Context
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector);
|
||||
|
||||
export function DmsCustomerSelector({ bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [customerList, setcustomerList] = useState([]);
|
||||
const { socket } = useContext(SocketContext); // Use Socket Context
|
||||
const [customerList, setCustomerList] = useState([]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||
const [dmsType, setDmsType] = useState("cdk");
|
||||
|
||||
socket.on("cdk-select-customer", (customerList, callback) => {
|
||||
setOpen(true);
|
||||
setDmsType("cdk");
|
||||
setcustomerList(customerList);
|
||||
});
|
||||
socket.on("pbs-select-customer", (customerList, callback) => {
|
||||
setOpen(true);
|
||||
setDmsType("pbs");
|
||||
setcustomerList(customerList);
|
||||
});
|
||||
useEffect(() => {
|
||||
if (socket) {
|
||||
const handleCdkSelectCustomer = (customerList) => {
|
||||
setOpen(true);
|
||||
setDmsType("cdk");
|
||||
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 = () => {
|
||||
setOpen(false);
|
||||
@@ -69,17 +83,11 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
key: "name1",
|
||||
sorter: (a, b) => alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName)
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.dms.address"),
|
||||
//dataIndex: ["name2", "fullName"],
|
||||
key: "address",
|
||||
render: (record, value) =>
|
||||
`${
|
||||
record.address && record.address.addressLine && record.address.addressLine[0]
|
||||
}, ${record.address && record.address.city} ${
|
||||
record.address && record.address.stateOrProvince
|
||||
} ${record.address && record.address.postalCode}`
|
||||
render: (record) =>
|
||||
`${record.address?.addressLine?.[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -95,15 +103,15 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
sorter: (a, b) => alphaSort(a.LastName, b.LastName),
|
||||
render: (text, record) => `${record.FirstName || ""} ${record.LastName || ""}`
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.dms.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;
|
||||
|
||||
return (
|
||||
<Col span={24}>
|
||||
<Table
|
||||
@@ -125,7 +133,6 @@ export function DmsCustomerSelector({ bodyshop }) {
|
||||
columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
|
||||
rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)}
|
||||
dataSource={customerList}
|
||||
//onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (record) => {
|
||||
setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId);
|
||||
|
||||
@@ -4,11 +4,8 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
@@ -17,7 +14,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
|
||||
|
||||
export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
export function DmsLogEvents({ logs }) {
|
||||
return (
|
||||
<Timeline
|
||||
pending
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import dayjs from "../../utils/day";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import Board from "./trello-board/index";
|
||||
import { Button, notification, Skeleton, Space } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -23,6 +24,7 @@ import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -45,10 +47,136 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isMoving, setIsMoving] = useState(false);
|
||||
const [orientation, setOrientation] = useState("vertical");
|
||||
const { socket } = useContext(SocketContext); // Access socket from the context
|
||||
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
|
||||
const handleJobUpdated = useCallback(
|
||||
(updatedJob) => {
|
||||
setBoardLanes((prevBoardLanes) => {
|
||||
const updatedLanes = cloneDeep(prevBoardLanes.lanes);
|
||||
|
||||
// Find the lane containing the card with the updated job ID
|
||||
let sourceLane = updatedLanes.find((lane) => lane.cards.some((card) => card.id === updatedJob.id));
|
||||
|
||||
if (!sourceLane) {
|
||||
console.log("Card not found in any lane. Checking for valid status to add it.");
|
||||
|
||||
// Find the target lane based on the new status if the card does not exist
|
||||
const targetLane = updatedLanes.find((lane) => lane.id === updatedJob.status);
|
||||
|
||||
if (targetLane && updatedJob.isInProduction) {
|
||||
// Check if job is in production and status is valid
|
||||
console.log(`Adding card to lane ${targetLane.title}`);
|
||||
|
||||
// Add the new card to the target lane
|
||||
const newCard = {
|
||||
id: updatedJob.id,
|
||||
metadata: { ...updatedJob }
|
||||
};
|
||||
targetLane.cards.push(newCard);
|
||||
|
||||
// Update the lane title with the new card count
|
||||
targetLane.title = `${targetLane.title.split(" ")[0]} (${targetLane.cards.length})`;
|
||||
|
||||
return { lanes: updatedLanes }; // Return early since the card is added
|
||||
} else {
|
||||
console.error("No valid lane or status to add the job to.");
|
||||
return prevBoardLanes; // Return the previous state if no valid status or lane
|
||||
}
|
||||
}
|
||||
|
||||
// If the card exists, find it in the source lane
|
||||
const cardIndex = sourceLane.cards.findIndex((card) => card.id === updatedJob.id);
|
||||
const currentCard = sourceLane.cards[cardIndex];
|
||||
|
||||
// If we somehow can't find the card, return
|
||||
if (!currentCard) {
|
||||
console.error("Card not found for the updated job.");
|
||||
return prevBoardLanes; // Return the previous state if the card is not found
|
||||
}
|
||||
|
||||
// Iterate through the properties of updatedJob and update the corresponding values in currentCard.metadata
|
||||
Object.keys(updatedJob).forEach((key) => {
|
||||
// Normalize date fields by comparing their ISO strings or timestamps
|
||||
if (key === "updated_at") {
|
||||
const currentCardDate = dayjs(currentCard.metadata[key]).toISOString();
|
||||
const updatedJobDate = dayjs(updatedJob[key]).toISOString();
|
||||
|
||||
if (currentCardDate !== updatedJobDate) {
|
||||
console.log(`Updating ${key} from ${currentCardDate} to ${updatedJobDate}`);
|
||||
currentCard.metadata[key] = updatedJob[key]; // Assign the new value if different
|
||||
}
|
||||
} else if (key in currentCard.metadata && currentCard.metadata[key] !== updatedJob[key]) {
|
||||
console.log(`Updating ${key} from ${currentCard.metadata[key]} to ${updatedJob[key]}`);
|
||||
currentCard.metadata[key] = updatedJob[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Mark that data has been changed if any field was updated
|
||||
const isDataChanged = !isEqual(currentCard.metadata, updatedJob);
|
||||
|
||||
// Check if the lane (status) has changed
|
||||
const isLaneChanged = updatedJob.status !== sourceLane.id;
|
||||
|
||||
// Case 1: Both data and lane have changed
|
||||
if (isDataChanged && isLaneChanged) {
|
||||
console.log("Case 1: Data and Lane Changed");
|
||||
|
||||
// Remove the card from the source lane
|
||||
const [cardToMove] = sourceLane.cards.splice(cardIndex, 1);
|
||||
|
||||
// Find the target lane based on the new status
|
||||
const targetLane = updatedLanes.find((lane) => lane.id === updatedJob.status);
|
||||
if (targetLane) {
|
||||
targetLane.cards.push({ ...cardToMove, metadata: { ...currentCard.metadata } });
|
||||
sourceLane.title = `${sourceLane.title.split(" ")[0]} (${sourceLane.cards.length})`;
|
||||
targetLane.title = `${targetLane.title.split(" ")[0]} (${targetLane.cards.length})`;
|
||||
} else {
|
||||
console.error("Target lane not found for the updated job.");
|
||||
}
|
||||
}
|
||||
// Case 2: Only data has changed
|
||||
else if (isDataChanged && !isLaneChanged) {
|
||||
console.log("Case 2: Only Data Changed");
|
||||
sourceLane.cards[cardIndex] = { ...currentCard, metadata: { ...currentCard.metadata } };
|
||||
}
|
||||
// Case 3: Only the lane has changed
|
||||
else if (!isDataChanged && isLaneChanged) {
|
||||
console.log("Case 3: Only Lane Changed");
|
||||
|
||||
// Remove the card from the source lane
|
||||
const [cardToMove] = sourceLane.cards.splice(cardIndex, 1);
|
||||
|
||||
// Find the target lane based on the new status
|
||||
const targetLane = updatedLanes.find((lane) => lane.id === updatedJob.status);
|
||||
if (targetLane) {
|
||||
targetLane.cards.push(cardToMove);
|
||||
sourceLane.title = `${sourceLane.title.split(" ")[0]} (${sourceLane.cards.length})`;
|
||||
targetLane.title = `${targetLane.title.split(" ")[0]} (${targetLane.cards.length})`;
|
||||
} else {
|
||||
console.error("Target lane not found for the updated job.");
|
||||
}
|
||||
}
|
||||
|
||||
return { lanes: updatedLanes };
|
||||
});
|
||||
},
|
||||
[setBoardLanes]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Listen for the job-updated event from the socket
|
||||
if (socket) {
|
||||
socket.on("job-updated", handleJobUpdated);
|
||||
|
||||
return () => {
|
||||
socket.off("job-updated", handleJobUpdated);
|
||||
};
|
||||
}
|
||||
}, [socket, handleJobUpdated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (associationSettings) {
|
||||
setLoading(true);
|
||||
|
||||
@@ -28,22 +28,23 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
|
||||
});
|
||||
|
||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION, {
|
||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||
});
|
||||
// const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION, {
|
||||
// onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||
// });
|
||||
|
||||
const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, {
|
||||
variables: { email: currentUser.email },
|
||||
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 : {}));
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedJobs && data) {
|
||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||
}
|
||||
}, [updatedJobs, data, refetch]);
|
||||
// useEffect(() => {
|
||||
// if (updatedJobs && data) {
|
||||
// refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||
// }
|
||||
// }, [updatedJobs, data, refetch]);
|
||||
|
||||
const filteredAssociationSettings = useMemo(() => {
|
||||
return associationSettings?.associations[0] || null;
|
||||
|
||||
13
client/src/contexts/SocketIO/socketContext.jsx
Normal file
13
client/src/contexts/SocketIO/socketContext.jsx
Normal 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;
|
||||
75
client/src/contexts/SocketIO/useSocket.js
Normal file
75
client/src/contexts/SocketIO/useSocket.js
Normal file
@@ -0,0 +1,75 @@
|
||||
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(() => {
|
||||
const handleBodyshopMessage = (message) => {
|
||||
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// When the socket connects or reconnects, join the bodyshop room
|
||||
const joinRoomOnConnect = () => {
|
||||
console.log("Socket connected:", socketInstance.id);
|
||||
setClientId(socketInstance.id);
|
||||
|
||||
if (bodyshop.id) {
|
||||
socketInstance.emit("join-bodyshop-room", bodyshop.id);
|
||||
console.log(`Joined bodyshop room: ${bodyshop.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Set up the necessary socket event handlers
|
||||
socketInstance.on("connect", joinRoomOnConnect);
|
||||
|
||||
socketInstance.on("reconnect", (attempt) => {
|
||||
console.log(`Socket reconnected after ${attempt} attempts`);
|
||||
});
|
||||
|
||||
socketInstance.on("bodyshop-message", handleBodyshopMessage);
|
||||
|
||||
socketInstance.on("connect_error", (err) => {
|
||||
console.error("Socket connection error:", err);
|
||||
});
|
||||
|
||||
socketInstance.on("disconnect", () => {
|
||||
console.log("Socket disconnected");
|
||||
});
|
||||
|
||||
// Clean up on component unmount or when bodyshop changes
|
||||
return () => {
|
||||
if (bodyshop?.id) {
|
||||
socketInstance.emit("leave-bodyshop-room", bodyshop.id);
|
||||
}
|
||||
socketInstance.off("connect", joinRoomOnConnect);
|
||||
socketInstance.off("bodyshop-message", handleBodyshopMessage);
|
||||
socketInstance.disconnect();
|
||||
};
|
||||
}
|
||||
}, [bodyshop]);
|
||||
|
||||
// Return both socket and clientId
|
||||
return { socket, clientId };
|
||||
};
|
||||
|
||||
export default useSocket;
|
||||
@@ -1,20 +1,16 @@
|
||||
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 { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import SocketIO from "socket.io-client";
|
||||
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 { auth } from "../../firebase/firebase.utils";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
@@ -23,20 +19,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
|
||||
|
||||
export const socket = SocketIO(
|
||||
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 }) {
|
||||
export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
const { socket } = useContext(SocketContext);
|
||||
const [logLevel, setLogLevel] = useState("DEBUG");
|
||||
const history = useNavigate();
|
||||
const [logs, setLogs] = useState([]);
|
||||
@@ -67,40 +52,43 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("connect", () => socket.emit("set-log-level", logLevel));
|
||||
socket.on("reconnect", () => {
|
||||
setLogs((logs) => {
|
||||
return [
|
||||
if (socket) {
|
||||
const handleConnect = () => socket.emit("set-log-level", logLevel);
|
||||
const handleReconnect = () => {
|
||||
setLogs((logs) => [
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
message: "Reconnected to CDK 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) => {
|
||||
setLogs((logs) => {
|
||||
return [...logs, payload];
|
||||
});
|
||||
});
|
||||
socket.on("connect", handleConnect);
|
||||
socket.on("reconnect", handleReconnect);
|
||||
socket.on("log-event", handleLogEvent);
|
||||
socket.on("ap-export-complete", handleExportComplete);
|
||||
|
||||
socket.on("ap-export-complete", (payload) => {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("jobs.labels.dms.apexported")
|
||||
});
|
||||
});
|
||||
if (socket.disconnected) socket.connect();
|
||||
|
||||
if (socket.disconnected) socket.connect();
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
return () => {
|
||||
socket.off("connect", handleConnect);
|
||||
socket.off("reconnect", handleReconnect);
|
||||
socket.off("log-event", handleLogEvent);
|
||||
socket.off("ap-export-complete", handleExportComplete);
|
||||
};
|
||||
}
|
||||
}, [socket, logLevel, t]);
|
||||
|
||||
if (!state?.billids) {
|
||||
history(`/manage/accounting/payables`);
|
||||
@@ -136,27 +124,20 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLogs([]);
|
||||
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
if (socket) {
|
||||
socket.emit("clear-dms-session");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Reconnect
|
||||
Clear Session
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<DmsLogEvents socket={socket} logs={logs} />
|
||||
<DmsLogEvents logs={logs} />
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export const determineDmsType = (bodyshop) => {
|
||||
if (bodyshop.cdk_dealerid) return "cdk";
|
||||
else {
|
||||
return "pbs";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Card, Col, notification, Result, Row, Select, Space } from "antd";
|
||||
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 { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import SocketIO from "socket.io-client";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.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 LoadingSpinner from "../../components/loading-spinner/loading-spinner.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 { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -28,25 +27,21 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
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 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 }) {
|
||||
const { t } = useTranslation();
|
||||
const { socket } = useContext(SocketContext);
|
||||
const [logLevel, setLogLevel] = useState("DEBUG");
|
||||
const history = useNavigate();
|
||||
const [logs, setLogs] = useState([]);
|
||||
@@ -59,6 +54,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
const logsRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -83,47 +79,73 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("connect", () => socket.emit("set-log-level", logLevel));
|
||||
socket.on("reconnect", () => {
|
||||
setLogs((logs) => {
|
||||
return [
|
||||
if (socket) {
|
||||
const handleConnect = () => {
|
||||
socket.emit("set-log-level", logLevel);
|
||||
};
|
||||
|
||||
const handleReconnect = () => {
|
||||
setLogs((logs) => [
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
message: "Reconnected to CDK 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();
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const handleConnectError = (err) => {
|
||||
console.log(`connect_error due to ${err}`, err);
|
||||
notification.error({ message: err.message });
|
||||
};
|
||||
|
||||
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 (error) return <AlertComponent message={error.message} type="error" />;
|
||||
@@ -179,20 +201,21 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
4<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLogs([]);
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
if (socket) {
|
||||
socket.emit("clear-dms-session");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Reconnect
|
||||
Clear Session
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<DmsLogEvents socket={socket} logs={logs} />
|
||||
<DmsLogEvents logs={logs} />
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FloatButton, Layout, Spin } from "antd";
|
||||
// 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 { connect } from "react-redux";
|
||||
import { Link, Route, Routes } from "react-router-dom";
|
||||
@@ -18,12 +18,12 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
||||
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
|
||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||
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 UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import "./manage.page.styles.scss";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
|
||||
@@ -110,17 +110,7 @@ const mapDispatchToProps = (dispatch) => ({});
|
||||
export function Manage({ conflict, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const widgetId = InstanceRenderManager({
|
||||
imex: "IABVNO4scRKY11XBQkNr",
|
||||
rome: "mQdqARMzkZRUVugJ6TdS"
|
||||
});
|
||||
window.noticeable.render("widget", widgetId);
|
||||
requestForToken().catch((error) => {
|
||||
console.error(`Unable to request for token.`, error);
|
||||
});
|
||||
}, []);
|
||||
const { socket, clientId } = useContext(SocketContext);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = InstanceRenderManager({
|
||||
@@ -129,6 +119,7 @@ export function Manage({ conflict, bodyshop }) {
|
||||
promanager: t("titles.promanager")
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
const AppRouteTable = (
|
||||
<Suspense
|
||||
fallback={
|
||||
@@ -569,6 +560,13 @@ export function Manage({ conflict, bodyshop }) {
|
||||
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
|
||||
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 (
|
||||
<>
|
||||
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
|
||||
@@ -603,6 +601,8 @@ export function Manage({ conflict, bodyshop }) {
|
||||
</div>
|
||||
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
|
||||
</div>
|
||||
<button onClick={broadcastMessage}>Broadcast Message</button>
|
||||
|
||||
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
||||
Disclaimer & Notices
|
||||
</Link>
|
||||
|
||||
@@ -2,9 +2,9 @@ import axios from "axios";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import InstanceRenderManager from "./instanceRenderMgr";
|
||||
|
||||
axios.defaults.baseURL =
|
||||
import.meta.env.VITE_APP_AXIOS_BASE_API_URL ||
|
||||
(import.meta.env.MODE === "production" ? "https://api.imex.online/" : "http://localhost:4000/");
|
||||
axios.defaults.baseURL = import.meta.env.DEV
|
||||
? "/api/"
|
||||
: import.meta.env.VITE_APP_AXIOS_BASE_API_URL || "https://api.imex.online/";
|
||||
|
||||
export const axiosAuthInterceptorId = axios.interceptors.request.use(
|
||||
async (config) => {
|
||||
|
||||
@@ -3,16 +3,21 @@ import { promises as fsPromises } from "fs";
|
||||
import { createRequire } from "module";
|
||||
import * as path from "path";
|
||||
import * as url from "url";
|
||||
import { defineConfig } from "vite";
|
||||
import { createLogger, defineConfig } from "vite";
|
||||
import { ViteEjsPlugin } from "vite-plugin-ejs";
|
||||
import eslint from "vite-plugin-eslint";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
|
||||
import chalk from "chalk";
|
||||
|
||||
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
|
||||
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";`;
|
||||
|
||||
function reactVirtualizedFix() {
|
||||
@@ -32,6 +37,11 @@ function reactVirtualizedFix() {
|
||||
}
|
||||
};
|
||||
}
|
||||
/** End of hack */
|
||||
|
||||
export const logger = createLogger("info", {
|
||||
allowClearScreen: false
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
base: "/",
|
||||
@@ -99,7 +109,6 @@ export default defineConfig({
|
||||
reactVirtualizedFix(),
|
||||
react(),
|
||||
eslint()
|
||||
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
|
||||
],
|
||||
define: {
|
||||
APP_VERSION: JSON.stringify(process.env.npm_package_version)
|
||||
@@ -107,7 +116,57 @@ export default defineConfig({
|
||||
server: {
|
||||
host: true,
|
||||
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: {
|
||||
rollupOptions: {
|
||||
@@ -121,7 +180,18 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
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: {
|
||||
loader: {
|
||||
".js": "jsx"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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!"
|
||||
|
||||
@@ -4240,6 +4240,63 @@
|
||||
- active:
|
||||
_eq: true
|
||||
event_triggers:
|
||||
- name: job_modified
|
||||
definition:
|
||||
enable_manual: false
|
||||
update:
|
||||
columns:
|
||||
- clm_no
|
||||
- v_make_desc
|
||||
- date_next_contact
|
||||
- status
|
||||
- employee_csr
|
||||
- employee_prep
|
||||
- clm_total
|
||||
- suspended
|
||||
- employee_body
|
||||
- ro_number
|
||||
- actual_in
|
||||
- ownr_co_nm
|
||||
- v_model_yr
|
||||
- comment
|
||||
- job_totals
|
||||
- v_vin
|
||||
- ownr_fn
|
||||
- scheduled_completion
|
||||
- special_coverage_policy
|
||||
- v_color
|
||||
- ca_gst_registrant
|
||||
- scheduled_delivery
|
||||
- actual_delivery
|
||||
- actual_completion
|
||||
- kanbanparent
|
||||
- est_ct_fn
|
||||
- employee_refinish
|
||||
- ownr_ph1
|
||||
- date_last_contacted
|
||||
- alt_transport
|
||||
- inproduction
|
||||
- est_ct_ln
|
||||
- production_vars
|
||||
- category
|
||||
- v_model_desc
|
||||
- date_invoiced
|
||||
- est_co_nm
|
||||
- ownr_ln
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
num_retries: 0
|
||||
timeout_sec: 60
|
||||
webhook_from_env: HASURA_API_URL
|
||||
headers:
|
||||
- name: event-secret
|
||||
value_from_env: EVENT_SECRET
|
||||
request_transform:
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/job/job-updated'
|
||||
version: 2
|
||||
- name: job_status_transition
|
||||
definition:
|
||||
enable_manual: true
|
||||
|
||||
1035
package-lock.json
generated
1035
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -19,17 +19,18 @@
|
||||
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.629.0",
|
||||
"@aws-sdk/client-ses": "^3.629.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.629.0",
|
||||
"@opensearch-project/opensearch": "^2.11.0",
|
||||
"aws4": "^1.13.1",
|
||||
"axios": "^1.7.4",
|
||||
"@aws-sdk/client-secrets-manager": "^3.645.0",
|
||||
"@aws-sdk/client-ses": "^3.645.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.645.0",
|
||||
"@opensearch-project/opensearch": "^2.12.0",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
"aws4": "^1.13.2",
|
||||
"axios": "^1.7.7",
|
||||
"better-queue": "^3.8.12",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"body-parser": "^1.20.3",
|
||||
"canvas": "^2.11.2",
|
||||
"chart.js": "^4.4.3",
|
||||
"chart.js": "^4.4.4",
|
||||
"cloudinary": "^2.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
@@ -37,8 +38,8 @@
|
||||
"csrf": "^3.1.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"firebase-admin": "^12.3.1",
|
||||
"express": "^4.20.0",
|
||||
"firebase-admin": "^12.4.0",
|
||||
"graphql": "^16.9.0",
|
||||
"graphql-request": "^6.1.0",
|
||||
"graylog2": "^0.2.1",
|
||||
@@ -49,14 +50,16 @@
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-mailjet": "^6.0.5",
|
||||
"node-mailjet": "^6.0.6",
|
||||
"node-persist": "^4.0.3",
|
||||
"nodemailer": "^6.9.14",
|
||||
"phone": "^3.1.49",
|
||||
"nodemailer": "^6.9.15",
|
||||
"phone": "^3.1.50",
|
||||
"recursive-diff": "^1.0.9",
|
||||
"redis": "^4.7.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"soap": "^1.1.1",
|
||||
"soap": "^1.1.3",
|
||||
"socket.io": "^4.7.5",
|
||||
"socket.io-adapter": "^2.5.5",
|
||||
"ssh2-sftp-client": "^10.0.3",
|
||||
"twilio": "^4.23.0",
|
||||
"uuid": "^10.0.0",
|
||||
|
||||
370
server.js
370
server.js
@@ -1,4 +1,3 @@
|
||||
// Import core modules
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
const bodyParser = require("body-parser");
|
||||
@@ -7,104 +6,307 @@ const compression = require("compression");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const http = require("http");
|
||||
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
|
||||
require("dotenv").config({
|
||||
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"
|
||||
];
|
||||
|
||||
// Express app and server setup
|
||||
const app = express();
|
||||
const port = process.env.PORT || 5000;
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(server, {
|
||||
path: "/ws",
|
||||
cors: {
|
||||
origin: [
|
||||
"https://test.imex.online",
|
||||
"https://www.test.imex.online",
|
||||
"http://localhost:3000",
|
||||
"https://imex.online",
|
||||
"https://www.imex.online",
|
||||
"https://romeonline.io", //Added in all RO and PM routes to simplyify setup.
|
||||
"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"
|
||||
],
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true,
|
||||
exposedHeaders: ["set-cookie"]
|
||||
/**
|
||||
* Middleware for Express app
|
||||
* @param app
|
||||
*/
|
||||
const applyMiddleware = (app) => {
|
||||
app.use(compression());
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.json({ limit: "50mb" }));
|
||||
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
||||
app.use(cors({ credentials: true, exposedHeaders: ["set-cookie"] }));
|
||||
|
||||
// Helper middleware
|
||||
app.use((req, res, next) => {
|
||||
req.logger = logger;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Route groupings for Express app
|
||||
* @param app
|
||||
*/
|
||||
const applyRoutes = (app) => {
|
||||
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
|
||||
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
|
||||
app.use(compression());
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.json({ limit: "50mb" }));
|
||||
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
||||
app.use(cors({ credentials: true, exposedHeaders: ["set-cookie"] }));
|
||||
const io = new Server(server, {
|
||||
path: "/ws",
|
||||
adapter: createAdapter(pubClient, subClient),
|
||||
cors: {
|
||||
origin: SOCKETIO_CORS_ORIGIN,
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true,
|
||||
exposedHeaders: ["set-cookie"]
|
||||
}
|
||||
});
|
||||
|
||||
// Helper middleware
|
||||
app.use((req, res, next) => {
|
||||
req.logger = logger;
|
||||
next();
|
||||
});
|
||||
app.use((req, res, next) => {
|
||||
req.pubClient = pubClient;
|
||||
req.io = io;
|
||||
next();
|
||||
});
|
||||
|
||||
// Route groupings
|
||||
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"));
|
||||
Object.assign(module.exports, { io, pubClient });
|
||||
|
||||
// Default route for forbidden access
|
||||
app.get("/", (req, res) => {
|
||||
res.status(200).send("Access Forbidden.");
|
||||
});
|
||||
return { pubClient, io };
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
|
||||
Object.assign(module.exports, {
|
||||
setSessionData,
|
||||
getSessionData,
|
||||
clearSessionData,
|
||||
setMultipleSessionData,
|
||||
getMultipleSessionData,
|
||||
setMultipleFromArraySessionData,
|
||||
addItemToEndOfList,
|
||||
addItemToBeginningOfList,
|
||||
pubClient
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
req.sessionUtils = {
|
||||
setSessionData,
|
||||
getSessionData,
|
||||
clearSessionData,
|
||||
setMultipleSessionData,
|
||||
getMultipleSessionData,
|
||||
setMultipleFromArraySessionData,
|
||||
addItemToEndOfList,
|
||||
addItemToBeginningOfList,
|
||||
pubClient
|
||||
};
|
||||
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));
|
||||
//
|
||||
// // 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 () => {
|
||||
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
|
||||
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
|
||||
);
|
||||
});
|
||||
main();
|
||||
|
||||
@@ -12,67 +12,92 @@ const AxiosLib = require("axios").default;
|
||||
const axios = AxiosLib.create();
|
||||
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
|
||||
const { CheckForErrors } = require("./pbs-job-export");
|
||||
const { getSessionData, getMultipleSessionData, setMultipleSessionData } = require("../../../server");
|
||||
const uuid = require("uuid").v4;
|
||||
axios.interceptors.request.use((x) => {
|
||||
const socket = x.socket;
|
||||
|
||||
const headers = {
|
||||
...x.headers.common,
|
||||
...x.headers[x.method],
|
||||
...x.headers
|
||||
};
|
||||
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
|
||||
x.url
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
console.log(printable);
|
||||
axios.interceptors.request.use(
|
||||
async (x) => {
|
||||
const socket = x.socket;
|
||||
|
||||
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) => {
|
||||
const socket = x.config.socket;
|
||||
console.log(printable);
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
|
||||
console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
|
||||
// Use await properly here for the async operation
|
||||
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${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) {
|
||||
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 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.
|
||||
|
||||
const transactionlist = [];
|
||||
if (bills.length === 0) {
|
||||
CdkBase.createLogEvent(
|
||||
await CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`No bills found for export. Ensure they have not already been exported and try again.`
|
||||
);
|
||||
}
|
||||
|
||||
bills.forEach((bill) => {
|
||||
//Keep the allocations at the bill level.
|
||||
|
||||
const transactionObject = {
|
||||
SerialNumber: socket.bodyshop.pbs_serialnumber,
|
||||
SerialNumber: bodyshop.pbs_serialnumber,
|
||||
billid: bill.id,
|
||||
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.",
|
||||
//AdditionalInfo: "String",
|
||||
Source: "ImEX Online", //TODO:AIO Resolve this for rome online.
|
||||
Lines: [] //socket.apAllocations,
|
||||
Reference: bill.invoice_number,
|
||||
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],
|
||||
Amount: billHash[cc.name].Amount.add(lineDinero)
|
||||
};
|
||||
|
||||
//Does the line have taxes?
|
||||
// Does the line have taxes?
|
||||
if (bl.applicable_taxes.federal) {
|
||||
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();
|
||||
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({
|
||||
...billHash[key],
|
||||
Amount: billHash[key].Amount.toFormat("0.00")
|
||||
@@ -169,19 +194,19 @@ async function PbsCalculateAllocationsAp(socket, billids) {
|
||||
|
||||
return transactionlist;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
|
||||
await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
|
||||
|
||||
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 result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.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;
|
||||
}
|
||||
@@ -196,40 +221,49 @@ function getCostAccount(billline, respcenters) {
|
||||
}
|
||||
|
||||
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.
|
||||
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
|
||||
socket.txEnvelope = txEnvelope;
|
||||
for (const allocation of socket.apAllocations) {
|
||||
const apAllocations = await PbsCalculateAllocationsAp(socket, billids);
|
||||
|
||||
await setMultipleSessionData(socket.id, {
|
||||
apAllocations,
|
||||
txEnvelope
|
||||
});
|
||||
|
||||
for (const allocation of apAllocations) {
|
||||
const { billid, ...restAllocation } = allocation;
|
||||
const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
|
||||
auth: PBS_CREDENTIALS,
|
||||
socket
|
||||
});
|
||||
|
||||
CheckForErrors(socket, AccountPostingChange);
|
||||
CheckForErrors(socket, AccountPostingChange).catch((err) =>
|
||||
console.error(`Error running CheckingForErrors in pbs-ap-allocations`)
|
||||
);
|
||||
|
||||
if (AccountPostingChange.WasSuccessful) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
|
||||
await CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
|
||||
await MarkApExported(socket, [billid]);
|
||||
|
||||
socket.emit("ap-export-success", billid);
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
|
||||
await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
socket.emit("ap-export-failure", {
|
||||
billid,
|
||||
error: AccountPostingChange.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
socket.emit("ap-export-complete");
|
||||
};
|
||||
|
||||
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 result = await client
|
||||
return await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.MARK_BILLS_EXPORTED, {
|
||||
billids,
|
||||
@@ -237,13 +271,11 @@ async function MarkApExported(socket, billids) {
|
||||
exported: true,
|
||||
exported_at: new Date()
|
||||
},
|
||||
logs: socket.bills.map((bill) => ({
|
||||
bodyshopid: socket.bodyshop.id,
|
||||
logs: bills.map((bill) => ({
|
||||
bodyshopid: bodyshop.id,
|
||||
billid: bill.id,
|
||||
successful: true,
|
||||
useremail: socket.user.email
|
||||
}))
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -12,136 +12,169 @@ const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").defa
|
||||
const CdkBase = require("../../web-sockets/web-socket");
|
||||
const moment = require("moment-timezone");
|
||||
const Dinero = require("dinero.js");
|
||||
const { setSessionData, getSessionData, getMultipleSessionData, setMultipleSessionData } = require("../../../server");
|
||||
const InstanceManager = require("../../utils/instanceMgr").default;
|
||||
const axios = AxiosLib.create();
|
||||
|
||||
axios.interceptors.request.use((x) => {
|
||||
const socket = x.socket;
|
||||
axios.interceptors.request.use(
|
||||
async (x) => {
|
||||
const socket = x.socket;
|
||||
|
||||
const headers = {
|
||||
...x.headers.common,
|
||||
...x.headers[x.method],
|
||||
...x.headers
|
||||
};
|
||||
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
|
||||
x.url
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
console.log(printable);
|
||||
const headers = {
|
||||
...x.headers.common,
|
||||
...x.headers[x.method],
|
||||
...x.headers
|
||||
};
|
||||
|
||||
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) => {
|
||||
const socket = x.config.socket;
|
||||
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
|
||||
console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
|
||||
return x; // Make sure to return the request object
|
||||
},
|
||||
(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 }) {
|
||||
socket.logEvents = [];
|
||||
socket.recordid = jobid;
|
||||
socket.txEnvelope = txEnvelope;
|
||||
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);
|
||||
socket.JobData = JobData;
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`);
|
||||
//Query for the Vehicle record to get the associated customer.
|
||||
socket.DmsVeh = await QueryVehicleFromDms(socket);
|
||||
await setSessionData(socket.id, "JobData", JobData);
|
||||
|
||||
await CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`);
|
||||
|
||||
// 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.
|
||||
if (socket.DmsVeh && socket.DmsVeh.CustomerRef) {
|
||||
//Get the associated customer from the Vehicle Record.
|
||||
socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef);
|
||||
if (DmsVeh?.CustomerRef) {
|
||||
// Get the associated customer from the Vehicle Record
|
||||
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.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []),
|
||||
...socket.DMSCustList
|
||||
...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []),
|
||||
...DMSCustList
|
||||
]);
|
||||
} 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) {
|
||||
try {
|
||||
if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`);
|
||||
const JobData = await getSessionData(socket.id, "JobData");
|
||||
|
||||
//Upsert the contact information as per Wafaa's Email.
|
||||
CdkBase.createLogEvent(
|
||||
if (JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) {
|
||||
await CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`);
|
||||
|
||||
// Upsert the contact information as per Wafaa's Email
|
||||
await CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`Upserting contact information to DMS for ${
|
||||
socket.JobData.ownr_fn || ""
|
||||
} ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}`
|
||||
JobData.ownr_fn || ""
|
||||
} ${JobData.ownr_ln || ""} ${JobData.ownr_co_nm || ""}`
|
||||
);
|
||||
|
||||
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);
|
||||
} else {
|
||||
CdkBase.createLogEvent(
|
||||
await CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`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);
|
||||
|
||||
// TODO: Insert Clear session
|
||||
if (insertResponse.WasSuccessful) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
|
||||
await MarkJobExported(socket, socket.JobData.id);
|
||||
await CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
|
||||
await MarkJobExported(socket, JobData.id);
|
||||
|
||||
socket.emit("export-success", socket.JobData.id);
|
||||
socket.emit("export-success", JobData.id);
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
|
||||
await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
|
||||
async function CheckForErrors(socket, response) {
|
||||
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 {
|
||||
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, "ERROR", `Error received from DMS: ${response.Message}`);
|
||||
await CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.CheckForErrors = CheckForErrors;
|
||||
|
||||
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 result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.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;
|
||||
}
|
||||
|
||||
async function QueryVehicleFromDms(socket) {
|
||||
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(
|
||||
PBS_ENDPOINTS.VehicleGet,
|
||||
{
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
SerialNumber: JobData.bodyshop.pbs_serialnumber,
|
||||
// VehicleId: "00000000000000000000000000000000",
|
||||
// Year: "String",
|
||||
// Make: "String",
|
||||
@@ -149,7 +182,7 @@ async function QueryVehicleFromDms(socket) {
|
||||
// Trim: "String",
|
||||
// ModelNumber: "String",
|
||||
// StockNumber: "String",
|
||||
VIN: socket.JobData.v_vin
|
||||
VIN: JobData.v_vin
|
||||
// LicenseNumber: "String",
|
||||
// Lot: "String",
|
||||
// Status: "String",
|
||||
@@ -166,26 +199,31 @@ async function QueryVehicleFromDms(socket) {
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
|
||||
CheckForErrors(socket, VehicleGetResponse);
|
||||
await CheckForErrors(socket, VehicleGetResponse);
|
||||
|
||||
return VehicleGetResponse;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
|
||||
await CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function QueryCustomersFromDms(socket) {
|
||||
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(
|
||||
PBS_ENDPOINTS.ContactGet,
|
||||
{
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
SerialNumber: JobData.bodyshop.pbs_serialnumber,
|
||||
//ContactId: "00000000000000000000000000000000",
|
||||
// ContactCode: socket.JobData.owner.accountingid,
|
||||
FirstName: socket.JobData.ownr_fn,
|
||||
LastName: socket.JobData.ownr_co_nm ? socket.JobData.ownr_co_nm : socket.JobData.ownr_ln,
|
||||
PhoneNumber: socket.JobData.ownr_ph1,
|
||||
EmailAddress: socket.JobData.ownr_ea
|
||||
// ContactCode: JobData.owner.accountingid,
|
||||
FirstName: JobData.ownr_fn,
|
||||
LastName: JobData.ownr_co_nm ? JobData.ownr_co_nm : JobData.ownr_ln,
|
||||
PhoneNumber: JobData.ownr_ph1,
|
||||
EmailAddress: JobData.ownr_ea
|
||||
// ModifiedSince: "0001-01-01T00:00:00.0000000Z",
|
||||
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
|
||||
// ContactIdList: ["00000000000000000000000000000000"],
|
||||
@@ -197,27 +235,36 @@ async function QueryCustomersFromDms(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;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
|
||||
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(
|
||||
PBS_ENDPOINTS.ContactGet,
|
||||
{
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
SerialNumber: JobData.bodyshop.pbs_serialnumber,
|
||||
ContactId: CustomerRef
|
||||
//ContactCode: socket.JobData.owner.accountingid,
|
||||
//FirstName: socket.JobData.ownr_co_nm
|
||||
// ? socket.JobData.ownr_co_nm
|
||||
// : socket.JobData.ownr_fn,
|
||||
//LastName: socket.JobData.ownr_ln,
|
||||
//PhoneNumber: socket.JobData.ownr_ph1,
|
||||
//ContactCode: JobData.owner.accountingid,
|
||||
//FirstName: JobData.ownr_co_nm
|
||||
// ? JobData.ownr_co_nm
|
||||
// : JobData.ownr_fn,
|
||||
//LastName: JobData.ownr_ln,
|
||||
//PhoneNumber: JobData.ownr_ph1,
|
||||
// EmailAddress: "String",
|
||||
// ModifiedSince: "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 }
|
||||
);
|
||||
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;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
async function UpsertContactData(socket, selectedCustomerId) {
|
||||
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(
|
||||
PBS_ENDPOINTS.ContactChange,
|
||||
{
|
||||
ContactInfo: {
|
||||
// Id: socket.JobData.owner.id,
|
||||
// Id: JobData.owner.id,
|
||||
...(selectedCustomerId ? { ContactId: selectedCustomerId } : {}),
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
Code: socket.JobData.owner.accountingid,
|
||||
...(socket.JobData.ownr_co_nm
|
||||
SerialNumber: JobData.bodyshop.pbs_serialnumber,
|
||||
Code: JobData.owner.accountingid,
|
||||
...(JobData.ownr_co_nm
|
||||
? {
|
||||
//LastName: socket.JobData.ownr_ln,
|
||||
FirstName: socket.JobData.ownr_co_nm,
|
||||
//LastName: JobData.ownr_ln,
|
||||
FirstName: JobData.ownr_co_nm,
|
||||
IsBusiness: true
|
||||
}
|
||||
: {
|
||||
LastName: socket.JobData.ownr_ln,
|
||||
FirstName: socket.JobData.ownr_fn,
|
||||
LastName: JobData.ownr_ln,
|
||||
FirstName: JobData.ownr_fn,
|
||||
IsBusiness: false
|
||||
}),
|
||||
|
||||
@@ -266,20 +322,20 @@ async function UpsertContactData(socket, selectedCustomerId) {
|
||||
IsInactive: false,
|
||||
|
||||
//ApartmentNumber: "String",
|
||||
Address: socket.JobData.ownr_addr1,
|
||||
City: socket.JobData.ownr_city,
|
||||
//County: socket.JobData.ownr_addr1,
|
||||
State: socket.JobData.ownr_st,
|
||||
ZipCode: socket.JobData.ownr_zip,
|
||||
Address: JobData.ownr_addr1,
|
||||
City: JobData.ownr_city,
|
||||
//County: JobData.ownr_addr1,
|
||||
State: JobData.ownr_st,
|
||||
ZipCode: JobData.ownr_zip,
|
||||
//BusinessPhone: "String",
|
||||
//BusinessPhoneExt: "String",
|
||||
HomePhone: socket.JobData.ownr_ph2,
|
||||
CellPhone: socket.JobData.ownr_ph1,
|
||||
HomePhone: JobData.ownr_ph2,
|
||||
CellPhone: JobData.ownr_ph1,
|
||||
//BusinessPhoneRawReverse: "String",
|
||||
//HomePhoneRawReverse: "String",
|
||||
//CellPhoneRawReverse: "String",
|
||||
//FaxNumber: "String",
|
||||
EmailAddress: socket.JobData.ownr_ea
|
||||
EmailAddress: JobData.ownr_ea
|
||||
//Notes: "String",
|
||||
//CriticalMemo: "String",
|
||||
//BirthDate: "0001-01-01T00:00:00.0000000Z",
|
||||
@@ -312,39 +368,43 @@ async function UpsertContactData(socket, selectedCustomerId) {
|
||||
},
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, ContactChangeResponse);
|
||||
|
||||
await CheckForErrors(socket, ContactChangeResponse);
|
||||
|
||||
return ContactChangeResponse;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
|
||||
await CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function UpsertVehicleData(socket, ownerRef) {
|
||||
try {
|
||||
const JobData = await getSessionData(socket.id, "JobData");
|
||||
|
||||
const { data: VehicleChangeResponse } = await axios.post(
|
||||
PBS_ENDPOINTS.VehicleChange,
|
||||
{
|
||||
VehicleInfo: {
|
||||
//Id: "string/00000000-0000-0000-0000-000000000000",
|
||||
//VehicleId: "00000000000000000000000000000000",
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
SerialNumber: JobData.bodyshop.pbs_serialnumber,
|
||||
//StockNumber: "String",
|
||||
VIN: socket.JobData.v_vin,
|
||||
LicenseNumber: socket.JobData.plate_no,
|
||||
VIN: JobData.v_vin,
|
||||
LicenseNumber: JobData.plate_no,
|
||||
//FleetNumber: "String",
|
||||
//Status: "String",
|
||||
OwnerRef: ownerRef, // "00000000000000000000000000000000",
|
||||
ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode,
|
||||
Make: socket.JobData.v_make_desc,
|
||||
Model: socket.JobData.v_model_desc,
|
||||
Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode,
|
||||
ModelNumber: JobData.vehicle && JobData.vehicle.v_makecode,
|
||||
Make: JobData.v_make_desc,
|
||||
Model: JobData.v_model_desc,
|
||||
Trim: JobData.vehicle && JobData.vehicle.v_trimcode,
|
||||
//VehicleType: "String",
|
||||
Year: socket.JobData.v_model_yr,
|
||||
Odometer: socket.JobData.kmout,
|
||||
Year: JobData.v_model_yr,
|
||||
Odometer: JobData.kmout,
|
||||
ExteriorColor: {
|
||||
Code: socket.JobData.v_color,
|
||||
Description: socket.JobData.v_color
|
||||
Code: JobData.v_color,
|
||||
Description: JobData.v_color
|
||||
}
|
||||
// InteriorColor: { Code: "String", Description: "String" },
|
||||
//Engine: "String",
|
||||
@@ -465,100 +525,112 @@ async function UpsertVehicleData(socket, ownerRef) {
|
||||
},
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
CheckForErrors(socket, VehicleChangeResponse);
|
||||
|
||||
await CheckForErrors(socket, VehicleChangeResponse);
|
||||
|
||||
return VehicleChangeResponse;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
await CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function InsertAccountPostingData(socket) {
|
||||
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 = [];
|
||||
|
||||
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) {
|
||||
const item = {
|
||||
Account: alloc.profitCenter.dms_acctnumber,
|
||||
ControlNumber: socket.JobData.ro_number,
|
||||
ControlNumber: JobData.ro_number,
|
||||
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
|
||||
//Comment: "String",
|
||||
//AdditionalInfo: "String",
|
||||
InvoiceNumber: socket.JobData.ro_number,
|
||||
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
|
||||
// Comment: "String",
|
||||
// AdditionalInfo: "String",
|
||||
InvoiceNumber: JobData.ro_number,
|
||||
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
|
||||
};
|
||||
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) {
|
||||
const item = {
|
||||
Account: alloc.costCenter.dms_acctnumber,
|
||||
ControlNumber: socket.JobData.ro_number,
|
||||
ControlNumber: JobData.ro_number,
|
||||
Amount: alloc.cost.toFormat("0.00"),
|
||||
//Comment: "String",
|
||||
//AdditionalInfo: "String",
|
||||
InvoiceNumber: socket.JobData.ro_number,
|
||||
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
|
||||
// Comment: "String",
|
||||
// AdditionalInfo: "String",
|
||||
InvoiceNumber: JobData.ro_number,
|
||||
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
|
||||
};
|
||||
|
||||
wips.push(item);
|
||||
|
||||
const itemWip = {
|
||||
Account: alloc.costCenter.dms_wip_acctnumber,
|
||||
ControlNumber: socket.JobData.ro_number,
|
||||
ControlNumber: JobData.ro_number,
|
||||
Amount: alloc.cost.multiply(-1).toFormat("0.00"),
|
||||
//Comment: "String",
|
||||
//AdditionalInfo: "String",
|
||||
InvoiceNumber: socket.JobData.ro_number,
|
||||
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
|
||||
// Comment: "String",
|
||||
// AdditionalInfo: "String",
|
||||
InvoiceNumber: JobData.ro_number,
|
||||
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
|
||||
};
|
||||
|
||||
wips.push(itemWip);
|
||||
//Add to the WIP account.
|
||||
// Add to the WIP account.
|
||||
}
|
||||
|
||||
// Add tax-related entries if applicable
|
||||
if (alloc.tax) {
|
||||
if (alloc.sale.getAmount() > 0) {
|
||||
const item2 = {
|
||||
Account: alloc.profitCenter.dms_acctnumber,
|
||||
ControlNumber: socket.JobData.ro_number,
|
||||
ControlNumber: JobData.ro_number,
|
||||
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
|
||||
//Comment: "String",
|
||||
//AdditionalInfo: "String",
|
||||
InvoiceNumber: socket.JobData.ro_number,
|
||||
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
|
||||
// Comment: "String",
|
||||
// AdditionalInfo: "String",
|
||||
InvoiceNumber: JobData.ro_number,
|
||||
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
|
||||
};
|
||||
|
||||
wips.push(item2);
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.txEnvelope.payers.forEach((payer) => {
|
||||
|
||||
// Add payer information
|
||||
txEnvelope.payers.forEach((payer) => {
|
||||
const item = {
|
||||
Account: payer.dms_acctnumber,
|
||||
ControlNumber: payer.controlnumber,
|
||||
Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat("0.0"),
|
||||
//Comment: "String",
|
||||
//AdditionalInfo: "String",
|
||||
InvoiceNumber: socket.JobData.ro_number,
|
||||
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
|
||||
// Comment: "String",
|
||||
// AdditionalInfo: "String",
|
||||
InvoiceNumber: JobData.ro_number,
|
||||
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
|
||||
};
|
||||
|
||||
wips.push(item);
|
||||
});
|
||||
socket.transWips = wips;
|
||||
|
||||
await setSessionData(socket.id, "transWips", wips);
|
||||
|
||||
const { data: AccountPostingChange } = await axios.post(
|
||||
PBS_ENDPOINTS.AccountingPostingChange,
|
||||
{
|
||||
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
|
||||
SerialNumber: JobData.bodyshop.pbs_serialnumber,
|
||||
Posting: {
|
||||
Reference: socket.JobData.ro_number,
|
||||
JournalCode: socket.txEnvelope.journal,
|
||||
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
|
||||
Description: socket.txEnvelope.story,
|
||||
//AdditionalInfo: "String",
|
||||
Reference: JobData.ro_number,
|
||||
JournalCode: txEnvelope.journal,
|
||||
TransactionDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString(), // "0001-01-01T00:00:00.0000000Z",
|
||||
Description: txEnvelope.story,
|
||||
// AdditionalInfo: "String",
|
||||
Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
|
||||
Lines: wips
|
||||
}
|
||||
@@ -566,54 +638,56 @@ async function InsertAccountPostingData(socket) {
|
||||
{ auth: PBS_CREDENTIALS, socket }
|
||||
);
|
||||
|
||||
CheckForErrors(socket, AccountPostingChange);
|
||||
await CheckForErrors(socket, AccountPostingChange);
|
||||
|
||||
return AccountPostingChange;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
|
||||
await CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
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 result = await client
|
||||
|
||||
const { JobData, transWips } = await getMultipleSessionData(socket.id, ["JobData", "transWips"]);
|
||||
|
||||
return await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.MARK_JOB_EXPORTED, {
|
||||
jobId: jobid,
|
||||
job: {
|
||||
status: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
|
||||
status: JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
|
||||
date_exported: new Date()
|
||||
},
|
||||
log: {
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
bodyshopid: JobData.bodyshop.id,
|
||||
jobid: jobid,
|
||||
successful: true,
|
||||
useremail: socket.user.email,
|
||||
metadata: socket.transWips
|
||||
metadata: transWips
|
||||
},
|
||||
bill: {
|
||||
exported: true,
|
||||
exported_at: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function InsertFailedExportLog(socket, error) {
|
||||
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}` })
|
||||
.request(queries.INSERT_EXPORT_LOG, {
|
||||
log: {
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
jobid: socket.JobData.id,
|
||||
bodyshopid: JobData.bodyshop.id,
|
||||
jobid: JobData.id,
|
||||
successful: false,
|
||||
message: [error],
|
||||
useremail: socket.user.email
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ const { DiscountNotAlreadyCounted } = InstanceManager({
|
||||
|
||||
exports.defaultRoute = async function (req, res) {
|
||||
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);
|
||||
return res.status(200).json({ data: calculateAllocations(req, jobData) });
|
||||
return res.status(200).json({ data: await calculateAllocations(req, jobData) });
|
||||
} catch (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}` });
|
||||
}
|
||||
};
|
||||
@@ -31,22 +31,22 @@ exports.defaultRoute = async function (req, res) {
|
||||
exports.default = async function (socket, jobid) {
|
||||
try {
|
||||
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid);
|
||||
return calculateAllocations(socket, jobData);
|
||||
return await calculateAllocations(socket, jobData);
|
||||
} catch (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) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
function calculateAllocations(connectionData, job) {
|
||||
async function calculateAllocations(connectionData, job) {
|
||||
const { bodyshop } = job;
|
||||
|
||||
const taxAllocations = InstanceManager({
|
||||
@@ -171,7 +171,7 @@ function calculateAllocations(connectionData, job) {
|
||||
const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find(
|
||||
(d) => d.name === job.dms_allocation
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
await CdkBase.createLogEvent(
|
||||
connectionData,
|
||||
"DEBUG",
|
||||
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
|
||||
@@ -361,9 +361,10 @@ function calculateAllocations(connectionData, job) {
|
||||
}
|
||||
if (InstanceManager({ rome: true })) {
|
||||
//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 otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
|
||||
|
||||
if (otherAccount) {
|
||||
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
|
||||
|
||||
@@ -371,31 +372,41 @@ function calculateAllocations(connectionData, job) {
|
||||
Dinero(job.job_totals.parts.adjustments[key])
|
||||
);
|
||||
} else {
|
||||
CdkBase.createLogEvent(
|
||||
// Use await correctly here
|
||||
await CdkBase.createLogEvent(
|
||||
connectionData,
|
||||
"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
|
||||
Object.keys(job.job_totals.rates).forEach((key) => {
|
||||
if (job.job_totals.rates[key] && job.job_totals.rates[key].adjustment && Dinero(job.job_totals.rates[key].adjustment).isZero() === false) {
|
||||
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
|
||||
) {
|
||||
const accountName = selectedDmsAllocationConfig.profits[key.toUpperCase()];
|
||||
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
|
||||
|
||||
if (otherAccount) {
|
||||
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 {
|
||||
CdkBase.createLogEvent(
|
||||
// Await the log event creation here
|
||||
await CdkBase.createLogEvent(
|
||||
connectionData,
|
||||
"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) => {
|
||||
|
||||
@@ -86,7 +86,7 @@ async function GetCdkMakes(req, cdk_dealerid) {
|
||||
{}
|
||||
);
|
||||
|
||||
CheckCdkResponseForError(null, soapResponseVehicleSearch);
|
||||
await CheckCdkResponseForError(null, soapResponseVehicleSearch);
|
||||
const [result, rawResponse, , rawRequest] = soapResponseVehicleSearch;
|
||||
logger.log("cdk-replace-makes-models-request", "ERROR", req.user.email, null, {
|
||||
cdk_dealerid,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,10 +15,10 @@ exports.CDK_CREDENTIALS = CDK_CREDENTIALS;
|
||||
const cdkDomain =
|
||||
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]) {
|
||||
//The response was null, this might be ok, it might not.
|
||||
CdkBase.createLogEvent(
|
||||
await CdkBase.createLogEvent(
|
||||
socket,
|
||||
"WARNING",
|
||||
`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)) {
|
||||
ResultToCheck.forEach((result) => checkIndividualResult(socket, result));
|
||||
} else {
|
||||
checkIndividualResult(socket, ResultToCheck);
|
||||
await checkIndividualResult(socket, ResultToCheck);
|
||||
}
|
||||
}
|
||||
|
||||
exports.CheckCdkResponseForError = CheckCdkResponseForError;
|
||||
|
||||
function checkIndividualResult(socket, ResultToCheck) {
|
||||
async function checkIndividualResult(socket, ResultToCheck) {
|
||||
if (
|
||||
ResultToCheck.errorLevel === 0 ||
|
||||
ResultToCheck.errorLevel === "0" ||
|
||||
ResultToCheck.code === "success" ||
|
||||
(!ResultToCheck.code && !ResultToCheck.errorLevel)
|
||||
)
|
||||
) {
|
||||
//TODO: Verify that this is the best way to detect errors.
|
||||
return;
|
||||
else {
|
||||
CdkBase.createLogEvent(
|
||||
} else {
|
||||
await CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error detected in CDK Response - ${JSON.stringify(ResultToCheck, null, 2)}`
|
||||
|
||||
@@ -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") {
|
||||
// Handling SIGINT (e.g., Ctrl+C)
|
||||
process.on("SIGINT", async () => {
|
||||
logger.log("Clearing Tasks Email Queue...", "INFO", "redis", "api");
|
||||
await tasksEmailQueueCleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
461
server/job/job-updated.js
Normal file
461
server/job/job-updated.js
Normal file
@@ -0,0 +1,461 @@
|
||||
const { isObject } = require("lodash");
|
||||
|
||||
const jobUpdated = async (req, res) => {
|
||||
const { io, logger } = req;
|
||||
|
||||
console.dir(req.body, { depth: null });
|
||||
|
||||
if (!req?.body?.event?.data?.new || !isObject(req?.body?.event?.data?.new)) {
|
||||
logger.log("job-update-error", "ERROR", req.user?.email, null, {
|
||||
message: `Malformed Job Update request sent from Hasura`,
|
||||
body: req?.body
|
||||
});
|
||||
|
||||
return res.json({
|
||||
status: "error",
|
||||
message: `Malformed Job Update request sent from Hasura`
|
||||
});
|
||||
}
|
||||
|
||||
logger.log("job-update", "INFO", req.user?.email, null, {
|
||||
message: `Job updated event received from Hasura`,
|
||||
jobid: req?.body?.event?.data?.new?.id
|
||||
});
|
||||
|
||||
const updatedJob = req.body.event.data.new;
|
||||
const bodyshopID = updatedJob.shopid;
|
||||
|
||||
// Emit the job-updated event only to the room corresponding to the bodyshop
|
||||
io.to(bodyshopID).emit("job-updated", updatedJob);
|
||||
|
||||
return res.json({ message: "Job updated and event emitted" });
|
||||
};
|
||||
|
||||
module.exports = jobUpdated;
|
||||
|
||||
// {
|
||||
// actual_completion: null,
|
||||
// actual_delivery: null,
|
||||
// actual_in: '2024-09-05T23:04:31.439+00:00',
|
||||
// adj_g_disc: 0,
|
||||
// adj_strdis: 0,
|
||||
// adj_towdis: 0,
|
||||
// adjustment_bottom_line: null,
|
||||
// agt_addr1: null,
|
||||
// agt_addr2: null,
|
||||
// agt_city: null,
|
||||
// agt_co_id: null,
|
||||
// agt_co_nm: null,
|
||||
// agt_ct_fn: null,
|
||||
// agt_ct_ln: null,
|
||||
// agt_ct_ph: null,
|
||||
// agt_ct_phx: null,
|
||||
// agt_ctry: null,
|
||||
// agt_ea: null,
|
||||
// agt_fax: null,
|
||||
// agt_faxx: null,
|
||||
// agt_lic_no: null,
|
||||
// agt_ph1: null,
|
||||
// agt_ph1x: null,
|
||||
// agt_ph2: null,
|
||||
// agt_ph2x: null,
|
||||
// agt_st: null,
|
||||
// agt_zip: null,
|
||||
// alt_transport: 'No car',
|
||||
// area_of_damage: { impact1: '19', impact2: '0102' },
|
||||
// asgn_date: '2024-08-06',
|
||||
// asgn_no: '72361166-99',
|
||||
// asgn_type: null,
|
||||
// auto_add_ats: false,
|
||||
// ca_bc_pvrt: null,
|
||||
// ca_customer_gst: 500,
|
||||
// ca_gst_registrant: true,
|
||||
// cat_no: null,
|
||||
// category: null,
|
||||
// cieca_pfl: {},
|
||||
// cieca_pfo: {},
|
||||
// cieca_pft: {},
|
||||
// cieca_stl: {
|
||||
// data: [
|
||||
// [Object], [Object], [Object],
|
||||
// [Object], [Object], [Object],
|
||||
// [Object], [Object], [Object],
|
||||
// [Object], [Object], [Object],
|
||||
// [Object], [Object], [Object],
|
||||
// [Object], [Object], [Object],
|
||||
// [Object], [Object], [Object],
|
||||
// [Object]
|
||||
// ]
|
||||
// },
|
||||
// cieca_ttl: {
|
||||
// data: {
|
||||
// g_aa_amt: 0,
|
||||
// g_bett_amt: 0,
|
||||
// g_cust_amt: 0,
|
||||
// g_ded_amt: 0,
|
||||
// g_rpd_amt: 0,
|
||||
// g_tax: 854.37,
|
||||
// g_ttl_amt: 13721.35,
|
||||
// g_ttl_disc: 0,
|
||||
// g_upd_amt: 0,
|
||||
// gst_amt: 612.72,
|
||||
// n_ttl_amt: 13721.35,
|
||||
// prev_net: 0,
|
||||
// supp_amt: 958.53
|
||||
// }
|
||||
// },
|
||||
// ciecaid: '2273515',
|
||||
// class: null,
|
||||
// clm_addr1: null,
|
||||
// clm_addr2: null,
|
||||
// clm_city: null,
|
||||
// clm_ct_fn: null,
|
||||
// clm_ct_ln: null,
|
||||
// clm_ct_ph: null,
|
||||
// clm_ct_phx: null,
|
||||
// clm_ctry: null,
|
||||
// clm_ea: null,
|
||||
// clm_fax: null,
|
||||
// clm_faxx: null,
|
||||
// clm_no: '72361166-99',
|
||||
// clm_ofc_id: null,
|
||||
// clm_ofc_nm: null,
|
||||
// clm_ph1: null,
|
||||
// clm_ph1x: null,
|
||||
// clm_ph2: null,
|
||||
// clm_ph2x: null,
|
||||
// clm_st: null,
|
||||
// clm_title: null,
|
||||
// clm_total: 13721.3,
|
||||
// clm_zip: null,
|
||||
// comment: null,
|
||||
// completed_tasks: [],
|
||||
// converted: true,
|
||||
// created_at: '2024-09-05T22:50:11.37571+00:00',
|
||||
// cust_pr: 'C',
|
||||
// date_estimated: null,
|
||||
// date_exported: null,
|
||||
// date_invoiced: null,
|
||||
// date_last_contacted: null,
|
||||
// date_lost_sale: null,
|
||||
// date_next_contact: '2024-09-07T23:04:31.439+00:00',
|
||||
// date_open: '2024-09-05T22:50:12.083+00:00',
|
||||
// date_rentalresp: null,
|
||||
// date_repairstarted: null,
|
||||
// date_scheduled: '2024-09-05T23:04:12.182+00:00',
|
||||
// date_towin: null,
|
||||
// date_void: null,
|
||||
// ded_amt: 0,
|
||||
// ded_note: null,
|
||||
// ded_status: 'Y',
|
||||
// deliverchecklist: null,
|
||||
// depreciation_taxes: 0,
|
||||
// dms_allocation: null,
|
||||
// driveable: true,
|
||||
// employee_body: null,
|
||||
// employee_csr: null,
|
||||
// employee_prep: null,
|
||||
// employee_refinish: null,
|
||||
// est_addr1: null,
|
||||
// est_addr2: null,
|
||||
// est_city: null,
|
||||
// est_co_nm: null,
|
||||
// est_ct_fn: 'Monique',
|
||||
// est_ct_ln: 'Bruneau',
|
||||
// est_ctry: null,
|
||||
// est_ea: 'MONIQUE@STCAUTO.COM',
|
||||
// est_ph1: null,
|
||||
// est_st: null,
|
||||
// est_zip: null,
|
||||
// federal_tax_rate: 0.05,
|
||||
// g_bett_amt: 0,
|
||||
// id: '344fe1e0-4e0c-4f7b-9728-659c850d8192',
|
||||
// inproduction: true,
|
||||
// ins_addr1: null,
|
||||
// ins_addr2: null,
|
||||
// ins_city: null,
|
||||
// ins_co_id: null,
|
||||
// ins_co_nm: 'ICBC',
|
||||
// ins_ct_fn: null,
|
||||
// ins_ct_ln: null,
|
||||
// ins_ct_ph: null,
|
||||
// ins_ct_phx: null,
|
||||
// ins_ctry: null,
|
||||
// ins_ea: null,
|
||||
// ins_fax: null,
|
||||
// ins_faxx: null,
|
||||
// ins_memo: null,
|
||||
// ins_ph1: null,
|
||||
// ins_ph1x: null,
|
||||
// ins_ph2: null,
|
||||
// ins_ph2x: null,
|
||||
// ins_st: null,
|
||||
// ins_title: null,
|
||||
// ins_zip: null,
|
||||
// insd_addr1: null,
|
||||
// insd_addr2: null,
|
||||
// insd_city: null,
|
||||
// insd_co_nm: null,
|
||||
// insd_ctry: null,
|
||||
// insd_ea: null,
|
||||
// insd_fax: null,
|
||||
// insd_faxx: null,
|
||||
// insd_fn: null,
|
||||
// insd_ln: null,
|
||||
// insd_ph1: null,
|
||||
// insd_ph1x: null,
|
||||
// insd_ph2: null,
|
||||
// insd_ph2x: null,
|
||||
// insd_st: null,
|
||||
// insd_title: null,
|
||||
// insd_zip: null,
|
||||
// intakechecklist: {
|
||||
// addToProduction: true,
|
||||
// allow_text_message: false,
|
||||
// completed_at: '2024-09-05T23:04:31.439Z',
|
||||
// completed_by: 'allan@imex.dev',
|
||||
// form: [ [Object], [Object], [Object], [Object], [Object], [Object] ],
|
||||
// production_vars: {},
|
||||
// scheduled_completion: '2024-09-25T23:03:55.285Z',
|
||||
// scheduled_delivery: null
|
||||
// },
|
||||
// invoice_allocation: null,
|
||||
// invoice_date: null,
|
||||
// invoice_final_note: null,
|
||||
// iouparent: null,
|
||||
// job_totals: {
|
||||
// additional: {
|
||||
// additionalCostItems: [Array],
|
||||
// additionalCosts: [Object],
|
||||
// adjustments: [Object],
|
||||
// pvrt: [Object],
|
||||
// shipping: [Object],
|
||||
// storage: [Object],
|
||||
// total: [Object],
|
||||
// towing: [Object]
|
||||
// },
|
||||
// parts: { parts: [Object], sublets: [Object] },
|
||||
// rates: {
|
||||
// la1: [Object],
|
||||
// la2: [Object],
|
||||
// la3: [Object],
|
||||
// la4: [Object],
|
||||
// laa: [Object],
|
||||
// lab: [Object],
|
||||
// lad: [Object],
|
||||
// lae: [Object],
|
||||
// laf: [Object],
|
||||
// lag: [Object],
|
||||
// lam: [Object],
|
||||
// lar: [Object],
|
||||
// las: [Object],
|
||||
// lau: [Object],
|
||||
// mapa: [Object],
|
||||
// mash: [Object],
|
||||
// rates_subtotal: [Object],
|
||||
// subtotal: [Object]
|
||||
// },
|
||||
// totals: {
|
||||
// custPayable: [Object],
|
||||
// federal_tax: [Object],
|
||||
// local_tax: [Object],
|
||||
// net_repairs: [Object],
|
||||
// statePartsTax: [Object],
|
||||
// state_tax: [Object],
|
||||
// subtotal: [Object],
|
||||
// total_repairs: [Object]
|
||||
// }
|
||||
// },
|
||||
// kanbanparent: '-1',
|
||||
// kmin: null,
|
||||
// kmout: null,
|
||||
// labor_rate_desc: 'EST',
|
||||
// labor_rate_id: null,
|
||||
// lbr_adjustments: {},
|
||||
// local_tax_rate: null,
|
||||
// loss_cat: 'U',
|
||||
// loss_date: '2024-06-19',
|
||||
// loss_desc: 'Animal',
|
||||
// loss_of_use: null,
|
||||
// loss_type: 'A',
|
||||
// lost_sale_reason: null,
|
||||
// materials: {
|
||||
// mapa: { cal_maxdlr: 9999.99, cal_opcode: 'OP13' },
|
||||
// mash: { cal_maxdlr: 9999.99, cal_opcode: 'OP13' }
|
||||
// },
|
||||
// other_amount_payable: null,
|
||||
// owner_owing: 500,
|
||||
// ownerid: 'f392b24f-e828-47fa-bd17-2e5af8493147',
|
||||
// ownr_addr1: null,
|
||||
// ownr_addr2: null,
|
||||
// ownr_city: null,
|
||||
// ownr_co_nm: null,
|
||||
// ownr_ctry: null,
|
||||
// ownr_ea: null,
|
||||
// ownr_fax: null,
|
||||
// ownr_faxx: null,
|
||||
// ownr_fn: 'Neil',
|
||||
// ownr_ln: 'Leslie',
|
||||
// ownr_ph1: null,
|
||||
// ownr_ph1x: null,
|
||||
// ownr_ph2: null,
|
||||
// ownr_ph2x: null,
|
||||
// ownr_st: null,
|
||||
// ownr_title: null,
|
||||
// ownr_zip: null,
|
||||
// parts_tax_rates: {
|
||||
// CCC: {},
|
||||
// CCD: {},
|
||||
// CCDR: {},
|
||||
// CCF: {},
|
||||
// CCM: {},
|
||||
// PAA: {
|
||||
// prt_discp: 0,
|
||||
// prt_mktyp: false,
|
||||
// prt_mkupp: 0,
|
||||
// prt_tax_in: true,
|
||||
// prt_tax_rt: 0.07,
|
||||
// prt_type: 'PAA'
|
||||
// },
|
||||
// PAC: {
|
||||
// prt_discp: 0,
|
||||
// prt_mktyp: false,
|
||||
// prt_mkupp: 0,
|
||||
// prt_tax_in: true,
|
||||
// prt_tax_rt: 0.07,
|
||||
// prt_type: 'PAC'
|
||||
// },
|
||||
// PAG: {},
|
||||
// PAL: {
|
||||
// prt_discp: 0,
|
||||
// prt_mktyp: false,
|
||||
// prt_mkupp: 0,
|
||||
// prt_tax_in: true,
|
||||
// prt_tax_rt: 0.07,
|
||||
// prt_type: 'PAL'
|
||||
// },
|
||||
// PAM: {
|
||||
// prt_discp: 0,
|
||||
// prt_mktyp: false,
|
||||
// prt_mkupp: 0,
|
||||
// prt_tax_in: true,
|
||||
// prt_tax_rt: 0.07,
|
||||
// prt_type: 'PAM'
|
||||
// },
|
||||
// PAN: {
|
||||
// prt_discp: 0,
|
||||
// prt_mktyp: false,
|
||||
// prt_mkupp: 0,
|
||||
// prt_tax_in: true,
|
||||
// prt_tax_rt: 0.07,
|
||||
// prt_type: 'PAN'
|
||||
// },
|
||||
// PAO: {},
|
||||
// PAP: {},
|
||||
// PAR: {
|
||||
// prt_discp: 0,
|
||||
// prt_mktyp: false,
|
||||
// prt_mkupp: 0,
|
||||
// prt_tax_in: true,
|
||||
// prt_tax_rt: 0.07,
|
||||
// prt_type: 'PAR'
|
||||
// },
|
||||
// PAS: {
|
||||
// prt_discp: 0,
|
||||
// prt_mktyp: false,
|
||||
// prt_mkupp: 0,
|
||||
// prt_tax_in: true,
|
||||
// prt_tax_rt: 0.07,
|
||||
// prt_type: 'PAS'
|
||||
// },
|
||||
// PASL: {}
|
||||
// },
|
||||
// pay_amt: 0,
|
||||
// pay_chknm: '0',
|
||||
// pay_date: null,
|
||||
// pay_type: null,
|
||||
// payee_nms: null,
|
||||
// plate_no: 'B15717',
|
||||
// plate_st: 'MB',
|
||||
// po_number: null,
|
||||
// policy_no: '18292147',
|
||||
// production_vars: { note: 'testtsdsadsasdasdsadsdsadsd' },
|
||||
// qb_multiple_payers: null,
|
||||
// queued_for_parts: false,
|
||||
// rate_ats: null,
|
||||
// rate_la1: 0,
|
||||
// rate_la2: 0,
|
||||
// rate_la3: null,
|
||||
// rate_la4: 0,
|
||||
// rate_laa: 0,
|
||||
// rate_lab: 88.43,
|
||||
// rate_lad: null,
|
||||
// rate_lae: null,
|
||||
// rate_laf: 97.06,
|
||||
// rate_lag: 88.43,
|
||||
// rate_lam: 103.5,
|
||||
// rate_lar: 88.43,
|
||||
// rate_las: 88.43,
|
||||
// rate_lau: 0,
|
||||
// rate_ma2s: 0,
|
||||
// rate_ma2t: 0,
|
||||
// rate_ma3s: 0,
|
||||
// rate_mabl: null,
|
||||
// rate_macs: 0,
|
||||
// rate_mahw: 49.7,
|
||||
// rate_mapa: 56.67,
|
||||
// rate_mash: 7.23,
|
||||
// rate_matd: null,
|
||||
// referral_source: null,
|
||||
// referral_source_extra: null,
|
||||
// regie_number: null,
|
||||
// remove_from_ar: false,
|
||||
// ro_number: '10002',
|
||||
// scheduled_completion: '2024-09-25T23:03:55.285+00:00',
|
||||
// scheduled_delivery: null,
|
||||
// scheduled_in: '2024-09-05T22:00:55.2+00:00',
|
||||
// selling_dealer: null,
|
||||
// selling_dealer_contact: null,
|
||||
// servicing_dealer: null,
|
||||
// servicing_dealer_contact: null,
|
||||
// shopid: 'bfec8c8c-b7f1-49e0-be4c-524455f4e582',
|
||||
// special_coverage_policy: false,
|
||||
// state_tax_rate: null,
|
||||
// status: 'Repair Plan',
|
||||
// storage_payable: null,
|
||||
// suspended: false,
|
||||
// tax_lbr_rt: 0.07,
|
||||
// tax_levies_rt: 0.07,
|
||||
// tax_paint_mat_rt: 0.07,
|
||||
// tax_predis: 0,
|
||||
// tax_prethr: 0.07,
|
||||
// tax_pstthr: 0,
|
||||
// tax_registration_number: null,
|
||||
// tax_shop_mat_rt: 0.07,
|
||||
// tax_str_rt: 0.07,
|
||||
// tax_sub_rt: 0.07,
|
||||
// tax_thramt: 0,
|
||||
// tax_tow_rt: 0,
|
||||
// theft_ind: false,
|
||||
// tlos_ind: false,
|
||||
// towin: false,
|
||||
// towing_payable: null,
|
||||
// unit_number: null,
|
||||
// updated_at: '2024-09-12T19:25:48.492299+00:00',
|
||||
// v_color: 'Polished Metal Metal',
|
||||
// v_make_desc: 'Honda',
|
||||
// v_model_desc: 'Civic',
|
||||
// v_model_yr: '17',
|
||||
// v_vin: 'SHHFK7H20HU306419',
|
||||
// vehicleid: '811ba420-e9db-430b-b341-22e197c8dd0e',
|
||||
// voided: false
|
||||
// }
|
||||
// I have been hit, wewet
|
||||
// job-transition-update-result {
|
||||
// type: 'DEBUG',
|
||||
// env: 'development',
|
||||
// user: null,
|
||||
// record: '344fe1e0-4e0c-4f7b-9728-659c850d8192',
|
||||
// insert_transitions_one: { id: '88e73841-247d-4e31-b31c-f1b2ca8364d9' },
|
||||
// update_transitions: { affected_rows: 1 }
|
||||
// }
|
||||
@@ -14,3 +14,4 @@ exports.costing = require("./job-costing").JobCosting;
|
||||
exports.costingmulti = require("./job-costing").JobCostingMulti;
|
||||
exports.statustransition = require("./job-status-transition").statustransition;
|
||||
exports.lifecycle = require("./job-lifecycle");
|
||||
exports.jobUpdated = require("./job-updated");
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const job = require("../job/job");
|
||||
const ppc = require("../ccc/partspricechange");
|
||||
const { partsScan } = require("../parts-scan/parts-scan");
|
||||
const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMIddleware");
|
||||
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||
const { totals, statustransition, totalsSsu, costing, lifecycle, costingmulti } = require("../job/job");
|
||||
const { totals, statustransition, totalsSsu, costing, lifecycle, costingmulti, jobUpdated } = require("../job/job");
|
||||
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||
|
||||
router.post("/totals", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totals);
|
||||
@@ -16,5 +15,5 @@ router.post("/lifecycle", validateFirebaseIdTokenMiddleware, withUserGraphQLClie
|
||||
router.post("/costingmulti", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costingmulti);
|
||||
router.post("/partsscan", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, partsScan);
|
||||
router.post("/ppc", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, ppc.generatePpc);
|
||||
|
||||
router.post("/job-updated", eventAuthorizationMiddleware, jobUpdated);
|
||||
module.exports = router;
|
||||
|
||||
@@ -3,64 +3,79 @@ require("dotenv").config({
|
||||
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||
});
|
||||
|
||||
const { io } = require("../../server");
|
||||
const {
|
||||
io,
|
||||
setSessionData,
|
||||
clearSessionData,
|
||||
getMultipleSessionData,
|
||||
addItemToEndOfList,
|
||||
pubClient
|
||||
} = require("../../server");
|
||||
|
||||
const { admin } = require("../firebase/firebase-handler");
|
||||
const logger = require("../utils/logger");
|
||||
const { default: CdkJobExport, CdkSelectedCustomer } = require("../cdk/cdk-job-export");
|
||||
const CdkGetMakes = require("../cdk/cdk-get-makes").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 { PbsCalculateAllocationsAp, PbsExportAp } = require("../accounting/pbs/pbs-ap-allocations");
|
||||
|
||||
io.use(function (socket, next) {
|
||||
try {
|
||||
if (socket.handshake.auth.token) {
|
||||
admin
|
||||
.auth()
|
||||
.verifyIdToken(socket.handshake.auth.token)
|
||||
.then((user) => {
|
||||
socket.user = user;
|
||||
next();
|
||||
})
|
||||
.catch((error) => {
|
||||
next(new Error("Authentication error", JSON.stringify(error)));
|
||||
});
|
||||
} else {
|
||||
next(new Error("Authentication error - no authorization token."));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Uncaught connection error:::", error);
|
||||
logger.log("websocket-connection-error", "error", null, null, {
|
||||
token: socket.handshake.auth.token,
|
||||
...error
|
||||
// Middleware for verifying tokens via Firebase authentication
|
||||
function socketAuthMiddleware(socket, next) {
|
||||
const token = socket.handshake.auth.token;
|
||||
|
||||
if (!token) return next(new Error("Authentication error - no authorization token."));
|
||||
|
||||
admin
|
||||
.auth()
|
||||
.verifyIdToken(token)
|
||||
.then((user) => {
|
||||
socket.user = user;
|
||||
next();
|
||||
})
|
||||
.catch((error) => {
|
||||
next(new Error("Authentication error", JSON.stringify(error)));
|
||||
});
|
||||
next(new Error(`Authentication error ${error}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.log_level = "TRACE";
|
||||
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
|
||||
// Register all socket events for a given socket connection
|
||||
async function registerSocketEvents(socket) {
|
||||
await setSessionData(socket.id, "log_level", "TRACE");
|
||||
await createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
|
||||
|
||||
socket.on("set-log-level", (level) => {
|
||||
socket.log_level = level;
|
||||
socket.emit("log-event", {
|
||||
timestamp: new Date(),
|
||||
level: "INFO",
|
||||
message: `Updated log level to ${level}`
|
||||
});
|
||||
// Register CDK-related socket events
|
||||
registerCdkEvents(socket);
|
||||
|
||||
// Register PBS AR-related socket events
|
||||
registerPbsArEvents(socket);
|
||||
|
||||
// Register PBS AP-related socket events
|
||||
registerPbsApEvents(socket);
|
||||
|
||||
// Register room and broadcasting events
|
||||
registerRoomAndBroadcastEvents(socket);
|
||||
|
||||
// Register event to clear DMS session
|
||||
registerDmsClearSessionEvent(socket);
|
||||
|
||||
// Register Production Board events
|
||||
registerProductionBoardEvents(socket);
|
||||
|
||||
// Handle socket disconnection
|
||||
socket.on("disconnect", async () => {
|
||||
await createLogEvent(socket, "DEBUG", `User disconnected.`);
|
||||
});
|
||||
}
|
||||
|
||||
///CDK
|
||||
socket.on("cdk-export-job", (jobid) => {
|
||||
CdkJobExport(socket, jobid);
|
||||
});
|
||||
socket.on("cdk-selected-customer", (selectedCustomerId) => {
|
||||
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
CdkSelectedCustomer(socket, selectedCustomerId);
|
||||
// CDK-specific socket events
|
||||
function registerCdkEvents(socket) {
|
||||
socket.on("cdk-export-job", (jobid) => CdkJobExport(socket, jobid));
|
||||
socket.on("cdk-selected-customer", async (selectedCustomerId) => {
|
||||
await createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
|
||||
CdkSelectedCustomer(socket, selectedCustomerId).catch((err) =>
|
||||
console.error(`Error in cdk-selected-customer: ${err}`)
|
||||
);
|
||||
await setSessionData(socket.id, "selectedCustomer", selectedCustomerId);
|
||||
});
|
||||
|
||||
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
|
||||
@@ -68,159 +83,187 @@ io.on("connection", (socket) => {
|
||||
const makes = await CdkGetMakes(socket, cdk_dealerid);
|
||||
callback(makes);
|
||||
} 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)}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("cdk-calculate-allocations", async (jobid, callback) => {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
|
||||
await createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations)}`);
|
||||
callback(allocations);
|
||||
await setSessionData(socket.id, "cdk_allocations", allocations);
|
||||
});
|
||||
//END CDK
|
||||
}
|
||||
|
||||
//PBS AR
|
||||
// PBS AR-specific socket events
|
||||
function registerPbsArEvents(socket) {
|
||||
socket.on("pbs-calculate-allocations", async (jobid, callback) => {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
|
||||
await createLogEvent(socket, "DEBUG", `PBS AR allocations calculated.`);
|
||||
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations)}`);
|
||||
callback(allocations);
|
||||
await setSessionData(socket.id, "pbs_allocations", 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, "selectedCustomer", selectedCustomerId);
|
||||
});
|
||||
}
|
||||
|
||||
function registerProductionBoardEvents(socket) {}
|
||||
|
||||
// PBS AP-specific socket events
|
||||
function registerPbsApEvents(socket) {
|
||||
socket.on("pbs-calculate-allocations-ap", async (billids, callback) => {
|
||||
const allocations = await PbsCalculateAllocationsAp(socket, billids);
|
||||
createLogEvent(socket, "DEBUG", `AP Allocations calculated.`);
|
||||
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
|
||||
socket.apAllocations = allocations;
|
||||
await createLogEvent(socket, "DEBUG", `PBS AP allocations calculated.`);
|
||||
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations)}`);
|
||||
callback(allocations);
|
||||
await setSessionData(socket.id, "pbs_ap_allocations", allocations);
|
||||
});
|
||||
|
||||
socket.on("pbs-export-ap", ({ billids, txEnvelope }) => {
|
||||
socket.txEnvelope = txEnvelope;
|
||||
PbsExportAp(socket, { billids, txEnvelope });
|
||||
socket.on("pbs-export-ap", async ({ billids, txEnvelope }) => {
|
||||
await setSessionData(socket.id, "pbs_txEnvelope", txEnvelope);
|
||||
PbsExportAp(socket, {
|
||||
billids,
|
||||
txEnvelope
|
||||
}).catch((err) => console.error(`Error in pbs-export-ap: ${err}`));
|
||||
});
|
||||
}
|
||||
|
||||
//END PBS AP
|
||||
const getRedisKeyForSocket = (socketId) => `socket:${socketId}:rooms`;
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
createLogEvent(socket, "DEBUG", `User disconnected.`);
|
||||
});
|
||||
});
|
||||
|
||||
function createLogEvent(socket, level, message) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
|
||||
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", level, socket.user.email, socket.recordid, {
|
||||
wsmessage: message
|
||||
});
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
// Room management and broadcasting events
|
||||
function registerRoomAndBroadcastEvents(socket) {
|
||||
// Rejoin rooms on reconnect
|
||||
pubClient.lRange(getRedisKeyForSocket(socket.id), 0, -1, (err, rooms) => {
|
||||
if (rooms && rooms.length > 0) {
|
||||
rooms.forEach((room) => {
|
||||
socket.join(room);
|
||||
createLogEvent(socket, "DEBUG", `Client rejoined bodyshop room: ${room}`);
|
||||
});
|
||||
}
|
||||
// if (level === "ERROR") {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
function createJsonEvent(socket, level, message, json) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
|
||||
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)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message
|
||||
});
|
||||
}
|
||||
// if (level === "ERROR") {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
socket.on("join-bodyshop-room", async (bodyshopUUID) => {
|
||||
socket.join(bodyshopUUID);
|
||||
|
||||
// Store room in Redis
|
||||
pubClient.rPush(getRedisKeyForSocket(socket.id), bodyshopUUID);
|
||||
|
||||
await createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`);
|
||||
});
|
||||
|
||||
socket.on("leave-bodyshop-room", async (bodyshopUUID) => {
|
||||
socket.leave(bodyshopUUID);
|
||||
|
||||
// Remove room from Redis
|
||||
pubClient.lRem(getRedisKeyForSocket(socket.id), 0, bodyshopUUID);
|
||||
|
||||
await createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${bodyshopUUID}`);
|
||||
});
|
||||
|
||||
socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => {
|
||||
io.to(bodyshopUUID).emit("bodyshop-message", message);
|
||||
await createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${bodyshopUUID}`);
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
// Optional: Cleanup Redis entry on disconnect if needed
|
||||
createLogEvent(socket, "DEBUG", `Client disconnected: ${socket.id}`);
|
||||
});
|
||||
}
|
||||
// DMS session clearing event
|
||||
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}`);
|
||||
});
|
||||
}
|
||||
|
||||
function createXmlEvent(socket, xml, message, isError = false) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy("TRACE")) {
|
||||
socket.emit("log-event", {
|
||||
// Logging helper functions
|
||||
async function createLogEvent(socket, level, message) {
|
||||
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
|
||||
|
||||
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) {
|
||||
const logMessage = { timestamp: new Date(), level, message };
|
||||
|
||||
// Log the message to the console and emit it via the socket
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async function createJsonEvent(socket, level, message, json) {
|
||||
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
|
||||
|
||||
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) {
|
||||
const logMessage = { timestamp: new Date(), level, message };
|
||||
|
||||
// Emit the log message via the socket
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async function createXmlEvent(socket, xml, message, isError = false) {
|
||||
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
|
||||
|
||||
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy("TRACE")) {
|
||||
const logMessage = {
|
||||
timestamp: new Date(),
|
||||
level: isError ? "ERROR" : "TRACE",
|
||||
message: `${message}: ${xml}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
logger.log(
|
||||
isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
|
||||
isError ? "ERROR" : "TRACE",
|
||||
socket.user.email,
|
||||
socket.recordid,
|
||||
{
|
||||
wsmessage: message,
|
||||
xml
|
||||
}
|
||||
);
|
||||
// Emit the log message via the socket
|
||||
socket.emit("log-event", logMessage);
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
level: isError ? "ERROR" : "TRACE",
|
||||
message,
|
||||
xml
|
||||
});
|
||||
// Log the XML event via the logger
|
||||
logger.log(
|
||||
isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
|
||||
isError ? "ERROR" : "TRACE",
|
||||
socket.user.email,
|
||||
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) {
|
||||
switch (level) {
|
||||
case "XML":
|
||||
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;
|
||||
}
|
||||
const levels = { XML: 5, TRACE: 5, DEBUG: 4, INFO: 3, WARNING: 2, ERROR: 1 };
|
||||
return levels[level] || 3;
|
||||
}
|
||||
|
||||
// Socket.IO Middleware and Connection
|
||||
io.use(socketAuthMiddleware);
|
||||
io.on("connection", registerSocketEvents);
|
||||
|
||||
// Export logging helpers
|
||||
exports.createLogEvent = createLogEvent;
|
||||
exports.createXmlEvent = createXmlEvent;
|
||||
exports.createJsonEvent = createJsonEvent;
|
||||
|
||||
Reference in New Issue
Block a user