feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint - Remove old attempt at Reynolds Integration in favor of new library.
This commit is contained in:
377
client/package-lock.json
generated
377
client/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "0.2.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-browser": "^2.27.0",
|
||||
"@amplitude/analytics-browser": "^2.29.0",
|
||||
"@ant-design/pro-layout": "^7.22.6",
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@emotion/is-prop-valid": "^1.4.0",
|
||||
@@ -20,21 +20,21 @@
|
||||
"@firebase/firestore": "^4.9.2",
|
||||
"@firebase/messaging": "^0.12.22",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.9.1",
|
||||
"@sentry/cli": "^2.56.1",
|
||||
"@reduxjs/toolkit": "^2.9.2",
|
||||
"@sentry/cli": "^2.57.0",
|
||||
"@sentry/react": "^9.43.0",
|
||||
"@sentry/vite-plugin": "^4.4.0",
|
||||
"@sentry/vite-plugin": "^4.6.0",
|
||||
"@splitsoftware/splitio-react": "^2.5.0",
|
||||
"@tanem/react-nprogress": "^5.0.56",
|
||||
"antd": "^5.27.5",
|
||||
"antd": "^5.27.6",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.4.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.12.2",
|
||||
"axios": "^1.13.1",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"dayjs": "^1.11.18",
|
||||
"dayjs-business-days2": "^1.3.0",
|
||||
"dayjs-business-days2": "^1.3.1",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"env-cmd": "^10.1.0",
|
||||
@@ -43,7 +43,7 @@
|
||||
"i18next": "^25.6.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.12.24",
|
||||
"libphonenumber-js": "^1.12.25",
|
||||
"lightningcss": "^1.30.2",
|
||||
"logrocket": "^9.0.2",
|
||||
"markerjs2": "^2.32.7",
|
||||
@@ -51,7 +51,7 @@
|
||||
"normalize-url": "^8.1.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"phone": "^3.1.67",
|
||||
"posthog-js": "^1.276.0",
|
||||
"posthog-js": "^1.281.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.3.1",
|
||||
"raf-schd": "^4.0.3",
|
||||
@@ -79,7 +79,7 @@
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-saga": "^1.4.2",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.93.2",
|
||||
@@ -93,31 +93,31 @@
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@dotenvx/dotenvx": "^1.51.0",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.38.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@sentry/webpack-plugin": "^4.4.0",
|
||||
"@sentry/webpack-plugin": "^4.6.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"browserslist": "^4.26.3",
|
||||
"browserslist": "^4.27.0",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.6.2",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^15.15.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"memfs": "^4.49.0",
|
||||
"memfs": "^4.50.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"playwright": "^1.56.1",
|
||||
"react-error-overlay": "^6.1.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^7.1.10",
|
||||
"vite": "^7.1.12",
|
||||
"vite-plugin-babel": "^1.3.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
@@ -141,16 +141,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@amplitude/analytics-browser": {
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.27.0.tgz",
|
||||
"integrity": "sha512-1LBCLmnr7aUpLtOp64lpr8GzN3vPKM0fwiM/7tWJ9XU9/GKA+k3CUSjI8OdERKrw2yVywujoAVQo4anGZXYIDA==",
|
||||
"version": "2.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.29.0.tgz",
|
||||
"integrity": "sha512-e+9tdTisSjj//BhMK4Ej8rhldfhcZXurxsfqns2DyXjf+bxLQwO32ZTIbfQMy+CAiRwVRc2nuZXyfFCpW2DgMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-core": "^2.28.0",
|
||||
"@amplitude/plugin-autocapture-browser": "^1.15.3",
|
||||
"@amplitude/plugin-network-capture-browser": "^1.6.9",
|
||||
"@amplitude/plugin-page-view-tracking-browser": "^2.5.3",
|
||||
"@amplitude/plugin-web-vitals-browser": "^0.1.0-beta.31",
|
||||
"@amplitude/analytics-core": "^2.30.0",
|
||||
"@amplitude/plugin-autocapture-browser": "^1.16.2",
|
||||
"@amplitude/plugin-network-capture-browser": "^1.6.11",
|
||||
"@amplitude/plugin-page-url-enrichment-browser": "^0.5.0",
|
||||
"@amplitude/plugin-page-view-tracking-browser": "^2.5.5",
|
||||
"@amplitude/plugin-web-vitals-browser": "^0.1.0-beta.33",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
@@ -161,9 +162,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@amplitude/analytics-core": {
|
||||
"version": "2.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.28.0.tgz",
|
||||
"integrity": "sha512-Wj/xUHhiHk2xH0/lp5IgHlzyKJOIb/WkpWP8W66wf3EaLJwX0AtPWMEb557BHbYBG/KXonp6ob9DyD0xbW38dg==",
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.30.0.tgz",
|
||||
"integrity": "sha512-oWz13sQmfCRa9prfYcURPVNHQQOf0/rDEK7PjBi0m0nKXs+QaHu/Tq2y4F6f4K66YDsT7zYYO4DpkS+QJM3/Sw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-connector": "^1.6.4",
|
||||
@@ -171,34 +172,44 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/plugin-autocapture-browser": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-autocapture-browser/-/plugin-autocapture-browser-1.15.3.tgz",
|
||||
"integrity": "sha512-WPYw81fFdTzUxEzM3/lTalsxLNGDKFklGnDaHoLFB4LxL5XfIyC+lBnOls+9zjt7dyV0qvq5YzunbFNT2ysR8g==",
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-autocapture-browser/-/plugin-autocapture-browser-1.16.2.tgz",
|
||||
"integrity": "sha512-0ariOP5g8xsPlKUrC91DviG9nKuaC4hYPHu5OHpUcU/LdWiKG1k+w5qawUq+KIsyTCAFRbTHn+XoJPjhm6Mbcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-core": "^2.28.0",
|
||||
"@amplitude/analytics-core": "^2.30.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/plugin-network-capture-browser": {
|
||||
"version": "1.6.9",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-network-capture-browser/-/plugin-network-capture-browser-1.6.9.tgz",
|
||||
"integrity": "sha512-dBp0FiXGwreFfEZvWBqe+VbbwSRsljRwdYdtSQvBeYriBWZp7g3rD02xmt8zjlWxMpQTAE0wLjAi7E01NclBXg==",
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-network-capture-browser/-/plugin-network-capture-browser-1.6.11.tgz",
|
||||
"integrity": "sha512-kGbSZ5omr9842kzBN/Wx31kq59RcyclEhpsX2lDxkuHn4el+nhylztx2mNYJyEmeNmdV68MfA1IFkA//ZJzebA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-core": "^2.28.0",
|
||||
"@amplitude/analytics-core": "^2.30.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/plugin-page-view-tracking-browser": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.5.3.tgz",
|
||||
"integrity": "sha512-zqdZ01mScGHwvxoCLiz+qClA7sbryozeX2BmRt4mxkk9qkukKuMs2xGijgUDqsrWX0UbkYczFZhPbhm+4Jti9g==",
|
||||
"node_modules/@amplitude/plugin-page-url-enrichment-browser": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-url-enrichment-browser/-/plugin-page-url-enrichment-browser-0.5.0.tgz",
|
||||
"integrity": "sha512-MJOCHQa3H5t2khPuGwgGX36tuA7ohDwzXyuPVZbY6fN3qRT9LAE686VuvQSXH9365yHMcWp2vPkdn8Ju3i9IUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-core": "^2.28.0",
|
||||
"@amplitude/analytics-core": "^2.30.0",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/plugin-page-view-tracking-browser": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.5.5.tgz",
|
||||
"integrity": "sha512-pSeYW3YjdFdJEA8WFQbWwaez4q1hwr4alg+Z7y1DU0hoQ91ADxCrFDQ2zoTSyBZfu/paWx1Gds1v4sYjsgh5Bw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-core": "^2.30.0",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
@@ -2185,15 +2196,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/preset-react": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz",
|
||||
"integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz",
|
||||
"integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-validator-option": "^7.27.1",
|
||||
"@babel/plugin-transform-react-display-name": "^7.27.1",
|
||||
"@babel/plugin-transform-react-display-name": "^7.28.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.27.1",
|
||||
"@babel/plugin-transform-react-jsx-development": "^7.27.1",
|
||||
"@babel/plugin-transform-react-pure-annotations": "^7.27.1"
|
||||
@@ -3419,9 +3430,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@posthog/core": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.3.0.tgz",
|
||||
"integrity": "sha512-hxLL8kZNHH098geedcxCz8y6xojkNYbmJEW+1vFXsmPcExyCXIUUJ/34X6xa9GcprKxd0Wsx3vfJQLQX4iVPhw==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.4.0.tgz",
|
||||
"integrity": "sha512-jmW8/I//YOHAfjzokqas+Qtc2T57Ux8d2uIJu7FLcMGxywckHsl6od59CD18jtUzKToQdjQhV6Y3429qj+KeNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
@@ -3664,17 +3675,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@redux-saga/core": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.3.0.tgz",
|
||||
"integrity": "sha512-L+i+qIGuyWn7CIg7k1MteHGfttKPmxwZR5E7OsGikCL2LzYA0RERlaUY00Y3P3ZV2EYgrsYlBrGs6cJP5OKKqA==",
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.4.2.tgz",
|
||||
"integrity": "sha512-nIMLGKo6jV6Wc1sqtVQs1iqbB3Kq20udB/u9XEaZQisT6YZ0NRB8+4L6WqD/E+YziYutd27NJbG8EWUPkb7c6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.3",
|
||||
"@redux-saga/deferred": "^1.2.1",
|
||||
"@redux-saga/delay-p": "^1.2.1",
|
||||
"@redux-saga/is": "^1.1.3",
|
||||
"@redux-saga/symbols": "^1.1.3",
|
||||
"@redux-saga/types": "^1.2.1",
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@redux-saga/deferred": "^1.3.1",
|
||||
"@redux-saga/delay-p": "^1.3.1",
|
||||
"@redux-saga/is": "^1.2.1",
|
||||
"@redux-saga/symbols": "^1.2.1",
|
||||
"@redux-saga/types": "^1.3.1",
|
||||
"typescript-tuple": "^2.2.1"
|
||||
},
|
||||
"funding": {
|
||||
@@ -3683,46 +3694,46 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@redux-saga/deferred": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.2.1.tgz",
|
||||
"integrity": "sha512-cmin3IuuzMdfQjA0lG4B+jX+9HdTgHZZ+6u3jRAOwGUxy77GSlTi4Qp2d6PM1PUoTmQUR5aijlA39scWWPF31g==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.3.1.tgz",
|
||||
"integrity": "sha512-0YZ4DUivWojXBqLB/TmuRRpDDz7tyq1I0AuDV7qi01XlLhM5m51W7+xYtIckH5U2cMlv9eAuicsfRAi1XHpXIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redux-saga/delay-p": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.2.1.tgz",
|
||||
"integrity": "sha512-MdiDxZdvb1m+Y0s4/hgdcAXntpUytr9g0hpcOO1XFVyyzkrDu3SKPgBFOtHn7lhu7n24ZKIAT1qtKyQjHqRd+w==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.3.1.tgz",
|
||||
"integrity": "sha512-597I7L5MXbD/1i3EmcaOOjL/5suxJD7p5tnbV1PiWnE28c2cYiIHqmSMK2s7us2/UrhOL2KTNBiD0qBg6KnImg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redux-saga/symbols": "^1.1.3"
|
||||
"@redux-saga/symbols": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@redux-saga/is": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.3.tgz",
|
||||
"integrity": "sha512-naXrkETG1jLRfVfhOx/ZdLj0EyAzHYbgJWkXbB3qFliPcHKiWbv/ULQryOAEKyjrhiclmr6AMdgsXFyx7/yE6Q==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.2.1.tgz",
|
||||
"integrity": "sha512-x3aWtX3GmQfEvn8dh0ovPbsXgK9JjpiR24wKztpGbZP8JZUWWvUgKrvnWZ/T/4iphOBftyVc9VrIwhAnsM+OFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redux-saga/symbols": "^1.1.3",
|
||||
"@redux-saga/types": "^1.2.1"
|
||||
"@redux-saga/symbols": "^1.2.1",
|
||||
"@redux-saga/types": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@redux-saga/symbols": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.3.tgz",
|
||||
"integrity": "sha512-hCx6ZvU4QAEUojETnX8EVg4ubNLBFl1Lps4j2tX7o45x/2qg37m3c6v+kSp8xjDJY+2tJw4QB3j8o8dsl1FDXg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.2.1.tgz",
|
||||
"integrity": "sha512-3dh+uDvpBXi7EUp/eO+N7eFM4xKaU4yuGBXc50KnZGzIrR/vlvkTFQsX13zsY8PB6sCFYAgROfPSRUj8331QSA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redux-saga/types": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz",
|
||||
"integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.3.1.tgz",
|
||||
"integrity": "sha512-YRCrJdhQLobGIQ8Cj1sta3nn6DrZDTSUnrIYhS2e5V590BmfVDleKoAquclAiKSBKWJwmuXTb+b4BL6rSHnahw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.1.tgz",
|
||||
"integrity": "sha512-sETJ3qO72y7L7WiR5K54UFLT3jRzAtqeBPVO15xC3bGA6kDqCH8m/v7BKCPH4czydXzz/1lPEGLvew7GjOO3Qw==",
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.2.tgz",
|
||||
"integrity": "sha512-ZAYu/NXkl/OhqTz7rfPaAhY0+e8Fr15jqNxte/2exKUxvHyQ/hcqmdekiN1f+Lcw3pE+34FCgX+26zcUE3duCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
@@ -4385,9 +4396,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/babel-plugin-component-annotate": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.4.0.tgz",
|
||||
"integrity": "sha512-Pzjpn9MZg6yR61ThJgOoD28dLNCj457O0/t8d276K+Bzf8iOZKbrNO4sltp1vUB1yqhV+ulvIZO8xu8ABohtsg==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.0.tgz",
|
||||
"integrity": "sha512-3soTX50JPQQ51FSbb4qvNBf4z/yP7jTdn43vMTp9E4IxvJ9HKJR7OEuKkCMszrZmWsVABXl02msqO7QisePdiQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
@@ -4410,14 +4421,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/bundler-plugin-core": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.4.0.tgz",
|
||||
"integrity": "sha512-WTGhgwxzyolzOg0sudULK0rRgLndtsEiBt4QwltKW/WYArMtFyf286aZx19uQ+rD+bSx3Il81SD23nqDOTtnzg==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.0.tgz",
|
||||
"integrity": "sha512-Fub2XQqrS258jjS8qAxLLU1k1h5UCNJ76i8m4qZJJdogWWaF8t00KnnTyp9TEDJzrVD64tRXS8+HHENxmeUo3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.18.5",
|
||||
"@sentry/babel-plugin-component-annotate": "4.4.0",
|
||||
"@sentry/cli": "^2.51.0",
|
||||
"@sentry/babel-plugin-component-annotate": "4.6.0",
|
||||
"@sentry/cli": "^2.57.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"find-up": "^5.0.0",
|
||||
"glob": "^9.3.2",
|
||||
@@ -4441,9 +4452,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.56.1.tgz",
|
||||
"integrity": "sha512-VDAIg+gmjNtJS5VUZQMDSK9RaKC9hYQi3PoXpNa+owNfQNk60bCi8z8jkbWRcKbNGn3V51WqvrQAqLoNAdPc9w==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.57.0.tgz",
|
||||
"integrity": "sha512-oC4HPrVIX06GvUTgK0i+WbNgIA9Zl5YEcwf9N4eWFJJmjonr2j4SML9Hn2yNENbUWDgwepy4MLod3P8rM4bk/w==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -4460,20 +4471,20 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@sentry/cli-darwin": "2.56.1",
|
||||
"@sentry/cli-linux-arm": "2.56.1",
|
||||
"@sentry/cli-linux-arm64": "2.56.1",
|
||||
"@sentry/cli-linux-i686": "2.56.1",
|
||||
"@sentry/cli-linux-x64": "2.56.1",
|
||||
"@sentry/cli-win32-arm64": "2.56.1",
|
||||
"@sentry/cli-win32-i686": "2.56.1",
|
||||
"@sentry/cli-win32-x64": "2.56.1"
|
||||
"@sentry/cli-darwin": "2.57.0",
|
||||
"@sentry/cli-linux-arm": "2.57.0",
|
||||
"@sentry/cli-linux-arm64": "2.57.0",
|
||||
"@sentry/cli-linux-i686": "2.57.0",
|
||||
"@sentry/cli-linux-x64": "2.57.0",
|
||||
"@sentry/cli-win32-arm64": "2.57.0",
|
||||
"@sentry/cli-win32-i686": "2.57.0",
|
||||
"@sentry/cli-win32-x64": "2.57.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-darwin": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.56.1.tgz",
|
||||
"integrity": "sha512-zfhT8MrvB5x/xRdIVGwg+sG0Cx3i0G6RH2zCrdQ/moWn8TfkwsM0O1k/AxpwbpcRfAHCkVb04CU/yKciKwg2KA==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.57.0.tgz",
|
||||
"integrity": "sha512-v1wYQU3BcCO+Z3OVxxO+EnaW4oQhuOza6CXeYZ0z5ftza9r0QQBLz3bcZKTVta86xraNm0z8GDlREwinyddOxQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4484,9 +4495,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-arm": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.56.1.tgz",
|
||||
"integrity": "sha512-fNB/Ng11HrkGOSEIDg+fc3zfTCV7q6kJddp6ndK3QlYFsCffRSnclaX1SMp+mqxdWkHqe1kkp85OY8G/x5uAWw==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.57.0.tgz",
|
||||
"integrity": "sha512-uNHB8xyygqfMd1/6tFzl9NUkuVefg7jdZtM/vVCQVaF/rJLWZ++Wms+LLhYyKXKN8yd7J9wy7kTEl4Qu4jWbGQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -4502,9 +4513,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-arm64": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.56.1.tgz",
|
||||
"integrity": "sha512-AypXIwZvOMJb9RgjI/98hTAd06FcOjqjIm6G9IR0OI4pJCOcaAXz9NKXdJqxpZd7phSMJnD+Bx/8iYOUPeY73A==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.57.0.tgz",
|
||||
"integrity": "sha512-Kh1jTsMV5Fy/RvB381N/woXe1qclRMqsG6kM3Gq6m6afEF/+k3PyQdNW3HXAola6d63EptokLtxPG2xjWQ+w9Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4520,9 +4531,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-i686": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.56.1.tgz",
|
||||
"integrity": "sha512-vnH+WJEsUq7Lf7xc9udzE/M4hoDXXsniFFYr/7BvdnXtCQlNNaWFMXHbEDYAql3baIlHkWoG8cEHWuB/YKyniw==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.57.0.tgz",
|
||||
"integrity": "sha512-EYXghoK/tKd0zqz+KD/ewXXE3u1HLCwG89krweveytBy/qw7M5z58eFvw+iGb1Vnbl1f/fRD0G4E0AbEsPfmpg==",
|
||||
"cpu": [
|
||||
"x86",
|
||||
"ia32"
|
||||
@@ -4539,9 +4550,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-x64": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.56.1.tgz",
|
||||
"integrity": "sha512-3/BlKe5Vdnia36MeovghHJD8lbcum5TFIxLp+PSfH2sVb09+5Jo0L95oRTI2JkD8Fs+QNssvTqTxJj5eIo/n+A==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.57.0.tgz",
|
||||
"integrity": "sha512-CyZrP/ssHmAPLSzfd4ydy7icDnwmDD6o3QjhkWwVFmCd+9slSBMQxpIqpamZmrWE6X4R+xBRbSUjmdoJoZ5yMw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4557,9 +4568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-win32-arm64": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.56.1.tgz",
|
||||
"integrity": "sha512-Gg8RV7CV7Tz4fiR1EN1Af5AVhJsnEXiZvfvfQXI4lp51MKAhcxZIMtEfg9HaWsn3Dm/wgwYBinyeywfWbTXYDg==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.57.0.tgz",
|
||||
"integrity": "sha512-wji/GGE4Lh5I/dNCsuVbg6fRvttvZRG6db1yPW1BSvQRh8DdnVy1CVp+HMqSq0SRy/S4z60j2u+m4yXMoCL+5g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4573,9 +4584,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-win32-i686": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.56.1.tgz",
|
||||
"integrity": "sha512-6u6a060yC3i76Ze1apqgWr5luQSyhuD5ND84eWfh/UbddsEa42UHjoVHOiBwmpZqf/hvNZAtzLnE4NCvU4zOMg==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.57.0.tgz",
|
||||
"integrity": "sha512-hWvzyD7bTPh3b55qvJ1Okg3Wbl0Km8xcL6KvS7gfBl6uss+I6RldmQTP0gJKdHSdf/QlJN1FK0b7bLnCB3wHsg==",
|
||||
"cpu": [
|
||||
"x86",
|
||||
"ia32"
|
||||
@@ -4590,9 +4601,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-win32-x64": {
|
||||
"version": "2.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.56.1.tgz",
|
||||
"integrity": "sha512-11cdflajBrDWlRZqI9MOu7ok2vnPzFjKmbU3YvBYWQapNE+HHAsWdsRL/u/P1RmU62vj7Y42iSUcj6x1SNrdPw==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.57.0.tgz",
|
||||
"integrity": "sha512-QWYV/Y0sbpDSTyA4XQBOTaid4a6H2Iwa1Z8UI+qNxFlk0ADSEgIqo2NrRHDU8iRnghTkecQNX1NTt/7mXN3f/A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4653,12 +4664,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/vite-plugin": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-4.4.0.tgz",
|
||||
"integrity": "sha512-sOq1xJj5URIa/c4fSJomjOjp7l0ljk4WWRjol6ERwJ5wntOKDrw5Y7T1ZbyiDGD8/ndzQnn4Od03Z+jSvpqwog==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-4.6.0.tgz",
|
||||
"integrity": "sha512-fMR2d+EHwbzBa0S1fp45SNUTProxmyFBp+DeBWWQOSP9IU6AH6ea2rqrpMAnp/skkcdW4z4LSRrOEpMZ5rWXLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/bundler-plugin-core": "4.4.0",
|
||||
"@sentry/bundler-plugin-core": "4.6.0",
|
||||
"unplugin": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4666,13 +4677,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/webpack-plugin": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-4.4.0.tgz",
|
||||
"integrity": "sha512-s9Js4v++pbZaKu6ddG1LSXbSKfM71UxkS6PzmOWj4HyTHdiZr+469tbdanTJwz8XO87neFAP1mteuo1Cur3iHg==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-4.6.0.tgz",
|
||||
"integrity": "sha512-i9Yy2kXCbFKlRST09fV1HsI0naJAfeXxoiUPyh5iCgSo2w7ZwEUlk0tJhupnHZzfSa3OSg01+vVNeeyLYM4tdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/bundler-plugin-core": "4.4.0",
|
||||
"@sentry/bundler-plugin-core": "4.6.0",
|
||||
"unplugin": "1.0.1",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
@@ -5481,9 +5492,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/antd": {
|
||||
"version": "5.27.5",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.27.5.tgz",
|
||||
"integrity": "sha512-Ehd9mqtHvJ1clon1yJ/1BTV6eX/3SH2YXZZPTHUk8XdzXFwUioI+Lht47s+MaHIUBY77RnZrmtKwwR+VVu0l7A==",
|
||||
"version": "5.27.6",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.27.6.tgz",
|
||||
"integrity": "sha512-70HrjVbzDXvtiUQ5MP1XdNudr/wGAk9Ivaemk6f36yrAeJurJSmZ8KngOIilolLRHdGuNc6/Vk+4T1OZpSjpag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.2.1",
|
||||
@@ -5896,9 +5907,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz",
|
||||
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
@@ -6027,9 +6038,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz",
|
||||
"integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==",
|
||||
"version": "2.8.21",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz",
|
||||
"integrity": "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
@@ -6272,9 +6283,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.26.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
|
||||
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
|
||||
"version": "4.27.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
|
||||
"integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6291,11 +6302,11 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.9",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
"electron-to-chromium": "^1.5.227",
|
||||
"node-releases": "^2.0.21",
|
||||
"update-browserslist-db": "^1.1.3"
|
||||
"baseline-browser-mapping": "^2.8.19",
|
||||
"caniuse-lite": "^1.0.30001751",
|
||||
"electron-to-chromium": "^1.5.238",
|
||||
"node-releases": "^2.0.26",
|
||||
"update-browserslist-db": "^1.1.4"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -6472,9 +6483,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001748",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
|
||||
"integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
|
||||
"version": "1.0.30001751",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz",
|
||||
"integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -7246,12 +7257,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dayjs-business-days2": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dayjs-business-days2/-/dayjs-business-days2-1.3.0.tgz",
|
||||
"integrity": "sha512-OgDBnsNmlk9+vmRQaP4yFisXs29WDk0ItUUctIagmO6OIoxhf4vArTov5i+G4vjT9Sz8NXOLMLrOVP0X0lG/Hw==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dayjs-business-days2/-/dayjs-business-days2-1.3.1.tgz",
|
||||
"integrity": "sha512-zn5Athg9GGjuW7MJGz9qZKgpf1HzkK/jvHnUaR1P1TvDBmQ5Dc3auRbQJxkOcgmuK0XM+QG/Nxxe10IMHjw+4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.13"
|
||||
"dayjs": "^1.11.18"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
@@ -7605,9 +7616,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.231",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.231.tgz",
|
||||
"integrity": "sha512-cyl6vqZGkEBnz/PmvFHn/u9G/hbo+FF2CNAOXriG87QOeLsUdifCZ9UbHNscE9wGdrC8XstNMli0CbQnZQ+fkA==",
|
||||
"version": "1.5.243",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.243.tgz",
|
||||
"integrity": "sha512-ZCphxFW3Q1TVhcgS9blfut1PX8lusVi2SvXQgmEEnK4TCmE1JhH2JkjJN+DNt0pJJwfBri5AROBnz2b/C+YU9g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
@@ -10350,9 +10361,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/libphonenumber-js": {
|
||||
"version": "1.12.24",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.24.tgz",
|
||||
"integrity": "sha512-l5IlyL9AONj4voSd7q9xkuQOL4u8Ty44puTic7J88CmdXkxfGsRfoVLXHCxppwehgpb/Chdb80FFehHqjN3ItQ==",
|
||||
"version": "1.12.25",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.25.tgz",
|
||||
"integrity": "sha512-u90tUu/SEF8b+RaDKCoW7ZNFDakyBtFlX1ex3J+VH+ElWes/UaitJLt/w4jGu8uAE41lltV/s+kMVtywcMEg7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
@@ -10768,9 +10779,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/memfs": {
|
||||
"version": "4.49.0",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.49.0.tgz",
|
||||
"integrity": "sha512-L9uC9vGuc4xFybbdOpRLoOAOq1YEBBsocCs5NVW32DfU+CZWWIn3OVF+lB8Gp4ttBVSMazwrTrjv8ussX/e3VQ==",
|
||||
"version": "4.50.0",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.50.0.tgz",
|
||||
"integrity": "sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -11529,9 +11540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.23",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
|
||||
"integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
|
||||
"version": "2.0.26",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
|
||||
"integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-stdlib-browser": {
|
||||
@@ -12333,28 +12344,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/posthog-js": {
|
||||
"version": "1.276.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.276.0.tgz",
|
||||
"integrity": "sha512-FYZE1037LrAoKKeUU0pUL7u8WwNK2BVeg5TFApwquVPUdj9h7u5Z077A313hPN19Ar+7Y+VHxqYqdHc4VNsVgw==",
|
||||
"version": "1.281.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.281.0.tgz",
|
||||
"integrity": "sha512-t3sAlgVozpU1W1ppiF5zLG6eBRPUs0hmtxN8R1V7P0qZFmnECshAAk2cBxCsxEanadT3iUpS8Z7crBytATqWQQ==",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@posthog/core": "1.3.0",
|
||||
"@posthog/core": "1.4.0",
|
||||
"core-js": "^3.38.1",
|
||||
"fflate": "^0.4.8",
|
||||
"preact": "^10.19.3",
|
||||
"web-vitals": "^4.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rrweb/types": "2.0.0-alpha.17",
|
||||
"rrweb-snapshot": "2.0.0-alpha.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@rrweb/types": {
|
||||
"optional": true
|
||||
},
|
||||
"rrweb-snapshot": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/posthog-js/node_modules/core-js": {
|
||||
@@ -13839,12 +13838,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/redux-saga": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.3.0.tgz",
|
||||
"integrity": "sha512-J9RvCeAZXSTAibFY0kGw6Iy4EdyDNW7k6Q+liwX+bsck7QVsU78zz8vpBRweEfANxnnlG/xGGeOvf6r8UXzNJQ==",
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.4.2.tgz",
|
||||
"integrity": "sha512-QLIn/q+7MX/B+MkGJ/K6R3//60eJ4QNy65eqPsJrfGezbxdh1Jx+37VRKE2K4PsJnNET5JufJtgWdT30WBa+6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redux-saga/core": "^1.3.0"
|
||||
"@redux-saga/core": "^1.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/redux-state-sync": {
|
||||
@@ -16172,9 +16171,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
|
||||
"integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -16356,9 +16355,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
|
||||
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
|
||||
"version": "7.1.12",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
|
||||
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-browser": "^2.27.0",
|
||||
"@amplitude/analytics-browser": "^2.29.0",
|
||||
"@ant-design/pro-layout": "^7.22.6",
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@emotion/is-prop-valid": "^1.4.0",
|
||||
@@ -19,21 +19,21 @@
|
||||
"@firebase/firestore": "^4.9.2",
|
||||
"@firebase/messaging": "^0.12.22",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.9.1",
|
||||
"@sentry/cli": "^2.56.1",
|
||||
"@reduxjs/toolkit": "^2.9.2",
|
||||
"@sentry/cli": "^2.57.0",
|
||||
"@sentry/react": "^9.43.0",
|
||||
"@sentry/vite-plugin": "^4.4.0",
|
||||
"@sentry/vite-plugin": "^4.6.0",
|
||||
"@splitsoftware/splitio-react": "^2.5.0",
|
||||
"@tanem/react-nprogress": "^5.0.56",
|
||||
"antd": "^5.27.5",
|
||||
"antd": "^5.27.6",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.4.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.12.2",
|
||||
"axios": "^1.13.1",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"dayjs": "^1.11.18",
|
||||
"dayjs-business-days2": "^1.3.0",
|
||||
"dayjs-business-days2": "^1.3.1",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"env-cmd": "^10.1.0",
|
||||
@@ -42,7 +42,7 @@
|
||||
"i18next": "^25.6.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.12.24",
|
||||
"libphonenumber-js": "^1.12.25",
|
||||
"lightningcss": "^1.30.2",
|
||||
"logrocket": "^9.0.2",
|
||||
"markerjs2": "^2.32.7",
|
||||
@@ -50,7 +50,7 @@
|
||||
"normalize-url": "^8.1.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"phone": "^3.1.67",
|
||||
"posthog-js": "^1.276.0",
|
||||
"posthog-js": "^1.281.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.3.1",
|
||||
"raf-schd": "^4.0.3",
|
||||
@@ -78,7 +78,7 @@
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-saga": "^1.4.2",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.93.2",
|
||||
@@ -135,31 +135,31 @@
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@dotenvx/dotenvx": "^1.51.0",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.38.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@sentry/webpack-plugin": "^4.4.0",
|
||||
"@sentry/webpack-plugin": "^4.6.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"browserslist": "^4.26.3",
|
||||
"browserslist": "^4.27.0",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.6.2",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^15.15.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"memfs": "^4.49.0",
|
||||
"memfs": "^4.50.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"playwright": "^1.56.1",
|
||||
"react-error-overlay": "^6.1.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^7.1.10",
|
||||
"vite": "^7.1.12",
|
||||
"vite-plugin-babel": "^1.3.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
|
||||
1349
package-lock.json
generated
1349
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -18,30 +18,30 @@
|
||||
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.913.0",
|
||||
"@aws-sdk/client-elasticache": "^3.913.0",
|
||||
"@aws-sdk/client-s3": "^3.913.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.913.0",
|
||||
"@aws-sdk/client-ses": "^3.913.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.913.0",
|
||||
"@aws-sdk/lib-storage": "^3.913.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.913.0",
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.919.0",
|
||||
"@aws-sdk/client-elasticache": "^3.919.0",
|
||||
"@aws-sdk/client-s3": "^3.919.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.919.0",
|
||||
"@aws-sdk/client-ses": "^3.919.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.919.0",
|
||||
"@aws-sdk/lib-storage": "^3.919.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.919.0",
|
||||
"@opensearch-project/opensearch": "^2.13.0",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
"archiver": "^7.0.1",
|
||||
"aws4": "^1.13.2",
|
||||
"axios": "^1.12.2",
|
||||
"axios": "^1.13.1",
|
||||
"axios-curlirize": "^2.0.0",
|
||||
"better-queue": "^3.8.12",
|
||||
"bullmq": "^5.61.0",
|
||||
"bullmq": "^5.62.0",
|
||||
"chart.js": "^4.5.1",
|
||||
"cloudinary": "^2.7.0",
|
||||
"cloudinary": "^2.8.0",
|
||||
"compression": "^1.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"crisp-status-reporter": "^1.2.2",
|
||||
"dd-trace": "^5.72.0",
|
||||
"dd-trace": "^5.74.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.1",
|
||||
@@ -50,8 +50,8 @@
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-request": "^6.1.0",
|
||||
"intuit-oauth": "^4.2.1",
|
||||
"ioredis": "^5.8.1",
|
||||
"json-2-csv": "^5.5.9",
|
||||
"ioredis": "^5.8.2",
|
||||
"json-2-csv": "^5.5.10",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"juice": "^11.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -66,11 +66,11 @@
|
||||
"recursive-diff": "^1.0.9",
|
||||
"rimraf": "^6.0.1",
|
||||
"skia-canvas": "^3.0.8",
|
||||
"soap": "^1.5.0",
|
||||
"soap": "^1.6.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-adapter": "^2.5.5",
|
||||
"ssh2-sftp-client": "^11.0.0",
|
||||
"twilio": "^5.10.3",
|
||||
"twilio": "^5.10.4",
|
||||
"uuid": "^11.1.0",
|
||||
"winston": "^3.18.3",
|
||||
"winston-cloudwatch": "^6.3.0",
|
||||
|
||||
@@ -123,7 +123,7 @@ const applyRoutes = ({ app }) => {
|
||||
app.use("/payroll", require("./server/routes/payrollRoutes"));
|
||||
app.use("/sso", require("./server/routes/ssoRoutes"));
|
||||
app.use("/integrations", require("./server/routes/intergrationRoutes"));
|
||||
app.use("/rr", require("./server/rr/rrRoutes"));
|
||||
app.use("/rr", require("./server/rr"));
|
||||
|
||||
// Default route for forbidden access
|
||||
app.get("/", (req, res) => {
|
||||
|
||||
1
server/rr/index.js
Normal file
1
server/rr/index.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("./rrRoutes");
|
||||
1
server/rr/lib/index.cjs
Normal file
1
server/rr/lib/index.cjs
Normal file
File diff suppressed because one or more lines are too long
1
server/rr/lib/index.mjs
Normal file
1
server/rr/lib/index.mjs
Normal file
File diff suppressed because one or more lines are too long
3
server/rr/lib/types.cjs
Normal file
3
server/rr/lib/types.cjs
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
// CJS access point for JSDoc typedefs
|
||||
module.exports = require('./types.js');
|
||||
144
server/rr/lib/types.js
Normal file
144
server/rr/lib/types.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @typedef {Object} RRClientConfig
|
||||
* @property {string} baseUrl Base URL of the Rome endpoint
|
||||
* @property {string} username WS-Security username
|
||||
* @property {string} password WS-Security password
|
||||
* @property {'Text'|'Digest'} [wssePasswordType] Password type (defaults to Text)
|
||||
* @property {number} [timeoutMs] Request timeout in milliseconds
|
||||
* @property {{max?: number}} [retries] Retry configuration
|
||||
* @property {{ info:Function, warn:Function, error:Function, debug:Function }} [logger] Custom logger implementation
|
||||
*/
|
||||
|
||||
/** Dealer/store routing (required each call). */
|
||||
export const _routingDoc = null;
|
||||
/**
|
||||
* @typedef {Object} Routing
|
||||
* @property {string} dealerNumber
|
||||
* @property {string} [storeNumber]
|
||||
* @property {string} [areaNumber]
|
||||
*/
|
||||
|
||||
/** Optional envelope overrides; BODId/CreationDateTime auto-generated if omitted. */
|
||||
export const _envelopeDoc = null;
|
||||
/**
|
||||
* @typedef {Object} EnvelopeOptions
|
||||
* @property {string} [bodId]
|
||||
* @property {string|Date} [creationDateTime]
|
||||
* @property {{ component?: string, task?: string, referenceId?: string }} [sender]
|
||||
*/
|
||||
|
||||
/** Per-call options */
|
||||
export const _callOptionsDoc = null;
|
||||
/**
|
||||
* @typedef {Object} CallOptions
|
||||
* @property {Routing} routing
|
||||
* @property {EnvelopeOptions} [envelope]
|
||||
*/
|
||||
|
||||
/** @template T @typedef {Object} RRResult { @property {boolean} success @property {T} [data] @property {any} parsed @property {{request:string,response:string}} xml @property {{transaction?:any,roRecord?:any}} [statusBlocks] @property {any} [applicationArea] } */
|
||||
|
||||
/** @typedef {Object} CombinedSearchName { @property {string} [fname] @property {string} [lname] @property {string} [mname] @property {string} [name] } */
|
||||
/** @typedef {Object} CombinedSearchQuery
|
||||
* @property {'phone'|'license'|'vin'|'name'|'nameRecId'|'stkNo'} kind Search kind (only one criterion allowed)
|
||||
* @property {string|number|{phone:string}} [phone] Phone number criterion
|
||||
* @property {string|number|{license:string}} [license] License plate criterion
|
||||
* @property {string|number|{vin:string}} [vin] VIN criterion
|
||||
* @property {CombinedSearchName} [name] Customer name criterion
|
||||
* @property {string|number|{custId:string}|{nameRecId:string}} [nameRecId] Name record ID criterion
|
||||
* @property {string|number|{stkNo:string}} [stkNo] Stock number criterion
|
||||
* @property {number} [maxResults] Max results (capped at 50)
|
||||
* @property {string} [make] Vehicle make filter
|
||||
* @property {string|number} [model] Vehicle model filter
|
||||
* @property {string|number} [year] Vehicle year filter
|
||||
} */
|
||||
/** @typedef {Object} CombinedSearchVehicleDetail { @property {string} [LicNo] } */
|
||||
/** @typedef {Object} CombinedSearchVehicle { @property {string} [Vin] @property {string} [VehicleMake] @property {string|number} [VehicleYr] @property {string} [MdlNo] @property {string} [ModelDesc] @property {string} [Carline] @property {string} [ExtClrDesc] @property {string} [IntClrDesc] @property {string} [MakeName] @property {CombinedSearchVehicleDetail} [VehicleDetail] } */
|
||||
/** @typedef {Object} CombinedSearchVehicleWarranty { @property {string} [ContractNumber] @property {string} [ExpirationDate] @property {string|number} [ExpirationMileage] } */
|
||||
/** @typedef {Object} CombinedSearchAdvisorContactInfo { @property {string|number} [NameRecId] } */
|
||||
/** @typedef {Object} CombinedSearchAdvisor { @property {CombinedSearchAdvisorContactInfo} [ContactInfo] } */
|
||||
/** @typedef {Object} CombinedSearchVehicleServInfo { @property {string|number} [CustomerNo] @property {string|number} [SalesmanNo] @property {string|number} [InServiceDate] @property {string|number} [Mileage] @property {string} [TeamCode] @property {CombinedSearchVehicleWarranty} [VehExtWarranty] @property {CombinedSearchAdvisor} [Advisor] @property {string[]} [VehServComments] } */
|
||||
/** @typedef {Object} CombinedSearchServVehicle { @property {CombinedSearchVehicle} [Vehicle] @property {CombinedSearchVehicleServInfo} [VehicleServInfo] } */
|
||||
/** @typedef {Object} CombinedSearchNameId { @property {string|number} [NameRecId] @property {'I'|'B'} [IBFlag] @property {Object} [IndName] @property {Object} [BusName] } */
|
||||
/** @typedef {Object} CombinedSearchNameContactId { @property {CombinedSearchNameId} [NameId] @property {Object[]} [Address] @property {Object[]} [ContactOptions] @property {Object[]} [Phone] @property {Object[]} [Email] } */
|
||||
/** @typedef {Object} CombinedSearchMessage { @property {string|number} [MessageNo] @property {string} [Text] } */
|
||||
/** @typedef {Object} CombinedSearchBlock { @property {CombinedSearchNameContactId} [NameContactId] @property {CombinedSearchServVehicle[]} [ServVehicle] @property {CombinedSearchMessage[]} [Message] } */
|
||||
|
||||
/** @typedef {Object} CustomerAddress { @property {'P'|'B'|'M'|'S'|'D'} [type] @property {string} line1 @property {string} [line2] @property {string} [city] @property {string} [state] @property {string} [postalCode] @property {string} [county] @property {string} [country] } */
|
||||
/** @typedef {Object} CustomerPhone { @property {'H'|'W'|'M'|'C'|'F'} [type] @property {string|number} number @property {string|number} [extension] } */
|
||||
/** @typedef {Object} CustomerEmail { @property {string} address } */
|
||||
/** @typedef {Object} CustomerBirthDate { @property {'P'|'S'} [type] @property {string} date } */
|
||||
/** @typedef {Object} CustomerSSN { @property {'P'|'S'} [type] @property {string} ssn } */
|
||||
/** @typedef {Object} CustomerDriver { @property {'P'|'S'} [type] @property {string} licenseNumber @property {string} [licenseState] @property {string} [licenseExpDate] } */
|
||||
/** @typedef {Object} CustomerChild { @property {string} name } */
|
||||
/** @typedef {Object} CustomerPersonal { @property {'M'|'F'|'U'} [gender] @property {string} [otherName] @property {string} [anniversaryDate] @property {string} [employerName] @property {string} [employerPhone] @property {string} [occupation] @property {string} [optOut] @property {string} [optOutUse] @property {CustomerBirthDate[]} [birthDates] @property {CustomerSSN[]} [ssns] @property {CustomerDriver} [driver] @property {CustomerChild[]} [children] } */
|
||||
/** @typedef {Object} CustomerDmsFollowup { @property {string} type @property {string} value } */
|
||||
/** @typedef {Object} CustomerDmsInfo { @property {string} [taxExemptNum] @property {string} [salesTerritory] @property {string} [deliveryRoute] @property {string} [salesmanNum] @property {string} [lastContactMethod] @property {CustomerDmsFollowup[]} [followups] } */
|
||||
/** @typedef {Object} InsertCustomerPayload
|
||||
* @property {'I'|'B'} [ibFlag] Individual/Business flag (auto-inferred if omitted)
|
||||
* @property {'R'|'W'|'I'|'Retail'|'Wholesale'|'Internal'} [customerType] Customer type
|
||||
* @property {string} [createdBy] Username or identifier creating the record
|
||||
* @property {string} [customerName] Business name (alias for lastName when business)
|
||||
* @property {string} [lastName] Last name or business name
|
||||
* @property {string} [firstName] First name (required for individuals)
|
||||
* @property {string} [midName] Middle name
|
||||
* @property {string} [salut] Salutation
|
||||
* @property {string} [suffix] Suffix
|
||||
* @property {CustomerAddress[]} [addresses] Addresses list (line1 required per address)
|
||||
* @property {CustomerPhone[]} [phones] Phone numbers list (number required per phone)
|
||||
* @property {CustomerEmail[]} [emails] Email list (first entry used)
|
||||
* @property {CustomerPersonal} [personal] Personal details
|
||||
* @property {CustomerDmsInfo} [dms] DMS-specific supplemental info
|
||||
*/
|
||||
/** @typedef {InsertCustomerPayload & { nameRecId: string|number }} UpdateCustomerPayload */
|
||||
/** @typedef {Object} CustomerResponseData { @property {string|undefined} dmsRecKey @property {string|undefined} status @property {string|undefined} statusCode } */
|
||||
|
||||
/** @typedef {Object} ServiceVehicleDetail { @property {string} [licNo] } */
|
||||
/** @typedef {Object} ServiceVehicleWarranty { @property {string} [contractNumber] @property {string} [expirationDate] @property {string|number} [expirationMileage] } */
|
||||
/** @typedef {Object} ServiceVehicleAdvisorContactInfo { @property {string|number} nameRecId } */
|
||||
/** @typedef {Object} ServiceVehicleServInfo { @property {string|number} customerNo @property {string|number} [salesmanNo] @property {string|number} [inServiceDate] @property {string|number} [mileage] @property {string} [teamCode] @property {ServiceVehicleWarranty} [vehExtWarranty] @property {{contactInfo?: ServiceVehicleAdvisorContactInfo}} [advisor] } */
|
||||
/** @typedef {Object} InsertServiceVehiclePayload { @property {string} vin @property {string} [modelDesc] @property {string} [carline] @property {string} [extClrDesc] @property {string} [intClrDesc] @property {string} [trimDesc] @property {string} [bodyStyle] @property {string} [engineDesc] @property {string} [transDesc] @property {string|number} [year] @property {string|number} [odometer] @property {string} [odometerUnits] @property {ServiceVehicleDetail} [vehicleDetail] @property {ServiceVehicleServInfo} vehicleServInfo } */
|
||||
/** @typedef {Object} ServiceVehicleResponseData { @property {string|undefined} status @property {string|undefined} statusCode } */
|
||||
|
||||
/** @typedef {Object} RepairOrderEstimate { @property {string|number} [parts] @property {string|number} [labor] @property {string|number} [total] @property {string} [estimateType] } */
|
||||
/** @typedef {Object} RepairOrderTax { @property {'All'|'Cust'|'Intr'|'Warr'} [payType] @property {string} [taxCode] @property {string|number} [txblGrossAmt] @property {string|number} [grossTaxAmt] } */
|
||||
/** @typedef {Object} RepairOrderLaborBill { @property {'All'|'Cust'|'Intr'|'Warr'} [payType] @property {string|number} [jobTotalHrs] @property {string|number} [billTime] @property {string|number} [billRate] } */
|
||||
/** @typedef {Object} RepairOrderLaborCCC { @property {string} [cause] @property {string} [complaint] @property {string} [correction] } */
|
||||
/** @typedef {Object} RepairOrderLaborAmount { @property {'All'|'Cust'|'Intr'|'Warr'} [payType] @property {string} [amtType] @property {string|number} [custPrice] @property {string|number} [totalAmt] } */
|
||||
/** @typedef {Object} RepairOrderLaborOp { @property {string} [opCode] @property {string|number} [jobNo] @property {'T'|'N'} [custTxblNtxblFlag] @property {'T'|'N'} [warrTxblNtxblFlag] @property {'T'|'N'} [intrTxblNtxblFlag] @property {string} [custPayTypeFlag] @property {string} [warrPayTypeFlag] @property {string} [intrPayTypeFlag] @property {string} [vlrCode] @property {RepairOrderLaborBill} [bill] @property {RepairOrderLaborCCC} [ccc] @property {RepairOrderLaborAmount} [amount] } */
|
||||
/** @typedef {Object} RepairOrderLabor { @property {RepairOrderLaborOp[]} [ops] } */
|
||||
/** @typedef {Object} RepairOrderPartLine { @property {string} [partNo] @property {string} [partNoDesc] @property {string|number} [partQty] @property {string|number} [sale] @property {string|number} [cost] @property {string} [addDeleteFlag] } */
|
||||
/** @typedef {Object} RepairOrderPartJob { @property {string} [opCode] @property {string|number} [jobNo] @property {RepairOrderPartLine[]} [lines] } */
|
||||
/** @typedef {Object} RepairOrderPart { @property {RepairOrderPartJob[]} [jobs] } */
|
||||
/** @typedef {Object} RepairOrderGGLineAmount { @property {'All'|'Cust'|'Intr'|'Warr'} [payType] @property {string} [amtType] @property {string|number} [custPrice] @property {string|number} [dlrCost] } */
|
||||
/** @typedef {Object} RepairOrderGGLine { @property {string} [breakOut] @property {'G'|'P'|'S'|'F'} [itemType] @property {string} [itemDesc] @property {string|number} [custQty] @property {string|number} [warrQty] @property {string|number} [intrQty] @property {'T'|'N'} [custTxblNtxblFlag] @property {'T'|'N'} [warrTxblNtxblFlag] @property {'T'|'N'} [intrTxblNtxblFlag] @property {string} [custPayTypeFlag] @property {string} [warrPayTypeFlag] @property {string} [intrPayTypeFlag] @property {RepairOrderGGLineAmount} [amount] } */
|
||||
/** @typedef {Object} RepairOrderGGOp { @property {string} [opCode] @property {string|number} [jobNo] @property {RepairOrderGGLine[]} [lines] } */
|
||||
/** @typedef {Object} RepairOrderGG { @property {string|number} [roNo] @property {RepairOrderGGOp[]} [ops] } */
|
||||
/** @typedef {Object} RepairOrderMiscLine { @property {string} [miscCode] @property {'T'|'N'} [custTxblNtxblFlag] @property {'T'|'N'} [warrTxblNtxblFlag] @property {'T'|'N'} [intrTxblNtxblFlag] @property {string} [custPayTypeFlag] @property {string} [warrPayTypeFlag] @property {string} [intrPayTypeFlag] @property {string|number} [codeAmt] } */
|
||||
/** @typedef {Object} RepairOrderMiscOp { @property {string} [opCode] @property {string|number} [jobNo] @property {RepairOrderMiscLine[]} [lines] } */
|
||||
/** @typedef {Object} RepairOrderMisc { @property {string|number} [roNo] @property {RepairOrderMiscOp[]} [ops] } */
|
||||
/** @typedef {Object} CreateRepairOrderPayload
|
||||
* @property {string|number} customerNo Customer number (CustNo)
|
||||
* @property {string|number} departmentType Department type (DeptType)
|
||||
* @property {string} vin Vehicle VIN (Vin)
|
||||
* @property {string|number} outsdRoNo External RO identifier
|
||||
* @property {string|number} [advisorNo] Advisor number
|
||||
* @property {string|number} [tagNo] Tag number
|
||||
* @property {string|number} [mileageIn] Mileage in value
|
||||
* @property {string} [roComment] Repair order comment
|
||||
* @property {RepairOrderEstimate} [estimate] Repair order estimate block
|
||||
* @property {RepairOrderTax} [tax] Tax block
|
||||
* @property {RepairOrderLabor} [rolabor] Labor operations
|
||||
* @property {RepairOrderPart} [ropart] Parts jobs/lines
|
||||
* @property {RepairOrderGG} [rogg] General goods lines
|
||||
* @property {RepairOrderMisc} [romisc] Miscellaneous charges
|
||||
*/
|
||||
/** @typedef {Object} UpdateRepairOrderPayload { @property {'Y'|'N'} finalUpdate @property {string|number} outsdRoNo @property {string|number} [roNo] @property {string|number} [customerNo] @property {string|number} [tagNo] @property {string|number} [departmentType] @property {string} [vin] @property {string|number} [mileageIn] @property {string|number} [mileageOut] @property {string} [roComment] @property {RepairOrderEstimate} [estimate] @property {RepairOrderTax} [tax] @property {RepairOrderLabor} [rolabor] @property {RepairOrderPart} [ropart] @property {RepairOrderGG} [rogg] @property {RepairOrderMisc} [romisc] } */
|
||||
/** @typedef {Object} RepairOrderData { @property {string|undefined} status @property {string|undefined} date @property {string|undefined} time @property {string|undefined} outsdRoNo @property {string|undefined} dmsRoNo @property {string|undefined} errorMessage } */
|
||||
|
||||
/** @typedef {Object} GetAdvisorsParams { @property {'S'|'P'|'B'|'SERVICE'|'PARTS'|'BODY'|'BODYSHOP'|'BODY SHOP'} department @property {string|number} [advisorNumber] @property {number} [maxResults] } */
|
||||
/** @typedef {Object} AdvisorRow { @property {string|number|undefined} advisorId @property {string|undefined} firstName @property {string|undefined} lastName @property {'S'|'P'|'B'|undefined} department } */
|
||||
/** @typedef {Object} GetPartsParams { @property {string|number} roNumber } */
|
||||
/** @typedef {Object} PartRow { @property {string|undefined} partNumber @property {string|undefined} partDescription @property {string|number|undefined} quantityOrdered @property {string|number|undefined} quantityShipped @property {string|number|undefined} price @property {string|number|undefined} cost @property {string|undefined} processedFlag @property {string|undefined} addOrDelete } */
|
||||
|
||||
// Marker exports (no runtime impact)
|
||||
export const _extendedTypesDoc = null;
|
||||
1035
server/rr/lib/types/types.d.ts
vendored
Normal file
1035
server/rr/lib/types/types.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
32
server/rr/resolveRRConfigHttp.js
Normal file
32
server/rr/resolveRRConfigHttp.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const { getRRConfigForBodyshop } = require("./rr-config");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
|
||||
/**
|
||||
* Extracts bodyshopId from body, job, or header and loads RR config.
|
||||
* @returns {Promise<{ bodyshopId: string, config: any }>}
|
||||
*/
|
||||
async function resolveRRConfigHttp(req) {
|
||||
const body = req?.body || {};
|
||||
|
||||
const fromBody = body.bodyshopId;
|
||||
const fromJob = body.job && (body.job.shopid || body.job.bodyshopId);
|
||||
const fromHeader = typeof req.get === "function" ? req.get("x-bodyshop-id") : undefined;
|
||||
|
||||
const bodyshopId = fromBody || fromJob || fromHeader;
|
||||
|
||||
if (!bodyshopId) {
|
||||
throw new RrApiError(
|
||||
"Missing bodyshopId (expected in body.bodyshopId, body.job.shopid/bodyshopId, or x-bodyshop-id header)",
|
||||
"BAD_REQUEST"
|
||||
);
|
||||
}
|
||||
|
||||
const config = await getRRConfigForBodyshop(bodyshopId);
|
||||
if (!config?.dealerNumber) {
|
||||
throw new RrApiError(`RR config not found for bodyshopId=${bodyshopId} (missing dealerNumber)`, "NOT_CONFIGURED");
|
||||
}
|
||||
|
||||
return { bodyshopId, config };
|
||||
}
|
||||
|
||||
module.exports = { resolveRRConfigHttp };
|
||||
25
server/rr/rr-client.js
Normal file
25
server/rr/rr-client.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { RRClient } = require("./lib/index.cjs");
|
||||
|
||||
/**
|
||||
* Build an RR client using env credentials.
|
||||
* @param {{logger?: {debug?:Function,info?:Function,warn?:Function,error?:Function}}} opts
|
||||
*/
|
||||
function makeRRClient({ logger } = {}) {
|
||||
const baseUrl = process.env.RR_BASE_URL;
|
||||
const username = process.env.RR_USERNAME;
|
||||
const password = process.env.RR_PASSWORD;
|
||||
|
||||
if (!baseUrl || !username || !password) {
|
||||
throw new Error("RR creds missing (RR_BASE_URL, RR_USERNAME, RR_PASSWORD).");
|
||||
}
|
||||
|
||||
return new RRClient({
|
||||
baseUrl,
|
||||
username,
|
||||
password,
|
||||
logger
|
||||
// retries: { max: 3 }, // optional: override retry policy
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { makeRRClient };
|
||||
@@ -1,81 +1,63 @@
|
||||
// server/rr/rr-config.js
|
||||
/**
|
||||
* Loads per-bodyshop RR routing from Hasura.
|
||||
* Expected table fields (adapt if your schema differs):
|
||||
* - bodyshops.id = $bodyshopId
|
||||
* - bodyshops.rr_dealerid (string)
|
||||
* - bodyshops.rr_configuration JSON { storeNumber?, branchNumber? }
|
||||
*
|
||||
* Requires env:
|
||||
* HASURA_GRAPHQL_ENDPOINT, HASURA_ADMIN_SECRET
|
||||
*/
|
||||
const { GraphQLClient, gql } = require("graphql-request");
|
||||
|
||||
/**
|
||||
* Fetch the bodyshop row (dealer + per-shop RR json).
|
||||
* No fallback to env for dealer/store/branch.
|
||||
*/
|
||||
async function fetchBodyshopRRRow(bodyshopId) {
|
||||
if (!bodyshopId) throw new Error("Missing bodyshopId for RR config.");
|
||||
const HASURA_URL = process.env.HASURA_GRAPHQL_ENDPOINT || process.env.HASURA_URL;
|
||||
const HASURA_SECRET = process.env.HASURA_ADMIN_SECRET || process.env.HASURA_GRAPHQL_ADMIN_SECRET;
|
||||
|
||||
const endpoint = process.env.GRAPHQL_ENDPOINT;
|
||||
if (!endpoint) throw new Error("GRAPHQL_ENDPOINT env var is required.");
|
||||
if (!HASURA_URL || !HASURA_SECRET) {
|
||||
// Warn loudly at startup; you can hard fail if you prefer
|
||||
console.warn("[RR] HASURA env not set (HASURA_GRAPHQL_ENDPOINT / HASURA_ADMIN_SECRET).");
|
||||
}
|
||||
|
||||
const headers = {};
|
||||
if (process.env.HASURA_ADMIN_SECRET) {
|
||||
headers["x-hasura-admin-secret"] = process.env.HASURA_ADMIN_SECRET;
|
||||
} else if (process.env.GRAPHQL_BEARER) {
|
||||
headers["authorization"] = `Bearer ${process.env.GRAPHQL_BEARER}`;
|
||||
}
|
||||
const client = HASURA_URL
|
||||
? new GraphQLClient(HASURA_URL, {
|
||||
headers: { "x-hasura-admin-secret": HASURA_SECRET }
|
||||
})
|
||||
: null;
|
||||
|
||||
const client = new GraphQLClient(endpoint, { headers });
|
||||
|
||||
const Q = gql`
|
||||
query BodyshopRR($id: uuid!) {
|
||||
const Q_BODYSHOP_RR = gql`
|
||||
query RR_Config($id: uuid!) {
|
||||
bodyshops_by_pk(id: $id) {
|
||||
id
|
||||
rr_dealerid
|
||||
rr_configuration
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const { bodyshops_by_pk: bs } = await client.request(Q, { id: bodyshopId });
|
||||
if (!bs) throw new Error("Bodyshop not found.");
|
||||
if (!bs.rr_dealerid) throw new Error("Bodyshop is not configured for RR (missing rr_dealerid).");
|
||||
|
||||
let cfgJson = bs.rr_configuration || {};
|
||||
if (typeof cfgJson === "string") {
|
||||
try {
|
||||
cfgJson = JSON.parse(cfgJson);
|
||||
} catch {
|
||||
cfgJson = {};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dealerNumber: bs.rr_dealerid,
|
||||
storeNumber: cfgJson.storeNumber,
|
||||
branchNumber: cfgJson.branchNumber
|
||||
};
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Build the full RR config object used by downstream code.
|
||||
* - Dealer/Store/Branch: from DB (required)
|
||||
* - Transport/BaseURL/creds/PPSysId: from env (deployment-wide)
|
||||
* @param {string} bodyshopId
|
||||
* @returns {Promise<{dealerNumber:string, storeNumber?:string, areaNumber?:string}>}
|
||||
*/
|
||||
async function getRRConfigForBodyshop(bodyshopId) {
|
||||
const { dealerNumber, storeNumber, branchNumber } = await fetchBodyshopRRRow(bodyshopId);
|
||||
if (!client) throw new Error("Hasura client not configured.");
|
||||
|
||||
const rrTransport = (process.env.RR_TRANSPORT || "STAR").toUpperCase();
|
||||
return {
|
||||
// Per-bodyshop (DB)
|
||||
dealerNumber,
|
||||
storeNumber,
|
||||
branchNumber,
|
||||
const data = await client.request(Q_BODYSHOP_RR, { id: bodyshopId });
|
||||
const row = data?.bodyshops_by_pk;
|
||||
if (!row) throw new Error(`Bodyshop not found: ${bodyshopId}`);
|
||||
|
||||
// Duplicate snake_case for legacy call-sites that expect it
|
||||
dealer_number: dealerNumber,
|
||||
store_number: storeNumber,
|
||||
branch_number: branchNumber,
|
||||
const dealerNumber = row.rr_dealerid;
|
||||
|
||||
// Deployment-wide (env)
|
||||
baseUrl: process.env.RR_BASE_URL,
|
||||
username: process.env.RR_USERNAME,
|
||||
password: process.env.RR_PASSWORD,
|
||||
ppsysId: process.env.RR_PPSYSID,
|
||||
rrTransport
|
||||
};
|
||||
const cfg = row.rr_configuration || {};
|
||||
|
||||
if (!dealerNumber) {
|
||||
throw new Error(`RR not configured for bodyshop ${bodyshopId} (missing rr_dealerid).`);
|
||||
}
|
||||
|
||||
// The RR client expects "areaNumber" (Rome "branch")
|
||||
const storeNumber = cfg.storeNumber || cfg.store_no || cfg.store || null;
|
||||
const areaNumber = cfg.branchNumber || cfg.branch_no || cfg.branch || null;
|
||||
|
||||
return { dealerNumber, storeNumber, areaNumber };
|
||||
}
|
||||
|
||||
module.exports = { getRRConfigForBodyshop };
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* STAR-only constants for Reynolds & Reynolds (Rome/RCI)
|
||||
* Used by rr-helpers.js to build and send SOAP requests.
|
||||
*
|
||||
* IMPORTANT:
|
||||
* - Only rr-test.js should fall back to ENV for dealer/store/branch.
|
||||
* - All runtime code (sockets/routes/jobs) must pass per-bodyshop
|
||||
* values from the database (see rr-config.js#getRRConfigForBodyshop).
|
||||
*/
|
||||
|
||||
exports.RR_NS = Object.freeze({
|
||||
SOAP_ENV: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
SOAP_ENC: "http://schemas.xmlsoap.org/soap/encoding/",
|
||||
XSD: "http://www.w3.org/2001/XMLSchema",
|
||||
XSI: "http://www.w3.org/2001/XMLSchema-instance",
|
||||
WSSE: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
|
||||
WSU: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
|
||||
STAR_TRANSPORT: "http://www.starstandards.org/webservices/2005/10/transport",
|
||||
STAR_BUSINESS: "http://www.starstandards.org/STAR"
|
||||
});
|
||||
|
||||
const RR_STAR_SOAP_ACTION = "http://www.starstandards.org/webservices/2005/10/transport/ProcessMessage";
|
||||
exports.RR_SOAP_ACTION = RR_STAR_SOAP_ACTION;
|
||||
|
||||
const RR_SOAP_HEADERS = {
|
||||
"Content-Type": "text/xml; charset=utf-8",
|
||||
SOAPAction: RR_STAR_SOAP_ACTION
|
||||
};
|
||||
// Export if other modules need default STAR headers
|
||||
exports.RR_SOAP_HEADERS = RR_SOAP_HEADERS;
|
||||
|
||||
// All STAR-supported actions (mapped to Mustache templates)
|
||||
exports.RR_ACTIONS = Object.freeze({
|
||||
CombinedSearch: { template: "CombinedSearch" },
|
||||
GetAdvisors: { template: "GetAdvisors" },
|
||||
GetParts: { template: "GetParts" },
|
||||
InsertCustomer: { template: "InsertCustomer" },
|
||||
InsertServiceVehicle: { template: "InsertServiceVehicle" },
|
||||
CreateRepairOrder: { template: "CreateRepairOrder" },
|
||||
UpdateCustomer: { template: "UpdateCustomer" },
|
||||
UpdateRepairOrder: { template: "UpdateRepairOrder" }
|
||||
});
|
||||
|
||||
/**
|
||||
* Base config loader (environment-driven)
|
||||
*
|
||||
* ⚠️ Policy:
|
||||
* - Only rr-test.js should rely on the ENV values for dealer/store/branch.
|
||||
* - All other call sites must inject per-bodyshop values from DB.
|
||||
*/
|
||||
exports.getBaseRRConfig = function getBaseRRConfig() {
|
||||
return {
|
||||
baseUrl: process.env.RR_BASE_URL,
|
||||
username: process.env.RR_USERNAME,
|
||||
password: process.env.RR_PASSWORD,
|
||||
ppsysId: process.env.RR_PPSYSID, // optional legacy identifier
|
||||
|
||||
// ❗ These are ONLY for rr-test.js fallback.
|
||||
dealerNumber: process.env.RR_DEALER_NUMBER,
|
||||
storeNumber: process.env.RR_STORE_NUMBER,
|
||||
branchNumber: process.env.RR_BRANCH_NUMBER || "01",
|
||||
|
||||
wssePasswordType: process.env.RR_WSSE_PASSWORD_TYPE || "Text",
|
||||
timeout: Number(process.env.RR_TIMEOUT_MS || 30000)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize dealer/store/branch field names (camelCase vs snake_case).
|
||||
* Safe to use in helpers to tolerate mixed callers during migration.
|
||||
*/
|
||||
exports.normalizeRRDealerFields = function normalizeRRDealerFields(cfg = {}) {
|
||||
const dealerNumber = cfg.dealerNumber ?? cfg.dealer_number;
|
||||
const storeNumber = cfg.storeNumber ?? cfg.store_number;
|
||||
const branchNumber = cfg.branchNumber ?? cfg.branch_number;
|
||||
return { dealerNumber, storeNumber, branchNumber };
|
||||
};
|
||||
@@ -1,99 +1,17 @@
|
||||
/**
|
||||
* @file rr-customer.js
|
||||
* @description Rome (Reynolds & Reynolds) Customer Insert / Update integration.
|
||||
* Maps internal customer objects to Rome XML schemas and executes RCI calls.
|
||||
*/
|
||||
const { withClient } = require("../rr/withClient");
|
||||
|
||||
const { MakeRRCall } = require("./rr-helpers");
|
||||
const { mapCustomerInsert, mapCustomerUpdate } = require("./rr-mappers");
|
||||
const RRLogger = require("./rr-logger");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
|
||||
/**
|
||||
* Insert a new customer into Rome.
|
||||
* @param {Socket} socket - WebSocket connection for logging context
|
||||
* @param {Object} customer - Hasura customer record
|
||||
* @param {Object} bodyshopConfig - DMS configuration
|
||||
* @returns {Promise<Object>} result
|
||||
*/
|
||||
async function insertCustomer(socket, customer, bodyshopConfig) {
|
||||
const action = "InsertCustomer";
|
||||
const template = "InsertCustomer";
|
||||
|
||||
try {
|
||||
RRLogger(socket, "info", `Starting RR ${action} for customer ${customer.id}`);
|
||||
|
||||
const data = mapCustomerInsert(customer, bodyshopConfig);
|
||||
|
||||
const resultXml = await MakeRRCall({
|
||||
action,
|
||||
body: { template, data },
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig,
|
||||
jobid: customer.id
|
||||
async function insertCustomer({ bodyshopId, payload }) {
|
||||
return withClient(bodyshopId, async (client, routing) => {
|
||||
const res = await client.insertCustomer(payload, { routing });
|
||||
return res;
|
||||
});
|
||||
|
||||
RRLogger(socket, "debug", `${action} completed successfully`, { customerId: customer.id });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
dms: "Rome",
|
||||
action,
|
||||
customerId: customer.id,
|
||||
xml: resultXml
|
||||
};
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error in ${action} for customer ${customer.id}`, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw new RrApiError(`RR InsertCustomer failed: ${error.message}`, "INSERT_CUSTOMER_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing customer in Rome.
|
||||
* @param {Socket} socket
|
||||
* @param {Object} customer
|
||||
* @param {Object} bodyshopConfig
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function updateCustomer(socket, customer, bodyshopConfig) {
|
||||
const action = "UpdateCustomer";
|
||||
const template = "UpdateCustomer";
|
||||
|
||||
try {
|
||||
RRLogger(socket, "info", `Starting RR ${action} for customer ${customer.id}`);
|
||||
|
||||
const data = mapCustomerUpdate(customer, bodyshopConfig);
|
||||
|
||||
const resultXml = await MakeRRCall({
|
||||
action,
|
||||
body: { template, data },
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig,
|
||||
jobid: customer.id
|
||||
async function updateCustomer({ bodyshopId, payload }) {
|
||||
return withClient(bodyshopId, async (client, routing) => {
|
||||
const res = await client.updateCustomer(payload, { routing });
|
||||
return res;
|
||||
});
|
||||
|
||||
RRLogger(socket, "debug", `${action} completed successfully`, { customerId: customer.id });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
dms: "Rome",
|
||||
action,
|
||||
customerId: customer.id,
|
||||
xml: resultXml
|
||||
};
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error in ${action} for customer ${customer.id}`, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw new RrApiError(`RR UpdateCustomer failed: ${error.message}`, "UPDATE_CUSTOMER_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
insertCustomer,
|
||||
updateCustomer
|
||||
};
|
||||
module.exports = { insertCustomer, updateCustomer };
|
||||
|
||||
@@ -1,48 +1,11 @@
|
||||
/**
|
||||
* @file rr-error.js
|
||||
* @description Custom error types for the Reynolds & Reynolds (Rome) integration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base RR API Error class — always structured with a message and a code.
|
||||
*/
|
||||
class RrApiError extends Error {
|
||||
/**
|
||||
* @param {string} message - Human-readable message
|
||||
* @param {string} [code="RR_ERROR"] - Short machine-readable error code
|
||||
* @param {Object} [details] - Optional structured metadata
|
||||
*/
|
||||
constructor(message, code = "RR_ERROR", details = {}) {
|
||||
constructor(message, code, meta) {
|
||||
super(message);
|
||||
this.name = "RrApiError";
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
code: this.code,
|
||||
message: this.message,
|
||||
details: this.details
|
||||
};
|
||||
this.code = code || "RR_API_ERROR";
|
||||
if (meta) this.meta = meta;
|
||||
Error.captureStackTrace?.(this, RrApiError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to normalize thrown errors into a consistent RrApiError instance.
|
||||
*/
|
||||
function toRrError(err, defaultCode = "RR_ERROR") {
|
||||
if (!err) return new RrApiError("Unknown RR error", defaultCode);
|
||||
if (err instanceof RrApiError) return err;
|
||||
if (typeof err === "string") return new RrApiError(err, defaultCode);
|
||||
const msg = err.message || "Unspecified RR error";
|
||||
const code = err.code || defaultCode;
|
||||
const details = err.details || {};
|
||||
return new RrApiError(msg, code, details);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RrApiError,
|
||||
toRrError
|
||||
};
|
||||
module.exports = { RrApiError };
|
||||
|
||||
@@ -1,351 +0,0 @@
|
||||
/**
|
||||
* STAR-only SOAP transport + template rendering for Reynolds & Reynolds (Rome/RCI).
|
||||
* - Renders Mustache STAR business templates (rey_*Req rooted with STAR ns)
|
||||
* - Builds STAR SOAP envelope (ProcessMessage/payload/content + ApplicationArea)
|
||||
* - Posts to RCI endpoint with STAR SOAPAction (full URI)
|
||||
* - Parses XML response (faults + STAR payload result)
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const axios = require("axios");
|
||||
const mustache = require("mustache");
|
||||
const { XMLParser } = require("fast-xml-parser");
|
||||
const RRLogger = require("./rr-logger");
|
||||
const {
|
||||
RR_ACTIONS,
|
||||
RR_SOAP_HEADERS,
|
||||
RR_SOAP_ACTION,
|
||||
RR_NS,
|
||||
getBaseRRConfig,
|
||||
normalizeRRDealerFields
|
||||
} = require("./rr-constants");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
const xmlFormatter = require("xml-formatter");
|
||||
|
||||
/**
|
||||
* Collapse Mustache-induced whitespace and pretty print.
|
||||
* - strips inner XML decl
|
||||
* - removes lines that are only whitespace
|
||||
* - collapses inter-tag whitespace
|
||||
* - formats with consistent indentation
|
||||
*/
|
||||
function prettyPrintXml(xml) {
|
||||
let s = xml;
|
||||
|
||||
// strip any inner XML declaration
|
||||
s = s.replace(/^\s*<\?xml[^>]*\?>\s*/i, "");
|
||||
|
||||
// remove lines that are only whitespace
|
||||
s = s.replace(/^[\t ]*(?:\r?\n)/gm, "");
|
||||
|
||||
// collapse whitespace strictly between tags (not inside text nodes)
|
||||
s = s.replace(/>\s+</g, "><");
|
||||
|
||||
// final pretty print
|
||||
return xmlFormatter(s, {
|
||||
indentation: " ",
|
||||
collapseContent: true, // keep short elements on one line
|
||||
lineSeparator: "\n",
|
||||
strictMode: false
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Public action map (compat with rr-test.js) ----------
|
||||
const RRActions = Object.fromEntries(Object.entries(RR_ACTIONS).map(([k]) => [k, { action: k }]));
|
||||
|
||||
// ---------- Template cache ----------
|
||||
const templateCache = new Map();
|
||||
|
||||
async function loadTemplate(templateName) {
|
||||
if (templateCache.has(templateName)) return templateCache.get(templateName);
|
||||
const filePath = path.join(__dirname, "xml-templates", `${templateName}.xml`);
|
||||
const tpl = await fs.readFile(filePath, "utf8");
|
||||
templateCache.set(templateName, tpl);
|
||||
return tpl;
|
||||
}
|
||||
|
||||
async function renderXmlTemplate(templateName, data) {
|
||||
const tpl = await loadTemplate(templateName);
|
||||
// Render and strip any XML declaration to keep a single root element for the BOD
|
||||
const rendered = mustache.render(tpl, { STAR_NS: RR_NS.STAR_BUSINESS, ...(data || {}) });
|
||||
return rendered.replace(/^\s*<\?xml[^>]*\?>\s*/i, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve RR config for STAR transport.
|
||||
*
|
||||
* Policy:
|
||||
* - Base (transport) settings (baseUrl, username, password, ppsysId, wssePasswordType, timeout) come from env.
|
||||
* - Dealer identifiers (dealer/store/branch) MUST be provided by the caller (DB-driven).
|
||||
* - We DO NOT fall back to env for dealer/store/branch here. Only rr-test.js is allowed to do that.
|
||||
*/
|
||||
async function resolveRRConfig(_socket, bodyshopConfig) {
|
||||
const baseEnv = getBaseRRConfig();
|
||||
|
||||
const { dealerNumber, storeNumber, branchNumber } = normalizeRRDealerFields(bodyshopConfig || {});
|
||||
if (!dealerNumber || !storeNumber || !branchNumber) {
|
||||
throw new Error(
|
||||
"Missing dealer/store/branch in RR config. These must be loaded from the database (no env fallback here)."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl: bodyshopConfig?.baseUrl || baseEnv.baseUrl,
|
||||
username: bodyshopConfig?.username || baseEnv.username,
|
||||
password: bodyshopConfig?.password || baseEnv.password,
|
||||
ppsysId: bodyshopConfig?.ppsysId || baseEnv.ppsysId,
|
||||
wssePasswordType: bodyshopConfig?.wssePasswordType || baseEnv.wssePasswordType || "Text",
|
||||
timeout: baseEnv.timeout,
|
||||
|
||||
// canonical identifiers (DB-driven only)
|
||||
dealerNumber,
|
||||
storeNumber,
|
||||
branchNumber
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- Response parsing ----------
|
||||
function parseRRResponse(xml) {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
removeNSPrefix: true
|
||||
});
|
||||
|
||||
const doc = parser.parse(xml);
|
||||
|
||||
// Envelope/Body
|
||||
const body =
|
||||
doc?.Envelope?.Body ||
|
||||
doc?.["soapenv:Envelope"]?.["soapenv:Body"] ||
|
||||
doc?.["SOAP-ENV:Envelope"]?.["SOAP-ENV:Body"] ||
|
||||
doc?.["S:Envelope"]?.["S:Body"] ||
|
||||
doc?.Body ||
|
||||
doc;
|
||||
|
||||
// SOAP Fault?
|
||||
const fault = body?.Fault || body?.["soap:Fault"];
|
||||
if (fault) {
|
||||
return {
|
||||
success: false,
|
||||
code: fault.faultcode || "SOAP_FAULT",
|
||||
message: fault.faultstring || "Unknown SOAP Fault",
|
||||
raw: xml
|
||||
};
|
||||
}
|
||||
|
||||
// STAR transport path: ProcessMessage/payload/content
|
||||
const processMessage = body?.ProcessMessage || body?.["ns0:ProcessMessage"] || body?.["ProcessMessageResponse"];
|
||||
|
||||
if (processMessage?.payload?.content) {
|
||||
const content = processMessage.payload.content;
|
||||
if (content && typeof content === "object") {
|
||||
const keys = Object.keys(content).filter((k) => k !== "@_id");
|
||||
const respKey = keys.find((k) => /Resp$/.test(k)) || (keys[0] === "ApplicationArea" && keys[1]) || keys[0];
|
||||
|
||||
const respNode = respKey ? content[respKey] : content;
|
||||
|
||||
const resultCode = respNode?.ResultCode || respNode?.ResponseCode || respNode?.StatusCode || "OK";
|
||||
const resultMessage = respNode?.ResultMessage || respNode?.ResponseMessage || respNode?.StatusMessage || null;
|
||||
|
||||
return {
|
||||
success: ["OK", "Success"].includes(String(resultCode)),
|
||||
code: resultCode,
|
||||
message: resultMessage,
|
||||
raw: xml,
|
||||
parsed: respNode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: first element under Body (just in case)
|
||||
const keys = body && typeof body === "object" ? Object.keys(body) : [];
|
||||
const respNode = keys.length ? body[keys[0]] : body;
|
||||
|
||||
const resultCode = respNode?.ResultCode || respNode?.ResponseCode || "OK";
|
||||
const resultMessage = respNode?.ResultMessage || respNode?.ResponseMessage || null;
|
||||
|
||||
return {
|
||||
success: resultCode === "OK" || resultCode === "Success",
|
||||
code: resultCode,
|
||||
message: resultMessage,
|
||||
raw: xml,
|
||||
parsed: respNode
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- STAR envelope helpers ----------
|
||||
function wrapWithApplicationArea(innerXml, { CreationDateTime, BODId, Sender, Destination }) {
|
||||
// Strip any inner XML declaration (idempotent)
|
||||
let xml = innerXml.replace(/^\s*<\?xml[^>]*\?>\s*/i, "");
|
||||
|
||||
const appArea = `
|
||||
<ApplicationArea>
|
||||
<CreationDateTime>${CreationDateTime}</CreationDateTime>
|
||||
<BODId>${BODId}</BODId>
|
||||
<Sender>
|
||||
${Sender?.Component ? `<Component>${Sender.Component}</Component>` : ""}
|
||||
${Sender?.Task ? `<Task>${Sender.Task}</Task>` : ""}
|
||||
${Sender?.ReferenceId ? `<ReferenceId>${Sender.ReferenceId}</ReferenceId>` : ""}
|
||||
</Sender>
|
||||
<Destination>
|
||||
<DestinationNameCode>RR</DestinationNameCode>
|
||||
${Destination?.DealerNumber ? `<DealerNumber>${Destination.DealerNumber}</DealerNumber>` : ""}
|
||||
${Destination?.StoreNumber ? `<StoreNumber>${Destination.StoreNumber}</StoreNumber>` : ""}
|
||||
${Destination?.AreaNumber ? `<AreaNumber>${Destination.AreaNumber}</AreaNumber>` : ""}
|
||||
</Destination>
|
||||
</ApplicationArea>`.trim();
|
||||
|
||||
// Inject right after the opening tag of the root element
|
||||
xml = xml.replace(/^(\s*<[^!?][^>]*>)/, `$1\n${appArea}\n`);
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
const TASK_BY_ACTION = {
|
||||
CreateRepairOrder: { Task: "BSMRO", ReferenceId: "Insert" },
|
||||
UpdateRepairOrder: { Task: "BSMRO", ReferenceId: "Update" },
|
||||
InsertCustomer: { Task: "CU", ReferenceId: "Insert" },
|
||||
UpdateCustomer: { Task: "CU", ReferenceId: "Update" },
|
||||
InsertServiceVehicle: { Task: "SV", ReferenceId: "Insert" }
|
||||
};
|
||||
|
||||
async function buildStarEnvelope(innerBusinessXml, creds, appArea = {}, action) {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Derive sensible defaults for Sender from action, unless caller provided explicit values
|
||||
const senderDefaults =
|
||||
appArea?.Sender ||
|
||||
(action && TASK_BY_ACTION[action] ? { Component: "Rome", ...TASK_BY_ACTION[action] } : { Component: "Rome" });
|
||||
|
||||
const payloadWithAppArea = wrapWithApplicationArea(innerBusinessXml, {
|
||||
CreationDateTime: appArea.CreationDateTime || now,
|
||||
BODId: appArea.BODId || `BOD-${Date.now()}`,
|
||||
Sender: senderDefaults,
|
||||
Destination: appArea.Destination || {
|
||||
DealerNumber: creds.dealerNumber,
|
||||
StoreNumber: String(creds.storeNumber ?? "").padStart(2, "0"),
|
||||
AreaNumber: String(creds.branchNumber || "01").padStart(2, "0")
|
||||
}
|
||||
});
|
||||
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<soapenv:Envelope xmlns:soapenc="${RR_NS.SOAP_ENC}" xmlns:soapenv="${RR_NS.SOAP_ENV}" xmlns:xsd="${RR_NS.XSD}" xmlns:xsi="${RR_NS.XSI}">
|
||||
<soapenv:Header>
|
||||
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="${RR_NS.WSSE}">
|
||||
<wsse:UsernameToken>
|
||||
<wsse:Username>${creds.username}</wsse:Username>
|
||||
<wsse:Password>${creds.password}</wsse:Password>
|
||||
</wsse:UsernameToken>
|
||||
</wsse:Security>
|
||||
</soapenv:Header>
|
||||
<soapenv:Body>
|
||||
<ProcessMessage xmlns="${RR_NS.STAR_TRANSPORT}">
|
||||
<payload xmlns:soap="${RR_NS.SOAP_ENV}" xmlns:xsi="${RR_NS.XSI}" xmlns:xsd="${RR_NS.XSD}" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="${RR_NS.WSSE}" xmlns:wsu="${RR_NS.WSU}" xmlns="${RR_NS.STAR_TRANSPORT}">
|
||||
<content id="content0">
|
||||
${payloadWithAppArea}
|
||||
</content>
|
||||
</payload>
|
||||
</ProcessMessage>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>`;
|
||||
}
|
||||
|
||||
// ---------- Main transport (STAR only) ----------
|
||||
async function MakeRRCall({
|
||||
action,
|
||||
body,
|
||||
appArea, // <-- allow explicit ApplicationArea overrides at the top level
|
||||
socket,
|
||||
dealerConfig, // required in runtime code; rr-test.js can still pass env-inflated cfg
|
||||
retries = 1,
|
||||
jobid
|
||||
}) {
|
||||
if (!action || !RR_ACTIONS[action]) {
|
||||
throw new Error(`Invalid RR action: ${action}`);
|
||||
}
|
||||
|
||||
// Prefer explicit dealerConfig from caller; otherwise enforce DB-provided config via resolveRRConfig
|
||||
const cfg = dealerConfig || (await resolveRRConfig(socket, undefined));
|
||||
const baseUrl = cfg.baseUrl;
|
||||
if (!baseUrl) throw new Error("Missing RR base URL");
|
||||
|
||||
// Render STAR business body
|
||||
const templateName = body?.template || action;
|
||||
const renderedBusiness = await renderXmlTemplate(templateName, body?.data || {});
|
||||
|
||||
// Build STAR envelope (use explicit appArea if provided; else accept body.appArea; else derive from action)
|
||||
const selectedAppArea = appArea || body?.appArea || {};
|
||||
const envelope = await buildStarEnvelope(renderedBusiness, cfg, selectedAppArea, action);
|
||||
const formattedEnvelope = prettyPrintXml(envelope);
|
||||
|
||||
// Guardrails
|
||||
if (!formattedEnvelope.includes("<ProcessMessage") || !formattedEnvelope.includes("<ApplicationArea>")) {
|
||||
throw new Error("STAR envelope malformed: missing ProcessMessage/ApplicationArea");
|
||||
}
|
||||
|
||||
const headers = { ...RR_SOAP_HEADERS, SOAPAction: RR_SOAP_ACTION };
|
||||
|
||||
RRLogger(socket, "debug", `Sending RR SOAP request`, {
|
||||
action,
|
||||
soapAction: RR_SOAP_ACTION,
|
||||
endpoint: baseUrl,
|
||||
jobid,
|
||||
mode: "STAR"
|
||||
});
|
||||
|
||||
try {
|
||||
const { data: responseXml } = await axios.post(baseUrl, formattedEnvelope, {
|
||||
headers,
|
||||
timeout: cfg.timeout
|
||||
// Some RCI tenants require Basic in addition to WSSE
|
||||
// auth: { username: cfg.username, password: cfg.password }
|
||||
});
|
||||
|
||||
const parsed = parseRRResponse(responseXml);
|
||||
|
||||
if (!parsed.success) {
|
||||
RRLogger(socket, "error", `RR ${action} failed`, {
|
||||
code: parsed.code,
|
||||
message: parsed.message
|
||||
});
|
||||
throw new RrApiError(parsed.message || `RR ${action} failed`, parsed.code || "RR_ERROR");
|
||||
}
|
||||
|
||||
RRLogger(socket, "info", `RR ${action} success`, {
|
||||
result: parsed.code,
|
||||
message: parsed.message
|
||||
});
|
||||
|
||||
return responseXml;
|
||||
} catch (err) {
|
||||
if (retries > 0) {
|
||||
RRLogger(socket, "warn", `Retrying RR ${action} (${retries - 1} left)`, {
|
||||
error: err.message
|
||||
});
|
||||
return MakeRRCall({
|
||||
action,
|
||||
body,
|
||||
appArea: selectedAppArea,
|
||||
socket,
|
||||
dealerConfig: cfg,
|
||||
retries: retries - 1,
|
||||
jobid
|
||||
});
|
||||
}
|
||||
|
||||
RRLogger(socket, "error", `RR ${action} failed permanently`, { error: err.message });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MakeRRCall,
|
||||
renderXmlTemplate,
|
||||
resolveRRConfig,
|
||||
parseRRResponse,
|
||||
buildStarEnvelope,
|
||||
RRActions
|
||||
};
|
||||
@@ -1,158 +1,68 @@
|
||||
/**
|
||||
* @file rr-job-export.js
|
||||
* @description End-to-end export of a Hasura "job" to Reynolds & Reynolds (Rome).
|
||||
* Orchestrates Customer (insert/update), optional Vehicle insert, and RO (create/update),
|
||||
* mirroring behavior of PBS/Fortellis exporters for parity.
|
||||
*/
|
||||
const { withClient } = require("./withClient");
|
||||
|
||||
const RRLogger = require("./rr-logger");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
async function exportJobToRR({ bodyshopId, job, logger }) {
|
||||
return withClient(bodyshopId, logger, async (client, routing) => {
|
||||
// 1) Upsert Customer
|
||||
const custPayload = mapJobToCustomer(job);
|
||||
const custRes = job.customer?.nameRecId
|
||||
? await client.updateCustomer(custPayload, { routing })
|
||||
: await client.insertCustomer(custPayload, { routing });
|
||||
|
||||
const customerApi = require("./rr-customer");
|
||||
const roApi = require("./rr-repair-orders");
|
||||
const { MakeRRCall } = require("./rr-helpers"); // for optional vehicle insert
|
||||
const { mapServiceVehicle } = require("./rr-mappers");
|
||||
const customerNo = custRes?.data?.dmsRecKey || job.customer?.customerNo;
|
||||
if (!customerNo) throw new Error("Failed to resolve customerNo from RR response.");
|
||||
|
||||
/**
|
||||
* Decide if we should CREATE or UPDATE an entity in Rome based on external IDs
|
||||
*/
|
||||
function decideAction({ customer, vehicle, job }) {
|
||||
const hasCustId = !!(customer?.external_id || customer?.rr_customer_id);
|
||||
const hasVehId = !!(vehicle?.external_id || vehicle?.rr_vehicle_id);
|
||||
const hasRoId = !!(job?.external_id || job?.rr_repair_order_id || job?.dms_repair_order_id);
|
||||
// 2) Ensure Service Vehicle (optional, if VIN present)
|
||||
if (job?.vehicle?.vin) {
|
||||
await client.insertServiceVehicle(
|
||||
{
|
||||
vin: job.vehicle.vin,
|
||||
vehicleServInfo: { customerNo }
|
||||
},
|
||||
{ routing }
|
||||
);
|
||||
}
|
||||
|
||||
// 3) Create RO
|
||||
const roHeader = {
|
||||
customerNo,
|
||||
departmentType: "B",
|
||||
vin: job?.vehicle?.vin,
|
||||
outsdRoNo: job?.roExternal || job?.id,
|
||||
advisorNo: job?.advisorNo,
|
||||
mileageIn: job?.mileageIn
|
||||
};
|
||||
|
||||
const roBody = mapJobToRO(job); // extend if you want lines/tax/etc
|
||||
const roRes = await client.createRepairOrder({ ...roHeader, ...roBody }, { routing });
|
||||
return roRes?.data;
|
||||
});
|
||||
}
|
||||
|
||||
function mapJobToCustomer(job) {
|
||||
const c = job?.customer || {};
|
||||
return {
|
||||
customerAction: hasCustId ? "update" : "insert",
|
||||
vehicleAction: hasVehId ? "skip" : "insert", // Rome often generates vehicle IDs on RO create; we insert only if we have enough data and no id
|
||||
repairOrderAction: hasRoId ? "update" : "create"
|
||||
nameRecId: c.nameRecId,
|
||||
firstName: c.firstName || c.given_name,
|
||||
lastName: c.lastName || c.family_name,
|
||||
phone: c.phone || c.mobile,
|
||||
email: c.email,
|
||||
address: {
|
||||
line1: c.address1,
|
||||
line2: c.address2,
|
||||
city: c.city,
|
||||
state: c.province || c.state,
|
||||
postalCode: c.postal || c.zip
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a stage result to a consistent structure.
|
||||
*/
|
||||
function stageOk(name, extra = {}) {
|
||||
return { stage: name, success: true, ...extra };
|
||||
}
|
||||
function stageFail(name, error) {
|
||||
return { stage: name, success: false, error: error?.message || String(error) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a job into Rome (Customer → Vehicle → RepairOrder).
|
||||
* @param {Socket} socket - logging context (may be null in batch)
|
||||
* @param {Object} job - Hasura job object (must include customer, vehicle, lines, totals)
|
||||
* @param {Object} bodyshopConfig - per-shop RR config (dealer/store/branch + creds)
|
||||
* @param {Object} options - { insertVehicleIfMissing: boolean }
|
||||
* @returns {Promise<Object>} normalized result
|
||||
*/
|
||||
async function exportJobToRR(socket, job, bodyshopConfig, options = {}) {
|
||||
const { customer = {}, vehicle = {} } = job || {};
|
||||
const { insertVehicleIfMissing = true } = options;
|
||||
|
||||
const actions = decideAction({ customer, vehicle, job });
|
||||
|
||||
const stages = [];
|
||||
const summary = {
|
||||
dms: "Rome",
|
||||
jobid: job?.id,
|
||||
ro_action: actions.repairOrderAction,
|
||||
customer_action: actions.customerAction,
|
||||
vehicle_action: insertVehicleIfMissing ? actions.vehicleAction : "skip"
|
||||
function mapJobToRO(job) {
|
||||
return {
|
||||
// rolabor: [...],
|
||||
// roparts: [...],
|
||||
// estimate: {...},
|
||||
// tax: {...}
|
||||
};
|
||||
|
||||
RRLogger(socket, "info", `RR Export start`, summary);
|
||||
|
||||
// ---- 1) Customer ----
|
||||
try {
|
||||
if (actions.customerAction === "insert") {
|
||||
const res = await customerApi.insertCustomer(socket, customer, bodyshopConfig);
|
||||
stages.push(stageOk("customer.insert"));
|
||||
summary.customer_xml = res.xml;
|
||||
} else {
|
||||
const res = await customerApi.updateCustomer(socket, customer, bodyshopConfig);
|
||||
stages.push(stageOk("customer.update"));
|
||||
summary.customer_xml = res.xml;
|
||||
}
|
||||
} catch (error) {
|
||||
stages.push(stageFail(`customer.${actions.customerAction}`, error));
|
||||
RRLogger(socket, "error", `RR customer ${actions.customerAction} failed`, {
|
||||
jobid: job?.id,
|
||||
error: error.message
|
||||
});
|
||||
throw new RrApiError(`Customer ${actions.customerAction} failed: ${error.message}`, "RR_CUSTOMER_ERROR");
|
||||
}
|
||||
|
||||
// ---- 2) Vehicle (optional explicit insert) ----
|
||||
if (insertVehicleIfMissing && actions.vehicleAction === "insert") {
|
||||
try {
|
||||
// Only insert when we have at least VIN or plate+state/year
|
||||
const hasMinimumIdentity = !!(vehicle?.vin || (vehicle?.license_plate && vehicle?.license_state));
|
||||
if (hasMinimumIdentity) {
|
||||
const data = mapServiceVehicle(vehicle, customer, bodyshopConfig);
|
||||
const xml = await MakeRRCall({
|
||||
action: "InsertServiceVehicle",
|
||||
body: { template: "InsertServiceVehicle", data },
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig,
|
||||
jobid: job?.id
|
||||
});
|
||||
stages.push(stageOk("vehicle.insert"));
|
||||
summary.vehicle_xml = xml;
|
||||
} else {
|
||||
stages.push(stageOk("vehicle.skip", { reason: "insufficient_identity" }));
|
||||
}
|
||||
} catch (error) {
|
||||
stages.push(stageFail("vehicle.insert", error));
|
||||
RRLogger(socket, "error", `RR vehicle insert failed`, {
|
||||
jobid: job?.id,
|
||||
error: error.message
|
||||
});
|
||||
// Non-fatal for the overall export — many flows let RO creation create/associate vehicle.
|
||||
}
|
||||
} else {
|
||||
stages.push(stageOk("vehicle.skip", { reason: actions.vehicleAction === "skip" ? "already_has_id" : "disabled" }));
|
||||
}
|
||||
|
||||
// ---- 3) Repair Order ----
|
||||
try {
|
||||
let res;
|
||||
if (actions.repairOrderAction === "create") {
|
||||
res = await roApi.createRepairOrder(socket, job, bodyshopConfig);
|
||||
stages.push(stageOk("ro.create"));
|
||||
} else {
|
||||
res = await roApi.updateRepairOrder(socket, job, bodyshopConfig);
|
||||
stages.push(stageOk("ro.update"));
|
||||
}
|
||||
summary.ro_xml = res.xml;
|
||||
} catch (error) {
|
||||
stages.push(stageFail(`ro.${actions.repairOrderAction}`, error));
|
||||
RRLogger(socket, "error", `RR RO ${actions.repairOrderAction} failed`, {
|
||||
jobid: job?.id,
|
||||
error: error.message
|
||||
});
|
||||
throw new RrApiError(`RepairOrder ${actions.repairOrderAction} failed: ${error.message}`, "RR_RO_ERROR");
|
||||
}
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
...summary,
|
||||
stages
|
||||
};
|
||||
|
||||
RRLogger(socket, "info", `RR Export finished`, {
|
||||
jobid: job?.id,
|
||||
result: {
|
||||
success: result.success,
|
||||
customer_action: summary.customer_action,
|
||||
vehicle_action: summary.vehicle_action,
|
||||
ro_action: summary.ro_action
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
exportJobToRR
|
||||
};
|
||||
module.exports = { exportJobToRR };
|
||||
|
||||
@@ -1,55 +1,16 @@
|
||||
/**
|
||||
* @file rr-logger.js
|
||||
* @description Structured logger for Reynolds & Reynolds (Rome) integration.
|
||||
* Mirrors PBS/Fortellis log shape for consistent log parsing.
|
||||
*/
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const util = require("util");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
/**
|
||||
* @typedef {Object} LogContext
|
||||
* @property {string} [jobid]
|
||||
* @property {string} [action]
|
||||
* @property {string} [stage]
|
||||
* @property {string} [endpoint]
|
||||
* @property {Object} [meta]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emit a structured log event to console, Socket.IO, or upstream logger.
|
||||
* @param {Socket|null} socket - Optional socket for WsLogger passthrough
|
||||
* @param {"info"|"debug"|"warn"|"error"} level
|
||||
* @param {string} message - Primary log message
|
||||
* @param {LogContext|any} [context]
|
||||
*/
|
||||
function RRLogger(socket, level, message, context = {}) {
|
||||
const logEvent = {
|
||||
source: "RR",
|
||||
level,
|
||||
timestamp: dayjs().toISOString(),
|
||||
message,
|
||||
...context
|
||||
};
|
||||
|
||||
// Console log (stdout/stderr)
|
||||
const serialized = `[RR] ${logEvent.timestamp} [${level.toUpperCase()}] ${message}`;
|
||||
if (level === "error" || level === "warn") {
|
||||
console.error(serialized, context ? util.inspect(context, { depth: 4, colors: false }) : "");
|
||||
} else {
|
||||
console.log(serialized, context ? util.inspect(context, { depth: 4, colors: false }) : "");
|
||||
}
|
||||
|
||||
// Optional: forward to WsLogger (if your socket is configured that way)
|
||||
function RRLogger(socket) {
|
||||
return function log(level = "info", message = "", ctx = {}) {
|
||||
// Console
|
||||
const fn = logger.logger[level] || logger.log;
|
||||
fn(`[RR] ${new Date().toISOString()} [${level.toUpperCase()}] ${message}`, ctx);
|
||||
try {
|
||||
if (socket && typeof socket.emit === "function") {
|
||||
socket.emit("rr-log-event", logEvent);
|
||||
} else if (global.WsLogger && typeof global.WsLogger.createLogEvent === "function") {
|
||||
global.WsLogger.createLogEvent(socket, level.toUpperCase(), message, context.jobid, context);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[RRLogger] forwarding error", e.message);
|
||||
socket?.emit?.("RR:LOG", { level, message, ctx, ts: Date.now() });
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = RRLogger;
|
||||
|
||||
@@ -1,136 +1,24 @@
|
||||
/**
|
||||
* @file rr-lookup.js
|
||||
* @description Rome (Reynolds & Reynolds) lookup operations — Advisors, Parts, and CombinedSearch
|
||||
*/
|
||||
const { withClient } = require("./withClient");
|
||||
|
||||
const { MakeRRCall, parseRRResponse } = require("./rr-helpers");
|
||||
const { mapAdvisorLookup, mapPartsLookup, mapCombinedSearch } = require("./rr-mappers");
|
||||
const RRLogger = require("./rr-logger");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
|
||||
/**
|
||||
* Get a list of service advisors from Rome.
|
||||
*/
|
||||
async function getAdvisors(socket, criteria = {}, bodyshopConfig) {
|
||||
const action = "GetAdvisors";
|
||||
const template = "GetAdvisors";
|
||||
|
||||
try {
|
||||
RRLogger(socket, "info", `Starting RR ${action} lookup`);
|
||||
const data = mapAdvisorLookup(criteria, bodyshopConfig);
|
||||
|
||||
const resultXml = await MakeRRCall({
|
||||
action,
|
||||
body: { template, data },
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig
|
||||
async function getAdvisors({ bodyshopId, ...criteria }) {
|
||||
return withClient(bodyshopId, async (client, routing) => {
|
||||
const res = await client.getAdvisors(criteria, { routing });
|
||||
return res;
|
||||
});
|
||||
|
||||
const parsed = parseRRResponse(resultXml);
|
||||
if (!parsed.success) throw new RrApiError(parsed.message, parsed.code);
|
||||
|
||||
const advisors = parsed.parsed?.Advisors?.Advisor || parsed.parsed?.AdvisorList?.Advisor || [];
|
||||
const advisorList = Array.isArray(advisors) ? advisors : [advisors];
|
||||
|
||||
RRLogger(socket, "debug", `${action} lookup returned ${advisorList.length} advisors`);
|
||||
return { success: true, dms: "Rome", action, advisors: advisorList };
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error in ${action} lookup`, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw new RrApiError(`RR ${action} failed: ${error.message}`, "GET_ADVISORS_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parts information from Rome.
|
||||
*/
|
||||
async function getParts(socket, criteria = {}, bodyshopConfig) {
|
||||
const action = "GetParts";
|
||||
const template = "GetParts";
|
||||
|
||||
try {
|
||||
RRLogger(socket, "info", `Starting RR ${action} lookup`);
|
||||
const data = mapPartsLookup(criteria, bodyshopConfig);
|
||||
|
||||
const resultXml = await MakeRRCall({
|
||||
action,
|
||||
body: { template, data },
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig
|
||||
async function getParts({ bodyshopId, ...criteria }) {
|
||||
return withClient(bodyshopId, async (client, routing) => {
|
||||
const res = await client.getParts(criteria, { routing });
|
||||
return res;
|
||||
});
|
||||
|
||||
const parsed = parseRRResponse(resultXml);
|
||||
if (!parsed.success) throw new RrApiError(parsed.message, parsed.code);
|
||||
|
||||
const parts = parsed.parsed?.Parts?.Part || parsed.parsed?.PartList?.Part || [];
|
||||
const partList = Array.isArray(parts) ? parts : [parts];
|
||||
|
||||
RRLogger(socket, "debug", `${action} lookup returned ${partList.length} parts`);
|
||||
return { success: true, dms: "Rome", action, parts: partList };
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error in ${action} lookup`, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw new RrApiError(`RR ${action} failed: ${error.message}`, "GET_PARTS_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a combined customer / vehicle / company search.
|
||||
* Equivalent to Rome CombinedSearchRq / Resp.
|
||||
* @param {Socket} socket
|
||||
* @param {Object} criteria - { VIN, LicensePlate, CustomerName, Phone, Email }
|
||||
* @param {Object} bodyshopConfig
|
||||
* @returns {Promise<Object>} { customers, vehicles, companies }
|
||||
*/
|
||||
async function combinedSearch(socket, criteria = {}, bodyshopConfig) {
|
||||
const action = "CombinedSearch";
|
||||
const template = "CombinedSearch";
|
||||
|
||||
try {
|
||||
RRLogger(socket, "info", `Starting RR ${action} request`);
|
||||
const data = mapCombinedSearch(criteria, bodyshopConfig);
|
||||
|
||||
const resultXml = await MakeRRCall({
|
||||
action,
|
||||
body: { template, data },
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig
|
||||
async function combinedSearch({ bodyshopId, ...query }) {
|
||||
return withClient(bodyshopId, async (client, routing) => {
|
||||
const res = await client.combinedSearch(query, { routing });
|
||||
return res;
|
||||
});
|
||||
|
||||
const parsed = parseRRResponse(resultXml);
|
||||
if (!parsed.success) throw new RrApiError(parsed.message, parsed.code);
|
||||
|
||||
const customers = parsed.parsed?.Customers?.Customer || [];
|
||||
const vehicles = parsed.parsed?.Vehicles?.ServiceVehicle || [];
|
||||
const companies = parsed.parsed?.Companies?.Company || [];
|
||||
|
||||
const result = {
|
||||
customers: Array.isArray(customers) ? customers : [customers],
|
||||
vehicles: Array.isArray(vehicles) ? vehicles : [vehicles],
|
||||
companies: Array.isArray(companies) ? companies : [companies]
|
||||
};
|
||||
|
||||
RRLogger(
|
||||
socket,
|
||||
"debug",
|
||||
`${action} returned ${result.customers.length} customers, ${result.vehicles.length} vehicles, ${result.companies.length} companies`
|
||||
);
|
||||
return { success: true, dms: "Rome", action, ...result };
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error in ${action}`, {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw new RrApiError(`RR ${action} failed: ${error.message}`, "COMBINED_SEARCH_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAdvisors,
|
||||
getParts,
|
||||
combinedSearch
|
||||
};
|
||||
module.exports = { getAdvisors, getParts, combinedSearch };
|
||||
|
||||
@@ -1,428 +0,0 @@
|
||||
/**
|
||||
* @file rr-mappers.js
|
||||
* @description Maps internal ImEX (Hasura) entities into Rome (Reynolds & Reynolds) XML structures.
|
||||
* Each function returns a plain JS object that matches Mustache templates in xml-templates/.
|
||||
*/
|
||||
|
||||
const dayjs = require("dayjs");
|
||||
const { normalizeRRDealerFields } = require("./rr-constants");
|
||||
|
||||
/**
|
||||
* Utility: formats date/time to R&R’s preferred format (ISO or yyyy-MM-dd).
|
||||
*/
|
||||
const formatDate = (val) => {
|
||||
if (!val) return undefined;
|
||||
return dayjs(val).format("YYYY-MM-DD");
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility: safely pick numeric values and stringify for XML.
|
||||
*/
|
||||
const num = (val) => (val != null ? String(val) : undefined);
|
||||
|
||||
const toBoolStr = (v) => (v === true ? "true" : v === false ? "false" : undefined);
|
||||
const hasAny = (obj) => !!obj && Object.values(obj).some((v) => v !== undefined && v !== null && v !== "");
|
||||
|
||||
/**
|
||||
* Pull canonical Dealer/Store/Branch fields from cfg (tolerate snake_case during migration).
|
||||
* Enforces DB-provided values upstream (no env fallback here).
|
||||
*/
|
||||
function getDSB(cfg) {
|
||||
const { dealerNumber, storeNumber, branchNumber } = normalizeRRDealerFields(cfg || {});
|
||||
return { dealerNumber, storeNumber, branchNumber };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an address-like object to the template's <Address> block.
|
||||
*/
|
||||
function mapAddress(addr) {
|
||||
if (!addr) return undefined;
|
||||
const out = {
|
||||
Line1: addr.line1,
|
||||
Line2: addr.line2,
|
||||
City: addr.city,
|
||||
State: addr.state,
|
||||
PostalCode: addr.postal_code || addr.postalCode,
|
||||
Country: addr.country
|
||||
};
|
||||
return hasAny(out) ? out : undefined;
|
||||
}
|
||||
|
||||
//
|
||||
// ===================== CUSTOMER =====================
|
||||
//
|
||||
|
||||
/**
|
||||
* Map internal customer record to Rome CustomerInsertRq.
|
||||
*/
|
||||
function mapCustomerInsert(src) {
|
||||
const name = src.company_name?.trim() || [src.first_name, src.last_name].filter(Boolean).join(" ").trim();
|
||||
return {
|
||||
CustomerNumber: src.external_id, // optional
|
||||
CustomerType: src.type === "BUSINESS" ? "BUSINESS" : "RETAIL",
|
||||
CustomerName: name,
|
||||
DisplayName: src.display_name || name,
|
||||
Language: src.language || "EN",
|
||||
TaxExempt: src.tax_exempt ? "Y" : "N",
|
||||
Active: src.active ? "Y" : "N",
|
||||
Addresses: (src.addresses || []).map((a) => ({
|
||||
Type: a.type || "P",
|
||||
Line1: a.line1,
|
||||
Line2: a.line2,
|
||||
City: a.city,
|
||||
State: a.state,
|
||||
PostalCode: a.postal_code,
|
||||
Country: a.country
|
||||
})),
|
||||
Phones: (src.phones || []).map((p) => ({
|
||||
Type: p.type || "H",
|
||||
Number: p.number,
|
||||
Extension: p.extension,
|
||||
Preferred: p.preferred ? "Y" : "N"
|
||||
})),
|
||||
Emails: (src.emails || []).map((e) => ({
|
||||
Type: e.type || "W",
|
||||
Address: e.address,
|
||||
Preferred: e.preferred ? "Y" : "N"
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map internal customer record to Rome CustomerUpdateRq.
|
||||
*/
|
||||
function mapCustomerUpdate(customer, bodyshopConfig) {
|
||||
if (!customer) return {};
|
||||
return {
|
||||
...mapCustomerInsert(customer, bodyshopConfig),
|
||||
RequestId: `CUST-UPDATE-${customer.id}`
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// ===================== VEHICLE =====================
|
||||
//
|
||||
|
||||
/**
|
||||
* Map vehicle to Rome ServiceVehicleAddRq.
|
||||
*/
|
||||
function mapServiceVehicle(vehicle, ownerCustomer, bodyshopConfig) {
|
||||
if (!vehicle) return {};
|
||||
const { dealerNumber, storeNumber, branchNumber } = getDSB(bodyshopConfig);
|
||||
|
||||
return {
|
||||
DealerCode: bodyshopConfig?.dealer_code || "ROME",
|
||||
DealerNumber: dealerNumber,
|
||||
StoreNumber: storeNumber,
|
||||
BranchNumber: branchNumber,
|
||||
RequestId: `VEH-${vehicle.id}`,
|
||||
|
||||
CustomerId: ownerCustomer?.external_id,
|
||||
|
||||
VIN: vehicle.vin,
|
||||
UnitNumber: vehicle.unit_number,
|
||||
StockNumber: vehicle.stock_number,
|
||||
Year: num(vehicle.year),
|
||||
Make: vehicle.make,
|
||||
Model: vehicle.model,
|
||||
Trim: vehicle.trim,
|
||||
BodyStyle: vehicle.body_style,
|
||||
Transmission: vehicle.transmission,
|
||||
Engine: vehicle.engine,
|
||||
FuelType: vehicle.fuel_type,
|
||||
DriveType: vehicle.drive_type,
|
||||
Color: vehicle.color,
|
||||
LicensePlate: vehicle.license_plate,
|
||||
LicenseState: vehicle.license_state,
|
||||
Odometer: num(vehicle.odometer),
|
||||
OdometerUnits: vehicle.odometer_units || "KM",
|
||||
InServiceDate: formatDate(vehicle.in_service_date),
|
||||
|
||||
Insurance: vehicle.insurance
|
||||
? {
|
||||
CompanyName: vehicle.insurance.company,
|
||||
PolicyNumber: vehicle.insurance.policy,
|
||||
ExpirationDate: formatDate(vehicle.insurance.expiration_date)
|
||||
}
|
||||
: undefined,
|
||||
|
||||
Warranty: vehicle.warranty
|
||||
? {
|
||||
WarrantyCompany: vehicle.warranty.company,
|
||||
WarrantyNumber: vehicle.warranty.number,
|
||||
WarrantyType: vehicle.warranty.type,
|
||||
ExpirationDate: formatDate(vehicle.warranty.expiration_date)
|
||||
}
|
||||
: undefined,
|
||||
|
||||
VehicleNotes: vehicle.notes?.length ? { Items: vehicle.notes.map((n) => n.text || n) } : undefined
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// ===================== REPAIR ORDER =====================
|
||||
//
|
||||
|
||||
/**
|
||||
* Map internal job to Rome RepairOrderInsertRq.
|
||||
* NOTE: The CreateRepairOrder.xml template expects *flat* fields for Customer and ServiceVehicle
|
||||
* (no {{#Customer}} or {{#ServiceVehicle}} sections). Therefore, we flatten those values here.
|
||||
*/
|
||||
function mapRepairOrderCreate(job, bodyshopConfig) {
|
||||
if (!job) return {};
|
||||
const { dealerNumber, storeNumber, branchNumber } = getDSB(bodyshopConfig);
|
||||
|
||||
const cust = job.customer || {};
|
||||
const veh = job.vehicle || {};
|
||||
|
||||
// Prefer a concrete address on the customer, fall back to job-level
|
||||
const customerAddress = cust.address || job.customer_address || job.address || undefined;
|
||||
|
||||
return {
|
||||
// Routing/meta we keep available for logging or other templates
|
||||
DealerCode: bodyshopConfig?.dealer_code || "ROME",
|
||||
DealerNumber: dealerNumber,
|
||||
StoreNumber: storeNumber,
|
||||
BranchNumber: branchNumber,
|
||||
RequestId: `RO-${job.id}`,
|
||||
Environment: process.env.NODE_ENV,
|
||||
|
||||
// Header fields
|
||||
RepairOrderNumber: job.ro_number,
|
||||
DmsRepairOrderId: job.external_id,
|
||||
|
||||
OpenDate: formatDate(job.open_date),
|
||||
PromisedDate: formatDate(job.promised_date),
|
||||
CloseDate: formatDate(job.close_date),
|
||||
|
||||
ServiceAdvisorId: job.advisor_id,
|
||||
TechnicianId: job.technician_id,
|
||||
Department: job.department,
|
||||
ProfitCenter: job.profit_center,
|
||||
|
||||
ROType: job.ro_type,
|
||||
Status: job.status,
|
||||
IsBodyShop: "true",
|
||||
DRPFlag: toBoolStr(!!job.drp_flag) || "false",
|
||||
|
||||
// Customer block is FLAT (template does not use {{#Customer}} section)
|
||||
CustomerId: cust.external_id,
|
||||
CustomerName: cust.full_name || [cust.first_name, cust.last_name].filter(Boolean).join(" ").trim() || undefined,
|
||||
PhoneNumber: cust.phone,
|
||||
EmailAddress: cust.email,
|
||||
Address: mapAddress(customerAddress),
|
||||
|
||||
// ServiceVehicle block is FLAT (template does not use {{#ServiceVehicle}} section)
|
||||
VehicleId: veh.external_id,
|
||||
VIN: veh.vin,
|
||||
LicensePlate: veh.license_plate,
|
||||
Year: num(veh.year),
|
||||
Make: veh.make,
|
||||
Model: veh.model,
|
||||
Odometer: num(veh.odometer),
|
||||
Color: veh.color,
|
||||
|
||||
// Lines
|
||||
JobLines: (job.joblines || []).map((l, i) => ({
|
||||
Sequence: i + 1,
|
||||
ParentSequence: l.parent_sequence,
|
||||
LineType: l.line_type,
|
||||
Category: l.category,
|
||||
OpCode: l.op_code,
|
||||
Description: l.description,
|
||||
LaborHours: num(l.labor_hours),
|
||||
LaborRate: num(l.labor_rate),
|
||||
PartNumber: l.part_number,
|
||||
PartDescription: l.part_description,
|
||||
Quantity: num(l.quantity),
|
||||
UnitPrice: num(l.unit_price),
|
||||
ExtendedPrice: num(l.extended_price),
|
||||
DiscountAmount: num(l.discount_amount),
|
||||
TaxCode: l.tax_code,
|
||||
GLAccount: l.gl_account,
|
||||
ControlNumber: l.control_number,
|
||||
Taxes: l.taxes?.length
|
||||
? {
|
||||
Items: l.taxes.map((t) => ({
|
||||
Code: t.code,
|
||||
Amount: num(t.amount),
|
||||
Rate: num(t.rate)
|
||||
}))
|
||||
}
|
||||
: undefined
|
||||
})),
|
||||
|
||||
// Totals
|
||||
Totals: hasAny({
|
||||
Currency: job.currency || "CAD",
|
||||
LaborTotal: job.totals?.labor,
|
||||
PartsTotal: job.totals?.parts,
|
||||
MiscTotal: job.totals?.misc,
|
||||
DiscountTotal: job.totals?.discount,
|
||||
TaxTotal: job.totals?.tax,
|
||||
GrandTotal: job.totals?.grand
|
||||
})
|
||||
? {
|
||||
Currency: job.currency || "CAD",
|
||||
LaborTotal: num(job.totals?.labor),
|
||||
PartsTotal: num(job.totals?.parts),
|
||||
MiscTotal: num(job.totals?.misc),
|
||||
DiscountTotal: num(job.totals?.discount),
|
||||
TaxTotal: num(job.totals?.tax),
|
||||
GrandTotal: num(job.totals?.grand)
|
||||
}
|
||||
: undefined,
|
||||
|
||||
// Payments
|
||||
Payments: job.payments?.length
|
||||
? {
|
||||
Items: job.payments.map((p) => ({
|
||||
PayerType: p.payer_type,
|
||||
PayerName: p.payer_name,
|
||||
Amount: num(p.amount),
|
||||
Method: p.method,
|
||||
Reference: p.reference,
|
||||
ControlNumber: p.control_number
|
||||
}))
|
||||
}
|
||||
: undefined,
|
||||
|
||||
// Insurance
|
||||
Insurance: job.insurance
|
||||
? {
|
||||
CompanyName: job.insurance.company,
|
||||
ClaimNumber: job.insurance.claim_number,
|
||||
AdjusterName: job.insurance.adjuster_name,
|
||||
AdjusterPhone: job.insurance.adjuster_phone
|
||||
}
|
||||
: undefined,
|
||||
|
||||
// Notes
|
||||
Notes: job.notes?.length ? { Items: job.notes.map((n) => n.text || n) } : undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map for repair order updates.
|
||||
*/
|
||||
function mapRepairOrderUpdate(job, bodyshopConfig) {
|
||||
return {
|
||||
...mapRepairOrderCreate(job, bodyshopConfig),
|
||||
RequestId: `RO-UPDATE-${job.id}`
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// ===================== LOOKUPS =====================
|
||||
//
|
||||
|
||||
function mapAdvisorLookup(criteria, bodyshopConfig) {
|
||||
const { dealerNumber, storeNumber, branchNumber } = getDSB(bodyshopConfig);
|
||||
return {
|
||||
DealerCode: bodyshopConfig?.dealer_code || "ROME",
|
||||
DealerNumber: dealerNumber,
|
||||
StoreNumber: storeNumber,
|
||||
BranchNumber: branchNumber,
|
||||
RequestId: `LOOKUP-ADVISOR-${Date.now()}`,
|
||||
SearchCriteria: {
|
||||
Department: criteria.department || "Body Shop",
|
||||
Status: criteria.status || "ACTIVE"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mapPartsLookup(criteria, bodyshopConfig) {
|
||||
const { dealerNumber, storeNumber, branchNumber } = getDSB(bodyshopConfig);
|
||||
return {
|
||||
DealerCode: bodyshopConfig?.dealer_code || "ROME",
|
||||
DealerNumber: dealerNumber,
|
||||
StoreNumber: storeNumber,
|
||||
BranchNumber: branchNumber,
|
||||
RequestId: `LOOKUP-PART-${Date.now()}`,
|
||||
SearchCriteria: {
|
||||
PartNumber: criteria.part_number,
|
||||
Description: criteria.description,
|
||||
Make: criteria.make,
|
||||
Model: criteria.model,
|
||||
Year: num(criteria.year),
|
||||
Category: criteria.category,
|
||||
MaxResults: criteria.max_results || 25
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mapCombinedSearch(criteria = {}, bodyshopConfig) {
|
||||
const { dealerNumber, storeNumber, branchNumber } = getDSB(bodyshopConfig);
|
||||
|
||||
// accept nested or flat input
|
||||
const c = criteria || {};
|
||||
const cust = c.customer || c.Customer || {};
|
||||
const veh = c.vehicle || c.Vehicle || {};
|
||||
const comp = c.company || c.Company || {};
|
||||
|
||||
// build optional blocks only if they have at least one value
|
||||
const customerBlock = {
|
||||
FirstName: cust.firstName || cust.FirstName || c.firstName,
|
||||
LastName: cust.lastName || cust.LastName || c.lastName,
|
||||
PhoneNumber: cust.phoneNumber || cust.PhoneNumber || c.phoneNumber || c.phone,
|
||||
EmailAddress: cust.email || cust.EmailAddress || c.email,
|
||||
CompanyName: cust.companyName || cust.CompanyName || c.companyName,
|
||||
CustomerId: cust.customerId || cust.CustomerId || c.customerId
|
||||
};
|
||||
const vehicleBlock = {
|
||||
VIN: veh.vin || veh.VIN || c.vin,
|
||||
LicensePlate: veh.licensePlate || veh.LicensePlate || c.licensePlate,
|
||||
Make: veh.make || veh.Make || c.make,
|
||||
Model: veh.model || veh.Model || c.model,
|
||||
Year: veh.year != null ? String(veh.year) : c.year != null ? String(c.year) : undefined,
|
||||
VehicleId: veh.vehicleId || veh.VehicleId || c.vehicleId
|
||||
};
|
||||
const companyBlock = {
|
||||
Name: comp.name || comp.Name || c.companyName,
|
||||
Phone: comp.phone || comp.Phone || c.companyPhone
|
||||
};
|
||||
|
||||
return {
|
||||
DealerCode: bodyshopConfig?.dealer_code || "ROME",
|
||||
DealerName: bodyshopConfig?.dealer_name,
|
||||
DealerNumber: dealerNumber,
|
||||
StoreNumber: storeNumber,
|
||||
BranchNumber: branchNumber,
|
||||
|
||||
RequestId: c.requestId || `COMBINED-${Date.now()}`,
|
||||
Environment: process.env.NODE_ENV,
|
||||
|
||||
// Only include these blocks when they have content; Mustache {{#Block}} respects undefined
|
||||
Customer: hasAny(customerBlock) ? customerBlock : undefined,
|
||||
Vehicle: hasAny(vehicleBlock) ? vehicleBlock : undefined, // template wraps as <rr:ServiceVehicle>…</rr:ServiceVehicle>
|
||||
Company: hasAny(companyBlock) ? companyBlock : undefined,
|
||||
|
||||
// Search behavior flags
|
||||
SearchMode: c.searchMode || c.SearchMode, // EXACT | PARTIAL
|
||||
ExactMatch: toBoolStr(c.exactMatch ?? c.ExactMatch),
|
||||
PartialMatch: toBoolStr(c.partialMatch ?? c.PartialMatch),
|
||||
CaseInsensitive: toBoolStr(c.caseInsensitive ?? c.CaseInsensitive),
|
||||
|
||||
// Result shaping (default to true when unspecified)
|
||||
ReturnCustomers: toBoolStr(c.returnCustomers ?? c.ReturnCustomers ?? true),
|
||||
ReturnVehicles: toBoolStr(c.returnVehicles ?? c.ReturnVehicles ?? true),
|
||||
ReturnCompanies: toBoolStr(c.returnCompanies ?? c.ReturnCompanies ?? true),
|
||||
|
||||
// Paging / sorting
|
||||
MaxResults: c.maxResults ?? c.MaxResults,
|
||||
PageNumber: c.pageNumber ?? c.PageNumber,
|
||||
SortBy: c.sortBy ?? c.SortBy, // e.g., NAME, VIN, PARTNUMBER
|
||||
SortDirection: c.sortDirection ?? c.SortDirection // ASC | DESC
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mapCustomerInsert,
|
||||
mapCustomerUpdate,
|
||||
mapServiceVehicle,
|
||||
mapRepairOrderCreate,
|
||||
mapRepairOrderUpdate,
|
||||
mapAdvisorLookup,
|
||||
mapPartsLookup,
|
||||
mapCombinedSearch
|
||||
};
|
||||
@@ -1,165 +1,17 @@
|
||||
/**
|
||||
* @file rr-repair-orders.js
|
||||
* @description Rome (Reynolds & Reynolds) Repair Order Integration.
|
||||
* Handles creation and updates of repair orders (BSMRepairOrderRq/Resp).
|
||||
*/
|
||||
const { withClient } = require("./withClient");
|
||||
|
||||
"use strict";
|
||||
|
||||
const { MakeRRCall } = require("./rr-helpers");
|
||||
const { mapRepairOrderCreate, mapRepairOrderUpdate } = require("./rr-mappers");
|
||||
const RRLogger = require("./rr-logger");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
|
||||
/**
|
||||
* Very light sanity checks before we build XML.
|
||||
* We keep these minimal because the mapper may derive some fields.
|
||||
* Throws RrApiError when a required precondition is missing.
|
||||
* @param {"CreateRepairOrder"|"UpdateRepairOrder"} action
|
||||
* @param {Object} job
|
||||
*/
|
||||
function preflight(action, job) {
|
||||
if (!job || !job.id) {
|
||||
throw new RrApiError("Missing job payload or job.id", "RR_BAD_JOB_PAYLOAD");
|
||||
}
|
||||
// VIN is almost always required for BSM RO flows
|
||||
const vin = job?.vehicle?.vin || job?.vehicle?.VIN || job?.VIN || job?.vin;
|
||||
if (!vin) {
|
||||
throw new RrApiError("Missing VIN on job.vehicle", "RR_MISSING_VIN");
|
||||
}
|
||||
|
||||
if (action === "UpdateRepairOrder") {
|
||||
// If your mapper expects a DMS RO number or an external RO number,
|
||||
// you can tighten this guard based on your schema, e.g.:
|
||||
// const hasKey = job?.dms_ro_no || job?.external_ro_number || job?.roNumber;
|
||||
// if (!hasKey) throw new RrApiError("Missing RO key for update", "RR_MISSING_RO_KEY");
|
||||
}
|
||||
async function createRepairOrder({ bodyshopId, payload }) {
|
||||
return withClient(bodyshopId, async (client, routing) => {
|
||||
const res = await client.createRepairOrder(payload, { routing });
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an explicit ApplicationArea override so the envelope is always correct,
|
||||
* even if the helper falls back to defaults. We include routing info
|
||||
* and set Task/ReferenceId for BSM repair orders.
|
||||
* @param {"CreateRepairOrder"|"UpdateRepairOrder"} action
|
||||
* @param {Object} cfg
|
||||
*/
|
||||
function buildAppArea(action, cfg) {
|
||||
const isCreate = action === "CreateRepairOrder";
|
||||
return {
|
||||
Sender: {
|
||||
Component: "Rome",
|
||||
Task: "BSMRO",
|
||||
ReferenceId: isCreate ? "Insert" : "Update"
|
||||
},
|
||||
Destination: {
|
||||
DealerNumber: cfg?.DealerNumber || cfg?.dealerNumber,
|
||||
StoreNumber: cfg?.StoreNumber || cfg?.storeNumber,
|
||||
AreaNumber: cfg?.AreaNumber || cfg?.areaNumber,
|
||||
DestinationNameCode: "RR"
|
||||
}
|
||||
// CreationDateTime and BODId will be provided by rr-helpers if omitted.
|
||||
};
|
||||
async function updateRepairOrder({ bodyshopId, payload }) {
|
||||
return withClient(bodyshopId, async (client, routing) => {
|
||||
const res = await client.updateRepairOrder(payload, { routing });
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new repair order in Rome.
|
||||
* @param {Socket} socket - active socket connection
|
||||
* @param {Object} job - Hasura job object (including vehicle, customer, joblines)
|
||||
* @param {Object} bodyshopConfig - DMS config for current bodyshop
|
||||
* @returns {Promise<Object>} normalized result
|
||||
*/
|
||||
async function createRepairOrder(socket, job, bodyshopConfig) {
|
||||
const action = "CreateRepairOrder";
|
||||
const template = "CreateRepairOrder"; // maps to xml-templates/CreateRepairOrder.xml
|
||||
|
||||
try {
|
||||
RRLogger(socket, "info", `Starting RR ${action} for job ${job?.id}`, {
|
||||
jobid: job?.id,
|
||||
dealer: bodyshopConfig?.DealerNumber || bodyshopConfig?.dealerNumber,
|
||||
store: bodyshopConfig?.StoreNumber || bodyshopConfig?.storeNumber,
|
||||
area: bodyshopConfig?.AreaNumber || bodyshopConfig?.areaNumber
|
||||
});
|
||||
|
||||
preflight(action, job);
|
||||
const data = mapRepairOrderCreate(job, bodyshopConfig);
|
||||
|
||||
const resultXml = await MakeRRCall({
|
||||
action,
|
||||
body: { template, data },
|
||||
appArea: buildAppArea(action, bodyshopConfig),
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig,
|
||||
jobid: job.id
|
||||
});
|
||||
|
||||
RRLogger(socket, "debug", `${action} completed successfully`, { jobid: job.id });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
dms: "Rome",
|
||||
jobid: job.id,
|
||||
action,
|
||||
xml: resultXml
|
||||
};
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error in ${action} for job ${job?.id}`, {
|
||||
message: error?.message,
|
||||
stack: error?.stack
|
||||
});
|
||||
throw new RrApiError(`RR CreateRepairOrder failed: ${error.message}`, "CREATE_RO_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing repair order in Rome.
|
||||
* @param {Socket} socket
|
||||
* @param {Object} job
|
||||
* @param {Object} bodyshopConfig
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function updateRepairOrder(socket, job, bodyshopConfig) {
|
||||
const action = "UpdateRepairOrder";
|
||||
const template = "UpdateRepairOrder";
|
||||
|
||||
try {
|
||||
RRLogger(socket, "info", `Starting RR ${action} for job ${job?.id}`, {
|
||||
jobid: job?.id,
|
||||
dealer: bodyshopConfig?.DealerNumber || bodyshopConfig?.dealerNumber,
|
||||
store: bodyshopConfig?.StoreNumber || bodyshopConfig?.storeNumber,
|
||||
area: bodyshopConfig?.AreaNumber || bodyshopConfig?.areaNumber
|
||||
});
|
||||
|
||||
preflight(action, job);
|
||||
const data = mapRepairOrderUpdate(job, bodyshopConfig);
|
||||
|
||||
const resultXml = await MakeRRCall({
|
||||
action,
|
||||
body: { template, data },
|
||||
appArea: buildAppArea(action, bodyshopConfig),
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig,
|
||||
jobid: job.id
|
||||
});
|
||||
|
||||
RRLogger(socket, "debug", `${action} completed successfully`, { jobid: job.id });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
dms: "Rome",
|
||||
jobid: job.id,
|
||||
action,
|
||||
xml: resultXml
|
||||
};
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error in ${action} for job ${job?.id}`, {
|
||||
message: error?.message,
|
||||
stack: error?.stack
|
||||
});
|
||||
throw new RrApiError(`RR UpdateRepairOrder failed: ${error.message}`, "UPDATE_RO_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createRepairOrder,
|
||||
updateRepairOrder
|
||||
};
|
||||
module.exports = { createRepairOrder, updateRepairOrder };
|
||||
|
||||
@@ -1,484 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* rr-test.js — end-to-end exerciser for Reynolds "Rome"/STAR actions.
|
||||
*
|
||||
* Key improvements vs prior version:
|
||||
* - Prints FULL XML responses (no truncation).
|
||||
* - CombinedSearch now sends at least one search criterion by default.
|
||||
* - InsertCustomer sets required IBFlag + LastName/FirstName.
|
||||
* - Orchestrates dependent steps (CombinedSearch → InsertCustomer → InsertVehicle → Create/Update RO).
|
||||
*
|
||||
* Usage examples:
|
||||
* node rr-test.js --ping
|
||||
* node rr-test.js --all
|
||||
* node rr-test.js --combined --last "SMITH" --phone 9375550001 --vin 1FTFW1E50JFA00000
|
||||
*
|
||||
* Common flags:
|
||||
* --first, --last, --phone, --email, --vin, --plate, --advisor, --max N
|
||||
*
|
||||
* Env fallbacks:
|
||||
* RR_TEST_LAST, RR_TEST_FIRST, RR_TEST_PHONE, RR_TEST_EMAIL, RR_TEST_VIN, RR_TEST_PLATE, RR_TEST_PARTDESC
|
||||
*
|
||||
* Dealer creds/env (used only in this test harness; runtime pulls from DB):
|
||||
* RR_DEALER_NUMBER, RR_STORE_NUMBER, RR_AREA_NUMBER, RR_USERNAME, RR_PASSWORD, RR_ENDPOINT
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const fsp = require("fs/promises");
|
||||
const minimist = require("minimist");
|
||||
const dayjs = require("dayjs");
|
||||
const { XMLParser } = require("fast-xml-parser");
|
||||
|
||||
// Load dev env if present
|
||||
const envPath = path.resolve(__dirname, "../../.env.development");
|
||||
if (fs.existsSync(envPath)) {
|
||||
require("dotenv").config({ path: envPath });
|
||||
console.log(envPath);
|
||||
console.log(
|
||||
`[dotenv@${require("dotenv/package.json").version}] injecting env (${Object.keys(process.env).length}) from ../../.env.development`
|
||||
);
|
||||
}
|
||||
|
||||
const { MakeRRCall, parseRRResponse } = require("./rr-helpers");
|
||||
const RRLogger = require("./rr-logger");
|
||||
|
||||
// CLI flags
|
||||
const argv = minimist(process.argv.slice(2));
|
||||
const FLAG = (k, d) => (argv[k] !== undefined ? argv[k] : d);
|
||||
|
||||
// For templates that expect STAR NS in {{STAR_NS}}
|
||||
const STAR_NS = "http://www.starstandards.org/STAR";
|
||||
|
||||
// Basic XML parser for optional extractions
|
||||
const parser = new XMLParser({ ignoreAttributes: false, removeNSPrefix: true });
|
||||
|
||||
// Ensure templates exist
|
||||
async function verifyTemplates() {
|
||||
const required = [
|
||||
"CombinedSearch",
|
||||
"GetAdvisors",
|
||||
"GetParts",
|
||||
"InsertCustomer",
|
||||
"UpdateCustomer",
|
||||
"InsertServiceVehicle",
|
||||
"CreateRepairOrder",
|
||||
"UpdateRepairOrder"
|
||||
];
|
||||
for (const t of required) {
|
||||
const p = path.join(__dirname, "xml-templates", `${t}.xml`);
|
||||
await fsp.readFile(p, "utf8");
|
||||
}
|
||||
console.log("✅ Templates verified.");
|
||||
}
|
||||
|
||||
// Format AppArea per action (Sender.Task/ReferenceId)
|
||||
function appAreaFor(action, cfg) {
|
||||
const pad2 = (x) => String(x ?? "").padStart(2, "0");
|
||||
const base = {
|
||||
CreationDateTime: new Date().toISOString(),
|
||||
BODId: `BOD-${Date.now()}`,
|
||||
Sender: { Component: "Rome", CreatorNameCode: "RCI", SenderNameCode: "RCI" },
|
||||
Destination: {
|
||||
DealerNumber: cfg.dealerNumber,
|
||||
StoreNumber: pad2(cfg.storeNumber),
|
||||
AreaNumber: pad2(cfg.branchNumber || "01")
|
||||
}
|
||||
};
|
||||
switch (action) {
|
||||
case "CreateRepairOrder":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "BSMRO", ReferenceId: "Insert" } };
|
||||
case "UpdateRepairOrder":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "BSMRO", ReferenceId: "Update" } };
|
||||
case "InsertCustomer":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "CU", ReferenceId: "Insert" } };
|
||||
case "UpdateCustomer":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "CU", ReferenceId: "Update" } };
|
||||
case "InsertServiceVehicle":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "SV", ReferenceId: "Insert" } };
|
||||
case "CombinedSearch":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "SV", ReferenceId: "Query" } };
|
||||
case "GetAdvisors":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "RCI", ReferenceId: "Lookup" } };
|
||||
case "GetParts":
|
||||
return { ...base, Sender: { ...base.Sender, Task: "RCI", ReferenceId: "Lookup" } };
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
// Build cfg for test harness (env fallbacks allowed here)
|
||||
function cfgFromEnv() {
|
||||
return {
|
||||
baseUrl:
|
||||
process.env.RR_ENDPOINT || process.env.RR_BASE_URL || "https://b2b-test.reyrey.com/Sync/RCI/Rome/Receive.ashx",
|
||||
username: process.env.RR_USERNAME || "Rome",
|
||||
password: process.env.RR_PASSWORD || "",
|
||||
timeout: Number(process.env.RR_TIMEOUT || 30000),
|
||||
dealerNumber: process.env.RR_DEALER_NUMBER || "PPERASV02000000",
|
||||
storeNumber: process.env.RR_STORE_NUMBER || "05",
|
||||
branchNumber: process.env.RR_AREA_NUMBER || "03"
|
||||
};
|
||||
}
|
||||
|
||||
// Utility: get a value from parsed JSON or raw XML (element or attribute)
|
||||
function findFirstId(parsed, rawXml, keyRegex) {
|
||||
// search parsed object
|
||||
if (parsed && typeof parsed === "object") {
|
||||
const stack = [parsed];
|
||||
while (stack.length) {
|
||||
const cur = stack.pop();
|
||||
if (cur && typeof cur === "object") {
|
||||
for (const [k, v] of Object.entries(cur)) {
|
||||
if (keyRegex.test(k) && (typeof v === "string" || typeof v === "number")) {
|
||||
const s = String(v).trim();
|
||||
if (s) return s;
|
||||
}
|
||||
if (v && typeof v === "object") stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// search raw XML: <Key>value</Key> or Key="value"
|
||||
if (rawXml) {
|
||||
const re1 = new RegExp(`<([A-Za-z0-9:_-]*${keyRegex.source}[A-Za-z0-9:_-]*)>([^<]+)</\\1>`, "i");
|
||||
const m1 = re1.exec(rawXml);
|
||||
if (m1 && m1[2]) return m1[2].trim();
|
||||
const re2 = new RegExp(`${keyRegex.source}="([^"]+)"`, "i");
|
||||
const m2 = re2.exec(rawXml);
|
||||
if (m2 && m2[1]) return m2[1].trim();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Call wrapper: prints FULL XML
|
||||
async function callRR(action, dataObj, cfg) {
|
||||
const respXml = await MakeRRCall({
|
||||
action,
|
||||
dealerConfig: cfg,
|
||||
body: { data: { STAR_NS, ...dataObj }, appArea: appAreaFor(action, cfg) }
|
||||
});
|
||||
const parsed = parseRRResponse(respXml);
|
||||
return { respXml, parsed };
|
||||
}
|
||||
|
||||
// --------- Individual Steps (full XML printed) ---------
|
||||
|
||||
async function stepGetAdvisors(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
const data = {
|
||||
SearchCriteria: {
|
||||
Department: FLAG("dept", "B"), // critical to avoid 201
|
||||
Status: FLAG("status", "ACTIVE"),
|
||||
MaxResults: Number(FLAG("max", 5)) || 5
|
||||
}
|
||||
};
|
||||
const { respXml, parsed } = await callRR("GetAdvisors", data, cfg);
|
||||
|
||||
console.log("\n[GetAdvisors] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
const advId =
|
||||
findFirstId(parsed.parsed, respXml, /(Advisor(ID|No|Number)?|AdvNo)/i) ||
|
||||
findFirstId(parsed.parsed, respXml, /(Employee(ID|No|Number)?)/i);
|
||||
if (advId) ctx.advisorId = advId;
|
||||
|
||||
return { ok: true, code: parsed.code, advisorId: advId || null };
|
||||
}
|
||||
|
||||
async function stepCombinedSearch(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
|
||||
// Provide at least one criterion by default to avoid 201
|
||||
const defaultLast = FLAG("last", process.env.RR_TEST_LAST || "SMITH");
|
||||
const defaultPhone = FLAG("phone", process.env.RR_TEST_PHONE || "");
|
||||
const defaultVin = FLAG("vin", process.env.RR_TEST_VIN || "");
|
||||
|
||||
const data = {
|
||||
MaxResults: Number(FLAG("max", 5)) || 5,
|
||||
Customer: {},
|
||||
Vehicle: {}
|
||||
};
|
||||
if (defaultLast) data.Customer.LastName = defaultLast;
|
||||
if (defaultPhone) data.Customer.PhoneNumber = defaultPhone;
|
||||
if (defaultVin) data.Vehicle.VIN = defaultVin;
|
||||
|
||||
const { respXml, parsed } = await callRR("CombinedSearch", data, cfg);
|
||||
|
||||
console.log("\n[CombinedSearch] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
const custId = findFirstId(parsed.parsed, respXml, /(Cust(omer)?(No|Id|ID|Number|Key)|NameRecId)/i);
|
||||
const vehId = findFirstId(parsed.parsed, respXml, /(Veh(icle)?(No|Id|ID|Number|Key))/i);
|
||||
const vin = findFirstId(parsed.parsed, respXml, /VIN/i);
|
||||
|
||||
if (custId) ctx.customerId = custId;
|
||||
if (vehId) ctx.vehicleId = vehId;
|
||||
if (vin) ctx.vin = vin;
|
||||
|
||||
return { ok: true, code: parsed.code, customerId: custId || null, vehicleId: vehId || null, vin: vin || null };
|
||||
}
|
||||
|
||||
async function stepInsertCustomer(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
|
||||
// RR requires IBFlag and LastName for individual customers
|
||||
const ts = dayjs().format("YYYYMMDD-HHmmss");
|
||||
const firstName = FLAG("first", process.env.RR_TEST_FIRST || "QA");
|
||||
const lastName = FLAG("last", process.env.RR_TEST_LAST || "Test");
|
||||
const email = FLAG("email", process.env.RR_TEST_EMAIL || `qa.${ts}@example.com`);
|
||||
const phone = FLAG(
|
||||
"phone",
|
||||
process.env.RR_TEST_PHONE || `937555${String(Math.floor(Math.random() * 10000)).padStart(4, "0")}`
|
||||
);
|
||||
|
||||
const data = {
|
||||
IBFlag: "I", // <== REQUIRED (I=Individual, B=Business)
|
||||
FirstName: firstName, // <== REQUIRED with IBFlag=I
|
||||
LastName: lastName, // <== REQUIRED with IBFlag=I
|
||||
Active: "Y",
|
||||
Phones: [{ Type: "Mobile", CountryCode: "1", Number: phone, Preferred: "Y" }],
|
||||
Emails: [{ Type: "Personal", Address: email, Preferred: "Y" }],
|
||||
Addresses: [
|
||||
{
|
||||
Type: "Home",
|
||||
Line1: "123 Test St",
|
||||
City: "Dayton",
|
||||
State: "OH",
|
||||
PostalCode: "45402",
|
||||
Country: "US",
|
||||
IsPrimary: "Y"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const { respXml, parsed } = await callRR("InsertCustomer", data, cfg);
|
||||
|
||||
console.log("\n[InsertCustomer] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
// RR often returns the new customer key as DMSRecKey attribute in TransStatus
|
||||
const custId = findFirstId(parsed.parsed, respXml, /(DMSRecKey|Cust(omer)?(No|Id|ID|Number|Key)|NameRecId)/i);
|
||||
|
||||
if (custId) ctx.customerId = custId;
|
||||
return { ok: !!custId, code: parsed.code, customerId: custId || null };
|
||||
}
|
||||
|
||||
async function stepUpdateCustomer(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
if (!ctx.customerId) return { ok: false, code: "NO_CONTEXT", message: "No customerId" };
|
||||
|
||||
const data = {
|
||||
CustomerId: ctx.customerId,
|
||||
Emails: [{ Type: "Personal", Address: `qa.${dayjs().format("HHmmss")}@example.com`, Preferred: "Y" }]
|
||||
};
|
||||
|
||||
const { respXml, parsed } = await callRR("UpdateCustomer", data, cfg);
|
||||
|
||||
console.log("\n[UpdateCustomer] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
return { ok: parsed.success, code: parsed.code };
|
||||
}
|
||||
|
||||
async function stepInsertServiceVehicle(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
if (!ctx.customerId) return { ok: false, code: "NO_CONTEXT", message: "No customerId" };
|
||||
|
||||
const vin =
|
||||
FLAG("vin", process.env.RR_TEST_VIN) ||
|
||||
`1FTFW1E50JFA${String(Math.floor(Math.random() * 1000000)).padStart(6, "0")}`;
|
||||
|
||||
const data = {
|
||||
CustomerId: ctx.customerId,
|
||||
VIN: vin,
|
||||
Year: "2018",
|
||||
Make: "FORD",
|
||||
Model: "F-150",
|
||||
Color: "WHITE",
|
||||
LicensePlate:
|
||||
FLAG("plate", process.env.RR_TEST_PLATE) || `QA${String(Math.floor(Math.random() * 100000)).padStart(5, "0")}`,
|
||||
LicenseState: "OH",
|
||||
Odometer: "123456",
|
||||
OdometerUnits: "MI"
|
||||
};
|
||||
|
||||
const { respXml, parsed } = await callRR("InsertServiceVehicle", data, cfg);
|
||||
|
||||
console.log("\n[InsertServiceVehicle] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
const vehId = findFirstId(parsed.parsed, respXml, /(Veh(icle)?(No|Id|ID|Number|Key))/i);
|
||||
if (vehId) ctx.vehicleId = vehId;
|
||||
ctx.vin = vin;
|
||||
|
||||
return { ok: parsed.success || !!vehId, code: parsed.code, vehicleId: vehId || null, vin };
|
||||
}
|
||||
|
||||
async function stepCreateRepairOrder(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
if (!ctx.customerId) return { ok: false, code: "NO_CONTEXT", message: "No customerId" };
|
||||
|
||||
const roNumber = `BSM-${dayjs().format("MMDD-HHmmss")}-${String(Math.floor(Math.random() * 1000)).padStart(3, "0")}`;
|
||||
|
||||
const data = {
|
||||
RepairOrderNumber: roNumber,
|
||||
Department: "B",
|
||||
ROType: "INS",
|
||||
Status: "OPEN",
|
||||
IsBodyShop: "Y",
|
||||
ServiceAdvisorId: ctx.advisorId || FLAG("advisor", process.env.RR_TEST_ADVISOR) || undefined,
|
||||
Customer: {
|
||||
CustomerId: ctx.customerId,
|
||||
FirstName: FLAG("first", process.env.RR_TEST_FIRST || "QA"),
|
||||
LastName: FLAG("last", process.env.RR_TEST_LAST || "Test"),
|
||||
PhoneNumber: FLAG("phone", process.env.RR_TEST_PHONE || "9375550000"),
|
||||
EmailAddress: FLAG("email", process.env.RR_TEST_EMAIL || "qa@example.com")
|
||||
},
|
||||
ServiceVehicle: {
|
||||
VIN: ctx.vin || FLAG("vin", process.env.RR_TEST_VIN) || undefined,
|
||||
Odometer: "4321"
|
||||
},
|
||||
Totals: {
|
||||
Currency: "USD",
|
||||
LaborTotal: "0",
|
||||
PartsTotal: "0",
|
||||
MiscTotal: "0",
|
||||
DiscountTotal: "0",
|
||||
TaxTotal: "0",
|
||||
GrandTotal: "0"
|
||||
}
|
||||
};
|
||||
|
||||
const respXml = await MakeRRCall({
|
||||
action: "CreateRepairOrder",
|
||||
dealerConfig: cfg,
|
||||
body: { data: { STAR_NS, ...data }, appArea: appAreaFor("CreateRepairOrder", cfg) }
|
||||
});
|
||||
|
||||
const parsed = parseRRResponse(respXml);
|
||||
|
||||
console.log("\n[CreateRepairOrder] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
const roNo = findFirstId(parsed.parsed, respXml, /(DMSRoNo|RO?No|OutsdRoNo|RepairOrder(Id|Number))/i) || roNumber;
|
||||
|
||||
ctx.roNumber = roNo;
|
||||
|
||||
return { ok: parsed.success, code: parsed.code, roNumber: roNo };
|
||||
}
|
||||
|
||||
async function stepUpdateRepairOrder(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
if (!ctx.roNumber) return { ok: false, code: "NO_CONTEXT", message: "No roNumber" };
|
||||
|
||||
const data = {
|
||||
RepairOrderNumber: ctx.roNumber,
|
||||
Department: "B",
|
||||
Status: "OPEN",
|
||||
Totals: { LaborTotal: "0", PartsTotal: "0", MiscTotal: "0", TaxTotal: "0", GrandTotal: "0" }
|
||||
};
|
||||
|
||||
const { respXml, parsed } = await callRR("UpdateRepairOrder", data, cfg);
|
||||
|
||||
console.log("\n[UpdateRepairOrder] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
return { ok: parsed.success, code: parsed.code };
|
||||
}
|
||||
|
||||
async function stepGetParts(ctx) {
|
||||
const cfg = ctx.cfg;
|
||||
|
||||
const data = {
|
||||
MaxResults: Number(FLAG("max", 5)) || 5,
|
||||
SearchMode: "Description",
|
||||
Description: FLAG("partdesc", process.env.RR_TEST_PARTDESC || "clip")
|
||||
};
|
||||
|
||||
const { respXml, parsed } = await callRR("GetParts", data, cfg);
|
||||
|
||||
console.log("\n[GetParts] RESPONSE (FULL):\n");
|
||||
console.log(respXml);
|
||||
|
||||
return { ok: parsed.success, code: parsed.code };
|
||||
}
|
||||
|
||||
// --------- Runner ---------
|
||||
|
||||
async function runAll() {
|
||||
await verifyTemplates();
|
||||
|
||||
const cfg = cfgFromEnv();
|
||||
const ctx = { cfg };
|
||||
|
||||
const results = [];
|
||||
|
||||
async function runStep(name, fn) {
|
||||
const t0 = Date.now();
|
||||
try {
|
||||
const res = await fn(ctx);
|
||||
results.push({ step: name, ok: !!res.ok, code: res.code || "OK", ms: Date.now() - t0 });
|
||||
console.log(`\n✔ ${name} done in ${Date.now() - t0}ms`);
|
||||
return res;
|
||||
} catch (err) {
|
||||
results.push({ step: name, ok: false, code: err.code || "ERR", ms: Date.now() - t0, error: err.message });
|
||||
console.error(`\n✖ ${name} failed: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Sequence (dependencies respected)
|
||||
await runStep("GetAdvisors", stepGetAdvisors);
|
||||
await runStep("CombinedSearch", stepCombinedSearch);
|
||||
|
||||
if (!ctx.customerId) await runStep("InsertCustomer", stepInsertCustomer);
|
||||
if (!ctx.vehicleId) await runStep("InsertServiceVehicle", stepInsertServiceVehicle);
|
||||
|
||||
await runStep("CreateRepairOrder", stepCreateRepairOrder);
|
||||
await runStep("UpdateRepairOrder", stepUpdateRepairOrder);
|
||||
await runStep("GetParts", stepGetParts);
|
||||
|
||||
// Summary
|
||||
console.log("\n=== SUMMARY ===");
|
||||
console.table(results.map((r) => ({ step: r.step, ok: r.ok ? "✅" : "❌", code: r.code, ms: r.ms })));
|
||||
const failed = results.some((r) => !r.ok);
|
||||
console.log(`Total: ${results.reduce((a, r) => a + r.ms, 0)}ms | Status: ${failed ? "FAIL" : "PASS"}`);
|
||||
if (failed) process.exitCode = 1;
|
||||
}
|
||||
|
||||
// Entrypoint
|
||||
(async () => {
|
||||
if (FLAG("ping", false)) {
|
||||
await verifyTemplates();
|
||||
const cfg = cfgFromEnv();
|
||||
console.log("\n▶ Calling Rome action: GetAdvisors");
|
||||
const r = await stepGetAdvisors({ cfg });
|
||||
if (r.ok) console.log("\n✅ RR call completed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (FLAG("all", true)) {
|
||||
await runAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Individual flags (optional)
|
||||
await verifyTemplates();
|
||||
const cfg = cfgFromEnv();
|
||||
const ctx = { cfg };
|
||||
const want = [];
|
||||
if (FLAG("get-advisors", false)) want.push(stepGetAdvisors);
|
||||
if (FLAG("combined", false)) want.push(stepCombinedSearch);
|
||||
if (FLAG("insert-customer", false)) want.push(stepInsertCustomer);
|
||||
if (FLAG("update-customer", false)) want.push(stepUpdateCustomer);
|
||||
if (FLAG("insert-vehicle", false)) want.push(stepInsertServiceVehicle);
|
||||
if (FLAG("create-ro", false)) want.push(stepCreateRepairOrder);
|
||||
if (FLAG("update-ro", false)) want.push(stepUpdateRepairOrder);
|
||||
if (FLAG("get-parts", false)) want.push(stepGetParts);
|
||||
|
||||
for (const fn of want) {
|
||||
const res = await fn(ctx).catch((e) => ({ ok: false, code: "ERR", error: e.message }));
|
||||
console.log(res);
|
||||
}
|
||||
})();
|
||||
@@ -1,126 +0,0 @@
|
||||
/**
|
||||
* @file rr-wsdl.js
|
||||
* @description Lightweight service description + utilities for the Rome (R&R) SOAP actions.
|
||||
* - Maps actions to SOAPAction headers (from rr-constants)
|
||||
* - Maps actions to Mustache template filenames (xml-templates/*.xml)
|
||||
* - Provides verification helpers to ensure templates exist
|
||||
* - Provides normalized SOAP headers used by the transport
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs/promises");
|
||||
const { RR_ACTIONS, RR_SOAP_HEADERS } = require("./rr-constants");
|
||||
const mustache = require("mustache");
|
||||
|
||||
// ---- Action <-> Template wiring ----
|
||||
// Keep action names consistent with rr-helpers / rr-lookup / rr-repair-orders / rr-customer
|
||||
const ACTION_TEMPLATES = Object.freeze({
|
||||
InsertCustomer: "InsertCustomer",
|
||||
UpdateCustomer: "UpdateCustomer",
|
||||
InsertServiceVehicle: "InsertServiceVehicle",
|
||||
CreateRepairOrder: "CreateRepairOrder",
|
||||
UpdateRepairOrder: "UpdateRepairOrder",
|
||||
GetAdvisors: "GetAdvisors",
|
||||
GetParts: "GetParts",
|
||||
CombinedSearch: "CombinedSearch"
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the SOAPAction string for a known action.
|
||||
* Throws if action is unknown.
|
||||
*/
|
||||
function getSoapAction(action) {
|
||||
const entry = RR_ACTIONS[action];
|
||||
if (!entry) {
|
||||
const known = Object.keys(RR_ACTIONS).join(", ");
|
||||
throw new Error(`Unknown RR action "${action}". Known: ${known}`);
|
||||
}
|
||||
return entry.soapAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template filename (without extension) for a known action.
|
||||
* e.g., "CreateRepairOrder" -> "CreateRepairOrder"
|
||||
*/
|
||||
function getTemplateForAction(action) {
|
||||
const tpl = ACTION_TEMPLATES[action];
|
||||
if (!tpl) {
|
||||
const known = Object.keys(ACTION_TEMPLATES).join(", ");
|
||||
throw new Error(`No template mapping for RR action "${action}". Known: ${known}`);
|
||||
}
|
||||
return tpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build headers for a SOAP request, including SOAPAction.
|
||||
* Consumers: rr-helpers (transport).
|
||||
*/
|
||||
function buildSoapHeadersForAction(action) {
|
||||
return {
|
||||
...RR_SOAP_HEADERS,
|
||||
SOAPAction: getSoapAction(action)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List all known actions with their SOAPAction + template.
|
||||
* Useful for diagnostics (e.g., /rr/actions route).
|
||||
*/
|
||||
function listActions() {
|
||||
return Object.keys(ACTION_TEMPLATES).map((action) => ({
|
||||
action,
|
||||
soapAction: getSoapAction(action),
|
||||
template: getTemplateForAction(action)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that every required template exists in xml-templates/.
|
||||
* Returns an array of issues; empty array means all good.
|
||||
*/
|
||||
async function verifyTemplatesExist() {
|
||||
const issues = [];
|
||||
const baseDir = path.join(__dirname, "xml-templates");
|
||||
|
||||
for (const [action, tpl] of Object.entries(ACTION_TEMPLATES)) {
|
||||
const filePath = path.join(baseDir, `${tpl}.xml`);
|
||||
try {
|
||||
const contents = await fs.readFile(filePath, "utf8"); // throws if missing
|
||||
try {
|
||||
// Parse-only to catch “Unclosed section …” and similar
|
||||
mustache.parse(contents);
|
||||
} catch (parseErr) {
|
||||
issues.push({ action, template: tpl, error: `Mustache parse error: ${parseErr.message}`, filePath });
|
||||
}
|
||||
} catch {
|
||||
issues.push({ action, template: tpl, error: `Missing file: ${filePath}` });
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
/**
|
||||
* Quick assert that throws if any template is missing.
|
||||
* You can call this once during boot and log the result.
|
||||
*/
|
||||
async function assertTemplates() {
|
||||
const issues = await verifyTemplatesExist();
|
||||
if (issues.length) {
|
||||
const msg =
|
||||
"RR xml-templates verification failed:\n" +
|
||||
issues.map((i) => ` - ${i.action} -> ${i.template}.xml :: ${i.error}`).join("\n");
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// Maps / helpers
|
||||
ACTION_TEMPLATES,
|
||||
listActions,
|
||||
getSoapAction,
|
||||
getTemplateForAction,
|
||||
buildSoapHeadersForAction,
|
||||
|
||||
// Verification
|
||||
verifyTemplatesExist,
|
||||
assertTemplates
|
||||
};
|
||||
@@ -1,18 +1,5 @@
|
||||
/**
|
||||
* @file rrRoutes.js
|
||||
* @description Express routes for Reynolds & Reynolds (Rome) integration.
|
||||
* Endpoints:
|
||||
* - POST /rr/customer/insert
|
||||
* - POST /rr/customer/update
|
||||
* - POST /rr/repair-order/create
|
||||
* - POST /rr/repair-order/update
|
||||
* - POST /rr/lookup/advisors
|
||||
* - POST /rr/lookup/parts
|
||||
* - POST /rr/lookup/combined-search
|
||||
* - POST /rr/export/job
|
||||
* - GET /rr/actions
|
||||
* - GET /rr/templates/verify
|
||||
*/
|
||||
// server/rr/rrRoutes.js
|
||||
"use strict";
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
@@ -20,19 +7,12 @@ const router = express.Router();
|
||||
const RRLogger = require("./rr-logger");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
|
||||
// Domain modules
|
||||
const customerApi = require("./rr-customer"); // insertCustomer, updateCustomer
|
||||
const roApi = require("./rr-repair-orders"); // createRepairOrder, updateRepairOrder
|
||||
const lookupApi = require("./rr-lookup"); // getAdvisors, getParts, combinedSearch
|
||||
const { exportJobToRR } = require("./rr-job-export"); // orchestrator
|
||||
const customerApi = require("./rr-customer");
|
||||
const roApi = require("./rr-repair-orders");
|
||||
const lookupApi = require("./rr-lookup");
|
||||
const { exportJobToRR } = require("./rr-job-export");
|
||||
|
||||
// Diagnostics
|
||||
const { listActions, verifyTemplatesExist } = require("./rr-wsdl");
|
||||
|
||||
// DB-driven RR config (no env fallback for dealer/store/branch here)
|
||||
const { getRRConfigForBodyshop } = require("./rr-config");
|
||||
|
||||
// -------------------- Helpers --------------------
|
||||
// --- helpers ---
|
||||
|
||||
function ok(res, payload = {}) {
|
||||
return res.json({ success: true, ...payload });
|
||||
@@ -44,174 +24,144 @@ function fail(res, error, status = 400) {
|
||||
}
|
||||
|
||||
function socketOf(req) {
|
||||
// If you stash a socket/logging context on the app, grab it; otherwise null
|
||||
return (req.app && req.app.get && req.app.get("socket")) || null;
|
||||
try {
|
||||
return req.app?.get?.("socket") || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the per-bodyshop RR config strictly from DB.
|
||||
* Looks for bodyshopId in:
|
||||
* - req.body.bodyshopId
|
||||
* - req.body.job?.shopid
|
||||
* - x-bodyshop-id header
|
||||
* Throws if not found.
|
||||
*/
|
||||
async function resolveRRConfigHttp(req) {
|
||||
const candidateHeader = req.get && req.get("x-bodyshop-id");
|
||||
const body = req.body || {};
|
||||
const bodyshopId = body.bodyshopId || (body.job && (body.job.shopid || body.job.bodyshopId)) || candidateHeader;
|
||||
|
||||
function requireBodyshopId(req) {
|
||||
const body = req?.body || {};
|
||||
const fromBody = body.bodyshopId;
|
||||
const fromJob = body.job && (body.job.shopid || body.job.bodyshopId);
|
||||
const fromHeader = typeof req.get === "function" ? req.get("x-bodyshop-id") : undefined;
|
||||
const bodyshopId = fromBody || fromJob || fromHeader;
|
||||
if (!bodyshopId) {
|
||||
throw new RrApiError(
|
||||
"Missing bodyshopId (expected in body.bodyshopId, body.job.shopid, or x-bodyshop-id header)",
|
||||
"Missing bodyshopId (in body.bodyshopId, body.job.shopid/bodyshopId, or x-bodyshop-id header)",
|
||||
"BAD_REQUEST"
|
||||
);
|
||||
}
|
||||
|
||||
return getRRConfigForBodyshop(bodyshopId);
|
||||
return bodyshopId;
|
||||
}
|
||||
|
||||
// -------------------- Customers --------------------
|
||||
// --- customers ---
|
||||
|
||||
router.post("/rr/customer/insert", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const { customer } = req.body || {};
|
||||
|
||||
try {
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const { customer } = req.body || {};
|
||||
if (!customer) throw new RrApiError("Missing 'customer' in request body", "BAD_REQUEST");
|
||||
const cfg = await resolveRRConfigHttp(req); // DB-driven, required
|
||||
const result = await customerApi.insertCustomer(socket, customer, cfg);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /customer/insert failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
const result = await customerApi.insertCustomer({ bodyshopId, payload: customer });
|
||||
RRLogger(socket)("info", "RR customer insert", { bodyshopId });
|
||||
return ok(res, { data: result.data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/customer/insert failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/rr/customer/update", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const { customer } = req.body || {};
|
||||
|
||||
try {
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const { customer } = req.body || {};
|
||||
if (!customer) throw new RrApiError("Missing 'customer' in request body", "BAD_REQUEST");
|
||||
const cfg = await resolveRRConfigHttp(req);
|
||||
const result = await customerApi.updateCustomer(socket, customer, cfg);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /customer/update failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
const result = await customerApi.updateCustomer({ bodyshopId, payload: customer });
|
||||
RRLogger(socket)("info", "RR customer update", { bodyshopId });
|
||||
return ok(res, { data: result.data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/customer/update failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
// -------------------- Repair Orders --------------------
|
||||
// --- repair orders ---
|
||||
|
||||
router.post("/rr/repair-order/create", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const { job } = req.body || {};
|
||||
|
||||
try {
|
||||
if (!job) throw new RrApiError("Missing 'job' in request body", "BAD_REQUEST");
|
||||
const cfg = await resolveRRConfigHttp(req);
|
||||
const result = await roApi.createRepairOrder(socket, job, cfg);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /repair-order/create failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const { ro } = req.body || {};
|
||||
if (!ro) throw new RrApiError("Missing 'ro' in request body", "BAD_REQUEST");
|
||||
const result = await roApi.createRepairOrder({ bodyshopId, payload: ro });
|
||||
RRLogger(socket)("info", "RR create RO", { bodyshopId });
|
||||
return ok(res, { data: result.data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/repair-order/create failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/rr/repair-order/update", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const { job } = req.body || {};
|
||||
|
||||
try {
|
||||
if (!job) throw new RrApiError("Missing 'job' in request body", "BAD_REQUEST");
|
||||
const cfg = await resolveRRConfigHttp(req);
|
||||
const result = await roApi.updateRepairOrder(socket, job, cfg);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /repair-order/update failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const { ro } = req.body || {};
|
||||
if (!ro) throw new RrApiError("Missing 'ro' in request body", "BAD_REQUEST");
|
||||
const result = await roApi.updateRepairOrder({ bodyshopId, payload: ro });
|
||||
RRLogger(socket)("info", "RR update RO", { bodyshopId });
|
||||
return ok(res, { data: result.data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/repair-order/update failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
// -------------------- Lookups --------------------
|
||||
// --- lookups ---
|
||||
|
||||
router.post("/rr/lookup/advisors", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const { criteria = {} } = req.body || {};
|
||||
|
||||
try {
|
||||
const cfg = await resolveRRConfigHttp(req);
|
||||
const result = await lookupApi.getAdvisors(socket, criteria, cfg);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /lookup/advisors failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const result = await lookupApi.getAdvisors({ bodyshopId, ...req.body });
|
||||
return ok(res, { data: result.data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/lookup/advisors failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/rr/lookup/parts", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const { criteria = {} } = req.body || {};
|
||||
|
||||
try {
|
||||
const cfg = await resolveRRConfigHttp(req);
|
||||
const result = await lookupApi.getParts(socket, criteria, cfg);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /lookup/parts failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const result = await lookupApi.getParts({ bodyshopId, ...req.body });
|
||||
return ok(res, { data: result.data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/lookup/parts failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/rr/lookup/combined-search", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const { criteria = {} } = req.body || {};
|
||||
|
||||
try {
|
||||
const cfg = await resolveRRConfigHttp(req);
|
||||
const result = await lookupApi.combinedSearch(socket, criteria, cfg);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /lookup/combined-search failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const result = await lookupApi.combinedSearch({ bodyshopId, ...req.body });
|
||||
return ok(res, { data: result.data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/lookup/combined-search failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
// -------------------- Orchestrated export --------------------
|
||||
// --- export orchestrator ---
|
||||
|
||||
router.post("/rr/export/job", async (req, res) => {
|
||||
const socket = socketOf(req);
|
||||
const logger = (level, message, ctx) => RRLogger(socket)(level, message, ctx);
|
||||
try {
|
||||
const bodyshopId = requireBodyshopId(req);
|
||||
const { job, options = {} } = req.body || {};
|
||||
|
||||
try {
|
||||
if (!job) throw new RrApiError("Missing 'job' in request body", "BAD_REQUEST");
|
||||
const cfg = await resolveRRConfigHttp(req);
|
||||
const result = await exportJobToRR(socket, job, cfg, options);
|
||||
return ok(res, result);
|
||||
} catch (err) {
|
||||
RRLogger(socket, "error", "RR /export/job failed", { err: err.message });
|
||||
return fail(res, err);
|
||||
}
|
||||
});
|
||||
|
||||
// -------------------- Diagnostics --------------------
|
||||
|
||||
router.get("/rr/actions", (_req, res) => {
|
||||
try {
|
||||
return ok(res, { actions: listActions() });
|
||||
} catch (err) {
|
||||
return fail(res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/rr/templates/verify", async (_req, res) => {
|
||||
try {
|
||||
const issues = await verifyTemplatesExist();
|
||||
return ok(res, { ok: issues.length === 0, issues });
|
||||
} catch (err) {
|
||||
return fail(res, err);
|
||||
const data = await exportJobToRR({ bodyshopId, job, logger, ...options });
|
||||
return ok(res, { data });
|
||||
} catch (e) {
|
||||
RRLogger(socket)("error", "RR /rr/export/job failed", { error: e.message });
|
||||
return fail(res, e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
11
server/rr/withClient.js
Normal file
11
server/rr/withClient.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { getRRConfigForBodyshop } = require("./rr-config");
|
||||
const { makeRRClient } = require("./rr-client");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
async function withClient(bodyshopId, fn) {
|
||||
const routing = await getRRConfigForBodyshop(bodyshopId);
|
||||
const client = makeRRClient({ logger });
|
||||
return fn(client, routing);
|
||||
}
|
||||
|
||||
module.exports = { withClient };
|
||||
@@ -1,15 +0,0 @@
|
||||
<rey_RomeCustServVehCombReq xmlns="http://www.starstandards.org/STAR" revision="1.0">
|
||||
<!-- NOTE: ApplicationArea is injected by buildStarEnvelope(); do not include it here. -->
|
||||
<CustServVehCombReq>
|
||||
<QueryData{{#MaxResults}} MaxRecs="{{MaxResults}}"{{/MaxResults}}>
|
||||
{{#Customer.PhoneNumber}}<Phone Num="{{Customer.PhoneNumber}}"/>{{/Customer.PhoneNumber}}
|
||||
|
||||
{{#Customer.FirstName}}<FirstName>{{Customer.FirstName}}</FirstName>{{/Customer.FirstName}}
|
||||
{{#Customer.LastName}}<LastName>{{Customer.LastName}}</LastName>{{/Customer.LastName}}
|
||||
{{#Customer.EmailAddress}}<EMail>{{Customer.EmailAddress}}</EMail>{{/Customer.EmailAddress}}
|
||||
|
||||
{{#Vehicle.VIN}}<VIN>{{Vehicle.VIN}}</VIN>{{/Vehicle.VIN}}
|
||||
{{#Vehicle.LicensePlate}}<LicensePlate>{{Vehicle.LicensePlate}}</LicensePlate>{{/Vehicle.LicensePlate}}
|
||||
</QueryData>
|
||||
</CustServVehCombReq>
|
||||
</rey_RomeCustServVehCombReq>
|
||||
@@ -1,123 +0,0 @@
|
||||
<rey_RomeCreateBSMRepairOrderReq xmlns="{{STAR_NS}}" revision="1.0">
|
||||
<BSMRepairOrderReq>
|
||||
<RepairOrder>
|
||||
<RepairOrderNumber>{{RepairOrderNumber}}</RepairOrderNumber>
|
||||
{{#DmsRepairOrderId}}<DmsRepairOrderId>{{DmsRepairOrderId}}</DmsRepairOrderId>{{/DmsRepairOrderId}}
|
||||
{{#OpenDate}}<OpenDate>{{OpenDate}}</OpenDate>{{/OpenDate}}
|
||||
{{#PromisedDate}}<PromisedDate>{{PromisedDate}}</PromisedDate>{{/PromisedDate}}
|
||||
{{#CloseDate}}<CloseDate>{{CloseDate}}</CloseDate>{{/CloseDate}}
|
||||
{{#ServiceAdvisorId}}<ServiceAdvisorId>{{ServiceAdvisorId}}</ServiceAdvisorId>{{/ServiceAdvisorId}}
|
||||
{{#TechnicianId}}<TechnicianId>{{TechnicianId}}</TechnicianId>{{/TechnicianId}}
|
||||
{{#Department}}<Department>{{Department}}</Department>{{/Department}}
|
||||
{{#ProfitCenter}}<ProfitCenter>{{ProfitCenter}}</ProfitCenter>{{/ProfitCenter}}
|
||||
{{#ROType}}<ROType>{{ROType}}</ROType>{{/ROType}}
|
||||
{{#Status}}<Status>{{Status}}</Status>{{/Status}}
|
||||
{{#IsBodyShop}}<IsBodyShop>{{IsBodyShop}}</IsBodyShop>{{/IsBodyShop}}
|
||||
{{#DRPFlag}}<DRPFlag>{{DRPFlag}}</DRPFlag>{{/DRPFlag}}
|
||||
|
||||
<Customer>
|
||||
{{#CustomerId}}<CustomerId>{{CustomerId}}</CustomerId>{{/CustomerId}}
|
||||
{{#CustomerName}}<CustomerName>{{CustomerName}}</CustomerName>{{/CustomerName}}
|
||||
{{#PhoneNumber}}<PhoneNumber>{{PhoneNumber}}</PhoneNumber>{{/PhoneNumber}}
|
||||
{{#EmailAddress}}<EmailAddress>{{EmailAddress}}</EmailAddress>{{/EmailAddress}}
|
||||
{{#Address}}
|
||||
<Address>
|
||||
{{#Line1}}<Line1>{{Line1}}</Line1>{{/Line1}}
|
||||
{{#Line2}}<Line2>{{Line2}}</Line2>{{/Line2}}
|
||||
{{#City}}<City>{{City}}</City>{{/City}}
|
||||
{{#State}}<State>{{State}}</State>{{/State}}
|
||||
{{#PostalCode}}<PostalCode>{{PostalCode}}</PostalCode>{{/PostalCode}}
|
||||
{{#Country}}<Country>{{Country}}</Country>{{/Country}}
|
||||
</Address>
|
||||
{{/Address}}
|
||||
</Customer>
|
||||
|
||||
<ServiceVehicle>
|
||||
{{#VehicleId}}<VehicleId>{{VehicleId}}</VehicleId>{{/VehicleId}}
|
||||
{{#VIN}}<VIN>{{VIN}}</VIN>{{/VIN}}
|
||||
{{#LicensePlate}}<LicensePlate>{{LicensePlate}}</LicensePlate>{{/LicensePlate}}
|
||||
{{#Year}}<Year>{{Year}}</Year>{{/Year}}
|
||||
{{#Make}}<Make>{{Make}}</Make>{{/Make}}
|
||||
{{#Model}}<Model>{{Model}}</Model>{{/Model}}
|
||||
{{#Odometer}}<Odometer>{{Odometer}}</Odometer>{{/Odometer}}
|
||||
{{#Color}}<Color>{{Color}}</Color>{{/Color}}
|
||||
</ServiceVehicle>
|
||||
|
||||
{{#JobLines}}
|
||||
<JobLine>
|
||||
<Sequence>{{Sequence}}</Sequence>
|
||||
{{#ParentSequence}}<ParentSequence>{{ParentSequence}}</ParentSequence>{{/ParentSequence}}
|
||||
{{#LineType}}<LineType>{{LineType}}</LineType>{{/LineType}}
|
||||
{{#Category}}<Category>{{Category}}</Category>{{/Category}}
|
||||
{{#OpCode}}<OpCode>{{OpCode}}</OpCode>{{/OpCode}}
|
||||
{{#Description}}<Description>{{Description}}</Description>{{/Description}}
|
||||
{{#LaborHours}}<LaborHours>{{LaborHours}}</LaborHours>{{/LaborHours}}
|
||||
{{#LaborRate}}<LaborRate>{{LaborRate}}</LaborRate>{{/LaborRate}}
|
||||
{{#PartNumber}}<PartNumber>{{PartNumber}}</PartNumber>{{/PartNumber}}
|
||||
{{#PartDescription}}<PartDescription>{{PartDescription}}</PartDescription>{{/PartDescription}}
|
||||
{{#Quantity}}<Quantity>{{Quantity}}</Quantity>{{/Quantity}}
|
||||
{{#UnitPrice}}<UnitPrice>{{UnitPrice}}</UnitPrice>{{/UnitPrice}}
|
||||
{{#ExtendedPrice}}<ExtendedPrice>{{ExtendedPrice}}</ExtendedPrice>{{/ExtendedPrice}}
|
||||
{{#DiscountAmount}}<DiscountAmount>{{DiscountAmount}}</DiscountAmount>{{/DiscountAmount}}
|
||||
{{#TaxCode}}<TaxCode>{{TaxCode}}</TaxCode>{{/TaxCode}}
|
||||
{{#GLAccount}}<GLAccount>{{GLAccount}}</GLAccount>{{/GLAccount}}
|
||||
{{#ControlNumber}}<ControlNumber>{{ControlNumber}}</ControlNumber>{{/ControlNumber}}
|
||||
|
||||
{{#Taxes}}
|
||||
<Taxes>
|
||||
{{#Items}}
|
||||
<Tax>
|
||||
<Code>{{Code}}</Code>
|
||||
<Amount>{{Amount}}</Amount>
|
||||
{{#Rate}}<Rate>{{Rate}}</Rate>{{/Rate}}
|
||||
</Tax>
|
||||
{{/Items}}
|
||||
</Taxes>
|
||||
{{/Taxes}}
|
||||
</JobLine>
|
||||
{{/JobLines}}
|
||||
|
||||
{{#Totals}}
|
||||
<Totals>
|
||||
{{#Currency}}<Currency>{{Currency}}</Currency>{{/Currency}}
|
||||
{{#LaborTotal}}<LaborTotal>{{LaborTotal}}</LaborTotal>{{/LaborTotal}}
|
||||
{{#PartsTotal}}<PartsTotal>{{PartsTotal}}</PartsTotal>{{/PartsTotal}}
|
||||
{{#MiscTotal}}<MiscTotal>{{MiscTotal}}</MiscTotal>{{/MiscTotal}}
|
||||
{{#DiscountTotal}}<DiscountTotal>{{DiscountTotal}}</DiscountTotal>{{/DiscountTotal}}
|
||||
{{#TaxTotal}}<TaxTotal>{{TaxTotal}}</TaxTotal>{{/TaxTotal}}
|
||||
<GrandTotal>{{GrandTotal}}</GrandTotal>
|
||||
</Totals>
|
||||
{{/Totals}}
|
||||
|
||||
{{#Payments}}
|
||||
<Payments>
|
||||
{{#Items}}
|
||||
<Payment>
|
||||
<PayerType>{{PayerType}}</PayerType>
|
||||
{{#PayerName}}<PayerName>{{PayerName}}</PayerName>{{/PayerName}}
|
||||
<Amount>{{Amount}}</Amount>
|
||||
{{#Method}}<Method>{{Method}}</Method>{{/Method}}
|
||||
{{#Reference}}<Reference>{{Reference}}</Reference>{{/Reference}}
|
||||
{{#ControlNumber}}<ControlNumber>{{ControlNumber}}</ControlNumber>{{/ControlNumber}}
|
||||
</Payment>
|
||||
{{/Items}}
|
||||
</Payments>
|
||||
{{/Payments}}
|
||||
|
||||
{{#Insurance}}
|
||||
<Insurance>
|
||||
{{#CompanyName}}<CompanyName>{{CompanyName}}</CompanyName>{{/CompanyName}}
|
||||
{{#ClaimNumber}}<ClaimNumber>{{ClaimNumber}}</ClaimNumber>{{/ClaimNumber}}
|
||||
{{#AdjusterName}}<AdjusterName>{{AdjusterName}}</AdjusterName>{{/AdjusterName}}
|
||||
{{#AdjusterPhone}}<AdjusterPhone>{{AdjusterPhone}}</AdjusterPhone>{{/AdjusterPhone}}
|
||||
</Insurance>
|
||||
{{/Insurance}}
|
||||
|
||||
{{#Notes}}
|
||||
<Notes>
|
||||
{{#Items}}<Note>{{.}}</Note>{{/Items}}
|
||||
</Notes>
|
||||
{{/Notes}}
|
||||
</RepairOrder>
|
||||
</BSMRepairOrderReq>
|
||||
</rey_RomeCreateBSMRepairOrderReq>
|
||||
@@ -1,15 +0,0 @@
|
||||
<rey_RomeGetAdvisorsReq xmlns="http://www.starstandards.org/STAR" revision="1.0">
|
||||
<GetAdvisorsReq>
|
||||
<QueryData{{#SearchCriteria.MaxResults}} MaxRecs="{{SearchCriteria.MaxResults}}"{{/SearchCriteria.MaxResults}}>
|
||||
{{#SearchCriteria.AdvisorId}}<AdvisorID>{{SearchCriteria.AdvisorId}}</AdvisorID>{{/SearchCriteria.AdvisorId}}
|
||||
{{#SearchCriteria.FirstName}}<FirstName>{{SearchCriteria.FirstName}}</FirstName>{{/SearchCriteria.FirstName}}
|
||||
{{#SearchCriteria.LastName}}<LastName>{{SearchCriteria.LastName}}</LastName>{{/SearchCriteria.LastName}}
|
||||
{{#SearchCriteria.Department}}<Department>{{SearchCriteria.Department}}</Department>{{/SearchCriteria.Department}}
|
||||
{{#SearchCriteria.Status}}<Status>{{SearchCriteria.Status}}</Status>{{/SearchCriteria.Status}}
|
||||
{{#SearchCriteria.IncludeInactive}}<IncludeInactive>{{SearchCriteria.IncludeInactive}}</IncludeInactive>{{/SearchCriteria.IncludeInactive}}
|
||||
{{#SearchCriteria.PageNumber}}<PageNumber>{{SearchCriteria.PageNumber}}</PageNumber>{{/SearchCriteria.PageNumber}}
|
||||
{{#SearchCriteria.SortBy}}<SortBy>{{SearchCriteria.SortBy}}</SortBy>{{/SearchCriteria.SortBy}}
|
||||
{{#SearchCriteria.SortDirection}}<SortDirection>{{SearchCriteria.SortDirection}}</SortDirection>{{/SearchCriteria.SortDirection}}
|
||||
</QueryData>
|
||||
</GetAdvisorsReq>
|
||||
</rey_RomeGetAdvisorsReq>
|
||||
@@ -1,25 +0,0 @@
|
||||
<rey_RomeGetPartsReq xmlns="{{STAR_NS}}" revision="1.0">
|
||||
<GetPartReq>
|
||||
<QueryData{{#MaxResults}} MaxRecs="{{MaxResults}}"{{/MaxResults}}{{#PageNumber}} Page="{{PageNumber}}"{{/PageNumber}}>
|
||||
{{#PartNumber}}<PartNumber>{{PartNumber}}</PartNumber>{{/PartNumber}}
|
||||
{{#Description}}<Description>{{Description}}</Description>{{/Description}}
|
||||
{{#Make}}<Make>{{Make}}</Make>{{/Make}}
|
||||
{{#Model}}<Model>{{Model}}</Model>{{/Model}}
|
||||
{{#Year}}<Year>{{Year}}</Year>{{/Year}}
|
||||
{{#Vendor}}<Vendor>{{Vendor}}</Vendor>{{/Vendor}}
|
||||
{{#Category}}<Category>{{Category}}</Category>{{/Category}}
|
||||
{{#Brand}}<Brand>{{Brand}}</Brand>{{/Brand}}
|
||||
{{#IsOEM}}<IsOEM>{{IsOEM}}</IsOEM>{{/IsOEM}}
|
||||
{{#IsAftermarket}}<IsAftermarket>{{IsAftermarket}}</IsAftermarket>{{/IsAftermarket}}
|
||||
{{#InStock}}<InStock>{{InStock}}</InStock>{{/InStock}}
|
||||
{{#Warehouse}}<Warehouse>{{Warehouse}}</Warehouse>{{/Warehouse}}
|
||||
{{#Location}}<Location>{{Location}}</Location>{{/Location}}
|
||||
{{#MinPrice}}<MinPrice>{{MinPrice}}</MinPrice>{{/MinPrice}}
|
||||
{{#MaxPrice}}<MaxPrice>{{MaxPrice}}</MaxPrice>{{/MaxPrice}}
|
||||
{{#Currency}}<Currency>{{Currency}}</Currency>{{/Currency}}
|
||||
{{#SearchMode}}<SearchMode>{{SearchMode}}</SearchMode>{{/SearchMode}}
|
||||
{{#SortBy}}<SortBy>{{SortBy}}</SortBy>{{/SortBy}}
|
||||
{{#SortDirection}}<SortDirection>{{SortDirection}}</SortDirection>{{/SortDirection}}
|
||||
</QueryData>
|
||||
</GetPartReq>
|
||||
</rey_RomeGetPartsReq>
|
||||
@@ -1,63 +0,0 @@
|
||||
<rey_RomeCustomerInsertReq xmlns="{{STAR_NS}}" revision="1.0">
|
||||
<CustomerInsertReq>
|
||||
<Customer>
|
||||
{{#CustomerNumber}}<CustomerNumber>{{CustomerNumber}}</CustomerNumber>{{/CustomerNumber}}
|
||||
{{#CustomerType}}<CustomerType>
|
||||
{{CustomerType}}</CustomerType>{{/CustomerType}}
|
||||
<CustomerName>{{CustomerName}}</CustomerName>
|
||||
{{#DisplayName}}<DisplayName>{{DisplayName}}</DisplayName>{{/DisplayName}}
|
||||
{{#Language}}<Language>{{Language}}</Language>{{/Language}}
|
||||
{{#GroupName}}<GroupName>{{GroupName}}</GroupName>{{/GroupName}}
|
||||
{{#TaxExempt}}<TaxExempt>{{TaxExempt}}</TaxExempt>{{/TaxExempt}}
|
||||
{{#DiscountLevel}}<DiscountLevel>{{DiscountLevel}}</DiscountLevel>{{/DiscountLevel}}
|
||||
{{#Active}}<Active>{{Active}}</Active>{{/Active}}
|
||||
{{#Addresses}}
|
||||
<Address>
|
||||
{{#Type}}<Type>{{Type}}</Type>{{/Type}}
|
||||
{{#Line1}}<Line1>{{Line1}}</Line1>{{/Line1}}
|
||||
{{#Line2}}<Line2>{{Line2}}</Line2>{{/Line2}}
|
||||
{{#City}}<City>{{City}}</City>{{/City}}
|
||||
{{#State}}<State>{{State}}</State>{{/State}}
|
||||
{{#PostalCode}}<PostalCode>{{PostalCode}}</PostalCode>{{/PostalCode}}
|
||||
{{#Country}}<Country>{{Country}}</Country>{{/Country}}
|
||||
</Address>
|
||||
{{/Addresses}}
|
||||
{{#Phones}}
|
||||
<Phone>
|
||||
<Type>{{Type}}</Type>
|
||||
<Number>{{Number}}</Number>
|
||||
{{#Extension}}<Extension>{{Extension}}</Extension>{{/Extension}}
|
||||
{{#Preferred}}<Preferred>{{Preferred}}</Preferred>{{/Preferred}}
|
||||
</Phone>
|
||||
{{/Phones}}
|
||||
{{#Emails}}
|
||||
<Email>
|
||||
<Type>{{Type}}</Type>
|
||||
<Address>{{Address}}</Address>
|
||||
{{#Preferred}}<Preferred>{{Preferred}}</Preferred>{{/Preferred}}
|
||||
</Email>
|
||||
{{/Emails}}
|
||||
{{#Insurance}}
|
||||
<Insurance>
|
||||
{{#CompanyName}}<CompanyName>{{CompanyName}}</CompanyName>{{/CompanyName}}
|
||||
{{#PolicyNumber}}<PolicyNumber>{{PolicyNumber}}</PolicyNumber>{{/PolicyNumber}}
|
||||
{{#ExpirationDate}}<ExpirationDate>{{ExpirationDate}}</ExpirationDate>{{/ExpirationDate}}
|
||||
{{#ContactName}}<ContactName>{{ContactName}}</ContactName>{{/ContactName}}
|
||||
{{#ContactPhone}}<ContactPhone>{{ContactPhone}}</ContactPhone>{{/ContactPhone}}
|
||||
</Insurance>
|
||||
{{/Insurance}}
|
||||
{{#LinkedAccounts}}
|
||||
<LinkedAccount>
|
||||
<Type>{{Type}}</Type>
|
||||
<AccountNumber>{{AccountNumber}}</AccountNumber>
|
||||
{{#CreditLimit}}<CreditLimit>{{CreditLimit}}</CreditLimit>{{/CreditLimit}}
|
||||
</LinkedAccount>
|
||||
{{/LinkedAccounts}}
|
||||
{{#Notes}}
|
||||
<Notes>
|
||||
{{#Items}}<Note>{{.}}</Note>{{/Items}}
|
||||
</Notes>
|
||||
{{/Notes}}
|
||||
</Customer>
|
||||
</CustomerInsertReq>
|
||||
</rey_RomeCustomerInsertReq>
|
||||
@@ -1,57 +0,0 @@
|
||||
<rey_RomeServVehicleInsertReq xmlns="{{STAR_NS}}" revision="1.0">
|
||||
<ServVehicleInsertReq>
|
||||
<ServiceVehicle>
|
||||
{{#CustomerId}}<CustomerId>{{CustomerId}}</CustomerId>{{/CustomerId}}
|
||||
{{#VIN}}<VIN>{{VIN}}</VIN>{{/VIN}}
|
||||
{{#UnitNumber}}<UnitNumber>{{UnitNumber}}</UnitNumber>{{/UnitNumber}}
|
||||
{{#StockNumber}}<StockNumber>{{StockNumber}}</StockNumber>{{/StockNumber}}
|
||||
{{#Year}}<Year>{{Year}}</Year>{{/Year}}
|
||||
{{#Make}}<Make>{{Make}}</Make>{{/Make}}
|
||||
{{#Model}}<Model>{{Model}}</Model>{{/Model}}
|
||||
{{#Trim}}<Trim>{{Trim}}</Trim>{{/Trim}}
|
||||
{{#BodyStyle}}<BodyStyle>{{BodyStyle}}</BodyStyle>{{/BodyStyle}}
|
||||
{{#Transmission}}<Transmission>{{Transmission}}</Transmission>{{/Transmission}}
|
||||
{{#Engine}}<Engine>{{Engine}}</Engine>{{/Engine}}
|
||||
{{#FuelType}}<FuelType>{{FuelType}}</FuelType>{{/FuelType}}
|
||||
{{#DriveType}}<DriveType>{{DriveType}}</DriveType>{{/DriveType}}
|
||||
{{#Color}}<Color>{{Color}}</Color>{{/Color}}
|
||||
{{#LicensePlate}}<LicensePlate>{{LicensePlate}}</LicensePlate>{{/LicensePlate}}
|
||||
{{#LicenseState}}<LicenseState>{{LicenseState}}</LicenseState>{{/LicenseState}}
|
||||
{{#RegistrationExpiry}}<RegistrationExpiry>{{RegistrationExpiry}}</RegistrationExpiry>{{/RegistrationExpiry}}
|
||||
{{#Odometer}}<Odometer>{{Odometer}}</Odometer>{{/Odometer}}
|
||||
{{#OdometerUnits}}<OdometerUnits>
|
||||
{{OdometerUnits}}</OdometerUnits>{{/OdometerUnits}} <!-- MI | KM -->
|
||||
{{#InServiceDate}}<InServiceDate>{{InServiceDate}}</InServiceDate>{{/InServiceDate}}
|
||||
{{#Ownership}}
|
||||
<Ownership>
|
||||
{{#OwnerId}}<OwnerId>{{OwnerId}}</OwnerId>{{/OwnerId}}
|
||||
{{#OwnerName}}<OwnerName>{{OwnerName}}</OwnerName>{{/OwnerName}}
|
||||
{{#OwnershipType}}<OwnershipType>
|
||||
{{OwnershipType}}</OwnershipType>{{/OwnershipType}}
|
||||
</Ownership>
|
||||
{{/Ownership}}
|
||||
{{#Insurance}}
|
||||
<Insurance>
|
||||
{{#CompanyName}}<CompanyName>{{CompanyName}}</CompanyName>{{/CompanyName}}
|
||||
{{#PolicyNumber}}<PolicyNumber>{{PolicyNumber}}</PolicyNumber>{{/PolicyNumber}}
|
||||
{{#ExpirationDate}}<ExpirationDate>{{ExpirationDate}}</ExpirationDate>{{/ExpirationDate}}
|
||||
{{#ContactName}}<ContactName>{{ContactName}}</ContactName>{{/ContactName}}
|
||||
{{#ContactPhone}}<ContactPhone>{{ContactPhone}}</ContactPhone>{{/ContactPhone}}
|
||||
</Insurance>
|
||||
{{/Insurance}}
|
||||
{{#Warranty}}
|
||||
<Warranty>
|
||||
{{#WarrantyCompany}}<WarrantyCompany>{{WarrantyCompany}}</WarrantyCompany>{{/WarrantyCompany}}
|
||||
{{#WarrantyNumber}}<WarrantyNumber>{{WarrantyNumber}}</WarrantyNumber>{{/WarrantyNumber}}
|
||||
{{#WarrantyType}}<WarrantyType>{{WarrantyType}}</WarrantyType>{{/WarrantyType}}
|
||||
{{#ExpirationDate}}<ExpirationDate>{{ExpirationDate}}</ExpirationDate>{{/ExpirationDate}}
|
||||
</Warranty>
|
||||
{{/Warranty}}
|
||||
{{#VehicleNotes}}
|
||||
<Notes>
|
||||
{{#Items}}<Note>{{.}}</Note>{{/Items}}
|
||||
</Notes>
|
||||
{{/VehicleNotes}}
|
||||
</ServiceVehicle>
|
||||
</ServVehicleInsertReq>
|
||||
</rey_RomeServVehicleInsertReq>
|
||||
@@ -1,83 +0,0 @@
|
||||
<rey_RomeCustomerUpdateReq xmlns="{{STAR_NS}}" revision="1.0">
|
||||
<CustomerUpdateReq>
|
||||
<Customer>
|
||||
<CustomerId>{{CustomerId}}</CustomerId>
|
||||
{{#CustomerType}}<CustomerType>
|
||||
{{CustomerType}}</CustomerType>{{/CustomerType}}
|
||||
{{#CustomerName}}<CustomerName>{{CustomerName}}</CustomerName>{{/CustomerName}}
|
||||
{{#DisplayName}}<DisplayName>{{DisplayName}}</DisplayName>{{/DisplayName}}
|
||||
{{#PreferredName}}<PreferredName>{{PreferredName}}</PreferredName>{{/PreferredName}}
|
||||
{{#Language}}<Language>{{Language}}</Language>{{/Language}}
|
||||
{{#GroupName}}<GroupName>{{GroupName}}</GroupName>{{/GroupName}}
|
||||
{{#TaxExempt}}<TaxExempt>{{TaxExempt}}</TaxExempt>{{/TaxExempt}}
|
||||
{{#DiscountLevel}}<DiscountLevel>{{DiscountLevel}}</DiscountLevel>{{/DiscountLevel}}
|
||||
{{#Active}}<Active>{{Active}}</Active>{{/Active}}
|
||||
{{#Addresses}}
|
||||
<Address>
|
||||
{{#AddressId}}<AddressId>{{AddressId}}</AddressId>{{/AddressId}}
|
||||
{{#Type}}<Type>
|
||||
{{Type}}</Type>{{/Type}}
|
||||
{{#Line1}}<Line1>{{Line1}}</Line1>{{/Line1}}
|
||||
{{#Line2}}<Line2>{{Line2}}</Line2>{{/Line2}}
|
||||
{{#City}}<City>{{City}}</City>{{/City}}
|
||||
{{#State}}<State>{{State}}</State>{{/State}}
|
||||
{{#PostalCode}}<PostalCode>{{PostalCode}}</PostalCode>{{/PostalCode}}
|
||||
{{#Country}}<Country>{{Country}}</Country>{{/Country}}
|
||||
{{#IsPrimary}}<IsPrimary>{{IsPrimary}}</IsPrimary>{{/IsPrimary}}
|
||||
{{#IsDeleted}}<IsDeleted>{{IsDeleted}}</IsDeleted>{{/IsDeleted}}
|
||||
</Address>
|
||||
{{/Addresses}}
|
||||
{{#Phones}}
|
||||
<Phone>
|
||||
{{#PhoneId}}<PhoneId>{{PhoneId}}</PhoneId>{{/PhoneId}}
|
||||
{{#Type}}<Type>{{Type}}</Type>{{/Type}}
|
||||
{{#Number}}<Number>{{Number}}</Number>{{/Number}}
|
||||
{{#Extension}}<Extension>{{Extension}}</Extension>{{/Extension}}
|
||||
{{#Preferred}}<Preferred>{{Preferred}}</Preferred>{{/Preferred}}
|
||||
{{#IsDeleted}}<IsDeleted>{{IsDeleted}}</IsDeleted>{{/IsDeleted}}
|
||||
</Phone>
|
||||
{{/Phones}}
|
||||
{{#Emails}}
|
||||
<Email>
|
||||
{{#EmailId}}<EmailId>{{EmailId}}</EmailId>{{/EmailId}}
|
||||
{{#Type}}<Type>{{Type}}</Type>{{/Type}}
|
||||
{{#Address}}<Address>{{Address}}</Address>{{/Address}}
|
||||
{{#Preferred}}<Preferred>{{Preferred}}</Preferred>{{/Preferred}}
|
||||
{{#IsDeleted}}<IsDeleted>{{IsDeleted}}</IsDeleted>{{/IsDeleted}}
|
||||
</Email>
|
||||
{{/Emails}}
|
||||
{{#DriverLicense}}
|
||||
<DriverLicense>
|
||||
{{#LicenseNumber}}<LicenseNumber>{{LicenseNumber}}</LicenseNumber>{{/LicenseNumber}}
|
||||
{{#LicenseState}}<LicenseState>{{LicenseState}}</LicenseState>{{/LicenseState}}
|
||||
{{#ExpirationDate}}<ExpirationDate>{{ExpirationDate}}</ExpirationDate>{{/ExpirationDate}}
|
||||
</DriverLicense>
|
||||
{{/DriverLicense}}
|
||||
{{#Insurance}}
|
||||
<Insurance>
|
||||
{{#CompanyName}}<CompanyName>{{CompanyName}}</CompanyName>{{/CompanyName}}
|
||||
{{#PolicyNumber}}<PolicyNumber>{{PolicyNumber}}</PolicyNumber>{{/PolicyNumber}}
|
||||
{{#ExpirationDate}}<ExpirationDate>{{ExpirationDate}}</ExpirationDate>{{/ExpirationDate}}
|
||||
{{#ContactName}}<ContactName>{{ContactName}}</ContactName>{{/ContactName}}
|
||||
{{#ContactPhone}}<ContactPhone>{{ContactPhone}}</ContactPhone>{{/ContactPhone}}
|
||||
</Insurance>
|
||||
{{/Insurance}}
|
||||
{{#LinkedAccounts}}
|
||||
<LinkedAccount>
|
||||
{{#AccountId}}<AccountId>{{AccountId}}</AccountId>{{/AccountId}}
|
||||
<Type>{{Type}}
|
||||
</Type>
|
||||
{{#AccountNumber}}<AccountNumber>{{AccountNumber}}</AccountNumber>{{/AccountNumber}}
|
||||
{{#CreditLimit}}<CreditLimit>{{CreditLimit}}</CreditLimit>{{/CreditLimit}}
|
||||
{{#IsDeleted}}<IsDeleted>{{IsDeleted}}</IsDeleted>{{/IsDeleted}}
|
||||
</LinkedAccount>
|
||||
{{/LinkedAccounts}}
|
||||
|
||||
{{#Notes}}
|
||||
<Notes>
|
||||
{{#Items}}<Note>{{.}}</Note>{{/Items}}
|
||||
</Notes>
|
||||
{{/Notes}}
|
||||
</Customer>
|
||||
</CustomerUpdateReq>
|
||||
</rey_RomeCustomerUpdateReq>
|
||||
@@ -1,123 +0,0 @@
|
||||
<rey_RomeUpdateBSMRepairOrderReq xmlns="{{STAR_NS}}" revision="1.0">
|
||||
<BSMRepairOrderReq>
|
||||
<RepairOrder>
|
||||
<RepairOrderNumber>{{RepairOrderNumber}}</RepairOrderNumber>
|
||||
{{#DmsRepairOrderId}}<DmsRepairOrderId>{{DmsRepairOrderId}}</DmsRepairOrderId>{{/DmsRepairOrderId}}
|
||||
{{#OpenDate}}<OpenDate>{{OpenDate}}</OpenDate>{{/OpenDate}}
|
||||
{{#PromisedDate}}<PromisedDate>{{PromisedDate}}</PromisedDate>{{/PromisedDate}}
|
||||
{{#CloseDate}}<CloseDate>{{CloseDate}}</CloseDate>{{/CloseDate}}
|
||||
{{#ServiceAdvisorId}}<ServiceAdvisorId>{{ServiceAdvisorId}}</ServiceAdvisorId>{{/ServiceAdvisorId}}
|
||||
{{#TechnicianId}}<TechnicianId>{{TechnicianId}}</TechnicianId>{{/TechnicianId}}
|
||||
{{#Department}}<Department>{{Department}}</Department>{{/Department}}
|
||||
{{#ProfitCenter}}<ProfitCenter>{{ProfitCenter}}</ProfitCenter>{{/ProfitCenter}}
|
||||
{{#ROType}}<ROType>{{ROType}}</ROType>{{/ROType}}
|
||||
{{#Status}}<Status>{{Status}}</Status>{{/Status}}
|
||||
{{#IsBodyShop}}<IsBodyShop>{{IsBodyShop}}</IsBodyShop>{{/IsBodyShop}}
|
||||
{{#DRPFlag}}<DRPFlag>{{DRPFlag}}</DRPFlag>{{/DRPFlag}}
|
||||
|
||||
<Customer>
|
||||
{{#CustomerId}}<CustomerId>{{CustomerId}}</CustomerId>{{/CustomerId}}
|
||||
{{#CustomerName}}<CustomerName>{{CustomerName}}</CustomerName>{{/CustomerName}}
|
||||
{{#PhoneNumber}}<PhoneNumber>{{PhoneNumber}}</PhoneNumber>{{/PhoneNumber}}
|
||||
{{#EmailAddress}}<EmailAddress>{{EmailAddress}}</EmailAddress>{{/EmailAddress}}
|
||||
{{#Address}}
|
||||
<Address>
|
||||
{{#Line1}}<Line1>{{Line1}}</Line1>{{/Line1}}
|
||||
{{#Line2}}<Line2>{{Line2}}</Line2>{{/Line2}}
|
||||
{{#City}}<City>{{City}}</City>{{/City}}
|
||||
{{#State}}<State>{{State}}</State>{{/State}}
|
||||
{{#PostalCode}}<PostalCode>{{PostalCode}}</PostalCode>{{/PostalCode}}
|
||||
{{#Country}}<Country>{{Country}}</Country>{{/Country}}
|
||||
</Address>
|
||||
{{/Address}}
|
||||
</Customer>
|
||||
|
||||
<ServiceVehicle>
|
||||
{{#VehicleId}}<VehicleId>{{VehicleId}}</VehicleId>{{/VehicleId}}
|
||||
{{#VIN}}<VIN>{{VIN}}</VIN>{{/VIN}}
|
||||
{{#LicensePlate}}<LicensePlate>{{LicensePlate}}</LicensePlate>{{/LicensePlate}}
|
||||
{{#Year}}<Year>{{Year}}</Year>{{/Year}}
|
||||
{{#Make}}<Make>{{Make}}</Make>{{/Make}}
|
||||
{{#Model}}<Model>{{Model}}</Model>{{/Model}}
|
||||
{{#Odometer}}<Odometer>{{Odometer}}</Odometer>{{/Odometer}}
|
||||
{{#Color}}<Color>{{Color}}</Color>{{/Color}}
|
||||
</ServiceVehicle>
|
||||
|
||||
{{#JobLines}}
|
||||
<JobLine>
|
||||
<Sequence>{{Sequence}}</Sequence>
|
||||
{{#ParentSequence}}<ParentSequence>{{ParentSequence}}</ParentSequence>{{/ParentSequence}}
|
||||
{{#LineType}}<LineType>{{LineType}}</LineType>{{/LineType}}
|
||||
{{#Category}}<Category>{{Category}}</Category>{{/Category}}
|
||||
{{#OpCode}}<OpCode>{{OpCode}}</OpCode>{{/OpCode}}
|
||||
{{#Description}}<Description>{{Description}}</Description>{{/Description}}
|
||||
{{#LaborHours}}<LaborHours>{{LaborHours}}</LaborHours>{{/LaborHours}}
|
||||
{{#LaborRate}}<LaborRate>{{LaborRate}}</LaborRate>{{/LaborRate}}
|
||||
{{#PartNumber}}<PartNumber>{{PartNumber}}</PartNumber>{{/PartNumber}}
|
||||
{{#PartDescription}}<PartDescription>{{PartDescription}}</PartDescription>{{/PartDescription}}
|
||||
{{#Quantity}}<Quantity>{{Quantity}}</Quantity>{{/Quantity}}
|
||||
{{#UnitPrice}}<UnitPrice>{{UnitPrice}}</UnitPrice>{{/UnitPrice}}
|
||||
{{#ExtendedPrice}}<ExtendedPrice>{{ExtendedPrice}}</ExtendedPrice>{{/ExtendedPrice}}
|
||||
{{#DiscountAmount}}<DiscountAmount>{{DiscountAmount}}</DiscountAmount>{{/DiscountAmount}}
|
||||
{{#TaxCode}}<TaxCode>{{TaxCode}}</TaxCode>{{/TaxCode}}
|
||||
{{#GLAccount}}<GLAccount>{{GLAccount}}</GLAccount>{{/GLAccount}}
|
||||
{{#ControlNumber}}<ControlNumber>{{ControlNumber}}</ControlNumber>{{/ControlNumber}}
|
||||
|
||||
{{#Taxes}}
|
||||
<Taxes>
|
||||
{{#Items}}
|
||||
<Tax>
|
||||
<Code>{{Code}}</Code>
|
||||
<Amount>{{Amount}}</Amount>
|
||||
{{#Rate}}<Rate>{{Rate}}</Rate>{{/Rate}}
|
||||
</Tax>
|
||||
{{/Items}}
|
||||
</Taxes>
|
||||
{{/Taxes}}
|
||||
</JobLine>
|
||||
{{/JobLines}}
|
||||
|
||||
{{#Totals}}
|
||||
<Totals>
|
||||
{{#Currency}}<Currency>{{Currency}}</Currency>{{/Currency}}
|
||||
{{#LaborTotal}}<LaborTotal>{{LaborTotal}}</LaborTotal>{{/LaborTotal}}
|
||||
{{#PartsTotal}}<PartsTotal>{{PartsTotal}}</PartsTotal>{{/PartsTotal}}
|
||||
{{#MiscTotal}}<MiscTotal>{{MiscTotal}}</MiscTotal>{{/MiscTotal}}
|
||||
{{#DiscountTotal}}<DiscountTotal>{{DiscountTotal}}</DiscountTotal>{{/DiscountTotal}}
|
||||
{{#TaxTotal}}<TaxTotal>{{TaxTotal}}</TaxTotal>{{/TaxTotal}}
|
||||
<GrandTotal>{{GrandTotal}}</GrandTotal>
|
||||
</Totals>
|
||||
{{/Totals}}
|
||||
|
||||
{{#Payments}}
|
||||
<Payments>
|
||||
{{#Items}}
|
||||
<Payment>
|
||||
<PayerType>{{PayerType}}</PayerType>
|
||||
{{#PayerName}}<PayerName>{{PayerName}}</PayerName>{{/PayerName}}
|
||||
<Amount>{{Amount}}</Amount>
|
||||
{{#Method}}<Method>{{Method}}</Method>{{/Method}}
|
||||
{{#Reference}}<Reference>{{Reference}}</Reference>{{/Reference}}
|
||||
{{#ControlNumber}}<ControlNumber>{{ControlNumber}}</ControlNumber>{{/ControlNumber}}
|
||||
</Payment>
|
||||
{{/Items}}
|
||||
</Payments>
|
||||
{{/Payments}}
|
||||
|
||||
{{#Insurance}}
|
||||
<Insurance>
|
||||
{{#CompanyName}}<CompanyName>{{CompanyName}}</CompanyName>{{/CompanyName}}
|
||||
{{#ClaimNumber}}<ClaimNumber>{{ClaimNumber}}</ClaimNumber>{{/ClaimNumber}}
|
||||
{{#AdjusterName}}<AdjusterName>{{AdjusterName}}</AdjusterName>{{/AdjusterName}}
|
||||
{{#AdjusterPhone}}<AdjusterPhone>{{AdjusterPhone}}</AdjusterPhone>{{/AdjusterPhone}}
|
||||
</Insurance>
|
||||
{{/Insurance}}
|
||||
|
||||
{{#Notes}}
|
||||
<Notes>
|
||||
{{#Items}}<Note>{{.}}</Note>{{/Items}}
|
||||
</Notes>
|
||||
{{/Notes}}
|
||||
</RepairOrder>
|
||||
</BSMRepairOrderReq>
|
||||
</rey_RomeUpdateBSMRepairOrderReq>
|
||||
@@ -5,7 +5,6 @@ const { FortellisJobExport, FortellisSelectedCustomer } = require("../fortellis/
|
||||
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
const { exportJobToRR } = require("../rr/rr-job-export");
|
||||
const lookupApi = require("../rr/rr-lookup");
|
||||
const { getRRConfigForBodyshop } = require("../rr/rr-config");
|
||||
|
||||
const redisSocketEvents = ({
|
||||
io,
|
||||
@@ -341,94 +340,122 @@ const redisSocketEvents = ({
|
||||
});
|
||||
};
|
||||
|
||||
// Reynolds & Reynolds socket events (uses new client-backed ops)
|
||||
const registerRREvents = (socket) => {
|
||||
const log = (level, message, ctx) => RRLogger(socket)(level, message, ctx);
|
||||
|
||||
const resolveBodyshopId = (payload, job) =>
|
||||
payload?.bodyshopId || socket.bodyshopId || job?.shopid || job?.bodyshopId;
|
||||
|
||||
const resolveJobId = (explicitJobId, payload, job) =>
|
||||
explicitJobId ||
|
||||
payload?.jobid ||
|
||||
payload?.txEnvelope?.jobid ||
|
||||
job?.id ||
|
||||
payload?.txEnvelope?.job?.id ||
|
||||
null;
|
||||
|
||||
// Orchestrated Export (Customer → Vehicle → Repair Order)
|
||||
socket.on("rr-export-job", async (payload = {}) => {
|
||||
try {
|
||||
// Back-compat: old callers: { jobid, txEnvelope }; new: { job, options }
|
||||
// Prefer direct job, otherwise try txEnvelope.job
|
||||
const job = payload.job || payload.txEnvelope?.job;
|
||||
const options = payload.options || payload.txEnvelope?.options || {};
|
||||
// Resolve per-bodyshop RR config strictly from DB:
|
||||
const bodyshopId = payload.bodyshopId || socket.bodyshopId || job?.shopid;
|
||||
const cfg = await getRRConfigForBodyshop(bodyshopId);
|
||||
const bodyshopId = resolveBodyshopId(payload, job);
|
||||
const jobid = resolveJobId(payload.jobid, payload, job);
|
||||
|
||||
if (!job) {
|
||||
RRLogger(socket, "error", "RR export missing job payload");
|
||||
log("error", "RR export missing job payload", { jobid });
|
||||
return;
|
||||
}
|
||||
if (!bodyshopId) {
|
||||
log("error", "RR export missing bodyshopId", { jobid });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await exportJobToRR(socket, job, cfg, options);
|
||||
// Broadcast to bodyshop room for UI to pick up
|
||||
const room = getBodyshopRoom(socket.bodyshopId);
|
||||
io.to(room).emit("rr-export-job:result", { jobid: job.id, result });
|
||||
const result = await exportJobToRR({ bodyshopId, job, logger: log, ...options });
|
||||
|
||||
// Broadcast keyed by bodyshop + include jobid
|
||||
const room = getBodyshopRoom(bodyshopId);
|
||||
io.to(room).emit("rr-export-job:result", { jobid, bodyshopId, result });
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error during RR export: ${error.message}`);
|
||||
logger.log("rr-job-export-error", "error", null, null, { message: error.message, stack: error.stack });
|
||||
const jobid = resolveJobId(payload?.jobid, payload, payload?.job || payload?.txEnvelope?.job);
|
||||
log("error", `Error during RR export: ${error.message}`, { jobid, stack: error.stack });
|
||||
logger.log("rr-job-export-error", "error", null, null, { jobid, message: error.message, stack: error.stack });
|
||||
}
|
||||
});
|
||||
|
||||
// Combined search
|
||||
// Combined search (customer/vehicle)
|
||||
socket.on("rr-lookup-combined", async ({ jobid, params } = {}, cb) => {
|
||||
try {
|
||||
const cfg = await getRRConfigForBodyshop(socket.bodyshopId);
|
||||
const data = await lookupApi.combinedSearch(socket, params || {}, cfg);
|
||||
cb?.(data);
|
||||
const bodyshopId = resolveBodyshopId({ bodyshopId: params?.bodyshopId }, null);
|
||||
const resolvedJobId = resolveJobId(jobid, { jobid }, null);
|
||||
if (!bodyshopId) throw new Error("Missing bodyshopId");
|
||||
|
||||
const res = await lookupApi.combinedSearch({ bodyshopId, ...(params || {}) });
|
||||
cb?.({ jobid: resolvedJobId, data: res?.data ?? res });
|
||||
} catch (e) {
|
||||
RRLogger(socket, "error", `RR combined lookup error: ${e.message}`);
|
||||
cb?.(null);
|
||||
log("error", `RR combined lookup error: ${e.message}`, { jobid });
|
||||
cb?.({ jobid, error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Get Advisors
|
||||
socket.on("rr-get-advisors", async ({ jobid, params } = {}, cb) => {
|
||||
try {
|
||||
const cfg = await getRRConfigForBodyshop(socket.bodyshopId);
|
||||
const data = await lookupApi.getAdvisors(socket, params || {}, cfg);
|
||||
cb?.(data);
|
||||
const bodyshopId = resolveBodyshopId({ bodyshopId: params?.bodyshopId }, null);
|
||||
const resolvedJobId = resolveJobId(jobid, { jobid }, null);
|
||||
if (!bodyshopId) throw new Error("Missing bodyshopId");
|
||||
|
||||
const res = await lookupApi.getAdvisors({ bodyshopId, ...(params || {}) });
|
||||
cb?.({ jobid: resolvedJobId, data: res?.data ?? res });
|
||||
} catch (e) {
|
||||
RRLogger(socket, "error", `RR get advisors error: ${e.message}`);
|
||||
cb?.(null);
|
||||
log("error", `RR get advisors error: ${e.message}`, { jobid });
|
||||
cb?.({ jobid, error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Get Parts
|
||||
socket.on("rr-get-parts", async ({ jobid, params } = {}, cb) => {
|
||||
try {
|
||||
const cfg = await getRRConfigForBodyshop(socket.bodyshopId);
|
||||
const data = await lookupApi.getParts(socket, params || {}, cfg);
|
||||
cb?.(data);
|
||||
const bodyshopId = resolveBodyshopId({ bodyshopId: params?.bodyshopId }, null);
|
||||
const resolvedJobId = resolveJobId(jobid, { jobid }, null);
|
||||
if (!bodyshopId) throw new Error("Missing bodyshopId");
|
||||
|
||||
const res = await lookupApi.getParts({ bodyshopId, ...(params || {}) });
|
||||
cb?.({ jobid: resolvedJobId, data: res?.data ?? res });
|
||||
} catch (e) {
|
||||
RRLogger(socket, "error", `RR get parts error: ${e.message}`);
|
||||
cb?.(null);
|
||||
log("error", `RR get parts error: ${e.message}`, { jobid });
|
||||
cb?.({ jobid, error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// (Optional) Selected customer — only keep this if you actually implement it for RR
|
||||
// Optional: Selected customer — currently a no-op for RR
|
||||
socket.on("rr-selected-customer", async ({ jobid, selectedCustomerId } = {}) => {
|
||||
try {
|
||||
RRLogger(socket, "info", "rr-selected-customer not implemented for RR (no-op)", {
|
||||
jobid,
|
||||
const resolvedJobId = resolveJobId(jobid, { jobid }, null);
|
||||
log("info", "rr-selected-customer not implemented for RR (no-op)", {
|
||||
jobid: resolvedJobId,
|
||||
selectedCustomerId
|
||||
});
|
||||
// If later you add support, call your implementation here.
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error during RR selected-customer: ${error.message}`);
|
||||
logger.log("rr-selected-customer-error", "error", null, null, { message: error.message, stack: error.stack });
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate allocations (unchanged — CDK utility)
|
||||
// Calculate allocations (CDK utility unchanged)
|
||||
socket.on("rr-calculate-allocations", async (jobid, callback) => {
|
||||
try {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
callback(allocations);
|
||||
const resolvedJobId = resolveJobId(jobid, { jobid }, null);
|
||||
const allocations = await CdkCalculateAllocations(socket, resolvedJobId);
|
||||
callback({ jobid: resolvedJobId, allocations });
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `Error during RR calculate allocations: ${error.message}`);
|
||||
logger.log("rr-calc-allocations-error", "error", null, null, { message: error.message, stack: error.stack });
|
||||
log("error", `Error during RR calculate allocations: ${error.message}`, { jobid, stack: error.stack });
|
||||
logger.log("rr-calc-allocations-error", "error", null, null, {
|
||||
jobid,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
callback?.({ jobid, error: error.message });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Call Handlers
|
||||
registerRoomAndBroadcastEvents(socket);
|
||||
registerUpdateEvents(socket);
|
||||
|
||||
Reference in New Issue
Block a user