Compare commits
67 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac6856b136 | ||
|
|
ddd3b3d056 | ||
|
|
2660466db1 | ||
|
|
fe67efe47c | ||
|
|
69a35772e5 | ||
|
|
38932f4bf9 | ||
|
|
3fcb36a28e | ||
|
|
fe78f5c7ff | ||
|
|
683846c3b0 | ||
|
|
2cc0b247b6 | ||
|
|
31579354d4 | ||
|
|
0e7531dc54 | ||
|
|
268b57c38a | ||
|
|
2b8b8b8073 | ||
|
|
808eeb91e9 | ||
|
|
23dd8fc9de | ||
|
|
f499859078 | ||
|
|
84d9e3251a | ||
|
|
bd7db4dd02 | ||
|
|
e9804b736b | ||
|
|
a14874f116 | ||
|
|
ac9fac458c | ||
|
|
8f9db15852 | ||
|
|
61b3d3c18c | ||
|
|
e6f08d3b1c | ||
|
|
d5cf0f8371 | ||
|
|
52e230fc54 | ||
|
|
4011237c22 | ||
|
|
c24bfbf655 | ||
|
|
08fe8c3c70 | ||
|
|
771a239773 | ||
|
|
82195a0584 | ||
|
|
838c24b3f1 | ||
|
|
13cb68b0af | ||
|
|
7c84b08707 | ||
|
|
3165957e95 | ||
|
|
d9f59fcad4 | ||
|
|
edaeb5d77a | ||
|
|
5365d95d6f | ||
|
|
5cfefd5afd | ||
|
|
bbccdb0650 | ||
|
|
f3535c01af | ||
|
|
7f8c82b300 | ||
|
|
609ac2bd33 | ||
|
|
0883274320 | ||
|
|
fa33b88632 | ||
|
|
bec32c1d70 | ||
|
|
eb18130e51 | ||
|
|
0fbf63dec8 | ||
|
|
f817902d5c | ||
|
|
7a383aaec9 | ||
|
|
e5c0ace6cb | ||
|
|
814447373a | ||
|
|
d766a468c3 | ||
|
|
0ede2d0649 | ||
|
|
54089c2ab3 | ||
|
|
73e3d71cf1 | ||
|
|
f071a5cc9e | ||
|
|
67002b8443 | ||
|
|
3f83c6afa7 | ||
|
|
b7f57e91aa | ||
|
|
b8465c0cc7 | ||
|
|
553c154e46 | ||
|
|
5b400dce4f | ||
|
|
130745d7e7 | ||
|
|
a722ab9758 | ||
|
|
41c9c0be49 |
@@ -15,4 +15,5 @@ VITE_APP_INSTANCE=IMEX
|
|||||||
TEST_USERNAME="test@imex.dev"
|
TEST_USERNAME="test@imex.dev"
|
||||||
TEST_PASSWORD="test123"
|
TEST_PASSWORD="test123"
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
@@ -17,4 +17,5 @@ VITE_APP_INSTANCE=ROME
|
|||||||
TEST_USERNAME="test@imex.dev"
|
TEST_USERNAME="test@imex.dev"
|
||||||
TEST_PASSWORD="test123"
|
TEST_PASSWORD="test123"
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
@@ -14,4 +14,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
|||||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
@@ -14,4 +14,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
|||||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
@@ -14,4 +14,5 @@ VITE_APP_IS_TEST=true
|
|||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
@@ -14,4 +14,5 @@ VITE_APP_IS_TEST=true
|
|||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
308
client/package-lock.json
generated
308
client/package-lock.json
generated
@@ -9,48 +9,49 @@
|
|||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-browser": "^2.23.1",
|
"@amplitude/analytics-browser": "^2.23.5",
|
||||||
"@ant-design/pro-layout": "^7.22.6",
|
"@ant-design/pro-layout": "^7.22.6",
|
||||||
"@apollo/client": "^3.13.9",
|
"@apollo/client": "^3.13.9",
|
||||||
"@emotion/is-prop-valid": "^1.3.1",
|
"@emotion/is-prop-valid": "^1.4.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||||
"@firebase/analytics": "^0.10.17",
|
"@firebase/analytics": "^0.10.17",
|
||||||
"@firebase/app": "^0.14.1",
|
"@firebase/app": "^0.14.2",
|
||||||
"@firebase/auth": "^1.10.8",
|
"@firebase/auth": "^1.10.8",
|
||||||
"@firebase/firestore": "^4.8.0",
|
"@firebase/firestore": "^4.9.1",
|
||||||
"@firebase/messaging": "^0.12.22",
|
"@firebase/messaging": "^0.12.22",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.9.0",
|
||||||
"@sentry/cli": "^2.52.0",
|
"@sentry/cli": "^2.53.0",
|
||||||
"@sentry/react": "^9.43.0",
|
"@sentry/react": "^9.43.0",
|
||||||
"@sentry/vite-plugin": "^4.1.1",
|
"@sentry/vite-plugin": "^4.3.0",
|
||||||
"@splitsoftware/splitio-react": "^2.3.1",
|
"@splitsoftware/splitio-react": "^2.3.1",
|
||||||
"@tanem/react-nprogress": "^5.0.53",
|
"@tanem/react-nprogress": "^5.0.53",
|
||||||
"antd": "^5.27.1",
|
"antd": "^5.27.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^4.4.0",
|
"apollo-link-sentry": "^4.4.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.18",
|
||||||
"dayjs-business-days2": "^1.3.0",
|
"dayjs-business-days2": "^1.3.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.2",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"graphql": "^16.11.0",
|
"graphql": "^16.11.0",
|
||||||
"i18next": "^25.4.0",
|
"i18next": "^25.5.2",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.12.13",
|
"libphonenumber-js": "^1.12.15",
|
||||||
|
"lightningcss": "^1.30.1",
|
||||||
"logrocket": "^9.0.2",
|
"logrocket": "^9.0.2",
|
||||||
"markerjs2": "^2.32.6",
|
"markerjs2": "^2.32.6",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.0.2",
|
"normalize-url": "^8.0.2",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"phone": "^3.1.67",
|
"phone": "^3.1.67",
|
||||||
"posthog-js": "^1.260.2",
|
"posthog-js": "^1.261.7",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.2.2",
|
"query-string": "^9.2.2",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.1",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^15.7.1",
|
"react-i18next": "^15.7.3",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
@@ -81,7 +82,7 @@
|
|||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.1",
|
||||||
"sass": "^1.90.0",
|
"sass": "^1.92.0",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"styled-components": "^6.1.19",
|
"styled-components": "^6.1.19",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
@@ -110,7 +111,6 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"lightningcss": "^1.30.1",
|
|
||||||
"memfs": "^4.36.3",
|
"memfs": "^4.36.3",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"playwright": "^1.55.0",
|
"playwright": "^1.55.0",
|
||||||
@@ -141,28 +141,28 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@amplitude/analytics-browser": {
|
"node_modules/@amplitude/analytics-browser": {
|
||||||
"version": "2.23.1",
|
"version": "2.23.5",
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.23.5.tgz",
|
||||||
"integrity": "sha512-TYsh7ORT9UoEF3JpmWVpyyRyeE4k8SS+6TNgEoCRj4ZtjiiWKP1CE7lEspgVBjWdSCUqS1o85Cte7c2mkj+SiA==",
|
"integrity": "sha512-R1N506rifI3/axSTM3EQkVjCgeJsmhybRONOdnA3MCJwOIC77UVEOIzTVNjnAAzgBSxDNTCy6ejGgBf3PgzBog==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-core": "^2.21.1",
|
"@amplitude/analytics-core": "^2.22.1",
|
||||||
"@amplitude/analytics-remote-config": "^0.4.0",
|
"@amplitude/analytics-remote-config": "^0.4.0",
|
||||||
"@amplitude/plugin-autocapture-browser": "^1.10.1",
|
"@amplitude/plugin-autocapture-browser": "^1.11.1",
|
||||||
"@amplitude/plugin-network-capture-browser": "^1.5.1",
|
"@amplitude/plugin-network-capture-browser": "^1.5.4",
|
||||||
"@amplitude/plugin-page-view-tracking-browser": "^2.3.42",
|
"@amplitude/plugin-page-view-tracking-browser": "^2.3.45",
|
||||||
"@amplitude/plugin-web-vitals-browser": "^0.1.0-beta.17",
|
"@amplitude/plugin-web-vitals-browser": "^0.1.0-beta.20",
|
||||||
"tslib": "^2.4.1"
|
"tslib": "^2.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@amplitude/analytics-client-common": {
|
"node_modules/@amplitude/analytics-client-common": {
|
||||||
"version": "2.3.36",
|
"version": "2.3.39",
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.3.36.tgz",
|
"resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.3.39.tgz",
|
||||||
"integrity": "sha512-4MmuUuX8V9HOCrZ3VMQ3v3lkdksKQxswsO6mpm4YJvznty16+AaaupajubHik5GmmK8MV89ZqG0yLQLKiQm4yg==",
|
"integrity": "sha512-Dt31IIalME8whTXLgnKPLh9HbHTr8dC9F51reS1gngXAkOTErzAvbBl6UIc09bjqHWmimsRYgi6nflubnqwvMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-connector": "^1.4.8",
|
"@amplitude/analytics-connector": "^1.4.8",
|
||||||
"@amplitude/analytics-core": "^2.21.1",
|
"@amplitude/analytics-core": "^2.22.1",
|
||||||
"@amplitude/analytics-types": "^2.10.0",
|
"@amplitude/analytics-types": "^2.10.0",
|
||||||
"tslib": "^2.4.1"
|
"tslib": "^2.4.1"
|
||||||
}
|
}
|
||||||
@@ -174,9 +174,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@amplitude/analytics-core": {
|
"node_modules/@amplitude/analytics-core": {
|
||||||
"version": "2.21.1",
|
"version": "2.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.22.1.tgz",
|
||||||
"integrity": "sha512-4lfjUDl4VF4H+O9uZJsf6hlmOlVte+CJI45i8gV8vh9jUEn0/Ad3Cyeu2D9p2dUtLUgKVcXglqkoSpxPzhGWFw==",
|
"integrity": "sha512-nzlulhS7jYQc91wOc392avBLDAiPZmIBuJ1apA640YlleX/egVxKgZVYHH3Ge4ZNkaxoESwUb4mf2R+ZI0fXxA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-connector": "^1.6.4",
|
"@amplitude/analytics-connector": "^1.6.4",
|
||||||
@@ -202,12 +202,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@amplitude/plugin-autocapture-browser": {
|
"node_modules/@amplitude/plugin-autocapture-browser": {
|
||||||
"version": "1.10.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-autocapture-browser/-/plugin-autocapture-browser-1.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@amplitude/plugin-autocapture-browser/-/plugin-autocapture-browser-1.11.1.tgz",
|
||||||
"integrity": "sha512-fLsad4xnxkiZ62mEFxze5SgNyxbc6qk7FMlzUPCpgkPhdbJkiogajTonEnRi+p5HU2Ze8K242gsfnR66xLEU1Q==",
|
"integrity": "sha512-6nus1nXlH1ru/yjx07yk1cyjc9scAsE9dO4f0xxH8xpHlYQ4yVCuYApcguIpogISlPiySAxSZ+4WDreLrpQiDw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-core": "^2.21.1",
|
"@amplitude/analytics-core": "^2.22.1",
|
||||||
"@amplitude/analytics-remote-config": "^0.6.3",
|
"@amplitude/analytics-remote-config": "^0.6.3",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"tslib": "^2.4.1"
|
"tslib": "^2.4.1"
|
||||||
@@ -241,23 +241,23 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@amplitude/plugin-network-capture-browser": {
|
"node_modules/@amplitude/plugin-network-capture-browser": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-network-capture-browser/-/plugin-network-capture-browser-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@amplitude/plugin-network-capture-browser/-/plugin-network-capture-browser-1.5.4.tgz",
|
||||||
"integrity": "sha512-45KD4wo+7dfFIi3Q7w3u6x3R9FQdYifSZPyDG02V7YYdOjmRFC0K4Jzx0fpmbYqsl4BQDwe4q2DC6eDPKYDn3A==",
|
"integrity": "sha512-GRvi44tNx2TdHQ/dnC9DLqwsaBE1gC/bmHNaudTbp/nwIM8nVCAxZaXaXJEUouK7WBAamr7a3WmFruecqCeOlA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-core": "^2.21.1",
|
"@amplitude/analytics-core": "^2.22.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"tslib": "^2.4.1"
|
"tslib": "^2.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@amplitude/plugin-page-view-tracking-browser": {
|
"node_modules/@amplitude/plugin-page-view-tracking-browser": {
|
||||||
"version": "2.3.42",
|
"version": "2.3.45",
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.3.42.tgz",
|
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.3.45.tgz",
|
||||||
"integrity": "sha512-MSO5hOSXdPXAUSW3vFqUz08/MrAfzn4TU1uyYL0q1MZz63bEwxppVaMnwgx1NfkyYf4zlWn0KZ6PREhXeWL0YA==",
|
"integrity": "sha512-L2JH/TDTdjfexkY5hHVS3dCb4+q5H1jeIKhXUcBQ/Wx91asLY9BsH91J4bo9EK4J4Al8jVRwqJz0tIQ17qW9RQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-client-common": "^2.3.36",
|
"@amplitude/analytics-client-common": "^2.3.39",
|
||||||
"@amplitude/analytics-types": "^2.10.0",
|
"@amplitude/analytics-types": "^2.10.0",
|
||||||
"tslib": "^2.4.1"
|
"tslib": "^2.4.1"
|
||||||
}
|
}
|
||||||
@@ -2534,9 +2534,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/is-prop-valid": {
|
"node_modules/@emotion/is-prop-valid": {
|
||||||
"version": "1.3.1",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
|
||||||
"integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
|
"integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/memoize": "^0.9.0"
|
"@emotion/memoize": "^0.9.0"
|
||||||
@@ -3280,9 +3280,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/app": {
|
"node_modules/@firebase/app": {
|
||||||
"version": "0.14.1",
|
"version": "0.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.2.tgz",
|
||||||
"integrity": "sha512-jxTrDbxnGoX7cGz7aP9E7v9iKvBbQfZ8Gz4TH3SfrrkcyIojJM3+hJnlbGnGxHrABts844AxRcg00arMZEyA6Q==",
|
"integrity": "sha512-Ecx2ig/JLC9ayIQwZHqm41Tzlf4c1WUuFhFUZB1y+JIJqDRE579x7Uil7tKT8MwDpOPwrK5ZtpxdSsrfy/LF8Q==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.7.0",
|
"@firebase/component": "0.7.0",
|
||||||
@@ -3333,9 +3333,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/firestore": {
|
"node_modules/@firebase/firestore": {
|
||||||
"version": "4.9.0",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.1.tgz",
|
||||||
"integrity": "sha512-5zl0+/h1GvlCSLt06RMwqFsd7uqRtnNZt4sW99k2rKRd6k/ECObIWlEnvthm2cuOSnUmwZknFqtmd1qyYSLUuQ==",
|
"integrity": "sha512-PYVUTkhC9y8pydrqC3O1Oc4AMfkGSWdmuH9xgPJjiEbpUIUPQ4J8wJhyuash+o2u+axmyNRFP8ULNUKb+WzBzQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.7.0",
|
"@firebase/component": "0.7.0",
|
||||||
@@ -4090,6 +4090,12 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@posthog/core": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-hWk3rUtJl2crQK0WNmwg13n82hnTwB99BT99/XI5gZSvIlYZ1TPmMZE8H2dhJJ98J/rm9vYJ/UXNzw3RV5HTpQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@protobufjs/aspromise": {
|
"node_modules/@protobufjs/aspromise": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
@@ -4387,9 +4393,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@reduxjs/toolkit": {
|
"node_modules/@reduxjs/toolkit": {
|
||||||
"version": "2.8.2",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz",
|
||||||
"integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
|
"integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
@@ -5055,6 +5061,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.1.1.tgz",
|
||||||
"integrity": "sha512-HUpqrCK7zDVojTV6KL6BO9ZZiYrEYQqvYQrscyMsq04z+WCupXaH6YEliiNRvreR8DBJgdsG3lBRpebhUGmvfA==",
|
"integrity": "sha512-HUpqrCK7zDVojTV6KL6BO9ZZiYrEYQqvYQrscyMsq04z+WCupXaH6YEliiNRvreR8DBJgdsG3lBRpebhUGmvfA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
@@ -5080,6 +5087,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.1.1.tgz",
|
||||||
"integrity": "sha512-Hx9RgXaD1HEYmL5aYoWwCKkVvPp4iklwfD9mvmdpQtcwLg6b6oLnPVDQaOry1ak6Pxt8smlrWcKy4IiKASlvig==",
|
"integrity": "sha512-Hx9RgXaD1HEYmL5aYoWwCKkVvPp4iklwfD9mvmdpQtcwLg6b6oLnPVDQaOry1ak6Pxt8smlrWcKy4IiKASlvig==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.18.5",
|
"@babel/core": "^7.18.5",
|
||||||
@@ -5099,6 +5107,7 @@
|
|||||||
"version": "16.6.1",
|
"version": "16.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||||
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -5108,9 +5117,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli": {
|
"node_modules/@sentry/cli": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.53.0.tgz",
|
||||||
"integrity": "sha512-PXyo7Yv7+rVMSBGZfI/eFEzzhiKedTs25sDCjz4a3goAZ/F5R5tn3MKq30pnze5wNnoQmLujAa0uUjfNcWP+uQ==",
|
"integrity": "sha512-n2ZNb+5Z6AZKQSI0SusQ7ZzFL637mfw3Xh4C3PEyVSn9LiF683fX0TTq8OeGmNZQS4maYfS95IFD+XpydU0dEA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5127,20 +5136,20 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@sentry/cli-darwin": "2.52.0",
|
"@sentry/cli-darwin": "2.53.0",
|
||||||
"@sentry/cli-linux-arm": "2.52.0",
|
"@sentry/cli-linux-arm": "2.53.0",
|
||||||
"@sentry/cli-linux-arm64": "2.52.0",
|
"@sentry/cli-linux-arm64": "2.53.0",
|
||||||
"@sentry/cli-linux-i686": "2.52.0",
|
"@sentry/cli-linux-i686": "2.53.0",
|
||||||
"@sentry/cli-linux-x64": "2.52.0",
|
"@sentry/cli-linux-x64": "2.53.0",
|
||||||
"@sentry/cli-win32-arm64": "2.52.0",
|
"@sentry/cli-win32-arm64": "2.53.0",
|
||||||
"@sentry/cli-win32-i686": "2.52.0",
|
"@sentry/cli-win32-i686": "2.53.0",
|
||||||
"@sentry/cli-win32-x64": "2.52.0"
|
"@sentry/cli-win32-x64": "2.53.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-darwin": {
|
"node_modules/@sentry/cli-darwin": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.53.0.tgz",
|
||||||
"integrity": "sha512-ieQs/p4yTHT27nBzy0wtAb8BSISfWlpXdgsACcwXimYa36NJRwyCqgOXUaH/BYiTdwWSHpuANbUHGJW6zljzxw==",
|
"integrity": "sha512-NNPfpILMwKgpHiyJubHHuauMKltkrgLQ5tvMdxNpxY60jBNdo5VJtpESp4XmXlnidzV4j1z61V4ozU6ttDgt5Q==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5151,9 +5160,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-linux-arm": {
|
"node_modules/@sentry/cli-linux-arm": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.53.0.tgz",
|
||||||
"integrity": "sha512-tWMLU+hj+iip5Akx+S76biAOE1eMMWTDq8c0MqMv/ahHgb6/HiVngMcUsp59Oz3EczJGbTkcnS3vRTDodEcMDw==",
|
"integrity": "sha512-NdRzQ15Ht83qG0/Lyu11ciy/Hu/oXbbtJUgwzACc7bWvHQA8xEwTsehWexqn1529Kfc5EjuZ0Wmj3MHmp+jOWw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -5169,9 +5178,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-linux-arm64": {
|
"node_modules/@sentry/cli-linux-arm64": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.53.0.tgz",
|
||||||
"integrity": "sha512-RxT5uzxjCkcvplmx0bavJIEYerRex2Rg/2RAVBdVvWLKFOcmeerTn/VVxPZVuDIVMVyjlZsteWPYwfUm+Ia3wQ==",
|
"integrity": "sha512-xY/CZ1dVazsSCvTXzKpAgXaRqfljVfdrFaYZRUaRPf1ZJRGa3dcrivoOhSIeG/p5NdYtMvslMPY9Gm2MT0M83A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -5187,9 +5196,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-linux-i686": {
|
"node_modules/@sentry/cli-linux-i686": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.53.0.tgz",
|
||||||
"integrity": "sha512-sKcJmIg7QWFtlNU5Bs5OZprwdIzzyYMRpFkWioPZ4TE82yvP1+2SAX31VPUlTx+7NLU6YVEWNwvSxh8LWb7iOw==",
|
"integrity": "sha512-0REmBibGAB4jtqt9S6JEsFF4QybzcXHPcHtJjgMi5T0ueh952uG9wLzjSxQErCsxTKF+fL8oG0Oz5yKBuCwCCQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x86",
|
"x86",
|
||||||
"ia32"
|
"ia32"
|
||||||
@@ -5206,9 +5215,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-linux-x64": {
|
"node_modules/@sentry/cli-linux-x64": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.53.0.tgz",
|
||||||
"integrity": "sha512-aPZ7bP02zGkuEqTiOAm4np/ggfgtzrq4ti1Xze96Csi/DV3820SCfLrPlsvcvnqq7x69IL9cI3kXjdEpgrfGxw==",
|
"integrity": "sha512-9UGJL+Vy5N/YL1EWPZ/dyXLkShlNaDNrzxx4G7mTS9ywjg+BIuemo6rnN7w43K1NOjObTVO6zY0FwumJ1pCyLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -5224,9 +5233,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-win32-arm64": {
|
"node_modules/@sentry/cli-win32-arm64": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.53.0.tgz",
|
||||||
"integrity": "sha512-90hrB5XdwJVhRpCmVrEcYoKW8nl5/V9OfVvOGeKUPvUkApLzvsInK74FYBZEVyAn1i/NdUv+Xk9q2zqUGK1aLQ==",
|
"integrity": "sha512-G1kjOjrjMBY20rQcJV2GA8KQE74ufmROCDb2GXYRfjvb1fKAsm4Oh8N5+Tqi7xEHdjQoLPkE4CNW0aH68JSUDQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -5240,9 +5249,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-win32-i686": {
|
"node_modules/@sentry/cli-win32-i686": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.53.0.tgz",
|
||||||
"integrity": "sha512-HXlSE4CaLylNrELx4KVmOQjV5bURCNuky6sjCWiTH7HyDqHEak2Rk8iLE0JNLj5RETWMvmaZnZZFfmyGlY1opg==",
|
"integrity": "sha512-qbGTZUzesuUaPtY9rPXdNfwLqOZKXrJRC1zUFn52hdo6B+Dmv0m/AHwRVFHZP53Tg1NCa8bDei2K/uzRN0dUZw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x86",
|
"x86",
|
||||||
"ia32"
|
"ia32"
|
||||||
@@ -5257,9 +5266,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-win32-x64": {
|
"node_modules/@sentry/cli-win32-x64": {
|
||||||
"version": "2.52.0",
|
"version": "2.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.53.0.tgz",
|
||||||
"integrity": "sha512-hJT0C3FwHk1Mt9oFqcci88wbO1D+yAWUL8J29HEGM5ZAqlhdh7sAtPDIC3P2LceUJOjnXihow47Bkj62juatIQ==",
|
"integrity": "sha512-1TXYxYHtwgUq5KAJt3erRzzUtPqg7BlH9T7MdSPHjJatkrr/kwZqnVe2H6Arr/5NH891vOlIeSPHBdgJUAD69g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -5320,18 +5329,58 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/vite-plugin": {
|
"node_modules/@sentry/vite-plugin": {
|
||||||
"version": "4.1.1",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-4.3.0.tgz",
|
||||||
"integrity": "sha512-kNIZiqRbFHJHzV0QF1RyuwMprwK2Lk354qs98P7DduU1TkzrNG3+2f8liYJaiYCrsjDvJlPHyVFBDF9IRhJGdA==",
|
"integrity": "sha512-MeTAHMmTOgBPMAjeW7/ONyXwgScZdaFFtNiALKcAODnVqC7eoHdSRIWeH5mkLr2Dvs7nqtBaDpKxRjUBgfm9LQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/bundler-plugin-core": "4.1.1",
|
"@sentry/bundler-plugin-core": "4.3.0",
|
||||||
"unplugin": "1.0.1"
|
"unplugin": "1.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sentry/vite-plugin/node_modules/@sentry/babel-plugin-component-annotate": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-OuxqBprXRyhe8Pkfyz/4yHQJc5c3lm+TmYWSSx8u48g5yKewSQDOxkiLU5pAk3WnbLPy8XwU/PN+2BG0YFU9Nw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/vite-plugin/node_modules/@sentry/bundler-plugin-core": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-dmR4DJhJ4jqVWGWppuTL2blNFqOZZnt4aLkewbD1myFG3KVfUx8CrMQWEmGjkgPOtj5TO6xH9PyTJjXC6o5tnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.18.5",
|
||||||
|
"@sentry/babel-plugin-component-annotate": "4.3.0",
|
||||||
|
"@sentry/cli": "^2.51.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"find-up": "^5.0.0",
|
||||||
|
"glob": "^9.3.2",
|
||||||
|
"magic-string": "0.30.8",
|
||||||
|
"unplugin": "1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/vite-plugin/node_modules/dotenv": {
|
||||||
|
"version": "16.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||||
|
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sentry/webpack-plugin": {
|
"node_modules/@sentry/webpack-plugin": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-4.1.1.tgz",
|
||||||
@@ -6148,9 +6197,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/antd": {
|
"node_modules/antd": {
|
||||||
"version": "5.27.1",
|
"version": "5.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/antd/-/antd-5.27.3.tgz",
|
||||||
"integrity": "sha512-jGMSdBN7hAMvPV27B4RhzZfL6n6yu8yDbo7oXrlJasaOqB7bSDPcjdEy1kXy3JPsny/Qazb1ykzRI4EfcByAPQ==",
|
"integrity": "sha512-Jewp1ek1iyqoAyjWyPgzc2kioZ+7S3jh39a+tld/j4ucnuf/cBk4omfyIdhLz49pVNsaEcRp5LtJOSQPFwPgpA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/colors": "^7.2.1",
|
"@ant-design/colors": "^7.2.1",
|
||||||
@@ -6192,7 +6241,7 @@
|
|||||||
"rc-slider": "~11.1.8",
|
"rc-slider": "~11.1.8",
|
||||||
"rc-steps": "~6.0.1",
|
"rc-steps": "~6.0.1",
|
||||||
"rc-switch": "~4.1.0",
|
"rc-switch": "~4.1.0",
|
||||||
"rc-table": "~7.51.1",
|
"rc-table": "~7.52.6",
|
||||||
"rc-tabs": "~15.7.0",
|
"rc-tabs": "~15.7.0",
|
||||||
"rc-textarea": "~1.10.2",
|
"rc-textarea": "~1.10.2",
|
||||||
"rc-tooltip": "~6.4.0",
|
"rc-tooltip": "~6.4.0",
|
||||||
@@ -7897,9 +7946,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dayjs": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.11.13",
|
"version": "1.11.18",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
|
||||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dayjs-business-days2": {
|
"node_modules/dayjs-business-days2": {
|
||||||
@@ -8074,7 +8123,6 @@
|
|||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -8197,9 +8245,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "17.2.1",
|
"version": "17.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
|
||||||
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -9846,9 +9894,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/i18next": {
|
"node_modules/i18next": {
|
||||||
"version": "25.4.0",
|
"version": "25.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz",
|
||||||
"integrity": "sha512-UH5aiamXsO3cfrZFurCHiB6YSs3C+s+XY9UaJllMMSbmaoXILxFgqDEZu4NbfzJFjmUo3BNMa++Rjkr3ofjfLw==",
|
"integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -10992,16 +11040,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/libphonenumber-js": {
|
"node_modules/libphonenumber-js": {
|
||||||
"version": "1.12.13",
|
"version": "1.12.15",
|
||||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.13.tgz",
|
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.15.tgz",
|
||||||
"integrity": "sha512-QZXnR/OGiDcBjF4hGk0wwVrPcZvbSSyzlvkjXv5LFfktj7O2VZDrt4Xs8SgR/vOFco+qk1i8J43ikMXZoTrtPw==",
|
"integrity": "sha512-TMDCtIhWUDHh91wRC+wFuGlIzKdPzaTUHHVrIZ3vPUEoNaXFLrsIQ1ZpAeZeXApIF6rvDksMTvjrIQlLKaYxqQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^2.0.3"
|
"detect-libc": "^2.0.3"
|
||||||
@@ -11033,7 +11080,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11054,7 +11100,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11075,7 +11120,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11096,7 +11140,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11117,7 +11160,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11138,7 +11180,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11159,7 +11200,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11180,7 +11220,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11201,7 +11240,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11222,7 +11260,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -13152,11 +13189,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/posthog-js": {
|
"node_modules/posthog-js": {
|
||||||
"version": "1.260.2",
|
"version": "1.261.7",
|
||||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.260.2.tgz",
|
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.261.7.tgz",
|
||||||
"integrity": "sha512-2Q+QUz9j9+uG16wp0WcOEbezVsLZCobZyTX8NvWPMGKyPaf2lOsjbPjznsq5JiIt324B6NAqzpWYZTzvhn9k9Q==",
|
"integrity": "sha512-Fjpbz6VfIMsEbKIN/UyTWhU1DGgVIngqoRjPGRolemIMOVzTfI77OZq8WwiBhMug+rU+wNhGCQhC41qRlR5CxA==",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@posthog/core": "1.0.2",
|
||||||
"core-js": "^3.38.1",
|
"core-js": "^3.38.1",
|
||||||
"fflate": "^0.4.8",
|
"fflate": "^0.4.8",
|
||||||
"preact": "^10.19.3",
|
"preact": "^10.19.3",
|
||||||
@@ -13892,9 +13930,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rc-table": {
|
"node_modules/rc-table": {
|
||||||
"version": "7.51.1",
|
"version": "7.52.7",
|
||||||
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.1.tgz",
|
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.52.7.tgz",
|
||||||
"integrity": "sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==",
|
"integrity": "sha512-yuZfnTpuHwRa4JH+F28wQfGeDzqtgIDvLBBJk5sFncXQjTExhtBNc6dPfVo5pL5SjabJEoejefs6wsrAKfhDoQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.10.1",
|
"@babel/runtime": "^7.10.1",
|
||||||
@@ -14204,16 +14242,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-i18next": {
|
"node_modules/react-i18next": {
|
||||||
"version": "15.7.1",
|
"version": "15.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.3.tgz",
|
||||||
"integrity": "sha512-o4VsKh30fy7p0z5ACHuyWqB6xu9WpQIQy2/ZcbCqopNnrnTVOPn/nAv9uYP4xYAWg99QMpvZ9Bu/si3eGurzGw==",
|
"integrity": "sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.6",
|
"@babel/runtime": "^7.27.6",
|
||||||
"html-parse-stringify": "^3.0.1"
|
"html-parse-stringify": "^3.0.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"i18next": ">= 23.4.0",
|
"i18next": ">= 25.4.1",
|
||||||
"react": ">= 16.8.0",
|
"react": ">= 16.8.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
@@ -15134,9 +15172,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.90.0",
|
"version": "1.92.0",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.92.0.tgz",
|
||||||
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
|
"integrity": "sha512-KDNI0BxgIRDAfJgzNm5wuy+4yOCIZyrUbjSpiU/JItfih+KGXAVefKL53MTml054MmBA3DDKIBMSI/7XLxZJ3A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
|
|||||||
@@ -8,48 +8,48 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-browser": "^2.23.1",
|
"@amplitude/analytics-browser": "^2.23.5",
|
||||||
"@ant-design/pro-layout": "^7.22.6",
|
"@ant-design/pro-layout": "^7.22.6",
|
||||||
"@apollo/client": "^3.13.9",
|
"@apollo/client": "^3.13.9",
|
||||||
"@emotion/is-prop-valid": "^1.3.1",
|
"@emotion/is-prop-valid": "^1.4.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||||
"@firebase/analytics": "^0.10.17",
|
"@firebase/analytics": "^0.10.17",
|
||||||
"@firebase/app": "^0.14.1",
|
"@firebase/app": "^0.14.2",
|
||||||
"@firebase/auth": "^1.10.8",
|
"@firebase/auth": "^1.10.8",
|
||||||
"@firebase/firestore": "^4.8.0",
|
"@firebase/firestore": "^4.9.1",
|
||||||
"@firebase/messaging": "^0.12.22",
|
"@firebase/messaging": "^0.12.22",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.9.0",
|
||||||
"@sentry/cli": "^2.52.0",
|
"@sentry/cli": "^2.53.0",
|
||||||
"@sentry/react": "^9.43.0",
|
"@sentry/react": "^9.43.0",
|
||||||
"@sentry/vite-plugin": "^4.1.1",
|
"@sentry/vite-plugin": "^4.3.0",
|
||||||
"@splitsoftware/splitio-react": "^2.3.1",
|
"@splitsoftware/splitio-react": "^2.3.1",
|
||||||
"@tanem/react-nprogress": "^5.0.53",
|
"@tanem/react-nprogress": "^5.0.53",
|
||||||
"antd": "^5.27.1",
|
"antd": "^5.27.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^4.4.0",
|
"apollo-link-sentry": "^4.4.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.18",
|
||||||
"dayjs-business-days2": "^1.3.0",
|
"dayjs-business-days2": "^1.3.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.2",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"graphql": "^16.11.0",
|
"graphql": "^16.11.0",
|
||||||
"i18next": "^25.4.0",
|
"i18next": "^25.5.2",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.12.13",
|
"libphonenumber-js": "^1.12.15",
|
||||||
"logrocket": "^9.0.2",
|
"logrocket": "^9.0.2",
|
||||||
"markerjs2": "^2.32.6",
|
"markerjs2": "^2.32.6",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.0.2",
|
"normalize-url": "^8.0.2",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"phone": "^3.1.67",
|
"phone": "^3.1.67",
|
||||||
"posthog-js": "^1.260.2",
|
"posthog-js": "^1.261.7",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.2.2",
|
"query-string": "^9.2.2",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
@@ -57,11 +57,12 @@
|
|||||||
"react-big-calendar": "^1.19.4",
|
"react-big-calendar": "^1.19.4",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^8.0.1",
|
"react-cookie": "^8.0.1",
|
||||||
|
"lightningcss": "^1.30.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.1",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^15.7.1",
|
"react-i18next": "^15.7.3",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
@@ -80,7 +81,7 @@
|
|||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.1",
|
||||||
"sass": "^1.90.0",
|
"sass": "^1.92.0",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"styled-components": "^6.1.19",
|
"styled-components": "^6.1.19",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
@@ -152,7 +153,6 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"lightningcss": "^1.30.1",
|
|
||||||
"memfs": "^4.36.3",
|
"memfs": "^4.36.3",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"playwright": "^1.55.0",
|
"playwright": "^1.55.0",
|
||||||
|
|||||||
@@ -233,9 +233,7 @@ export function App({
|
|||||||
path="/parts/*"
|
path="/parts/*"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
|
||||||
</SocketProvider>
|
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -109,6 +109,13 @@ export function BillsListTableComponent({
|
|||||||
key: "vendorname",
|
key: "vendorname",
|
||||||
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
||||||
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
||||||
|
filters: bills
|
||||||
|
? [...new Set(bills.map((bill) => bill.vendor.name))].map((name) => ({
|
||||||
|
text: name,
|
||||||
|
value: name
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
onFilter: (value, record) => record.vendor.name === value,
|
||||||
render: (text, record) => <span>{record.vendor.name}</span>
|
render: (text, record) => <span>{record.vendor.name}</span>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddComponent = (e) => {
|
const handleAddComponent = (e) => {
|
||||||
logImEXEvent("dashboard_add_component", { name: e });
|
logImEXEvent("dashboard_add_component", { name: e.key });
|
||||||
setState({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
@@ -67,11 +67,14 @@ export const uploadToS3 = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Key should be same as we provided to maintain backwards compatibility.
|
//Key should be same as we provided to maintain backwards compatibility.
|
||||||
const { presignedUrl: preSignedUploadUrlToS3, key: s3Key } = signedURLResponse.data.signedUrls[0];
|
const { presignedUrl: preSignedUploadUrlToS3, key: s3Key, contentType } = signedURLResponse.data.signedUrls[0];
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
onUploadProgress: (e) => {
|
onUploadProgress: (e) => {
|
||||||
if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
|
if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
...contentType ? { "Content-Type": fileType } : {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
|
|
||||||
export function GlobalFooter({ isPartsEntry }) {
|
export function GlobalFooter({ isPartsEntry }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (isPartsEntry) {
|
if (isPartsEntry) {
|
||||||
return (
|
return (
|
||||||
<Footer>
|
<Footer>
|
||||||
@@ -35,7 +35,6 @@ export function GlobalFooter({ isPartsEntry }) {
|
|||||||
rome: t("titles.romeonline")
|
rome: t("titles.romeonline")
|
||||||
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
||||||
</div>
|
</div>
|
||||||
<WssStatusDisplayComponent />
|
|
||||||
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
||||||
Disclaimer & Notices
|
Disclaimer & Notices
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|||||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
|
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
technician: selectTechnician
|
technician: selectTechnician,
|
||||||
|
isPartsEntry: selectIsPartsEntry
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({});
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
|
export function JobLinesPartPriceChange({ job, line, refetch, technician, isPartsEntry }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
|
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
@@ -64,6 +66,7 @@ export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
|
|||||||
|
|
||||||
const popcontent =
|
const popcontent =
|
||||||
!technician &&
|
!technician &&
|
||||||
|
!isPartsEntry &&
|
||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: null,
|
imex: null,
|
||||||
rome: (
|
rome: (
|
||||||
|
|||||||
@@ -481,48 +481,50 @@ export function JobLinesComponent({
|
|||||||
{Enhanced_Payroll.treatment === "on" && (
|
{Enhanced_Payroll.treatment === "on" && (
|
||||||
<JobLineBulkAssignComponent selectedLines={selectedLines} setSelectedLines={setSelectedLines} job={job} />
|
<JobLineBulkAssignComponent selectedLines={selectedLines} setSelectedLines={setSelectedLines} job={job} />
|
||||||
)}
|
)}
|
||||||
<Button
|
{!isPartsEntry && (
|
||||||
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
<Button
|
||||||
onClick={() => {
|
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
||||||
setBillEnterContext({
|
onClick={() => {
|
||||||
actions: { refetch: refetch },
|
setBillEnterContext({
|
||||||
context: {
|
actions: { refetch: refetch },
|
||||||
disableInvNumber: true,
|
context: {
|
||||||
job: { id: job.id },
|
disableInvNumber: true,
|
||||||
bill: {
|
job: { id: job.id },
|
||||||
vendorid: bodyshop.inhousevendorid,
|
bill: {
|
||||||
invoice_number: "ih",
|
vendorid: bodyshop.inhousevendorid,
|
||||||
isinhouse: true,
|
invoice_number: "ih",
|
||||||
date: dayjs(),
|
isinhouse: true,
|
||||||
total: 0,
|
date: dayjs(),
|
||||||
billlines: selectedLines.map((p) => {
|
total: 0,
|
||||||
return {
|
billlines: selectedLines.map((p) => {
|
||||||
joblineid: p.id,
|
return {
|
||||||
actual_price: p.act_price,
|
joblineid: p.id,
|
||||||
actual_cost: 0, //p.act_price,
|
actual_price: p.act_price,
|
||||||
line_desc: p.line_desc,
|
actual_cost: 0, //p.act_price,
|
||||||
line_remarks: p.line_remarks,
|
line_desc: p.line_desc,
|
||||||
part_type: p.part_type,
|
line_remarks: p.line_remarks,
|
||||||
quantity: p.quantity || 1,
|
part_type: p.part_type,
|
||||||
applicable_taxes: {
|
quantity: p.quantity || 1,
|
||||||
local: false,
|
applicable_taxes: {
|
||||||
state: false,
|
local: false,
|
||||||
federal: false
|
state: false,
|
||||||
}
|
federal: false
|
||||||
};
|
}
|
||||||
})
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
//Clear out the selected lines. IO-785
|
//Clear out the selected lines. IO-785
|
||||||
setSelectedLines([]);
|
setSelectedLines([]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HomeOutlined />
|
<HomeOutlined />
|
||||||
{t("parts.actions.orderinhouse")}
|
{t("parts.actions.orderinhouse")}
|
||||||
{selectedLines.length > 0 && ` (${selectedLines.length})`}
|
{selectedLines.length > 0 && ` (${selectedLines.length})`}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
id="job-lines-order-parts-button"
|
id="job-lines-order-parts-button"
|
||||||
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
||||||
@@ -578,7 +580,8 @@ export function JobLinesComponent({
|
|||||||
{t("joblines.actions.new")}
|
{t("joblines.actions.new")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
|
{!isPartsEntry &&
|
||||||
|
InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
|
||||||
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
|
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DEFAULT_COL_LAYOUT = { xs: 24, sm: 24, md: 8, lg: 4, xl: 4, xxl: 4 };
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
|
||||||
|
|
||||||
export function JobPartsQueueCount({ bodyshop, parts, style }) {
|
export function JobPartsQueueCount({ bodyshop, parts, defaultColLayout = DEFAULT_COL_LAYOUT }) {
|
||||||
const partsStatus = useMemo(() => {
|
const partsStatus = useMemo(() => {
|
||||||
if (!parts) return null;
|
if (!parts) return null;
|
||||||
return parts.reduce(
|
return parts.reduce(
|
||||||
@@ -35,35 +38,34 @@ export function JobPartsQueueCount({ bodyshop, parts, style }) {
|
|||||||
}, [bodyshop, parts]);
|
}, [bodyshop, parts]);
|
||||||
|
|
||||||
if (!parts) return null;
|
if (!parts) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row style={style}>
|
<Row>
|
||||||
<Col span={4}>
|
<Col {...defaultColLayout}>
|
||||||
<Tooltip title="Total">
|
<Tooltip title="Total">
|
||||||
<Tag>{partsStatus.total}</Tag>
|
<Tag>{partsStatus.total}</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col {...defaultColLayout}>
|
||||||
<Tooltip title="No Status">
|
<Tooltip title="No Status">
|
||||||
<Tag color="gold">{partsStatus["null"]}</Tag>
|
<Tag color="gold">{partsStatus["null"]}</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col {...defaultColLayout}>
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
|
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
|
||||||
<Tag color="blue">{partsStatus[bodyshop.md_order_statuses.default_ordered]}</Tag>
|
<Tag color="blue">{partsStatus[bodyshop.md_order_statuses.default_ordered]}</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col {...defaultColLayout}>
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_received}>
|
<Tooltip title={bodyshop.md_order_statuses.default_received}>
|
||||||
<Tag color="green">{partsStatus[bodyshop.md_order_statuses.default_received]}</Tag>
|
<Tag color="green">{partsStatus[bodyshop.md_order_statuses.default_received]}</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col {...defaultColLayout}>
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
|
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
|
||||||
<Tag color="orange">{partsStatus[bodyshop.md_order_statuses.default_returned]}</Tag>
|
<Tag color="orange">{partsStatus[bodyshop.md_order_statuses.default_returned]}</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col {...defaultColLayout}>
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
|
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
|
||||||
<Tag color="red">{partsStatus[bodyshop.md_order_statuses.default_bo]}</Tag>
|
<Tag color="red">{partsStatus[bodyshop.md_order_statuses.default_bo]}</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -20,35 +20,27 @@ export function JobTotalsCashDiscount({ bodyshop, amountDinero }) {
|
|||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
if (amountDinero && bodyshop) {
|
if (!amountDinero || !bodyshop) return;
|
||||||
setLoading(true);
|
|
||||||
let response;
|
|
||||||
try {
|
|
||||||
response = await axios.post("/intellipay/checkfee", {
|
|
||||||
bodyshop: { id: bodyshop.id, imexshopid: bodyshop.imexshopid, state: bodyshop.state },
|
|
||||||
amount: Dinero(amountDinero).toFormat("0.00")
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response?.data?.error) {
|
setLoading(true);
|
||||||
notification.open({
|
const errorMessage = "Error encountered when contacting IntelliPay service to determine cash discounted price.";
|
||||||
type: "error",
|
|
||||||
message:
|
try {
|
||||||
response.data?.error ||
|
const { id, imexshopid, state } = bodyshop;
|
||||||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
|
const { data } = await axios.post("/intellipay/checkfee", {
|
||||||
});
|
bodyshop: { id, imexshopid, state },
|
||||||
} else {
|
amount: Dinero(amountDinero).toUnit()
|
||||||
setFee(response.data?.fee || 0);
|
});
|
||||||
}
|
|
||||||
} catch (error) {
|
if (data?.error) {
|
||||||
notification.open({
|
notification.open({ type: "error", message: data.error || errorMessage });
|
||||||
type: "error",
|
} else {
|
||||||
message:
|
setFee(data?.fee ?? 0);
|
||||||
error.response?.data?.error ||
|
|
||||||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notification.open({ type: "error", message: error.response?.data?.error || errorMessage });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [amountDinero, bodyshop, notification]);
|
}, [amountDinero, bodyshop, notification]);
|
||||||
|
|
||||||
|
|||||||
@@ -139,17 +139,18 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
<ProductionListColumnComment record={job} />
|
<ProductionListColumnComment record={job} />
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>{job.ins_co_nm}</DataLabel>
|
{!isPartsEntry && <DataLabel label={t("jobs.fields.ins_co_nm_short")}>{job.ins_co_nm}</DataLabel>}
|
||||||
<DataLabel label={t("jobs.fields.clm_no")}>{job.clm_no}</DataLabel>
|
<DataLabel label={t("jobs.fields.clm_no")}>{job.clm_no}</DataLabel>
|
||||||
<DataLabel label={t("jobs.fields.ponumber")} hideIfNull>
|
<DataLabel label={t("jobs.fields.ponumber")} hideIfNull>
|
||||||
{job.po_number}
|
{job.po_number}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel label={t("jobs.fields.repairtotal")}>
|
{!isPartsEntry && (
|
||||||
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
|
<DataLabel label={t("jobs.fields.repairtotal")}>
|
||||||
<span style={{ margin: "0rem .5rem" }}>/</span>
|
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
|
||||||
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
|
<span style={{ margin: "0rem .5rem" }}>/</span>
|
||||||
</DataLabel>
|
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
|
||||||
|
</DataLabel>
|
||||||
|
)}
|
||||||
{!isPartsEntry && (
|
{!isPartsEntry && (
|
||||||
<>
|
<>
|
||||||
<DataLabel label={t("jobs.fields.alt_transport")}>
|
<DataLabel label={t("jobs.fields.alt_transport")}>
|
||||||
@@ -176,7 +177,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={!!job.estimate_sent_approval}
|
checked={!!job.estimate_sent_approval}
|
||||||
onChange={(e) => handleCheckboxChange("estimate_sent_approval", e.target.checked)}
|
onChange={(e) => handleCheckboxChange("estimate_sent_approval", e.target.checked)}
|
||||||
disabled={disabled}
|
disabled={disabled || isPartsEntry}
|
||||||
>
|
>
|
||||||
{job.estimate_sent_approval && (
|
{job.estimate_sent_approval && (
|
||||||
<span style={{ color: "#888" }}>
|
<span style={{ color: "#888" }}>
|
||||||
@@ -191,7 +192,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={!!job.estimate_approved}
|
checked={!!job.estimate_approved}
|
||||||
onChange={(e) => handleCheckboxChange("estimate_approved", e.target.checked)}
|
onChange={(e) => handleCheckboxChange("estimate_approved", e.target.checked)}
|
||||||
disabled={disabled}
|
disabled={disabled || isPartsEntry}
|
||||||
>
|
>
|
||||||
{job.estimate_approved && (
|
{job.estimate_approved && (
|
||||||
<span style={{ color: "#888" }}>
|
<span style={{ color: "#888" }}>
|
||||||
@@ -236,7 +237,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
<Card
|
<Card
|
||||||
style={{ height: "100%" }}
|
style={{ height: "100%" }}
|
||||||
title={
|
title={
|
||||||
disabled ? (
|
disabled || isPartsEntry ? (
|
||||||
<>{ownerTitle.length > 0 ? ownerTitle : t("owner.labels.noownerinfo")}</>
|
<>{ownerTitle.length > 0 ? ownerTitle : t("owner.labels.noownerinfo")}</>
|
||||||
) : (
|
) : (
|
||||||
<Link to={`/manage/owners/${job.owner.id}`}>
|
<Link to={`/manage/owners/${job.owner.id}`}>
|
||||||
@@ -247,14 +248,14 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
|
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||||
{disabled ? (
|
{disabled || isPartsEntry ? (
|
||||||
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
|
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
|
||||||
) : (
|
) : (
|
||||||
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
|
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
|
||||||
)}
|
)}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
|
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||||
{disabled ? (
|
{disabled || isPartsEntry ? (
|
||||||
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
|
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
|
||||||
) : (
|
) : (
|
||||||
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
|
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
|
||||||
@@ -266,7 +267,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
} ${job.ownr_st || ""} ${job.ownr_zip || ""}`}
|
} ${job.ownr_st || ""} ${job.ownr_zip || ""}`}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
|
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
|
||||||
{disabled ? (
|
{disabled || isPartsEntry ? (
|
||||||
<>{job.ownr_ea || ""}</>
|
<>{job.ownr_ea || ""}</>
|
||||||
) : job.ownr_ea ? (
|
) : job.ownr_ea ? (
|
||||||
<a href={`mailto:${job.ownr_ea}`}>{job.ownr_ea}</a>
|
<a href={`mailto:${job.ownr_ea}`}>{job.ownr_ea}</a>
|
||||||
@@ -316,7 +317,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
<DataLabel label={t("jobs.labels.relatedros")}>
|
<DataLabel label={t("jobs.labels.relatedros")}>
|
||||||
<JobsRelatedRos jobid={job.id} job={job} disabled={disabled} />
|
<JobsRelatedRos jobid={job.id} job={job} disabled={disabled} />
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
{job.vehicle && job.vehicle.notes && (
|
{job.vehicle?.notes && (
|
||||||
<DataLabel
|
<DataLabel
|
||||||
label={t("vehicles.fields.notes")}
|
label={t("vehicles.fields.notes")}
|
||||||
valueStyle={{ whiteSpace: "pre-wrap" }}
|
valueStyle={{ whiteSpace: "pre-wrap" }}
|
||||||
@@ -326,7 +327,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
|||||||
{job.vehicle.notes}
|
{job.vehicle.notes}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
)}
|
)}
|
||||||
{job.vehicle && job.vehicle.v_paint_codes && (
|
{job.vehicle?.v_paint_codes && (
|
||||||
<DataLabel label={t("vehicles.fields.v_paint_codes", { number: "" })}>
|
<DataLabel label={t("vehicles.fields.v_paint_codes", { number: "" })}>
|
||||||
<span style={{ whiteSpace: "pre" }}>
|
<span style={{ whiteSpace: "pre" }}>
|
||||||
{Object.keys(job.vehicle.v_paint_codes)
|
{Object.keys(job.vehicle.v_paint_codes)
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import { Space, Tag } from "antd";
|
import { Space, Tag } from "antd";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import getPartsBasePath from "../../utils/getPartsBasePath.js";
|
||||||
|
|
||||||
export default function JobsRelatedRos({ job, disabled }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
isPartsEntry: selectIsPartsEntry
|
||||||
|
});
|
||||||
|
|
||||||
|
function JobsRelatedRos({ job, disabled, isPartsEntry }) {
|
||||||
if (!(job?.vehicle && job.vehicle.jobs)) return null;
|
if (!(job?.vehicle && job.vehicle.jobs)) return null;
|
||||||
|
const basePath = getPartsBasePath(isPartsEntry);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{job.vehicle.jobs
|
{job.vehicle.jobs
|
||||||
@@ -12,7 +22,7 @@ export default function JobsRelatedRos({ job, disabled }) {
|
|||||||
{disabled ? (
|
{disabled ? (
|
||||||
<>{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${j.status ? ` | ${j.status}` : ""}`}</>
|
<>{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${j.status ? ` | ${j.status}` : ""}`}</>
|
||||||
) : (
|
) : (
|
||||||
<Link to={`/manage/jobs/${j?.id}`}>{`${j.ro_number || "N/A"}${
|
<Link to={`${basePath}/jobs/${j?.id}`}>{`${j.ro_number || "N/A"}${
|
||||||
j.clm_no ? ` | ${j.clm_no}` : ""
|
j.clm_no ? ` | ${j.clm_no}` : ""
|
||||||
}${j.status ? ` | ${j.status}` : ""}`}</Link>
|
}${j.status ? ` | ${j.status}` : ""}`}</Link>
|
||||||
)}
|
)}
|
||||||
@@ -21,3 +31,4 @@ export default function JobsRelatedRos({ job, disabled }) {
|
|||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export default connect(mapStateToProps)(JobsRelatedRos);
|
||||||
|
|||||||
@@ -227,15 +227,21 @@ export function PartsOrderListTableComponent({
|
|||||||
sorter: (a, b) => a.order_date - b.order_date,
|
sorter: (a, b) => a.order_date - b.order_date,
|
||||||
sortOrder: state.sortedInfo.columnKey === "order_date" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "order_date" && state.sortedInfo.order,
|
||||||
render: (text, record) => <DateFormatter>{record.order_date}</DateFormatter>
|
render: (text, record) => <DateFormatter>{record.order_date}</DateFormatter>
|
||||||
},
|
}
|
||||||
{
|
];
|
||||||
|
|
||||||
|
if (!isPartsEntry) {
|
||||||
|
columns.push({
|
||||||
title: t("parts_orders.fields.return"),
|
title: t("parts_orders.fields.return"),
|
||||||
dataIndex: "return",
|
dataIndex: "return",
|
||||||
key: "return",
|
key: "return",
|
||||||
sorter: (a, b) => a.return - b.return,
|
sorter: (a, b) => a.return - b.return,
|
||||||
sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order,
|
||||||
render: (text, record) => <Checkbox checked={record.return} />
|
render: (text, record) => <Checkbox checked={record.return} />
|
||||||
},
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.push(
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.deliver_by"),
|
title: t("parts_orders.fields.deliver_by"),
|
||||||
dataIndex: "deliver_by",
|
dataIndex: "deliver_by",
|
||||||
@@ -256,7 +262,7 @@ export function PartsOrderListTableComponent({
|
|||||||
render: (text, record) => recordActions(record, true),
|
render: (text, record) => recordActions(record, true),
|
||||||
id: "parts-order-list-table-actions"
|
id: "parts-order-list-table-actions"
|
||||||
}
|
}
|
||||||
];
|
);
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
|
|||||||
@@ -11,16 +11,27 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop,
|
||||||
|
isPartsEntry: selectIsPartsEntry
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(PartsOrderModalComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(PartsOrderModalComponent);
|
||||||
|
|
||||||
export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState, isReturn, preferredMake, job, form }) {
|
export function PartsOrderModalComponent({
|
||||||
|
bodyshop,
|
||||||
|
vendorList,
|
||||||
|
sendTypeState,
|
||||||
|
isReturn,
|
||||||
|
preferredMake,
|
||||||
|
job,
|
||||||
|
form,
|
||||||
|
isPartsEntry
|
||||||
|
}) {
|
||||||
const [sendType, setSendType] = sendTypeState;
|
const [sendType, setSendType] = sendTypeState;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -83,7 +94,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
|
|||||||
</Space>
|
</Space>
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
{!isReturn && (
|
{!isReturn && !isPartsEntry && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="removefrompartsqueue"
|
name="removefrompartsqueue"
|
||||||
label={t("parts_orders.labels.removefrompartsqueue")}
|
label={t("parts_orders.labels.removefrompartsqueue")}
|
||||||
@@ -92,7 +103,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
|
|||||||
<Checkbox />
|
<Checkbox />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
{OEConnection.treatment === "on" && !isReturn && (
|
{OEConnection.treatment === "on" && !isReturn && !isPartsEntry && (
|
||||||
<Form.Item name="is_quote" label={t("parts_orders.labels.is_quote")} valuePropName="checked">
|
<Form.Item name="is_quote" label={t("parts_orders.labels.is_quote")} valuePropName="checked">
|
||||||
<Checkbox />
|
<Checkbox />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -249,7 +260,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
|
|||||||
<Radio disabled={is_quote} value={"p"}>
|
<Radio disabled={is_quote} value={"p"}>
|
||||||
{t("parts_orders.labels.print")}
|
{t("parts_orders.labels.print")}
|
||||||
</Radio>
|
</Radio>
|
||||||
{OEConnection.treatment === "on" && !isReturn && (
|
{OEConnection.treatment === "on" && !isReturn && !isPartsEntry && (
|
||||||
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
|
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
|
||||||
)}
|
)}
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
title: t("jobs.fields.partsstatus"),
|
title: t("jobs.fields.partsstatus"),
|
||||||
dataIndex: "partsstatus",
|
dataIndex: "partsstatus",
|
||||||
key: "partsstatus",
|
key: "partsstatus",
|
||||||
render: (text, record) => <JobPartsQueueCount style={{ minWidth: "10rem" }} parts={record.joblines_status} />
|
render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.comment"),
|
title: t("jobs.fields.comment"),
|
||||||
|
|||||||
@@ -40,27 +40,26 @@ export function ScheduleCalendarWrapperComponent({
|
|||||||
const currentView = search.view || defaultView || "week";
|
const currentView = search.view || defaultView || "week";
|
||||||
|
|
||||||
const handleEventPropStyles = (event) => {
|
const handleEventPropStyles = (event) => {
|
||||||
const hasColor = Boolean(event?.color?.hex || event?.color);
|
const { color, block, arrived } = event ?? {};
|
||||||
|
const hasColor = Boolean(color?.hex || color);
|
||||||
const useBg = currentView !== "agenda";
|
const useBg = currentView !== "agenda";
|
||||||
|
|
||||||
// Prioritize explicit blocked-day background to ensure red in all themes
|
// Prioritize explicit blocked-day background to ensure red in all themes
|
||||||
let bg;
|
let bg;
|
||||||
if (useBg) {
|
if (useBg) {
|
||||||
if (event?.block) {
|
bg = block
|
||||||
bg = "var(--event-block-bg)";
|
? "var(--event-block-bg)"
|
||||||
} else if (hasColor) {
|
: arrived
|
||||||
bg = event?.color?.hex ?? event?.color;
|
? "var(--event-arrived-bg)"
|
||||||
} else {
|
: (color?.hex ?? color ?? "var(--event-bg-fallback)");
|
||||||
bg = "var(--event-bg-fallback)";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const usedFallback = !hasColor && !event?.block; // only mark as fallback when not blocked
|
const usedFallback = !hasColor && !block && !arrived; // only mark as fallback when not blocked or arrived
|
||||||
|
|
||||||
const classes = [
|
const classes = [
|
||||||
"imex-event",
|
"imex-event",
|
||||||
event.arrived && "imex-event-arrived",
|
arrived && "imex-event-arrived",
|
||||||
event.block && "imex-event-block",
|
block && "imex-event-block",
|
||||||
usedFallback && "imex-event-fallback"
|
usedFallback && "imex-event-fallback"
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -23,13 +23,24 @@ export default function ShopInfoContainer() {
|
|||||||
});
|
});
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
const combinedFeatureConfig = {
|
const combineFeatureConfigs = (...configs) =>
|
||||||
...FEATURE_CONFIGS.general,
|
(configs || [])
|
||||||
...FEATURE_CONFIGS.responsibilitycenters
|
.filter(Boolean)
|
||||||
};
|
.flatMap((cfg) => Object.entries(cfg))
|
||||||
|
.reduce((acc, [featureName, fieldPaths]) => {
|
||||||
|
if (!Array.isArray(fieldPaths)) return acc;
|
||||||
|
acc[featureName] = [...(acc[featureName] ?? []), ...fieldPaths];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const combinedFeatureConfig = combineFeatureConfigs(FEATURE_CONFIGS.general, FEATURE_CONFIGS.responsibilitycenters);
|
||||||
|
|
||||||
// Use form data preservation for all shop-info features
|
// Use form data preservation for all shop-info features
|
||||||
const { createSubmissionHandler } = useFormDataPreservation(form, data?.bodyshops[0], combinedFeatureConfig);
|
const { createSubmissionHandler, preserveHiddenFormData } = useFormDataPreservation(
|
||||||
|
form,
|
||||||
|
data?.bodyshops[0],
|
||||||
|
combinedFeatureConfig
|
||||||
|
);
|
||||||
|
|
||||||
const handleFinish = createSubmissionHandler((values) => {
|
const handleFinish = createSubmissionHandler((values) => {
|
||||||
setSaveLoading(true);
|
setSaveLoading(true);
|
||||||
@@ -51,8 +62,11 @@ export default function ShopInfoContainer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) form.resetFields();
|
if (!data) return;
|
||||||
}, [form, data]);
|
form.resetFields();
|
||||||
|
// After reset, re-apply hidden field preservation so values aren't wiped
|
||||||
|
preserveHiddenFormData();
|
||||||
|
}, [data, form, preserveHiddenFormData]);
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
if (loading) return <LoadingSpinner />;
|
if (loading) return <LoadingSpinner />;
|
||||||
|
|||||||
@@ -1183,7 +1183,17 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item key={field.key}>
|
<Form.Item key={field.key}>
|
||||||
<LayoutFormRow noDivider>
|
<LayoutFormRow noDivider>
|
||||||
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
|
<Form.Item
|
||||||
|
label={t("general.labels.label")}
|
||||||
|
key={`${index}label`}
|
||||||
|
name={[field.name, "label"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -1294,7 +1304,17 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item key={field.key}>
|
<Form.Item key={field.key}>
|
||||||
<LayoutFormRow noDivider>
|
<LayoutFormRow noDivider>
|
||||||
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
|
<Form.Item
|
||||||
|
label={t("general.labels.label")}
|
||||||
|
key={`${index}label`}
|
||||||
|
name={[field.name, "label"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -1483,15 +1503,31 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item key={field.key}>
|
<Form.Item key={field.key}>
|
||||||
<LayoutFormRow noDivider>
|
<LayoutFormRow noDivider>
|
||||||
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
|
<Form.Item
|
||||||
|
label={t("general.labels.label")}
|
||||||
|
key={`${index}label`}
|
||||||
|
name={[field.name, "label"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.labels.md_to_emails_emails")}
|
label={t("bodyshop.labels.md_to_emails_emails")}
|
||||||
key={`${index}emails`}
|
key={`${index}emails`}
|
||||||
name={[field.name, "emails"]}
|
name={[field.name, "emails"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
<FormItemEmail email={form.getFieldValue([field.name, "emails"])} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,73 +8,57 @@ import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component
|
|||||||
* @param {Object} featureConfig - Configuration object defining which features and their associated fields to preserve
|
* @param {Object} featureConfig - Configuration object defining which features and their associated fields to preserve
|
||||||
*/
|
*/
|
||||||
export const useFormDataPreservation = (form, bodyshop, featureConfig) => {
|
export const useFormDataPreservation = (form, bodyshop, featureConfig) => {
|
||||||
const getNestedValue = (obj, path) => {
|
// Safe nested getters/setters using path arrays
|
||||||
return path.reduce((current, key) => current?.[key], obj);
|
const getNestedValue = (obj, path) => path?.reduce((acc, key) => acc?.[key], obj);
|
||||||
};
|
|
||||||
|
|
||||||
const setNestedValue = (obj, path, value) => {
|
const setNestedValue = (obj, path, value) => {
|
||||||
const lastKey = path[path.length - 1];
|
const lastKey = path[path.length - 1];
|
||||||
const parentPath = path.slice(0, -1);
|
const parent = path.slice(0, -1).reduce((curr, key) => {
|
||||||
|
if (!curr[key] || typeof curr[key] !== "object") curr[key] = {};
|
||||||
const parent = parentPath.reduce((current, key) => {
|
return curr[key];
|
||||||
if (!current[key]) current[key] = {};
|
|
||||||
return current[key];
|
|
||||||
}, obj);
|
}, obj);
|
||||||
|
|
||||||
parent[lastKey] = value;
|
parent[lastKey] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const preserveHiddenFormData = useCallback(() => {
|
// Paths for features that are NOT accessible
|
||||||
const preservationData = {};
|
const disabledPaths = useMemo(() => {
|
||||||
let hasDataToPreserve = false;
|
const result = [];
|
||||||
|
if (!featureConfig) return result;
|
||||||
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
|
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
|
||||||
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
|
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
|
||||||
|
if (hasAccess || !Array.isArray(fieldPaths)) return;
|
||||||
|
fieldPaths.forEach((p) => Array.isArray(p) && p.length && result.push(p));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, [featureConfig, bodyshop]);
|
||||||
|
|
||||||
if (!hasAccess) {
|
const preserveHiddenFormData = useCallback(() => {
|
||||||
fieldPaths.forEach((fieldPath) => {
|
const currentValues = form.getFieldsValue();
|
||||||
const currentValues = form.getFieldsValue();
|
const preservationData = {};
|
||||||
let value = getNestedValue(currentValues, fieldPath);
|
let hasAny = false;
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
disabledPaths.forEach((path) => {
|
||||||
value = getNestedValue(bodyshop, fieldPath);
|
let value = getNestedValue(currentValues, path);
|
||||||
}
|
if (value == null) value = getNestedValue(bodyshop, path);
|
||||||
|
if (value != null) {
|
||||||
if (value !== undefined && value !== null) {
|
setNestedValue(preservationData, path, value);
|
||||||
setNestedValue(preservationData, fieldPath, value);
|
hasAny = true;
|
||||||
hasDataToPreserve = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasDataToPreserve) {
|
if (hasAny) form.setFieldsValue(preservationData);
|
||||||
form.setFieldsValue(preservationData);
|
}, [form, bodyshop, disabledPaths]);
|
||||||
}
|
|
||||||
}, [form, featureConfig, bodyshop]);
|
|
||||||
|
|
||||||
const getCompleteFormValues = () => {
|
const getCompleteFormValues = () => {
|
||||||
const currentFormValues = form.getFieldsValue();
|
const currentValues = form.getFieldsValue();
|
||||||
const completeValues = { ...currentFormValues };
|
const complete = { ...currentValues };
|
||||||
|
|
||||||
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
|
disabledPaths.forEach((path) => {
|
||||||
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
|
let value = getNestedValue(currentValues, path);
|
||||||
|
if (value == null) value = getNestedValue(bodyshop, path);
|
||||||
if (!hasAccess) {
|
if (value != null) setNestedValue(complete, path, value);
|
||||||
fieldPaths.forEach((fieldPath) => {
|
|
||||||
let value = getNestedValue(currentFormValues, fieldPath);
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
value = getNestedValue(bodyshop, fieldPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
setNestedValue(completeValues, fieldPath, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return completeValues;
|
return complete;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createSubmissionHandler = (originalHandler) => {
|
const createSubmissionHandler = (originalHandler) => {
|
||||||
@@ -103,8 +87,8 @@ export const FEATURE_CONFIGS = {
|
|||||||
["md_responsibility_centers", "profits"],
|
["md_responsibility_centers", "profits"],
|
||||||
["md_responsibility_centers", "defaults"],
|
["md_responsibility_centers", "defaults"],
|
||||||
["md_responsibility_centers", "dms_defaults"],
|
["md_responsibility_centers", "dms_defaults"],
|
||||||
["md_responsibility_centers", "taxes", "itemexemptcode"],
|
["md_responsibility_centers", "taxes"],
|
||||||
["md_responsibility_centers", "taxes", "invoiceexemptcode"],
|
["md_responsibility_centers", "cieca_pfl"],
|
||||||
["md_responsibility_centers", "ar"],
|
["md_responsibility_centers", "ar"],
|
||||||
["md_responsibility_centers", "refund"],
|
["md_responsibility_centers", "refund"],
|
||||||
["md_responsibility_centers", "sales_tax_codes"],
|
["md_responsibility_centers", "sales_tax_codes"],
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { connect } from "react-redux";
|
|||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|
||||||
import { pageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||||
import useLocalStorage from "../../utils/useLocalStorage";
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
@@ -144,31 +143,11 @@ export function SimplifiedPartsJobsListComponent({
|
|||||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||||
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
|
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("jobs.fields.ins_co_nm"),
|
|
||||||
dataIndex: "ins_co_nm",
|
|
||||||
key: "ins_co_nm",
|
|
||||||
ellipsis: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.clm_total"),
|
|
||||||
dataIndex: "clm_total",
|
|
||||||
key: "clm_total",
|
|
||||||
sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
|
|
||||||
sortOrder: sortcolumn === "clm_total" && sortorder,
|
|
||||||
render: (text, record) => {
|
|
||||||
return record.clm_total ? (
|
|
||||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
|
||||||
) : (
|
|
||||||
t("general.labels.unknown")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.partsstatus"),
|
title: t("jobs.fields.partsstatus"),
|
||||||
dataIndex: "partsstatus",
|
dataIndex: "partsstatus",
|
||||||
key: "partsstatus",
|
key: "partsstatus",
|
||||||
render: (text, record) => <JobPartsQueueCount style={{ minWidth: "10rem" }} parts={record.joblines_status} />
|
render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.comment"),
|
title: t("jobs.fields.comment"),
|
||||||
|
|||||||
@@ -14,16 +14,18 @@ import VendorsPhonebookAdd from "../vendors-phonebook-add/vendors-phonebook-add.
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop,
|
||||||
|
isPartsEntry: selectIsPartsEntry
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(VendorsFormComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(VendorsFormComponent);
|
||||||
|
|
||||||
export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete, selectedvendor }) {
|
export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete, selectedvendor, isPartsEntry }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
@@ -57,8 +59,7 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
|
|||||||
>
|
>
|
||||||
{t("general.actions.delete")}
|
{t("general.actions.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
|
{!isPartsEntry && <VendorsPhonebookAdd form={form} disabled={form.isFieldsTouched()} />}
|
||||||
<VendorsPhonebookAdd form={form} disabled={form.isFieldsTouched()} />
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -148,12 +149,18 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item label={t("vendors.fields.discount")} name="discount">
|
{!isPartsEntry && (
|
||||||
<InputNumber min={0} max={1} precision={2} step={0.01} />
|
<>
|
||||||
</Form.Item>
|
<Form.Item label={t("vendors.fields.discount")} name="discount">
|
||||||
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
|
<InputNumber min={0} max={1} precision={2} step={0.01} />
|
||||||
<InputNumber min={0} />
|
</Form.Item>
|
||||||
</Form.Item>
|
|
||||||
|
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
|
||||||
|
<InputNumber min={0} />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{
|
{
|
||||||
// <Form.Item
|
// <Form.Item
|
||||||
// label={t("vendors.fields.cost_center")}
|
// label={t("vendors.fields.cost_center")}
|
||||||
@@ -173,7 +180,7 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
|
|||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="tags"
|
name="tags"
|
||||||
label={t("vendor.fields.tags")}
|
label={t("vendors.fields.tags")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import "./translations/i18n";
|
|||||||
import "./utils/CleanAxios";
|
import "./utils/CleanAxios";
|
||||||
import * as amplitude from "@amplitude/analytics-browser";
|
import * as amplitude from "@amplitude/analytics-browser";
|
||||||
import { PostHogProvider } from "posthog-js/react";
|
import { PostHogProvider } from "posthog-js/react";
|
||||||
|
import posthog from "posthog-js";
|
||||||
|
|
||||||
window.global ||= window;
|
window.global ||= window;
|
||||||
|
|
||||||
@@ -25,7 +26,30 @@ registerSW({ immediate: true });
|
|||||||
// Dinero.globalLocale = "en-CA";
|
// Dinero.globalLocale = "en-CA";
|
||||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||||
|
|
||||||
amplitude.init("6228a598e57cd66875cfd41604f1f891", {});
|
amplitude.init("6228a598e57cd66875cfd41604f1f891", {
|
||||||
|
defaultTracking: true,
|
||||||
|
serverUrl: import.meta.env.VITE_APP_AMP_URL
|
||||||
|
// {
|
||||||
|
// attribution: {
|
||||||
|
// excludeReferrers: true,
|
||||||
|
// initialEmptyValue: true,
|
||||||
|
// resetSessionOnNewCampaign: true,
|
||||||
|
// },
|
||||||
|
// fileDownloads: true,
|
||||||
|
// formInteractions: true,
|
||||||
|
// pageViews: {
|
||||||
|
// trackHistoryChanges: 'all'
|
||||||
|
// },
|
||||||
|
// sessions: true
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
|
||||||
|
autocapture: false,
|
||||||
|
capture_exceptions: true,
|
||||||
|
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST
|
||||||
|
});
|
||||||
|
|
||||||
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);
|
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);
|
||||||
|
|
||||||
const router = sentryCreateBrowserRouter(createRoutesFromElements(<Route path="*" element={<AppContainer />} />));
|
const router = sentryCreateBrowserRouter(createRoutesFromElements(<Route path="*" element={<AppContainer />} />));
|
||||||
@@ -39,10 +63,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<PersistGate loading={<LoadingSpinner message="Restoring your settings..." />} persistor={persistor}>
|
<PersistGate loading={<LoadingSpinner message="Restoring your settings..." />} persistor={persistor}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PostHogProvider
|
<PostHogProvider client={posthog}>
|
||||||
apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_KEY}
|
|
||||||
options={{ autocapture: false, capture_exceptions: true }}
|
|
||||||
>
|
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</PostHogProvider>
|
</PostHogProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
|
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
|
||||||
|
import { useQuery } from "@apollo/client";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -16,6 +17,7 @@ import { TemplateList } from "../../utils/TemplateConstants";
|
|||||||
import { pageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import useLocalStorage from "../../utils/useLocalStorage";
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
|
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
|
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
|
||||||
@@ -33,25 +35,22 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
|
|||||||
});
|
});
|
||||||
const Templates = TemplateList("bill");
|
const Templates = TemplateList("bill");
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { data: vendorsData } = useQuery(QUERY_ALL_VENDORS);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("bills.fields.vendorname"),
|
title: t("bills.fields.vendorname"),
|
||||||
dataIndex: "vendorname",
|
dataIndex: "vendorname",
|
||||||
key: "vendorname",
|
key: "vendorname",
|
||||||
// sortObject: (direction) => {
|
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
||||||
// return {
|
sortObject: (order) => ({
|
||||||
// vendor: {
|
vendor: { name: order === "descend" ? "desc" : "asc" }
|
||||||
// name: direction
|
}),
|
||||||
// ? direction === "descend"
|
filters: (vendorsData?.vendors || []).map((v) => ({ text: v.name, value: v.id })),
|
||||||
// ? "desc"
|
filteredValue: state.filteredInfo.vendorname || null,
|
||||||
// : "asc"
|
onFilter: (value, record) => record.vendorid === value,
|
||||||
// : "desc",
|
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// },
|
|
||||||
// sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
|
||||||
// sortOrder:
|
|
||||||
// state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
|
||||||
render: (text, record) => <span>{record.vendor.name}</span>
|
render: (text, record) => <span>{record.vendor.name}</span>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -65,20 +64,11 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
|
|||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
// sortObject: (direction) => {
|
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
||||||
// return {
|
sortObject: (order) => ({
|
||||||
// job: {
|
job: { ro_number: order === "descend" ? "desc" : "asc" }
|
||||||
// ro_number: direction
|
}),
|
||||||
// ? direction === "descend"
|
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
// ? "desc"
|
|
||||||
// : "asc"
|
|
||||||
// : "desc",
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// },
|
|
||||||
// sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
|
||||||
// sortOrder:
|
|
||||||
// state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
|
||||||
render: (text, record) => record.job && <Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number}</Link>
|
render: (text, record) => record.job && <Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -175,7 +165,8 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
// Persist filters (including vendorname) and sorting
|
||||||
|
setState({ ...state, filteredInfo: { ...state.filteredInfo, ...filters }, sortedInfo: sorter });
|
||||||
search.page = pagination.current;
|
search.page = pagination.current;
|
||||||
if (sorter && sorter.column && sorter.column.sortObject) {
|
if (sorter && sorter.column && sorter.column.sortObject) {
|
||||||
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
|
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component.jsx";
|
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component.jsx";
|
||||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container.jsx";
|
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container.jsx";
|
||||||
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx";
|
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx";
|
||||||
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component.jsx";
|
|
||||||
import JobsChangeStatus from "../../components/jobs-change-status/jobs-change-status.component.jsx";
|
import JobsChangeStatus from "../../components/jobs-change-status/jobs-change-status.component.jsx";
|
||||||
import JobsDetailHeaderActions from "../../components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx";
|
import JobsDetailHeaderActions from "../../components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx";
|
||||||
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component.jsx";
|
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component.jsx";
|
||||||
@@ -133,9 +132,8 @@ export function SimplifiedPartsJobDetailComponent({ setPrintCenterContext, jobRO
|
|||||||
<JobLineUpsertModalContainer />
|
<JobLineUpsertModalContainer />
|
||||||
|
|
||||||
<PageHeader title={<Space>{job.ro_number || t("general.labels.na")}</Space>} extra={menuExtra} />
|
<PageHeader title={<Space>{job.ro_number || t("general.labels.na")}</Space>} extra={menuExtra} />
|
||||||
<JobsDetailHeader job={job} disabled={true} />
|
<JobsDetailHeader job={job} />
|
||||||
<Divider type="horizontal" />
|
<Divider type="horizontal" />
|
||||||
<JobProfileDataWarning job={job} />
|
|
||||||
<FormFieldsChanged form={form} />
|
<FormFieldsChanged form={form} />
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultActiveKey={search.tab}
|
defaultActiveKey={search.tab}
|
||||||
|
|||||||
@@ -298,6 +298,7 @@ export function* signInSuccessSaga({ payload }) {
|
|||||||
|
|
||||||
setUserId(analytics, payload.email);
|
setUserId(analytics, payload.email);
|
||||||
setUserProperties(analytics, payload);
|
setUserProperties(analytics, payload);
|
||||||
|
yield;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* onSendPasswordResetStart() {
|
export function* onSendPasswordResetStart() {
|
||||||
|
|||||||
@@ -1249,7 +1249,8 @@
|
|||||||
"sizelimit": "The selected items exceed the size limit.",
|
"sizelimit": "The selected items exceed the size limit.",
|
||||||
"sub_status": {
|
"sub_status": {
|
||||||
"expired": "The subscription for this shop has expired. Please contact Sales to reactivate.",
|
"expired": "The subscription for this shop has expired. Please contact Sales to reactivate.",
|
||||||
"trial-expired": "The trial for this shop has expired. Please contact Sales to reactivate."
|
"trial-expired": "The trial for this shop has expired. Please contact Sales to reactivate.",
|
||||||
|
"undefined": "The subscription for this shop is removed. Please contact Sales to reactivate."
|
||||||
},
|
},
|
||||||
"submit-for-testing": "Error submitting Job for testing."
|
"submit-for-testing": "Error submitting Job for testing."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1249,7 +1249,8 @@
|
|||||||
"sizelimit": "",
|
"sizelimit": "",
|
||||||
"sub_status": {
|
"sub_status": {
|
||||||
"expired": "",
|
"expired": "",
|
||||||
"trial-expired": ""
|
"trial-expired": "",
|
||||||
|
"undefined": ""
|
||||||
},
|
},
|
||||||
"submit-for-testing": ""
|
"submit-for-testing": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1249,7 +1249,8 @@
|
|||||||
"sizelimit": "",
|
"sizelimit": "",
|
||||||
"sub_status": {
|
"sub_status": {
|
||||||
"expired": "",
|
"expired": "",
|
||||||
"trial-expired": ""
|
"trial-expired": "",
|
||||||
|
"undefined": ""
|
||||||
},
|
},
|
||||||
"submit-for-testing": ""
|
"submit-for-testing": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -181,7 +181,10 @@ const cache = new InMemoryCache({
|
|||||||
const client = new ApolloClient({
|
const client = new ApolloClient({
|
||||||
link: ApolloLink.from(middlewares),
|
link: ApolloLink.from(middlewares),
|
||||||
cache,
|
cache,
|
||||||
connectToDevTools: import.meta.env.DEV,
|
devtools: {
|
||||||
|
name: "Imex Client",
|
||||||
|
enabled: import.meta.env.DEV
|
||||||
|
},
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
watchQuery: {
|
watchQuery: {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ services:
|
|||||||
- redis-node-1-data:/data
|
- redis-node-1-data:/data
|
||||||
- redis-lock:/redis-lock
|
- redis-lock:/redis-lock
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -39,7 +39,7 @@ services:
|
|||||||
- redis-node-2-data:/data
|
- redis-node-2-data:/data
|
||||||
- redis-lock:/redis-lock
|
- redis-lock:/redis-lock
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -57,7 +57,7 @@ services:
|
|||||||
- redis-node-3-data:/data
|
- redis-node-3-data:/data
|
||||||
- redis-lock:/redis-lock
|
- redis-lock:/redis-lock
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -85,7 +85,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "4566:4566"
|
- "4566:4566"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
|
test: [ "CMD", "curl", "-f", "http://localhost:4566/_localstack/health" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -118,6 +118,7 @@ services:
|
|||||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-job-totals --create-bucket-configuration LocationConstraint=ca-central-1
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-job-totals --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket parts-estimates --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
"
|
"
|
||||||
# Node App: The Main IMEX API
|
# Node App: The Main IMEX API
|
||||||
node-app:
|
node-app:
|
||||||
|
|||||||
@@ -959,6 +959,7 @@
|
|||||||
- enforce_referral
|
- enforce_referral
|
||||||
- entegral_configuration
|
- entegral_configuration
|
||||||
- entegral_id
|
- entegral_id
|
||||||
|
- external_shop_id
|
||||||
- features
|
- features
|
||||||
- federal_tax_id
|
- federal_tax_id
|
||||||
- id
|
- id
|
||||||
@@ -1012,6 +1013,8 @@
|
|||||||
- prodtargethrs
|
- prodtargethrs
|
||||||
- production_config
|
- production_config
|
||||||
- region_config
|
- region_config
|
||||||
|
- rr_configuration
|
||||||
|
- rr_dealerid
|
||||||
- schedule_end_time
|
- schedule_end_time
|
||||||
- schedule_start_time
|
- schedule_start_time
|
||||||
- scoreboard_target
|
- scoreboard_target
|
||||||
@@ -1035,7 +1038,6 @@
|
|||||||
- use_fippa
|
- use_fippa
|
||||||
- use_paint_scale_data
|
- use_paint_scale_data
|
||||||
- uselocalmediaserver
|
- uselocalmediaserver
|
||||||
- external_shop_id
|
|
||||||
- website
|
- website
|
||||||
- workingdays
|
- workingdays
|
||||||
- zip_post
|
- zip_post
|
||||||
@@ -1068,6 +1070,7 @@
|
|||||||
- enforce_conversion_category
|
- enforce_conversion_category
|
||||||
- enforce_conversion_csr
|
- enforce_conversion_csr
|
||||||
- enforce_referral
|
- enforce_referral
|
||||||
|
- external_shop_id
|
||||||
- federal_tax_id
|
- federal_tax_id
|
||||||
- id
|
- id
|
||||||
- inhousevendorid
|
- inhousevendorid
|
||||||
@@ -1113,6 +1116,7 @@
|
|||||||
- phone
|
- phone
|
||||||
- prodtargethrs
|
- prodtargethrs
|
||||||
- production_config
|
- production_config
|
||||||
|
- rr_configuration
|
||||||
- schedule_end_time
|
- schedule_end_time
|
||||||
- schedule_start_time
|
- schedule_start_time
|
||||||
- scoreboard_target
|
- scoreboard_target
|
||||||
@@ -1131,7 +1135,6 @@
|
|||||||
- use_fippa
|
- use_fippa
|
||||||
- use_paint_scale_data
|
- use_paint_scale_data
|
||||||
- uselocalmediaserver
|
- uselocalmediaserver
|
||||||
- external_shop_id
|
|
||||||
- website
|
- website
|
||||||
- workingdays
|
- workingdays
|
||||||
- zip_post
|
- zip_post
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."bodyshops" add column "rr_configuration" jsonb
|
||||||
|
-- null default jsonb_build_object();
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."bodyshops" add column "rr_configuration" jsonb
|
||||||
|
null default jsonb_build_object();
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."bodyshops" add column "rr_dealierid" text
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."bodyshops" add column "rr_dealierid" text
|
||||||
|
null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."bodyshops" rename column "rr_dealerid" to "rr_dealierid";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."bodyshops" rename column "rr_dealierid" to "rr_dealerid";
|
||||||
3235
package-lock.json
generated
3235
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -18,14 +18,14 @@
|
|||||||
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-cloudwatch-logs": "^3.873.0",
|
"@aws-sdk/client-cloudwatch-logs": "^3.882.0",
|
||||||
"@aws-sdk/client-elasticache": "^3.872.0",
|
"@aws-sdk/client-elasticache": "^3.882.0",
|
||||||
"@aws-sdk/client-s3": "^3.872.0",
|
"@aws-sdk/client-s3": "^3.882.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.872.0",
|
"@aws-sdk/client-secrets-manager": "^3.882.0",
|
||||||
"@aws-sdk/client-ses": "^3.872.0",
|
"@aws-sdk/client-ses": "^3.882.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.873.0",
|
"@aws-sdk/credential-provider-node": "^3.882.0",
|
||||||
"@aws-sdk/lib-storage": "^3.872.0",
|
"@aws-sdk/lib-storage": "^3.882.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.872.0",
|
"@aws-sdk/s3-request-presigner": "^3.882.0",
|
||||||
"@opensearch-project/opensearch": "^2.13.0",
|
"@opensearch-project/opensearch": "^2.13.0",
|
||||||
"@socket.io/admin-ui": "^0.5.1",
|
"@socket.io/admin-ui": "^0.5.1",
|
||||||
"@socket.io/redis-adapter": "^8.3.0",
|
"@socket.io/redis-adapter": "^8.3.0",
|
||||||
@@ -33,18 +33,18 @@
|
|||||||
"aws4": "^1.13.2",
|
"aws4": "^1.13.2",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"better-queue": "^3.8.12",
|
"better-queue": "^3.8.12",
|
||||||
"bullmq": "^5.58.0",
|
"bullmq": "^5.58.5",
|
||||||
"chart.js": "^4.5.0",
|
"chart.js": "^4.5.0",
|
||||||
"cloudinary": "^2.7.0",
|
"cloudinary": "^2.7.0",
|
||||||
"compression": "^1.8.1",
|
"compression": "^1.8.1",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crisp-status-reporter": "^1.2.2",
|
"crisp-status-reporter": "^1.2.2",
|
||||||
"dd-trace": "^5.63.3",
|
"dd-trace": "^5.65.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.2",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"firebase-admin": "^13.4.0",
|
"firebase-admin": "^13.5.0",
|
||||||
"graphql": "^16.11.0",
|
"graphql": "^16.11.0",
|
||||||
"graphql-request": "^6.1.0",
|
"graphql-request": "^6.1.0",
|
||||||
"intuit-oauth": "^4.2.0",
|
"intuit-oauth": "^4.2.0",
|
||||||
@@ -62,12 +62,12 @@
|
|||||||
"query-string": "7.1.3",
|
"query-string": "7.1.3",
|
||||||
"recursive-diff": "^1.0.9",
|
"recursive-diff": "^1.0.9",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"skia-canvas": "^3.0.3",
|
"skia-canvas": "^3.0.6",
|
||||||
"soap": "^1.3.0",
|
"soap": "^1.3.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-adapter": "^2.5.5",
|
"socket.io-adapter": "^2.5.5",
|
||||||
"ssh2-sftp-client": "^11.0.0",
|
"ssh2-sftp-client": "^11.0.0",
|
||||||
"twilio": "^5.8.0",
|
"twilio": "^5.9.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-cloudwatch": "^6.3.0",
|
"winston-cloudwatch": "^6.3.0",
|
||||||
@@ -76,8 +76,8 @@
|
|||||||
"yazl": "^3.3.1"
|
"yazl": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.35.0",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.35.0",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"mock-require": "^3.0.3",
|
"mock-require": "^3.0.3",
|
||||||
|
|||||||
@@ -775,6 +775,7 @@
|
|||||||
"csi:page": 11,
|
"csi:page": 11,
|
||||||
"jobs:void": 80,
|
"jobs:void": 80,
|
||||||
"shop:rbac": 99,
|
"shop:rbac": 99,
|
||||||
|
"shop:responsibilitycenter": 99,
|
||||||
"bills:list": 11,
|
"bills:list": 11,
|
||||||
"bills:view": 11,
|
"bills:view": 11,
|
||||||
"csi:export": 11,
|
"csi:export": 11,
|
||||||
|
|||||||
@@ -23,10 +23,16 @@ const KNOWN_PART_RATE_TYPES = [
|
|||||||
* @returns {object} The parts tax rates object.
|
* @returns {object} The parts tax rates object.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//TODO: Major validation would be required on this - EMS files are inconsistent with things like 5% being passed as 5.0 or .05.
|
||||||
const extractPartsTaxRates = (profile = {}) => {
|
const extractPartsTaxRates = (profile = {}) => {
|
||||||
const rateInfos = Array.isArray(profile.RateInfo) ? profile.RateInfo : [profile.RateInfo || {}];
|
const rateInfos = Array.isArray(profile.RateInfo) ? profile.RateInfo : [profile.RateInfo || {}];
|
||||||
const partsTaxRates = {};
|
const partsTaxRates = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this context, r.RateType._ accesses the property named _ on the RateType object.
|
||||||
|
* This pattern is common when handling data parsed from XML, where element values are stored under the _ key. So,
|
||||||
|
* _ aligns to the actual value/content of the RateType field when RateType is an object (not a string).
|
||||||
|
*/
|
||||||
for (const r of rateInfos) {
|
for (const r of rateInfos) {
|
||||||
const rateTypeRaw =
|
const rateTypeRaw =
|
||||||
typeof r?.RateType === "string"
|
typeof r?.RateType === "string"
|
||||||
|
|||||||
197
server/integrations/partsManagement/endpoints/lib/opCodes.json
Normal file
197
server/integrations/partsManagement/endpoints/lib/opCodes.json
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"OP0": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP1": {
|
||||||
|
"desc": "REFINISH / REPAIR",
|
||||||
|
"opcode": "OP1",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP2": {
|
||||||
|
"desc": "REMOVE / INSTALL",
|
||||||
|
"opcode": "OP2",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP3": {
|
||||||
|
"desc": "ADDITIONAL LABOR",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP4": {
|
||||||
|
"desc": "ALIGNMENT",
|
||||||
|
"opcode": "OP4",
|
||||||
|
"partcode": "PAS"
|
||||||
|
},
|
||||||
|
"OP5": {
|
||||||
|
"desc": "OVERHAUL",
|
||||||
|
"opcode": "OP5",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP6": {
|
||||||
|
"desc": "REFINISH",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP7": {
|
||||||
|
"desc": "INSPECT",
|
||||||
|
"opcode": "OP7",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP8": {
|
||||||
|
"desc": "CHECK / ADJUST",
|
||||||
|
"opcode": "OP8",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP9": {
|
||||||
|
"desc": "REPAIR",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP10": {
|
||||||
|
"desc": "REPAIR , PARTIAL",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP11": {
|
||||||
|
"desc": "REMOVE / REPLACE",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAN"
|
||||||
|
},
|
||||||
|
"OP12": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAN"
|
||||||
|
},
|
||||||
|
"OP13": {
|
||||||
|
"desc": "ADDITIONAL COSTS",
|
||||||
|
"opcode": "OP13",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP14": {
|
||||||
|
"desc": "ADDITIONAL OPERATIONS",
|
||||||
|
"opcode": "OP14",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP15": {
|
||||||
|
"desc": "BLEND",
|
||||||
|
"opcode": "OP15",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP16": {
|
||||||
|
"desc": "SUBLET",
|
||||||
|
"opcode": "OP16",
|
||||||
|
"partcode": "PAS"
|
||||||
|
},
|
||||||
|
"OP17": {
|
||||||
|
"desc": "POLICY LIMIT ADJUSTMENT",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP18": {
|
||||||
|
"desc": "APPEAR ALLOWANCE",
|
||||||
|
"opcode": "OP7",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP20": {
|
||||||
|
"desc": "REMOVE AND REINSTALL",
|
||||||
|
"opcode": "OP20",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP24": {
|
||||||
|
"desc": "CHIPGUARD",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP25": {
|
||||||
|
"desc": "TWO TONE",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP26": {
|
||||||
|
"desc": "PAINTLESS DENT REPAIR",
|
||||||
|
"opcode": "OP16",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP100": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP101": {
|
||||||
|
"desc": "REMOVE/REPLACE RECYCLED PART",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAL"
|
||||||
|
},
|
||||||
|
"OP103": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP104": {
|
||||||
|
"desc": "REMOVE / REPLACE PARTIAL LABOUR",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP105": {
|
||||||
|
"desc": "!!ADJUST MANUALLY!!",
|
||||||
|
"opcode": "OP99",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP106": {
|
||||||
|
"desc": "REPAIR , PARTIAL",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP107": {
|
||||||
|
"desc": "CHIPGUARD",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP108": {
|
||||||
|
"desc": "MULTI TONE",
|
||||||
|
"opcode": "OP6",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP109": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP110": {
|
||||||
|
"desc": "REFINISH / REPAIR",
|
||||||
|
"opcode": "OP1",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP111": {
|
||||||
|
"desc": "REMOVE / REPLACE",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAN"
|
||||||
|
},
|
||||||
|
"OP112": {
|
||||||
|
"desc": "REMOVE / REPLACE",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP113": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP114": {
|
||||||
|
"desc": "REPLACE PRE-PRICED",
|
||||||
|
"opcode": "OP11",
|
||||||
|
"partcode": "PAA"
|
||||||
|
},
|
||||||
|
"OP120": {
|
||||||
|
"desc": "REPAIR , PARTIAL",
|
||||||
|
"opcode": "OP9",
|
||||||
|
"partcode": "PAE"
|
||||||
|
},
|
||||||
|
"OP260": {
|
||||||
|
"desc": "SUBLET",
|
||||||
|
"opcode": "OP16",
|
||||||
|
"partcode": "PAE"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
const admin = require("firebase-admin");
|
const admin = require("firebase-admin");
|
||||||
const client = require("../../../graphql-client/graphql-client").client;
|
const client = require("../../../graphql-client/graphql-client").client;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
DELETE_SHOP,
|
DELETE_SHOP,
|
||||||
DELETE_VENDORS_BY_SHOP,
|
DELETE_VENDORS_BY_SHOP,
|
||||||
@@ -18,143 +17,154 @@ const {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a Firebase user by UID.
|
* Deletes a Firebase user by UID.
|
||||||
* @param uid
|
* @param {string} uid - The Firebase user ID
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const deleteFirebaseUser = async (uid) => {
|
const deleteFirebaseUser = async (uid) => {
|
||||||
|
if (!uid) throw new Error("User UID is required");
|
||||||
return admin.auth().deleteUser(uid);
|
return admin.auth().deleteUser(uid);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all vendors associated with a shop.
|
* Deletes all vendors associated with a shop.
|
||||||
* @param shopId
|
* @param {string} shopId - The shop ID
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const deleteVendorsByShop = async (shopId) => {
|
const deleteVendorsByShop = async (shopId) => {
|
||||||
|
if (!shopId) throw new Error("Shop ID is required");
|
||||||
await client.request(DELETE_VENDORS_BY_SHOP, { shopId });
|
await client.request(DELETE_VENDORS_BY_SHOP, { shopId });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a bodyshop from the database.
|
* Deletes a bodyshop from the database.
|
||||||
* @param shopId
|
* @param {string} shopId - The shop ID
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const deleteBodyshop = async (shopId) => {
|
const deleteBodyshop = async (shopId) => {
|
||||||
|
if (!shopId) throw new Error("Shop ID is required");
|
||||||
await client.request(DELETE_SHOP, { id: shopId });
|
await client.request(DELETE_SHOP, { id: shopId });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch job ids for a given shop
|
* Fetch job ids for a given shop
|
||||||
* @param shopId
|
* @param {string} shopId - The shop ID
|
||||||
* @returns {Promise<string[]>}
|
* @returns {Promise<string[]>}
|
||||||
*/
|
*/
|
||||||
const getJobIdsForShop = async (shopId) => {
|
const getJobIdsForShop = async (shopId) => {
|
||||||
|
if (!shopId) throw new Error("Shop ID is required");
|
||||||
const resp = await client.request(GET_JOBS_BY_SHOP, { shopId });
|
const resp = await client.request(GET_JOBS_BY_SHOP, { shopId });
|
||||||
return resp.jobs.map((j) => j.id);
|
return resp.jobs?.map((j) => j.id) || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete joblines for the given job ids
|
* Delete joblines for the given job ids
|
||||||
* @param jobIds {string[]}
|
* @param {string[]} jobIds - Array of job IDs
|
||||||
* @returns {Promise<number>} affected rows
|
* @returns {Promise<number>} affected rows
|
||||||
*/
|
*/
|
||||||
const deleteJoblinesForJobs = async (jobIds) => {
|
const deleteJoblinesForJobs = async (jobIds) => {
|
||||||
if (!jobIds.length) return 0;
|
if (!jobIds?.length) return 0;
|
||||||
const resp = await client.request(DELETE_JOBLINES_BY_JOB_IDS, { jobIds });
|
const resp = await client.request(DELETE_JOBLINES_BY_JOB_IDS, { jobIds });
|
||||||
return resp.delete_joblines.affected_rows;
|
return resp.delete_joblines?.affected_rows || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete jobs for the given job ids
|
* Delete jobs for the given job ids
|
||||||
* @param jobIds {string[]}
|
* @param {string[]} jobIds - Array of job IDs
|
||||||
* @returns {Promise<number>} affected rows
|
* @returns {Promise<number>} affected rows
|
||||||
*/
|
*/
|
||||||
const deleteJobsByIds = async (jobIds) => {
|
const deleteJobsByIds = async (jobIds) => {
|
||||||
if (!jobIds.length) return 0;
|
if (!jobIds?.length) return 0;
|
||||||
const resp = await client.request(DELETE_JOBS_BY_IDS, { jobIds });
|
const resp = await client.request(DELETE_JOBS_BY_IDS, { jobIds });
|
||||||
return resp.delete_jobs.affected_rows;
|
return resp.delete_jobs?.affected_rows || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles deprovisioning a shop for parts management.
|
* Handles deprovisioning a shop for parts management.
|
||||||
* @param req
|
* @param {Object} req - Express request object
|
||||||
* @param res
|
* @param {Object} res - Express response object
|
||||||
* @returns {Promise<*>}
|
* @returns {Promise<*>}
|
||||||
*/
|
*/
|
||||||
const partsManagementDeprovisioning = async (req, res) => {
|
const partsManagementDeprovisioning = async (req, res) => {
|
||||||
const { logger } = req;
|
const { logger } = req;
|
||||||
const p = req.body;
|
const { shopId } = req.body;
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production" || process.env.HOSTNAME?.endsWith("compute.internal")) {
|
||||||
return res.status(403).json({ error: "Deprovisioning not allowed in production environment." });
|
return res.status(403).json({ error: "Deprovisioning not allowed in production environment." });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!p.shopId) {
|
if (!shopId) {
|
||||||
throw { status: 400, message: "shopId is required." };
|
throw { status: 400, message: "shopId is required." };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch bodyshop and check external_shop_id
|
// Fetch bodyshop and check external_shop_id
|
||||||
const shopResp = await client.request(GET_BODYSHOP, { id: p.shopId });
|
const shopResp = await client.request(GET_BODYSHOP, { id: shopId });
|
||||||
const shop = shopResp.bodyshops_by_pk;
|
const shop = shopResp.bodyshops_by_pk;
|
||||||
if (!shop) {
|
if (!shop) {
|
||||||
throw { status: 404, message: `Bodyshop with id ${p.shopId} not found.` };
|
throw { status: 404, message: `Bodyshop with id ${shopId} not found.` };
|
||||||
}
|
}
|
||||||
if (!shop.external_shop_id) {
|
if (!shop.external_shop_id) {
|
||||||
throw { status: 400, message: "Cannot delete bodyshop without external_shop_id." };
|
throw { status: 400, message: "Cannot delete bodyshop without external_shop_id." };
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("admin-delete-shop", "debug", null, null, {
|
logger.log("admin-delete-shop", "debug", null, null, {
|
||||||
shopId: p.shopId,
|
shopId,
|
||||||
shopname: shop.shopname,
|
shopname: shop.shopname,
|
||||||
ioadmin: true
|
ioadmin: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get vendors
|
// Get vendors
|
||||||
const vendorsResp = await client.request(GET_VENDORS, { shopId: p.shopId });
|
const vendorsResp = await client.request(GET_VENDORS, { shopId });
|
||||||
const deletedVendors = vendorsResp.vendors.map((v) => v.name);
|
const deletedVendors = vendorsResp.vendors?.map((v) => v.name) || [];
|
||||||
|
|
||||||
// Get associated users
|
// Get associated users
|
||||||
const assocResp = await client.request(GET_ASSOCIATED_USERS, { shopId: p.shopId });
|
const assocResp = await client.request(GET_ASSOCIATED_USERS, { shopId });
|
||||||
const associatedUsers = assocResp.associations.map((assoc) => ({
|
const associatedUsers =
|
||||||
authId: assoc.user.authid,
|
assocResp.associations?.map((assoc) => ({
|
||||||
email: assoc.user.email
|
authId: assoc.user?.authid,
|
||||||
}));
|
email: assoc.user?.email
|
||||||
|
})) || [];
|
||||||
|
|
||||||
// Delete associations for the shop
|
// Delete associations for the shop
|
||||||
const assocDeleteResp = await client.request(DELETE_ASSOCIATIONS_BY_SHOP, { shopId: p.shopId });
|
const assocDeleteResp = await client.request(DELETE_ASSOCIATIONS_BY_SHOP, { shopId });
|
||||||
const associationsDeleted = assocDeleteResp.delete_associations.affected_rows;
|
const associationsDeleted = assocDeleteResp.delete_associations?.affected_rows || 0;
|
||||||
|
|
||||||
// For each user, check if they have remaining associations; if not, delete user and Firebase account
|
// Delete users with no remaining associations
|
||||||
const deletedUsers = [];
|
const deletedUsers = [];
|
||||||
for (const user of associatedUsers) {
|
for (const user of associatedUsers) {
|
||||||
const countResp = await client.request(GET_USER_ASSOCIATIONS_COUNT, { userEmail: user.email });
|
if (!user.email || !user.authId) continue;
|
||||||
const assocCount = countResp.associations_aggregate.aggregate.count;
|
try {
|
||||||
if (assocCount === 0) {
|
const countResp = await client.request(GET_USER_ASSOCIATIONS_COUNT, { userEmail: user.email });
|
||||||
await client.request(DELETE_USER, { email: user.email });
|
const assocCount = countResp.associations_aggregate?.aggregate?.count || 0;
|
||||||
await deleteFirebaseUser(user.authId);
|
if (assocCount === 0) {
|
||||||
deletedUsers.push(user.email);
|
await client.request(DELETE_USER, { email: user.email });
|
||||||
|
await deleteFirebaseUser(user.authId);
|
||||||
|
deletedUsers.push(user.email);
|
||||||
|
}
|
||||||
|
} catch (userError) {
|
||||||
|
logger.log("admin-delete-user-error", "warn", null, null, {
|
||||||
|
email: user.email,
|
||||||
|
error: userError.message || userError
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all job ids for this shop, then delete joblines and jobs (joblines first)
|
// Delete jobs and joblines
|
||||||
const jobIds = await getJobIdsForShop(p.shopId);
|
const jobIds = await getJobIdsForShop(shopId);
|
||||||
const joblinesDeleted = await deleteJoblinesForJobs(jobIds);
|
const joblinesDeleted = await deleteJoblinesForJobs(jobIds);
|
||||||
const jobsDeleted = await deleteJobsByIds(jobIds);
|
const jobsDeleted = await deleteJobsByIds(jobIds);
|
||||||
|
|
||||||
// Delete any audit trail entries tied to this bodyshop to avoid FK violations
|
// Delete audit trail
|
||||||
const auditResp = await client.request(DELETE_AUDIT_TRAIL_BY_SHOP, { shopId: p.shopId });
|
const auditResp = await client.request(DELETE_AUDIT_TRAIL_BY_SHOP, { shopId });
|
||||||
const auditDeleted = auditResp.delete_audit_trail.affected_rows;
|
const auditDeleted = auditResp.delete_audit_trail?.affected_rows || 0;
|
||||||
|
|
||||||
// Delete vendors
|
// Delete vendors and shop
|
||||||
await deleteVendorsByShop(p.shopId);
|
await deleteVendorsByShop(shopId);
|
||||||
|
await deleteBodyshop(shopId);
|
||||||
// Delete shop
|
|
||||||
await deleteBodyshop(p.shopId);
|
|
||||||
|
|
||||||
// Summary log
|
// Summary log
|
||||||
logger.log("admin-delete-shop-summary", "info", null, null, {
|
logger.log("admin-delete-shop-summary", "info", null, null, {
|
||||||
shopId: p.shopId,
|
shopId,
|
||||||
shopname: shop.shopname,
|
shopname: shop.shopname,
|
||||||
associationsDeleted,
|
associationsDeleted,
|
||||||
deletedUsers,
|
deletedUsers,
|
||||||
@@ -165,11 +175,11 @@ const partsManagementDeprovisioning = async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: `Bodyshop ${p.shopId} and associated resources deleted successfully.`,
|
message: `Bodyshop ${shopId} and associated resources deleted successfully.`,
|
||||||
deletedShop: { id: p.shopId, name: shop.shopname },
|
deletedShop: { id: shopId, name: shop.shopname },
|
||||||
deletedAssociationsCount: associationsDeleted,
|
deletedAssociationsCount: associationsDeleted,
|
||||||
deletedUsers: deletedUsers,
|
deletedUsers,
|
||||||
deletedVendors: deletedVendors,
|
deletedVendors,
|
||||||
deletedJoblinesCount: joblinesDeleted,
|
deletedJoblinesCount: joblinesDeleted,
|
||||||
deletedJobsCount: jobsDeleted,
|
deletedJobsCount: jobsDeleted,
|
||||||
deletedAuditTrailCount: auditDeleted
|
deletedAuditTrailCount: auditDeleted
|
||||||
@@ -177,9 +187,8 @@ const partsManagementDeprovisioning = async (req, res) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.log("admin-delete-shop-error", "error", null, null, {
|
logger.log("admin-delete-shop-error", "error", null, null, {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
detail: err.detail || err
|
detail: err.detail || err.stack || err
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(err.status || 500).json({ error: err.message || "Internal server error" });
|
return res.status(err.status || 500).json({ error: err.message || "Internal server error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ const insertUserAssociation = async (uid, email, shopId) => {
|
|||||||
authid: uid,
|
authid: uid,
|
||||||
validemail: true,
|
validemail: true,
|
||||||
associations: {
|
associations: {
|
||||||
data: [{ shopid: shopId, authlevel: 80, active: true }]
|
data: [{ shopid: shopId, authlevel: 99, active: true }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -139,11 +139,11 @@ const insertUserAssociation = async (uid, email, shopId) => {
|
|||||||
*/
|
*/
|
||||||
const partsManagementProvisioning = async (req, res) => {
|
const partsManagementProvisioning = async (req, res) => {
|
||||||
const { logger } = req;
|
const { logger } = req;
|
||||||
const p = { ...req.body, userEmail: req.body.userEmail?.toLowerCase() };
|
const body = { ...req.body, userEmail: req.body.userEmail?.toLowerCase() };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ensureEmailNotRegistered(p.userEmail);
|
await ensureEmailNotRegistered(body.userEmail);
|
||||||
requireFields(p, [
|
requireFields(body, [
|
||||||
"external_shop_id",
|
"external_shop_id",
|
||||||
"shopname",
|
"shopname",
|
||||||
"address1",
|
"address1",
|
||||||
@@ -155,27 +155,27 @@ const partsManagementProvisioning = async (req, res) => {
|
|||||||
"phone",
|
"phone",
|
||||||
"userEmail"
|
"userEmail"
|
||||||
]);
|
]);
|
||||||
await ensureExternalIdUnique(p.external_shop_id);
|
await ensureExternalIdUnique(body.external_shop_id);
|
||||||
|
|
||||||
logger.log("admin-create-shop-user", "debug", p.userEmail, null, {
|
logger.log("admin-create-shop-user", "debug", body.userEmail, null, {
|
||||||
request: req.body,
|
request: req.body,
|
||||||
ioadmin: true
|
ioadmin: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const shopInput = {
|
const shopInput = {
|
||||||
shopname: p.shopname,
|
shopname: body.shopname,
|
||||||
address1: p.address1,
|
address1: body.address1,
|
||||||
address2: p.address2 || null,
|
address2: body.address2 || null,
|
||||||
city: p.city,
|
city: body.city,
|
||||||
state: p.state,
|
state: body.state,
|
||||||
zip_post: p.zip_post,
|
zip_post: body.zip_post,
|
||||||
country: p.country,
|
country: body.country,
|
||||||
email: p.email,
|
email: body.email,
|
||||||
external_shop_id: p.external_shop_id,
|
external_shop_id: body.external_shop_id,
|
||||||
timezone: p.timezone || DefaultNewShop.timezone,
|
timezone: body.timezone || DefaultNewShop.timezone,
|
||||||
phone: p.phone,
|
phone: body.phone,
|
||||||
logo_img_path: {
|
logo_img_path: {
|
||||||
src: p.logoUrl,
|
src: body.logoUrl,
|
||||||
width: "",
|
width: "",
|
||||||
height: "",
|
height: "",
|
||||||
headerMargin: DefaultNewShop.logo_img_path.headerMargin
|
headerMargin: DefaultNewShop.logo_img_path.headerMargin
|
||||||
@@ -200,7 +200,7 @@ const partsManagementProvisioning = async (req, res) => {
|
|||||||
appt_alt_transport: DefaultNewShop.appt_alt_transport,
|
appt_alt_transport: DefaultNewShop.appt_alt_transport,
|
||||||
md_jobline_presets: DefaultNewShop.md_jobline_presets,
|
md_jobline_presets: DefaultNewShop.md_jobline_presets,
|
||||||
vendors: {
|
vendors: {
|
||||||
data: p.vendors.map((v) => ({
|
data: body.vendors.map((v) => ({
|
||||||
name: v.name,
|
name: v.name,
|
||||||
street1: v.street1 || null,
|
street1: v.street1 || null,
|
||||||
street2: v.street2 || null,
|
street2: v.street2 || null,
|
||||||
@@ -221,14 +221,14 @@ const partsManagementProvisioning = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const newShopId = await insertBodyshop(shopInput);
|
const newShopId = await insertBodyshop(shopInput);
|
||||||
const userRecord = await createFirebaseUser(p.userEmail, p.userPassword);
|
const userRecord = await createFirebaseUser(body.userEmail, body.userPassword);
|
||||||
let resetLink = null;
|
let resetLink = null;
|
||||||
if (!p.userPassword) resetLink = await generateResetLink(p.userEmail);
|
if (!body.userPassword) resetLink = await generateResetLink(body.userEmail);
|
||||||
|
|
||||||
const createdUser = await insertUserAssociation(userRecord.uid, p.userEmail, newShopId);
|
const createdUser = await insertUserAssociation(userRecord.uid, body.userEmail, newShopId);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
shop: { id: newShopId, shopname: p.shopname },
|
shop: { id: newShopId, shopname: body.shopname },
|
||||||
user: {
|
user: {
|
||||||
id: createdUser.id,
|
id: createdUser.id,
|
||||||
email: createdUser.email,
|
email: createdUser.email,
|
||||||
@@ -236,7 +236,7 @@ const partsManagementProvisioning = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.log("admin-create-shop-user-error", "error", p.userEmail, null, {
|
logger.log("admin-create-shop-user-error", "error", body.userEmail, null, {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
detail: err.detail || err
|
detail: err.detail || err
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// no-dd-sa:javascript-code-style/assignment-name
|
|
||||||
// CamelCase is used for GraphQL and database fields.
|
|
||||||
|
|
||||||
const client = require("../../../graphql-client/graphql-client").client;
|
const client = require("../../../graphql-client/graphql-client").client;
|
||||||
const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
|
const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
|
||||||
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
||||||
|
const opCodes = require("./lib/opCodes.json");
|
||||||
|
// New imports for S3 XML archival
|
||||||
|
const { uploadFileToS3 } = require("../../../utils/s3");
|
||||||
|
const InstanceMgr = require("../../../utils/instanceMgr").default;
|
||||||
|
|
||||||
// GraphQL Queries and Mutations
|
// GraphQL Queries and Mutations
|
||||||
const {
|
const {
|
||||||
@@ -12,9 +13,27 @@ const {
|
|||||||
INSERT_OWNER,
|
INSERT_OWNER,
|
||||||
INSERT_JOB_WITH_LINES
|
INSERT_JOB_WITH_LINES
|
||||||
} = require("../partsManagement.queries");
|
} = require("../partsManagement.queries");
|
||||||
|
const { v4: uuidv4 } = require("uuid");
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
const FALLBACK_DEFAULT_ORDER_STATUS = "Open";
|
const FALLBACK_DEFAULT_JOB_STATUS = "Open";
|
||||||
|
|
||||||
|
const ESTIMATE_XML_BUCKET =
|
||||||
|
process.env?.NODE_ENV === "development"
|
||||||
|
? "parts-estimates" // local/dev shared bucket name
|
||||||
|
: InstanceMgr({
|
||||||
|
imex: `imex-webest-xml`,
|
||||||
|
rome: `rome-webest-xml`
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildEstimateXmlKey = (rq) => {
|
||||||
|
const refClaimNum = rq.RefClaimNum;
|
||||||
|
const shopId = rq.ShopID;
|
||||||
|
|
||||||
|
const ts = new Date().toISOString().replace(/:/g, "-");
|
||||||
|
const safeClaim = (refClaimNum || "no-claim").toString().replace(/[^A-Za-z0-9_-]/g, "_");
|
||||||
|
return `addRequest/${shopId}/${safeClaim}/${ts}-${uuidv4()}.xml`;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the default order status for a bodyshop.
|
* Fetches the default order status for a bodyshop.
|
||||||
@@ -22,13 +41,13 @@ const FALLBACK_DEFAULT_ORDER_STATUS = "Open";
|
|||||||
* @param {object} logger - The logger instance.
|
* @param {object} logger - The logger instance.
|
||||||
* @returns {Promise<string>} The default status or fallback.
|
* @returns {Promise<string>} The default status or fallback.
|
||||||
*/
|
*/
|
||||||
const getDefaultOrderStatus = async (shopId, logger) => {
|
const getDefaultJobStatus = async (shopId, logger) => {
|
||||||
try {
|
try {
|
||||||
const { bodyshop_by_pk } = await client.request(GET_BODYSHOP_STATUS, { id: shopId });
|
const { bodyshop_by_pk } = await client.request(GET_BODYSHOP_STATUS, { id: shopId });
|
||||||
return bodyshop_by_pk?.md_order_statuses?.default_open || FALLBACK_DEFAULT_ORDER_STATUS;
|
return bodyshop_by_pk?.md_ro_statuses?.default_imported || FALLBACK_DEFAULT_JOB_STATUS;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.log("parts-bodyshop-fetch-failed", "warn", shopId, null, { error: err });
|
logger.log("parts-bodyshop-fetch-failed", "warn", shopId, null, { error: err });
|
||||||
return FALLBACK_DEFAULT_ORDER_STATUS;
|
return FALLBACK_DEFAULT_JOB_STATUS;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +84,9 @@ const extractJobData = (rq) => {
|
|||||||
const ci = rq.ClaimInfo || {};
|
const ci = rq.ClaimInfo || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
driveable: !!rq.VehicleInfo?.Condition?.DrivableInd,
|
||||||
shopId: rq.ShopID || rq.shopId,
|
shopId: rq.ShopID || rq.shopId,
|
||||||
|
// status: ci.ClaimStatus || null, Proper, setting it default for now
|
||||||
refClaimNum: rq.RefClaimNum,
|
refClaimNum: rq.RefClaimNum,
|
||||||
ciecaid: rq.RqUID || null,
|
ciecaid: rq.RqUID || null,
|
||||||
// Pull Cieca_ttl from ClaimInfo per schema/sample
|
// Pull Cieca_ttl from ClaimInfo per schema/sample
|
||||||
@@ -81,8 +102,6 @@ const extractJobData = (rq) => {
|
|||||||
scheduled_in: ev.RepairEvent?.RequestedPickUpDateTime || null,
|
scheduled_in: ev.RepairEvent?.RequestedPickUpDateTime || null,
|
||||||
scheduled_completion: ev.RepairEvent?.TargetCompletionDateTime || null,
|
scheduled_completion: ev.RepairEvent?.TargetCompletionDateTime || null,
|
||||||
clm_no: ci.ClaimNum || null,
|
clm_no: ci.ClaimNum || null,
|
||||||
// status: ci.ClaimStatus || null, Proper, setting it default for now
|
|
||||||
status: FALLBACK_DEFAULT_ORDER_STATUS,
|
|
||||||
policy_no: ci.PolicyInfo?.PolicyInfo?.PolicyNum || ci.PolicyInfo?.PolicyNum || null,
|
policy_no: ci.PolicyInfo?.PolicyInfo?.PolicyNum || ci.PolicyInfo?.PolicyNum || null,
|
||||||
ded_amt: parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0)
|
ded_amt: parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0)
|
||||||
};
|
};
|
||||||
@@ -101,17 +120,18 @@ const extractOwnerData = (rq, shopId) => {
|
|||||||
const personName = personInfo.PersonName || {};
|
const personName = personInfo.PersonName || {};
|
||||||
const address = personInfo.Communications?.Address || {};
|
const address = personInfo.Communications?.Address || {};
|
||||||
|
|
||||||
let ownr_ph1, ownr_ph2, ownr_ea, ownr_alt_ph;
|
let ownr_ph1, ownr_ph2, ownr_ea;
|
||||||
|
|
||||||
const comms = Array.isArray(ownerOrClaimant.ContactInfo?.Communications)
|
const comms = Array.isArray(ownerOrClaimant.ContactInfo?.Communications)
|
||||||
? ownerOrClaimant.ContactInfo.Communications
|
? ownerOrClaimant.ContactInfo.Communications
|
||||||
: [ownerOrClaimant.ContactInfo?.Communications || {}];
|
: [ownerOrClaimant.ContactInfo?.Communications || {}];
|
||||||
|
|
||||||
for (const c of comms) {
|
for (const c of comms) {
|
||||||
|
// -- Document
|
||||||
if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone;
|
if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone;
|
||||||
if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone;
|
if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone;
|
||||||
if (c.CommQualifier === "EM") ownr_ea = c.CommEmail;
|
if (c.CommQualifier === "EM") ownr_ea = c.CommEmail;
|
||||||
if (c.CommQualifier === "AL") ownr_alt_ph = c.CommPhone;
|
// if (c.CommQualifier === "AL") ownr_alt_ph = c.CommPhone;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -127,8 +147,8 @@ const extractOwnerData = (rq, shopId) => {
|
|||||||
ownr_ctry: address.Country || null,
|
ownr_ctry: address.Country || null,
|
||||||
ownr_ph1,
|
ownr_ph1,
|
||||||
ownr_ph2,
|
ownr_ph2,
|
||||||
ownr_ea,
|
ownr_ea
|
||||||
ownr_alt_ph
|
// ownr_alt_ph
|
||||||
// ownr_id_qualifier: ownerOrClaimant.IDInfo?.IDQualifierCode || null // New
|
// ownr_id_qualifier: ownerOrClaimant.IDInfo?.IDQualifierCode || null // New
|
||||||
// ownr_id_num: ownerOrClaimant.IDInfo?.IDNum || null, // New
|
// ownr_id_num: ownerOrClaimant.IDInfo?.IDNum || null, // New
|
||||||
// ownr_preferred_contact: ownerOrClaimant.PreferredContactMethod || null // New
|
// ownr_preferred_contact: ownerOrClaimant.PreferredContactMethod || null // New
|
||||||
@@ -159,37 +179,40 @@ const extractEstimatorData = (rq) => {
|
|||||||
* @param {object} rq - The VehicleDamageEstimateAddRq object.
|
* @param {object} rq - The VehicleDamageEstimateAddRq object.
|
||||||
* @returns {object} Adjuster data.
|
* @returns {object} Adjuster data.
|
||||||
*/
|
*/
|
||||||
const extractAdjusterData = (rq) => {
|
// const extractAdjusterData = (rq) => {
|
||||||
const adjParty = rq.AdminInfo?.Adjuster?.Party || {};
|
// const adjParty = rq.AdminInfo?.Adjuster?.Party || {};
|
||||||
const adjComms = Array.isArray(adjParty.ContactInfo?.Communications)
|
// const adjComms = Array.isArray(adjParty.ContactInfo?.Communications)
|
||||||
? adjParty.ContactInfo.Communications
|
// ? adjParty.ContactInfo.Communications
|
||||||
: [adjParty.ContactInfo?.Communications || {}];
|
// : [adjParty.ContactInfo?.Communications || {}];
|
||||||
|
//
|
||||||
return {
|
// return {
|
||||||
agt_ct_fn: adjParty.PersonInfo?.PersonName?.FirstName || null,
|
// //TODO (FUTURE): I dont think we display agt_ct_* fields in app. Have they typically been sending data here?
|
||||||
agt_ct_ln: adjParty.PersonInfo?.PersonName?.LastName || null,
|
// agt_ct_fn: adjParty.PersonInfo?.PersonName?.FirstName || null,
|
||||||
agt_ct_ph: adjComms.find((c) => c.CommQualifier === "CP")?.CommPhone || null,
|
// agt_ct_ln: adjParty.PersonInfo?.PersonName?.LastName || null,
|
||||||
agt_ea: adjComms.find((c) => c.CommQualifier === "EM")?.CommEmail || null
|
// agt_ct_ph: adjComms.find((c) => c.CommQualifier === "CP")?.CommPhone || null,
|
||||||
};
|
// agt_ea: adjComms.find((c) => c.CommQualifier === "EM")?.CommEmail || null
|
||||||
};
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts repair facility data from the XML request.
|
* Extracts repair facility data from the XML request.
|
||||||
* @param {object} rq - The VehicleDamageEstimateAddRq object.
|
* @param {object} rq - The VehicleDamageEstimateAddRq object.
|
||||||
* @returns {object} Repair facility data.
|
* @returns {object} Repair facility data.
|
||||||
*/
|
*/
|
||||||
const extractRepairFacilityData = (rq) => {
|
// const extractRepairFacilityData = (rq) => {
|
||||||
const rfParty = rq.AdminInfo?.RepairFacility?.Party || {};
|
// const rfParty = rq.AdminInfo?.RepairFacility?.Party || {};
|
||||||
const rfComms = Array.isArray(rfParty.ContactInfo?.Communications)
|
// const rfComms = Array.isArray(rfParty.ContactInfo?.Communications)
|
||||||
? rfParty.ContactInfo.Communications
|
// ? rfParty.ContactInfo.Communications
|
||||||
: [rfParty.ContactInfo?.Communications || {}];
|
// : [rfParty.ContactInfo?.Communications || {}];
|
||||||
|
//
|
||||||
return {
|
// return {
|
||||||
servicing_dealer: rfParty.OrgInfo?.CompanyName || null,
|
// servicing_dealer: rfParty.OrgInfo?.CompanyName || null,
|
||||||
servicing_dealer_contact:
|
// // TODO (Future): The servicing dealer fields are a relic from synergy for a few folks
|
||||||
rfComms.find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null
|
// // TODO (Future): I suspect RF data could be ignored since they are the RF.
|
||||||
};
|
// servicing_dealer_contact:
|
||||||
};
|
// rfComms.find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts loss information from the XML request.
|
* Extracts loss information from the XML request.
|
||||||
@@ -203,10 +226,12 @@ const extractLossInfo = (rq) => {
|
|||||||
loss_date: loss.LossDateTime || null,
|
loss_date: loss.LossDateTime || null,
|
||||||
loss_type: custom.LossTypeCode || null,
|
loss_type: custom.LossTypeCode || null,
|
||||||
loss_desc: custom.LossTypeDesc || null
|
loss_desc: custom.LossTypeDesc || null
|
||||||
// primary_poi: loss.PrimaryPOI?.POICode || null,
|
// area_of_impact: {
|
||||||
// secondary_poi: loss.SecondaryPOI?.POICode || null,
|
// impact_1: loss.PrimaryPOI?.POICode || null,
|
||||||
|
// imact_2 :loss.SecondaryPOI?.POICode || null,
|
||||||
|
// },
|
||||||
|
// tlosind: rq.ClaimInfo?.LossInfo?.TotalLossInd || null,
|
||||||
// damage_memo: loss.DamageMemo || null, //(maybe ins_memo)
|
// damage_memo: loss.DamageMemo || null, //(maybe ins_memo)
|
||||||
// total_loss_ind: rq.ClaimInfo?.LossInfo?.TotalLossInd || null // New
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -288,7 +313,8 @@ const extractVehicleData = (rq, shopId) => {
|
|||||||
v_color: exterior.Color?.ColorName || null,
|
v_color: exterior.Color?.ColorName || null,
|
||||||
v_bstyle: desc.BodyStyle || null,
|
v_bstyle: desc.BodyStyle || null,
|
||||||
v_engine: desc.EngineDesc || null,
|
v_engine: desc.EngineDesc || null,
|
||||||
v_options: desc.SubModelDesc || null,
|
// TODO (for future) Need to confirm with exact data, but this is typically a list of options. Not used AFAIK.
|
||||||
|
// v_options: desc.SubModelDesc || null,
|
||||||
v_type: desc.FuelType || null,
|
v_type: desc.FuelType || null,
|
||||||
v_cond: rq.VehicleInfo?.Condition?.DrivableInd,
|
v_cond: rq.VehicleInfo?.Condition?.DrivableInd,
|
||||||
v_trimcode: desc.TrimCode || null,
|
v_trimcode: desc.TrimCode || null,
|
||||||
@@ -337,27 +363,34 @@ const extractJobLines = (rq) => {
|
|||||||
const lineOut = { ...base };
|
const lineOut = { ...base };
|
||||||
|
|
||||||
// Manual line flag coercion
|
// Manual line flag coercion
|
||||||
if (line.ManualLineInd !== undefined) {
|
// if (line.ManualLineInd !== undefined) {
|
||||||
lineOut.manual_line =
|
// lineOut.manual_line =
|
||||||
line.ManualLineInd === true ||
|
// line.ManualLineInd === true ||
|
||||||
line.ManualLineInd === 1 ||
|
// line.ManualLineInd === 1 ||
|
||||||
line.ManualLineInd === "1" ||
|
// line.ManualLineInd === "1" ||
|
||||||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y");
|
// // TODO (FUTURE): manual line tracks manual in IO or not, this woudl presumably always be false
|
||||||
} else {
|
// (typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y");
|
||||||
lineOut.manual_line = null;
|
// } else {
|
||||||
}
|
// lineOut.manual_line = null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Is set to false because anything coming from the DMS is considered not a manual line, it becomes
|
||||||
|
// a manual line once it is edited in OUR system.
|
||||||
|
lineOut.manual_line = false;
|
||||||
|
|
||||||
// Parts (preferred) or Sublet (fallback when no PartInfo)
|
// Parts (preferred) or Sublet (fallback when no PartInfo)
|
||||||
const hasPart = Object.keys(partInfo).length > 0;
|
const hasPart = Object.keys(partInfo).length > 0;
|
||||||
const hasSublet = Object.keys(subletInfo).length > 0;
|
const hasSublet = Object.keys(subletInfo).length > 0;
|
||||||
|
|
||||||
if (hasPart) {
|
if (hasPart) {
|
||||||
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
|
|
||||||
lineOut.part_type = partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null;
|
lineOut.part_type = partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null;
|
||||||
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
|
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
|
||||||
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
|
lineOut.oem_partno = partInfo.OEMPartNum;
|
||||||
lineOut.db_price = isNaN(price) ? 0 : price;
|
lineOut.alt_partno = partInfo?.NonOEM?.NonOEMPartNum;
|
||||||
lineOut.act_price = isNaN(price) ? 0 : price;
|
|
||||||
|
// THIS NEEDS TO BE CHANGED IN CHANGE REQUEST
|
||||||
|
lineOut.act_price = parseFloat(partInfo?.PartPrice || 0);
|
||||||
|
lineOut.db_price = parseFloat(partInfo?.OEMPartPrice || 0);
|
||||||
|
|
||||||
// Tax flag from PartInfo.TaxableInd when provided
|
// Tax flag from PartInfo.TaxableInd when provided
|
||||||
if (
|
if (
|
||||||
@@ -372,7 +405,12 @@ const extractJobLines = (rq) => {
|
|||||||
partInfo.TaxableInd === "1" ||
|
partInfo.TaxableInd === "1" ||
|
||||||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
|
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
|
||||||
}
|
}
|
||||||
} else if (hasSublet) {
|
}
|
||||||
|
|
||||||
|
//TODO (FUTURE): Some nuance here. Usually a part and sublet amount shouldnt be on the same line, but they theoretically
|
||||||
|
// could. May require additional discussion.
|
||||||
|
// EMS - > Misc Amount, calibration for example, painting, etc
|
||||||
|
else if (hasSublet) {
|
||||||
const amt = parseFloat(subletInfo.SubletAmount || 0);
|
const amt = parseFloat(subletInfo.SubletAmount || 0);
|
||||||
lineOut.part_type = "PAS"; // Sublet as parts-as-service
|
lineOut.part_type = "PAS"; // Sublet as parts-as-service
|
||||||
lineOut.part_qty = 1;
|
lineOut.part_qty = 1;
|
||||||
@@ -386,14 +424,22 @@ const extractJobLines = (rq) => {
|
|||||||
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
|
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
|
||||||
(!isNaN(hrs) && hrs !== 0) ||
|
(!isNaN(hrs) && hrs !== 0) ||
|
||||||
(!isNaN(amt) && amt !== 0);
|
(!isNaN(amt) && amt !== 0);
|
||||||
|
|
||||||
if (hasLabor) {
|
if (hasLabor) {
|
||||||
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
|
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
|
||||||
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
|
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
|
||||||
lineOut.lbr_op = laborInfo.LaborOperation || null;
|
const opCodeKey =
|
||||||
|
typeof laborInfo.LaborOperation === "string" ? laborInfo.LaborOperation.trim().toUpperCase() : null;
|
||||||
|
lineOut.op_code_desc = opCodes?.[opCodeKey]?.desc || null;
|
||||||
|
|
||||||
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
|
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO (FUTURE): what's the BMS logic for this? Body and refinish operations can often happen to the same part,
|
||||||
|
// but most systems output a second line for the refinish labor.
|
||||||
|
//TODO (FUTURE): 2nd line may include a duplicate of the part price, but that can be removed. This is the case for CCC.
|
||||||
// Refinish labor (if present) recorded on the same line using secondary labor fields
|
// Refinish labor (if present) recorded on the same line using secondary labor fields
|
||||||
|
|
||||||
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
||||||
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
||||||
const hasRefinish =
|
const hasRefinish =
|
||||||
@@ -403,9 +449,9 @@ const extractJobLines = (rq) => {
|
|||||||
!isNaN(rAmt) ||
|
!isNaN(rAmt) ||
|
||||||
!!refinishInfo.LaborOperation);
|
!!refinishInfo.LaborOperation);
|
||||||
if (hasRefinish) {
|
if (hasRefinish) {
|
||||||
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
|
lineOut.lbr_typ_j = !!refinishInfo?.LaborAmtJudgmentInd;
|
||||||
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
|
lineOut.lbr_hrs_j = !!refinishInfo?.LaborHoursJudgmentInd;
|
||||||
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
|
lineOut.lbr_op_j = !!refinishInfo.LaborOperationJudgmentInd;
|
||||||
// Aggregate refinish labor amount into the total labor amount for the line
|
// Aggregate refinish labor amount into the total labor amount for the line
|
||||||
if (!isNaN(rAmt)) {
|
if (!isNaN(rAmt)) {
|
||||||
lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
|
lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
|
||||||
@@ -421,26 +467,26 @@ const extractJobLines = (rq) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Helper to extract a GRAND TOTAL amount from RepairTotalsInfo
|
// Helper to extract a GRAND TOTAL amount from RepairTotalsInfo
|
||||||
const extractGrandTotal = (rq) => {
|
// const extractGrandTotal = (rq) => {
|
||||||
const rti = rq.RepairTotalsInfo;
|
// const rti = rq.RepairTotalsInfo;
|
||||||
const groups = Array.isArray(rti) ? rti : rti ? [rti] : [];
|
// const groups = Array.isArray(rti) ? rti : rti ? [rti] : [];
|
||||||
for (const grp of groups) {
|
// for (const grp of groups) {
|
||||||
const sums = Array.isArray(grp.SummaryTotalsInfo)
|
// const sums = Array.isArray(grp.SummaryTotalsInfo)
|
||||||
? grp.SummaryTotalsInfo
|
// ? grp.SummaryTotalsInfo
|
||||||
: grp.SummaryTotalsInfo
|
// : grp.SummaryTotalsInfo
|
||||||
? [grp.SummaryTotalsInfo]
|
// ? [grp.SummaryTotalsInfo]
|
||||||
: [];
|
// : [];
|
||||||
for (const s of sums) {
|
// for (const s of sums) {
|
||||||
const type = (s.TotalType || "").toString().toUpperCase();
|
// const type = (s.TotalType || "").toString().toUpperCase();
|
||||||
const desc = (s.TotalTypeDesc || "").toString().toUpperCase();
|
// const desc = (s.TotalTypeDesc || "").toString().toUpperCase();
|
||||||
if (type.includes("GRAND") || type === "TOTAL" || desc.includes("GRAND")) {
|
// if (type.includes("GRAND") || type === "TOTAL" || desc.includes("GRAND")) {
|
||||||
const amt = parseFloat(s.TotalAmt ?? "NaN");
|
// const amt = parseFloat(s.TotalAmt ?? "NaN");
|
||||||
if (!isNaN(amt)) return amt;
|
// if (!isNaN(amt)) return amt;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return null;
|
// return null;
|
||||||
};
|
// };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts an owner and returns the owner ID.
|
* Inserts an owner and returns the owner ID.
|
||||||
@@ -459,24 +505,27 @@ const insertOwner = async (ownerInput, logger) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Fallback: compute a naive total from joblines (parts + sublet + labor amounts)
|
// Fallback: compute a naive total from joblines (parts + sublet + labor amounts)
|
||||||
const computeLinesTotal = (joblines = []) => {
|
// const computeLinesTotal = (joblines = []) => {
|
||||||
let parts = 0;
|
// let parts = 0;
|
||||||
let labor = 0;
|
// let labor = 0;
|
||||||
for (const jl of joblines) {
|
// for (const jl of joblines) {
|
||||||
if (jl?.part_type) {
|
// if (jl?.part_type) {
|
||||||
const qty = Number.isFinite(jl.part_qty) ? jl.part_qty : 1;
|
// const qty = Number.isFinite(jl.part_qty) ? jl.part_qty : 1;
|
||||||
const price = Number.isFinite(jl.act_price) ? jl.act_price : 0;
|
// const price = Number.isFinite(jl.act_price) ? jl.act_price : 0;
|
||||||
parts += price * (qty || 1);
|
// parts += price * (qty || 1);
|
||||||
} else if (!jl.part_type && Number.isFinite(jl.act_price)) {
|
// } else if (!jl.part_type && Number.isFinite(jl.act_price)) {
|
||||||
parts += jl.act_price;
|
// parts += jl.act_price;
|
||||||
}
|
// }
|
||||||
if (Number.isFinite(jl.lbr_amt)) {
|
// if (Number.isFinite(jl.lbr_amt)) {
|
||||||
labor += jl.lbr_amt;
|
// labor += jl.lbr_amt;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
const total = parts + labor;
|
// const total = parts + labor;
|
||||||
return Number.isFinite(total) && total > 0 ? total : 0;
|
//
|
||||||
};
|
// //TODO (FUTURE): clm_total is the 100% full amount of the repair including deductible, betterment and taxes. Typically provided by the source system.
|
||||||
|
// return Number.isFinite(total) && total > 0 ? total : 0;
|
||||||
|
// //TODO (FUTURE): clm_total is the 100% full amount of the repair including deductible,
|
||||||
|
// // betterment and taxes. Typically provided by the source system.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the VehicleDamageEstimateAddRq XML request from parts management.
|
* Handles the VehicleDamageEstimateAddRq XML request from parts management.
|
||||||
@@ -486,17 +535,10 @@ const computeLinesTotal = (joblines = []) => {
|
|||||||
*/
|
*/
|
||||||
const vehicleDamageEstimateAddRq = async (req, res) => {
|
const vehicleDamageEstimateAddRq = async (req, res) => {
|
||||||
const { logger } = req;
|
const { logger } = req;
|
||||||
|
const rawXml = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf8") : "";
|
||||||
try {
|
try {
|
||||||
// Parse XML
|
|
||||||
const payload = await parseXml(req.body, logger);
|
const payload = await parseXml(req.body, logger);
|
||||||
const rq = normalizeXmlObject(payload.VehicleDamageEstimateAddRq);
|
const rq = normalizeXmlObject(payload.VehicleDamageEstimateAddRq);
|
||||||
if (!rq) {
|
|
||||||
logger.log("parts-missing-root", "error");
|
|
||||||
return res.status(400).send("Missing <VehicleDamageEstimateAddRq>");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract job data
|
|
||||||
const {
|
const {
|
||||||
shopId,
|
shopId,
|
||||||
refClaimNum,
|
refClaimNum,
|
||||||
@@ -513,40 +555,23 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
scheduled_in,
|
scheduled_in,
|
||||||
scheduled_completion,
|
scheduled_completion,
|
||||||
clm_no,
|
clm_no,
|
||||||
status,
|
|
||||||
policy_no,
|
policy_no,
|
||||||
ded_amt
|
ded_amt,
|
||||||
|
driveable
|
||||||
} = extractJobData(rq);
|
} = extractJobData(rq);
|
||||||
|
const defaultStatus = await getDefaultJobStatus(shopId, logger);
|
||||||
if (!shopId) {
|
|
||||||
throw { status: 400, message: "Missing <ShopID> in XML" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get default status
|
|
||||||
const defaultStatus = await getDefaultOrderStatus(shopId, logger);
|
|
||||||
|
|
||||||
// Extract additional data
|
|
||||||
const parts_tax_rates = extractPartsTaxRates(rq.ProfileInfo);
|
const parts_tax_rates = extractPartsTaxRates(rq.ProfileInfo);
|
||||||
const ownerData = extractOwnerData(rq, shopId);
|
const ownerData = extractOwnerData(rq, shopId);
|
||||||
const estimatorData = extractEstimatorData(rq);
|
const estimatorData = extractEstimatorData(rq);
|
||||||
const adjusterData = extractAdjusterData(rq);
|
|
||||||
const repairFacilityData = extractRepairFacilityData(rq);
|
|
||||||
const vehicleData = extractVehicleData(rq, shopId);
|
const vehicleData = extractVehicleData(rq, shopId);
|
||||||
const lossInfo = extractLossInfo(rq);
|
const lossInfo = extractLossInfo(rq);
|
||||||
const joblinesData = extractJobLines(rq);
|
const joblinesData = extractJobLines(rq);
|
||||||
const insuranceData = extractInsuranceData(rq);
|
const insuranceData = extractInsuranceData(rq);
|
||||||
|
|
||||||
// Derive clm_total: prefer RepairTotalsInfo SummaryTotals GRAND TOTAL; else sum from lines
|
|
||||||
const grandTotal = extractGrandTotal(rq);
|
|
||||||
const computedTotal = grandTotal ?? computeLinesTotal(joblinesData);
|
|
||||||
|
|
||||||
// Find or create relationships
|
|
||||||
const ownerid = await insertOwner(ownerData, logger);
|
const ownerid = await insertOwner(ownerData, logger);
|
||||||
const vehicleid = await findExistingVehicle(shopId, vehicleData.v_vin, logger);
|
const vehicleid = await findExistingVehicle(shopId, vehicleData.v_vin, logger);
|
||||||
|
|
||||||
// Build job input
|
|
||||||
const jobInput = {
|
const jobInput = {
|
||||||
shopid: shopId,
|
shopid: shopId,
|
||||||
|
driveable,
|
||||||
converted: true,
|
converted: true,
|
||||||
ownerid,
|
ownerid,
|
||||||
ro_number: refClaimNum,
|
ro_number: refClaimNum,
|
||||||
@@ -557,8 +582,8 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
class: classType,
|
class: classType,
|
||||||
parts_tax_rates,
|
parts_tax_rates,
|
||||||
clm_no,
|
clm_no,
|
||||||
status: status || defaultStatus,
|
status: defaultStatus,
|
||||||
clm_total: computedTotal || null,
|
clm_total: 0,
|
||||||
policy_no,
|
policy_no,
|
||||||
ded_amt,
|
ded_amt,
|
||||||
comment,
|
comment,
|
||||||
@@ -568,14 +593,10 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
asgn_date,
|
asgn_date,
|
||||||
scheduled_in,
|
scheduled_in,
|
||||||
scheduled_completion,
|
scheduled_completion,
|
||||||
// Inline insurance/loss/contacts
|
|
||||||
...insuranceData,
|
...insuranceData,
|
||||||
...lossInfo,
|
...lossInfo,
|
||||||
...ownerData,
|
...ownerData,
|
||||||
...estimatorData,
|
...estimatorData,
|
||||||
...adjusterData,
|
|
||||||
...repairFacilityData,
|
|
||||||
// Inline vehicle data
|
|
||||||
v_vin: vehicleData.v_vin,
|
v_vin: vehicleData.v_vin,
|
||||||
v_model_yr: vehicleData.v_model_yr,
|
v_model_yr: vehicleData.v_model_yr,
|
||||||
v_model_desc: vehicleData.v_model_desc,
|
v_model_desc: vehicleData.v_model_desc,
|
||||||
@@ -586,10 +607,23 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
...(vehicleid ? { vehicleid } : { vehicle: { data: vehicleData } }),
|
...(vehicleid ? { vehicleid } : { vehicle: { data: vehicleData } }),
|
||||||
joblines: { data: joblinesData }
|
joblines: { data: joblinesData }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert job
|
|
||||||
const { insert_jobs_one: newJob } = await client.request(INSERT_JOB_WITH_LINES, { job: jobInput });
|
const { insert_jobs_one: newJob } = await client.request(INSERT_JOB_WITH_LINES, { job: jobInput });
|
||||||
|
|
||||||
|
// Upload AFTER job creation to include job id in filename
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const key = buildEstimateXmlKey(rq);
|
||||||
|
await uploadFileToS3({
|
||||||
|
bucketName: ESTIMATE_XML_BUCKET,
|
||||||
|
key,
|
||||||
|
content: rawXml || "",
|
||||||
|
contentType: "application/xml"
|
||||||
|
});
|
||||||
|
logger.log("parts-estimate-xml-uploaded", "info", shopId, newJob.id, { key, bytes: rawXml?.length || 0 });
|
||||||
|
} catch (e) {
|
||||||
|
logger.log("parts-estimate-xml-upload-failed", "warn", shopId, null, { error: e?.message });
|
||||||
|
}
|
||||||
|
})();
|
||||||
return res.status(200).json({ success: true, jobId: newJob.id });
|
return res.status(200).json({ success: true, jobId: newJob.id });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.log("parts-route-error", "error", null, null, { error: err });
|
logger.log("parts-route-error", "error", null, null, { error: err });
|
||||||
|
|||||||
@@ -4,17 +4,22 @@
|
|||||||
const client = require("../../../graphql-client/graphql-client").client;
|
const client = require("../../../graphql-client/graphql-client").client;
|
||||||
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
||||||
const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
|
const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
|
||||||
|
const opCodes = require("./lib/opCodes.json");
|
||||||
|
const { uploadFileToS3 } = require("../../../utils/s3");
|
||||||
|
const InstanceMgr = require("../../../utils/instanceMgr").default;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
GET_JOB_BY_ID,
|
GET_JOB_BY_ID,
|
||||||
UPDATE_JOB_BY_ID,
|
UPDATE_JOB_BY_ID,
|
||||||
SOFT_DELETE_JOBLINES_BY_IDS,
|
SOFT_DELETE_JOBLINES_BY_IDS,
|
||||||
INSERT_JOBLINES,
|
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ,
|
||||||
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ
|
GET_JOBLINE_IDS_BY_JOBID_UNQSEQ,
|
||||||
|
UPDATE_JOBLINE_BY_PK,
|
||||||
|
INSERT_JOBLINES
|
||||||
} = require("../partsManagement.queries");
|
} = require("../partsManagement.queries");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a job by shop ID and claim number.
|
* Finds a job by shop ID and job ID.
|
||||||
* @param shopId
|
* @param shopId
|
||||||
* @param jobId
|
* @param jobId
|
||||||
* @param logger
|
* @param logger
|
||||||
@@ -32,36 +37,37 @@ const findJob = async (shopId, jobId, logger) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts updated job data from the request payload.
|
* Extracts updated job data from the request payload.
|
||||||
|
* Mirrors AddRq for parts_tax_rates + driveable when present.
|
||||||
* @param rq
|
* @param rq
|
||||||
* @returns {{comment: (number|((comment: Comment, helper: postcss.Helpers) => (Promise<void> | void))|string|null), clm_no: null, status: (*|null), policy_no: (*|null)}}
|
|
||||||
*/
|
*/
|
||||||
const extractUpdatedJobData = (rq) => {
|
const extractUpdatedJobData = (rq) => {
|
||||||
const doc = rq.DocumentInfo || {};
|
const doc = rq.DocumentInfo || {};
|
||||||
const claim = rq.ClaimInfo || {};
|
const claim = rq.ClaimInfo || {};
|
||||||
|
|
||||||
const policyNo = claim.PolicyInfo?.PolicyInfo?.PolicyNum || claim.PolicyInfo?.PolicyNum || null;
|
const policyNo = claim.PolicyInfo?.PolicyInfo?.PolicyNum || claim.PolicyInfo?.PolicyNum || null;
|
||||||
|
|
||||||
const out = {
|
const out = {
|
||||||
comment: doc.Comment || null,
|
comment: doc.Comment || null,
|
||||||
clm_no: claim.ClaimNum || null,
|
clm_no: claim.ClaimNum || null,
|
||||||
status: claim.ClaimStatus || null,
|
// TODO (future): status omitted intentionally to avoid overwriting with 'Auth Cust'
|
||||||
policy_no: policyNo
|
policy_no: policyNo
|
||||||
};
|
};
|
||||||
|
|
||||||
// If ProfileInfo provided in ChangeRq, update parts_tax_rates to stay in sync with AddRq behavior
|
|
||||||
if (rq.ProfileInfo) {
|
if (rq.ProfileInfo) {
|
||||||
out.parts_tax_rates = extractPartsTaxRates(rq.ProfileInfo);
|
out.parts_tax_rates = extractPartsTaxRates(rq.ProfileInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rq.VehicleInfo?.Condition?.DrivableInd !== undefined) {
|
||||||
|
out.driveable = !!rq.VehicleInfo.Condition.DrivableInd;
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts updated job lines from the request payload without splitting parts and labor:
|
* Build jobline payloads for updates/inserts (no split between parts & labor).
|
||||||
* - Keep part and labor on the same jobline
|
* - Refinish labor aggregated into lbr_* secondary fields and lbr_amt.
|
||||||
* - Aggregate RefinishLabor into secondary labor fields and add its amount to lbr_amt
|
* - SUBLET-only -> PAS line with act_price = SubletAmount.
|
||||||
* - SUBLET-only lines become PAS part_type with act_price = SubletAmount
|
* - Notes merged with current DB value by unq_seq.
|
||||||
* Accepts currentJobLineNotes map for notes merging.
|
|
||||||
*/
|
*/
|
||||||
const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {}) => {
|
const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {}) => {
|
||||||
const linesIn = Array.isArray(addsChgs.DamageLineInfo) ? addsChgs.DamageLineInfo : [addsChgs.DamageLineInfo || {}];
|
const linesIn = Array.isArray(addsChgs.DamageLineInfo) ? addsChgs.DamageLineInfo : [addsChgs.DamageLineInfo || {}];
|
||||||
@@ -85,56 +91,39 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {})
|
|||||||
unq_seq: parseInt(line.UniqueSequenceNum || 0, 10),
|
unq_seq: parseInt(line.UniqueSequenceNum || 0, 10),
|
||||||
status: line.LineStatusCode || null,
|
status: line.LineStatusCode || null,
|
||||||
line_desc: line.LineDesc || null,
|
line_desc: line.LineDesc || null,
|
||||||
// notes will be set below
|
manual_line: false
|
||||||
manual_line: line.ManualLineInd !== undefined ? coerceManual(line.ManualLineInd) : null
|
// manual_line: line.ManualLineInd !== undefined ? coerceManual(line.ManualLineInd) : null
|
||||||
};
|
};
|
||||||
|
|
||||||
const lineOut = { ...base };
|
const lineOut = { ...base };
|
||||||
|
|
||||||
// --- Notes merge logic ---
|
// --- Notes merge ---
|
||||||
const unqSeq = lineOut.unq_seq;
|
const unqSeq = lineOut.unq_seq;
|
||||||
const currentNotes = currentJobLineNotes?.[unqSeq] || null;
|
const currentNotes = currentJobLineNotes?.[unqSeq] || null;
|
||||||
const newNotes = line.LineMemo || null;
|
const newNotes = line.LineMemo || null;
|
||||||
if (newNotes && currentNotes) {
|
if (newNotes && currentNotes) {
|
||||||
if (currentNotes === newNotes) {
|
if (currentNotes === newNotes || currentNotes.includes(newNotes)) lineOut.notes = currentNotes;
|
||||||
lineOut.notes = currentNotes;
|
else lineOut.notes = `${currentNotes} | ${newNotes}`;
|
||||||
} else if (currentNotes.includes(newNotes)) {
|
} else if (newNotes) lineOut.notes = newNotes;
|
||||||
lineOut.notes = currentNotes;
|
else if (currentNotes) lineOut.notes = currentNotes;
|
||||||
} else {
|
else lineOut.notes = null;
|
||||||
lineOut.notes = `${currentNotes} | ${newNotes}`;
|
// --- end notes merge ---
|
||||||
}
|
|
||||||
} else if (newNotes) {
|
|
||||||
lineOut.notes = newNotes;
|
|
||||||
} else if (currentNotes) {
|
|
||||||
lineOut.notes = currentNotes;
|
|
||||||
} else {
|
|
||||||
lineOut.notes = null;
|
|
||||||
}
|
|
||||||
// --- End notes merge logic ---
|
|
||||||
|
|
||||||
const hasPart = Object.keys(partInfo).length > 0;
|
const hasPart = Object.keys(partInfo).length > 0;
|
||||||
const hasSublet = Object.keys(subletInfo).length > 0;
|
const hasSublet = Object.keys(subletInfo).length > 0;
|
||||||
|
|
||||||
if (hasPart) {
|
if (hasPart) {
|
||||||
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
|
|
||||||
lineOut.part_type = partInfo.PartType ? String(partInfo.PartType).toUpperCase() : null;
|
|
||||||
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
|
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
|
||||||
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
|
lineOut.oem_partno = partInfo.OEMPartNum;
|
||||||
lineOut.db_price = isNaN(price) ? 0 : price;
|
lineOut.alt_partno = partInfo?.NonOEM?.NonOEMPartNum;
|
||||||
lineOut.act_price = isNaN(price) ? 0 : price;
|
lineOut.part_type = partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null;
|
||||||
|
|
||||||
// Optional: taxability flag for parts
|
lineOut.act_price = parseFloat(partInfo?.PartPrice || 0);
|
||||||
if (
|
lineOut.db_price = parseFloat(partInfo?.OEMPartPrice || 0);
|
||||||
partInfo.TaxableInd !== undefined &&
|
|
||||||
(typeof partInfo.TaxableInd === "string" ||
|
if (partInfo.TaxableInd !== undefined) {
|
||||||
typeof partInfo.TaxableInd === "number" ||
|
const t = partInfo.TaxableInd;
|
||||||
typeof partInfo.TaxableInd === "boolean")
|
lineOut.tax_part = t === true || t === 1 || t === "1" || (typeof t === "string" && t.toUpperCase() === "Y");
|
||||||
) {
|
|
||||||
lineOut.tax_part =
|
|
||||||
partInfo.TaxableInd === true ||
|
|
||||||
partInfo.TaxableInd === 1 ||
|
|
||||||
partInfo.TaxableInd === "1" ||
|
|
||||||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
|
|
||||||
}
|
}
|
||||||
} else if (hasSublet) {
|
} else if (hasSublet) {
|
||||||
const amt = parseFloat(subletInfo.SubletAmount || 0);
|
const amt = parseFloat(subletInfo.SubletAmount || 0);
|
||||||
@@ -143,7 +132,7 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {})
|
|||||||
lineOut.act_price = isNaN(amt) ? 0 : amt;
|
lineOut.act_price = isNaN(amt) ? 0 : amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primary labor on same line
|
// Primary labor
|
||||||
const hrs = parseFloat(laborInfo.LaborHours || 0);
|
const hrs = parseFloat(laborInfo.LaborHours || 0);
|
||||||
const amt = parseFloat(laborInfo.LaborAmt || 0);
|
const amt = parseFloat(laborInfo.LaborAmt || 0);
|
||||||
const hasLabor =
|
const hasLabor =
|
||||||
@@ -153,11 +142,15 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {})
|
|||||||
if (hasLabor) {
|
if (hasLabor) {
|
||||||
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
|
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
|
||||||
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
|
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
|
||||||
lineOut.lbr_op = laborInfo.LaborOperation || null;
|
|
||||||
|
const opCodeKey =
|
||||||
|
typeof laborInfo.LaborOperation === "string" ? laborInfo.LaborOperation.trim().toUpperCase() : null;
|
||||||
|
lineOut.op_code_desc = opCodeKey && opCodes?.[opCodeKey]?.desc ? opCodes[opCodeKey].desc : null;
|
||||||
|
|
||||||
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
|
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refinish labor on same line using secondary fields; aggregate amount into lbr_amt
|
// Refinish (secondary fields, add amount)
|
||||||
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
||||||
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
||||||
const hasRefinish =
|
const hasRefinish =
|
||||||
@@ -170,9 +163,7 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {})
|
|||||||
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
|
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
|
||||||
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
|
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
|
||||||
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
|
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
|
||||||
if (!isNaN(rAmt)) {
|
if (!isNaN(rAmt)) lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
|
||||||
lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
|
|
||||||
}
|
|
||||||
if (refinishInfo.PaintStagesNum !== undefined) lineOut.paint_stg = refinishInfo.PaintStagesNum;
|
if (refinishInfo.PaintStagesNum !== undefined) lineOut.paint_stg = refinishInfo.PaintStagesNum;
|
||||||
if (refinishInfo.PaintTonesNum !== undefined) lineOut.paint_tone = refinishInfo.PaintTonesNum;
|
if (refinishInfo.PaintTonesNum !== undefined) lineOut.paint_tone = refinishInfo.PaintTonesNum;
|
||||||
}
|
}
|
||||||
@@ -184,82 +175,186 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId, currentJobLineNotes = {})
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts deletion IDs from the deletions object, also removing any derived labor/refinish lines
|
* Expand deletion IDs to include derived labor/refinish offsets.
|
||||||
* by including offsets (base + 400000, base + 500000).
|
|
||||||
*/
|
*/
|
||||||
const extractDeletions = (deletions = {}) => {
|
const extractDeletions = (deletions = {}) => {
|
||||||
const items = Array.isArray(deletions.DamageLineInfo) ? deletions.DamageLineInfo : [deletions.DamageLineInfo || {}];
|
const items = Array.isArray(deletions.DamageLineInfo) ? deletions.DamageLineInfo : [deletions.DamageLineInfo || {}];
|
||||||
const baseSeqs = items.map((line) => parseInt(line.UniqueSequenceNum, 10)).filter((id) => Number.isInteger(id));
|
const baseSeqs = items.map((line) => parseInt(line.UniqueSequenceNum, 10)).filter((id) => Number.isInteger(id));
|
||||||
|
|
||||||
const allSeqs = [];
|
const allSeqs = [];
|
||||||
for (const u of baseSeqs) {
|
for (const u of baseSeqs) allSeqs.push(u, u + 400000, u + 500000);
|
||||||
allSeqs.push(u, u + 400000, u + 500000);
|
|
||||||
}
|
|
||||||
// De-dup
|
|
||||||
return Array.from(new Set(allSeqs));
|
return Array.from(new Set(allSeqs));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// S3 bucket + key builder (mirrors AddRq but with changeRequest prefix)
|
||||||
|
const ESTIMATE_XML_BUCKET =
|
||||||
|
process.env?.NODE_ENV === "development"
|
||||||
|
? "parts-estimates"
|
||||||
|
: InstanceMgr({
|
||||||
|
imex: `imex-webest-xml`,
|
||||||
|
rome: `rome-webest-xml`
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildEstimateXmlKey = (rq) => {
|
||||||
|
const shopId = rq.ShopID;
|
||||||
|
const jobId = rq.JobID;
|
||||||
|
const ts = new Date().toISOString().replace(/:/g, "-");
|
||||||
|
return `changeRequest/${shopId}/${jobId}/${ts}.xml`;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles VehicleDamageEstimateChgRq requests.
|
* Convert a full jobline object into a jobs_set_input for update_by_pk (omit immutable fields).
|
||||||
* @param req
|
*/
|
||||||
* @param res
|
const toJoblineSetInput = (jl) => {
|
||||||
* @returns {Promise<*>}
|
const {
|
||||||
|
// immutable identity fields:
|
||||||
|
// jobid,
|
||||||
|
// unq_seq,
|
||||||
|
// everything else:
|
||||||
|
line_no,
|
||||||
|
status,
|
||||||
|
line_desc,
|
||||||
|
manual_line,
|
||||||
|
notes,
|
||||||
|
part_qty,
|
||||||
|
oem_partno,
|
||||||
|
alt_partno,
|
||||||
|
part_type,
|
||||||
|
act_price,
|
||||||
|
db_price,
|
||||||
|
tax_part,
|
||||||
|
mod_lbr_ty,
|
||||||
|
mod_lb_hrs,
|
||||||
|
op_code_desc,
|
||||||
|
lbr_amt,
|
||||||
|
lbr_typ_j,
|
||||||
|
lbr_hrs_j,
|
||||||
|
lbr_op_j,
|
||||||
|
paint_stg,
|
||||||
|
paint_tone
|
||||||
|
} = jl;
|
||||||
|
|
||||||
|
return {
|
||||||
|
line_no,
|
||||||
|
status,
|
||||||
|
line_desc,
|
||||||
|
manual_line,
|
||||||
|
notes,
|
||||||
|
part_qty,
|
||||||
|
oem_partno,
|
||||||
|
alt_partno,
|
||||||
|
part_type,
|
||||||
|
act_price,
|
||||||
|
db_price,
|
||||||
|
tax_part,
|
||||||
|
mod_lbr_ty,
|
||||||
|
mod_lb_hrs,
|
||||||
|
op_code_desc,
|
||||||
|
lbr_amt,
|
||||||
|
lbr_typ_j,
|
||||||
|
lbr_hrs_j,
|
||||||
|
lbr_op_j,
|
||||||
|
paint_stg,
|
||||||
|
paint_tone
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles VehicleDamageEstimateChgRq requests:
|
||||||
|
* - Update core job fields
|
||||||
|
* - For lines: update by PK if existing; otherwise bulk insert
|
||||||
|
* - Soft-delete only explicit deletions (exclude any updated seqs)
|
||||||
*/
|
*/
|
||||||
const partsManagementVehicleDamageEstimateChgRq = async (req, res) => {
|
const partsManagementVehicleDamageEstimateChgRq = async (req, res) => {
|
||||||
const { logger } = req;
|
const { logger } = req;
|
||||||
|
const rawXml = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf8") : "";
|
||||||
try {
|
try {
|
||||||
const payload = await parseXml(req.body, logger);
|
const payload = await parseXml(req.body, logger);
|
||||||
const rq = normalizeXmlObject(payload.VehicleDamageEstimateChgRq);
|
const rq = normalizeXmlObject(payload.VehicleDamageEstimateChgRq);
|
||||||
if (!rq) return res.status(400).send("Missing <VehicleDamageEstimateChgRq>");
|
|
||||||
|
|
||||||
const shopId = rq.ShopID;
|
|
||||||
const jobId = rq.JobID;
|
const jobId = rq.JobID;
|
||||||
|
const shopId = rq.ShopID;
|
||||||
|
|
||||||
if (!shopId || !jobId) return res.status(400).send("Missing ShopID or JobID");
|
// Fire-and-forget archival on valid request
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const key = buildEstimateXmlKey(rq);
|
||||||
|
await uploadFileToS3({
|
||||||
|
bucketName: ESTIMATE_XML_BUCKET,
|
||||||
|
key,
|
||||||
|
content: rawXml || "",
|
||||||
|
contentType: "application/xml"
|
||||||
|
});
|
||||||
|
logger.log("parts-estimate-xml-uploaded", "info", jobId, null, { key, bytes: rawXml?.length || 0 });
|
||||||
|
} catch (e) {
|
||||||
|
logger.log("parts-estimate-xml-upload-failed", "warn", jobId, null, { error: e?.message });
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const job = await findJob(shopId, jobId, logger);
|
const job = await findJob(shopId, jobId, logger);
|
||||||
|
|
||||||
if (!job) return res.status(404).send("Job not found");
|
if (!job) return res.status(404).send("Job not found");
|
||||||
|
|
||||||
// --- Get updated lines and their unq_seq ---
|
// --- Updated seqs from incoming changes ---
|
||||||
const linesIn = Array.isArray(rq.AddsChgs?.DamageLineInfo)
|
const linesIn = Array.isArray(rq.AddsChgs?.DamageLineInfo)
|
||||||
? rq.AddsChgs.DamageLineInfo
|
? rq.AddsChgs.DamageLineInfo
|
||||||
: [rq.AddsChgs?.DamageLineInfo || {}];
|
: [rq.AddsChgs?.DamageLineInfo || {}];
|
||||||
|
|
||||||
const updatedSeqs = Array.from(
|
const updatedSeqs = Array.from(
|
||||||
new Set((linesIn || []).map((l) => parseInt(l?.UniqueSequenceNum || 0, 10)).filter((v) => Number.isInteger(v)))
|
new Set((linesIn || []).map((l) => parseInt(l?.UniqueSequenceNum || 0, 10)).filter((v) => Number.isInteger(v)))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- Fetch current notes for merge ---
|
||||||
let currentJobLineNotes = {};
|
let currentJobLineNotes = {};
|
||||||
if (updatedSeqs.length > 0) {
|
if (updatedSeqs.length > 0) {
|
||||||
const resp = await client.request(GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ, { jobid: job.id, unqSeqs: updatedSeqs });
|
const resp = await client.request(GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ, { jobid: job.id, unqSeqs: updatedSeqs });
|
||||||
if (resp?.joblines) {
|
if (resp?.joblines) {
|
||||||
for (const jl of resp.joblines) {
|
for (const jl of resp.joblines) currentJobLineNotes[jl.unq_seq] = jl.notes;
|
||||||
currentJobLineNotes[jl.unq_seq] = jl.notes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- End fetch current notes ---
|
|
||||||
|
|
||||||
const updatedJobData = extractUpdatedJobData(rq);
|
const updatedJobData = extractUpdatedJobData(rq);
|
||||||
const updatedLines = extractUpdatedJobLines(rq.AddsChgs, job.id, currentJobLineNotes);
|
const updatedLines = extractUpdatedJobLines(rq.AddsChgs, job.id, currentJobLineNotes);
|
||||||
const deletedLineIds = extractDeletions(rq.Deletions);
|
|
||||||
|
|
||||||
await client.request(UPDATE_JOB_BY_ID, { id: job.id, job: updatedJobData });
|
// --- Look up existing rows (by natural key) to decide update vs insert ---
|
||||||
|
let existingIdByUnqSeq = {};
|
||||||
if (deletedLineIds?.length || updatedSeqs?.length) {
|
if (updatedSeqs.length > 0) {
|
||||||
const allToDelete = Array.from(new Set([...(deletedLineIds || []), ...(updatedSeqs || [])]));
|
const existing = await client.request(GET_JOBLINE_IDS_BY_JOBID_UNQSEQ, { jobid: job.id, unqSeqs: updatedSeqs });
|
||||||
if (allToDelete.length) {
|
if (existing?.joblines) {
|
||||||
await client.request(SOFT_DELETE_JOBLINES_BY_IDS, { jobid: job.id, unqSeqs: allToDelete });
|
for (const row of existing.joblines) existingIdByUnqSeq[row.unq_seq] = row.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedLines.length > 0) {
|
const toUpdate = [];
|
||||||
// Insert fresh versions after deletion so we don’t depend on a unique constraint
|
const toInsert = [];
|
||||||
await client.request(INSERT_JOBLINES, {
|
for (const jl of updatedLines) {
|
||||||
joblines: updatedLines
|
const id = existingIdByUnqSeq[jl.unq_seq];
|
||||||
});
|
if (id) toUpdate.push({ id, _set: toJoblineSetInput(jl) });
|
||||||
|
else toInsert.push(jl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build deletions list and exclude any seqs we are updating (avoid accidental removal)
|
||||||
|
const deletedLineIdsAll = extractDeletions(rq.Deletions);
|
||||||
|
const deletionSeqs = deletedLineIdsAll.filter((u) => !updatedSeqs.includes(u));
|
||||||
|
|
||||||
|
// Mutations:
|
||||||
|
const updateJobPromise = client.request(UPDATE_JOB_BY_ID, { id: job.id, job: updatedJobData });
|
||||||
|
|
||||||
|
const softDeletePromise = deletionSeqs.length
|
||||||
|
? client.request(SOFT_DELETE_JOBLINES_BY_IDS, { jobid: job.id, unqSeqs: deletionSeqs })
|
||||||
|
: Promise.resolve({});
|
||||||
|
|
||||||
|
// Update each existing row by primary key (parallelized)
|
||||||
|
const perRowUpdatesPromise =
|
||||||
|
toUpdate.length > 0
|
||||||
|
? Promise.all(toUpdate.map(({ id, _set }) => client.request(UPDATE_JOBLINE_BY_PK, { id, jl: _set })))
|
||||||
|
: Promise.resolve([]);
|
||||||
|
|
||||||
|
// Insert brand-new rows in bulk
|
||||||
|
const insertPromise =
|
||||||
|
toInsert.length > 0 ? client.request(INSERT_JOBLINES, { joblines: toInsert }) : Promise.resolve({});
|
||||||
|
|
||||||
|
await Promise.all([updateJobPromise, softDeletePromise, perRowUpdatesPromise, insertPromise]);
|
||||||
|
|
||||||
logger.log("parts-job-changed", "info", job.id, null);
|
logger.log("parts-job-changed", "info", job.id, null);
|
||||||
return res.status(200).json({ success: true, jobId: job.id });
|
return res.status(200).json({ success: true, jobId: job.id });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
const GET_BODYSHOP_STATUS = `
|
const GET_BODYSHOP_STATUS = `
|
||||||
query GetBodyshopStatus($id: uuid!) {
|
query GetBodyshopStatus($id: uuid!) {
|
||||||
bodyshops_by_pk(id: $id) {
|
bodyshops_by_pk(id: $id) {
|
||||||
md_order_statuses
|
md_ro_statuses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -216,6 +216,88 @@ const GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Clear task links to parts orders for all jobs in a shop to avoid FK violations when deleting parts orders
|
||||||
|
const CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS = `
|
||||||
|
mutation ClearTasksPartsOrderLinks($jobIds: [uuid!]!) {
|
||||||
|
update_tasks(
|
||||||
|
where: { parts_order: { jobid: { _in: $jobIds } } },
|
||||||
|
_set: { partsorderid: null }
|
||||||
|
) {
|
||||||
|
affected_rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Delete parts order lines where the parent order belongs to any of the provided job IDs
|
||||||
|
const DELETE_PARTS_ORDER_LINES_BY_JOB_IDS = `
|
||||||
|
mutation DeletePartsOrderLinesByJobIds($jobIds: [uuid!]!) {
|
||||||
|
delete_parts_order_lines(where: { parts_order: { jobid: { _in: $jobIds } } }) {
|
||||||
|
affected_rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Delete parts orders for the given job IDs
|
||||||
|
const DELETE_PARTS_ORDERS_BY_JOB_IDS = `
|
||||||
|
mutation DeletePartsOrdersByJobIds($jobIds: [uuid!]!) {
|
||||||
|
delete_parts_orders(where: { jobid: { _in: $jobIds } }) {
|
||||||
|
affected_rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UPSERT_JOBLINES = `
|
||||||
|
mutation UpsertJoblines($joblines: [joblines_insert_input!]!) {
|
||||||
|
insert_joblines(
|
||||||
|
objects: $joblines,
|
||||||
|
on_conflict: {
|
||||||
|
constraint: joblines_jobid_unq_seq_key,
|
||||||
|
update_columns: [
|
||||||
|
status,
|
||||||
|
line_desc,
|
||||||
|
notes,
|
||||||
|
manual_line,
|
||||||
|
part_qty,
|
||||||
|
oem_partno,
|
||||||
|
alt_partno,
|
||||||
|
part_type,
|
||||||
|
act_price,
|
||||||
|
db_price,
|
||||||
|
tax_part,
|
||||||
|
mod_lbr_ty,
|
||||||
|
mod_lb_hrs,
|
||||||
|
op_code_desc,
|
||||||
|
lbr_amt,
|
||||||
|
lbr_typ_j,
|
||||||
|
lbr_hrs_j,
|
||||||
|
lbr_op_j,
|
||||||
|
paint_stg,
|
||||||
|
paint_tone
|
||||||
|
]
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
affected_rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Get jobline IDs for the incoming unq_seq values (only non-removed)
|
||||||
|
const GET_JOBLINE_IDS_BY_JOBID_UNQSEQ = `
|
||||||
|
query GetJoblineIdsByJobIdUnqSeq($jobid: uuid!, $unqSeqs: [Int!]!) {
|
||||||
|
joblines(where: { jobid: { _eq: $jobid }, unq_seq: { _in: $unqSeqs }, removed: { _neq: true } }) {
|
||||||
|
id
|
||||||
|
unq_seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Update a single jobline by primary key
|
||||||
|
const UPDATE_JOBLINE_BY_PK = `
|
||||||
|
mutation UpdateJoblineByPk($id: uuid!, $jl: joblines_set_input!) {
|
||||||
|
update_joblines_by_pk(pk_columns: { id: $id }, _set: $jl) { id }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
GET_BODYSHOP_STATUS,
|
GET_BODYSHOP_STATUS,
|
||||||
GET_VEHICLE_BY_SHOP_VIN,
|
GET_VEHICLE_BY_SHOP_VIN,
|
||||||
@@ -241,5 +323,11 @@ module.exports = {
|
|||||||
DELETE_JOBS_BY_IDS,
|
DELETE_JOBS_BY_IDS,
|
||||||
DELETE_AUDIT_TRAIL_BY_SHOP,
|
DELETE_AUDIT_TRAIL_BY_SHOP,
|
||||||
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ,
|
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ,
|
||||||
GET_JOB_BY_ID
|
GET_JOB_BY_ID,
|
||||||
|
CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS,
|
||||||
|
DELETE_PARTS_ORDER_LINES_BY_JOB_IDS,
|
||||||
|
DELETE_PARTS_ORDERS_BY_JOB_IDS,
|
||||||
|
UPSERT_JOBLINES,
|
||||||
|
GET_JOBLINE_IDS_BY_JOBID_UNQSEQ,
|
||||||
|
UPDATE_JOBLINE_BY_PK
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -252,35 +252,27 @@ const generatePaymentUrl = async (req, res) => {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const checkFee = async (req, res) => {
|
const checkFee = async (req, res) => {
|
||||||
const logResponseMeta = {
|
const { bodyshop = {}, amount } = req.body || {};
|
||||||
bodyshop: {
|
const { id, imexshopid, shopname, state } = bodyshop;
|
||||||
id: req.body?.bodyshop?.id,
|
const logResponseMeta = { bodyshop: { id, imexshopid, name: shopname, state }, amount };
|
||||||
imexshopid: req.body?.bodyshop?.imexshopid,
|
|
||||||
name: req.body?.bodyshop?.shopname,
|
|
||||||
state: req.body?.bodyshop?.state
|
|
||||||
},
|
|
||||||
amount: req.body?.amount
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, logResponseMeta);
|
logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, logResponseMeta);
|
||||||
|
|
||||||
if (!isNumber(req.body?.amount) || req.body?.amount <= 0) {
|
if (!isNumber(amount) || amount <= 0) {
|
||||||
logger.log("intellipay-checkfee-skip", "DEBUG", req.user?.email, null, {
|
logger.log("intellipay-checkfee-skip", "DEBUG", req.user?.email, null, {
|
||||||
message: "Amount is zero or undefined, skipping fee check.",
|
message: "Amount is zero or undefined, skipping fee check.",
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json({ fee: 0 });
|
return res.json({ fee: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const shopCredentials = await getShopCredentials(req.body.bodyshop);
|
const shopCredentials = await getShopCredentials(bodyshop);
|
||||||
|
|
||||||
if (shopCredentials?.error) {
|
if (shopCredentials?.error) {
|
||||||
logger.log("intellipay-checkfee-credentials-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-checkfee-credentials-error", "ERROR", req.user?.email, null, {
|
||||||
message: shopCredentials.error?.message,
|
message: shopCredentials.error?.message,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(400).json({ error: shopCredentials.error?.message, ...logResponseMeta });
|
return res.status(400).json({ error: shopCredentials.error?.message, ...logResponseMeta });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,13 +284,10 @@ const checkFee = async (req, res) => {
|
|||||||
{
|
{
|
||||||
method: "fee",
|
method: "fee",
|
||||||
...shopCredentials,
|
...shopCredentials,
|
||||||
amount: req.body.amount,
|
amount: String(amount), // Type cast to string as required by API
|
||||||
paymenttype: `CC`,
|
paymenttype: "CC",
|
||||||
cardnum: "4111111111111111", // Required for compatibility with API
|
cardnum: "4111111111111111", // Required for compatibility with API
|
||||||
state:
|
state: state?.toUpperCase() || "ZZ"
|
||||||
req.body.bodyshop?.state && req.body.bodyshop.state.length === 2
|
|
||||||
? req.body.bodyshop.state.toUpperCase()
|
|
||||||
: "ZZ"
|
|
||||||
},
|
},
|
||||||
{ sort: false } // Ensure query string order is preserved
|
{ sort: false } // Ensure query string order is preserved
|
||||||
),
|
),
|
||||||
@@ -310,46 +299,24 @@ const checkFee = async (req, res) => {
|
|||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await axios(options);
|
const { data } = await axios(options);
|
||||||
|
|
||||||
if (response.data?.error) {
|
if (data?.error || data < 0) {
|
||||||
logger.log("intellipay-checkfee-api-error", "ERROR", req.user?.email, null, {
|
const errorType = data?.error ? "intellipay-checkfee-api-error" : "intellipay-checkfee-negative-fee";
|
||||||
message: response.data?.error,
|
const errorMessage = data?.error
|
||||||
...logResponseMeta
|
? data?.error
|
||||||
});
|
: "Fee amount negative. Check API credentials & account configuration.";
|
||||||
|
logger.log(errorType, "ERROR", req.user?.email, null, { message: errorMessage, data, ...logResponseMeta });
|
||||||
return res.status(400).json({
|
return res.status(400).json({ error: errorMessage, type: errorType, data, ...logResponseMeta });
|
||||||
error: response.data?.error,
|
|
||||||
type: "intellipay-checkfee-api-error",
|
|
||||||
...logResponseMeta
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.data < 0) {
|
logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, { fee: data, ...logResponseMeta });
|
||||||
logger.log("intellipay-checkfee-negative-fee", "ERROR", req.user?.email, null, {
|
return res.json({ fee: data, ...logResponseMeta });
|
||||||
message: "Fee amount returned is negative.",
|
|
||||||
...logResponseMeta
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
error: "Fee amount negative. Check API credentials & account configuration.",
|
|
||||||
...logResponseMeta,
|
|
||||||
type: "intellipay-checkfee-negative-fee"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, {
|
|
||||||
fee: response.data,
|
|
||||||
...logResponseMeta
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.json({ fee: response.data, ...logResponseMeta });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("intellipay-checkfee-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-checkfee-error", "ERROR", req.user?.email, null, {
|
||||||
message: error?.message,
|
message: error?.message,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(500).json({ error: error?.message, logResponseMeta });
|
return res.status(500).json({ error: error?.message, logResponseMeta });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,8 +58,20 @@ const generateSignedUploadUrls = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const command = new PutObjectCommand(commandParams);
|
const command = new PutObjectCommand(commandParams);
|
||||||
const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 });
|
|
||||||
signedUrls.push({ filename, presignedUrl, key });
|
// For PDFs, we need to add conditions to the presigned URL to enforce content type
|
||||||
|
const presignedUrlOptions = { expiresIn: 360 };
|
||||||
|
if (isPdf) {
|
||||||
|
presignedUrlOptions.signableHeaders = new Set(['content-type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
const presignedUrl = await getSignedUrl(client, command, presignedUrlOptions);
|
||||||
|
signedUrls.push({
|
||||||
|
filename,
|
||||||
|
presignedUrl,
|
||||||
|
key,
|
||||||
|
...(isPdf && { contentType: "application/pdf" })
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("imgproxy-upload-success", "DEBUG", req.user?.email, jobid, { signedUrls });
|
logger.log("imgproxy-upload-success", "DEBUG", req.user?.email, jobid, { signedUrls });
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const logger = require("../../server/utils/logger");
|
||||||
|
|
||||||
// Pull secrets from env
|
// Pull secrets from env
|
||||||
const { VSSTA_INTEGRATION_SECRET, PARTS_MANAGEMENT_INTEGRATION_SECRET } = process.env;
|
const { VSSTA_INTEGRATION_SECRET, PARTS_MANAGEMENT_INTEGRATION_SECRET } = process.env;
|
||||||
@@ -11,7 +12,7 @@ if (typeof VSSTA_INTEGRATION_SECRET === "string" && VSSTA_INTEGRATION_SECRET.len
|
|||||||
|
|
||||||
router.post("/vssta", vsstaMiddleware, vsstaIntegration);
|
router.post("/vssta", vsstaMiddleware, vsstaIntegration);
|
||||||
} else {
|
} else {
|
||||||
console.warn("VSSTA_INTEGRATION_SECRET is not set — skipping /vssta integration route");
|
logger.logger.warn("VSSTA_INTEGRATION_SECRET is not set — skipping /vssta integration route");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only load Parts Management routes if that secret is set
|
// Only load Parts Management routes if that secret is set
|
||||||
@@ -45,14 +46,17 @@ if (typeof PARTS_MANAGEMENT_INTEGRATION_SECRET === "string" && PARTS_MANAGEMENT_
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Deprovisioning route
|
// Deprovisioning route
|
||||||
router.post("/parts-management/deprovision", partsManagementIntegrationMiddleware, partsManagementDeprovisioning);
|
if (process.env.NODE_ENV !== "production" && !process.env.HOSTNAME?.endsWith("compute.internal")) {
|
||||||
|
logger.logger.warn("Parts Management Deprovisioning route has been loaded.");
|
||||||
|
router.post("/parts-management/deprovision", partsManagementIntegrationMiddleware, partsManagementDeprovisioning);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route to handle Parts Management Provisioning
|
* Route to handle Parts Management Provisioning
|
||||||
*/
|
*/
|
||||||
router.post("/parts-management/provision", partsManagementIntegrationMiddleware, partsManagementProvisioning);
|
router.post("/parts-management/provision", partsManagementIntegrationMiddleware, partsManagementProvisioning);
|
||||||
} else {
|
} else {
|
||||||
console.warn("PARTS_MANAGEMENT_INTEGRATION_SECRET is not set — skipping /parts-management/provision route");
|
logger.logger.warn("PARTS_MANAGEMENT_INTEGRATION_SECRET is not set — skipping /parts-management/provision route");
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user