Merged in feature/IO-3255-simplified-part-management (pull request #2538)

Feature/IO-3255 simplified part management
This commit is contained in:
Dave Richer
2025-09-05 16:46:39 +00:00
14 changed files with 1009 additions and 787 deletions

308
client/package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -177,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" }}>
@@ -192,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" }}>
@@ -237,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}`}>
@@ -248,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} />
@@ -267,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>
@@ -317,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" }}
@@ -327,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)

View File

@@ -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);

View File

@@ -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 });

View File

@@ -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>

View File

@@ -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}

View File

@@ -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:

858
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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.876.0", "@aws-sdk/client-cloudwatch-logs": "^3.882.0",
"@aws-sdk/client-elasticache": "^3.876.0", "@aws-sdk/client-elasticache": "^3.882.0",
"@aws-sdk/client-s3": "^3.876.0", "@aws-sdk/client-s3": "^3.882.0",
"@aws-sdk/client-secrets-manager": "^3.876.0", "@aws-sdk/client-secrets-manager": "^3.882.0",
"@aws-sdk/client-ses": "^3.876.0", "@aws-sdk/client-ses": "^3.882.0",
"@aws-sdk/credential-provider-node": "^3.876.0", "@aws-sdk/credential-provider-node": "^3.882.0",
"@aws-sdk/lib-storage": "^3.876.0", "@aws-sdk/lib-storage": "^3.882.0",
"@aws-sdk/s3-request-presigner": "^3.876.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.2", "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.64.0", "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.4", "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.34.0", "@eslint/js": "^9.35.0",
"eslint": "^9.34.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",

View File

@@ -144,7 +144,7 @@ const partsManagementDeprovisioning = async (req, res) => {
} catch (userError) { } catch (userError) {
logger.log("admin-delete-user-error", "warn", null, null, { logger.log("admin-delete-user-error", "warn", null, null, {
email: user.email, email: user.email,
error: userError.message error: userError.message || userError
}); });
} }
} }

View File

@@ -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,10 +13,28 @@ 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_JOB_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.
* @param {string} shopId - The bodyshop UUID. * @param {string} shopId - The bodyshop UUID.
@@ -65,6 +84,7 @@ 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 // status: ci.ClaimStatus || null, Proper, setting it default for now
refClaimNum: rq.RefClaimNum, refClaimNum: rq.RefClaimNum,
@@ -107,8 +127,7 @@ const extractOwnerData = (rq, shopId) => {
: [ownerOrClaimant.ContactInfo?.Communications || {}]; : [ownerOrClaimant.ContactInfo?.Communications || {}];
for (const c of comms) { for (const c of comms) {
// TODO: Should document this logic. 1 and 2 don't // -- Document
// typically indicate type in EMS. This makes sense, but good to 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;
@@ -167,7 +186,7 @@ const extractEstimatorData = (rq) => {
// : [adjParty.ContactInfo?.Communications || {}]; // : [adjParty.ContactInfo?.Communications || {}];
// //
// return { // return {
// //TODO: I dont think we display agt_ct_* fields in app. Have they typically been sending data here? // //TODO (FUTURE): I dont think we display agt_ct_* fields in app. Have they typically been sending data here?
// agt_ct_fn: adjParty.PersonInfo?.PersonName?.FirstName || null, // agt_ct_fn: adjParty.PersonInfo?.PersonName?.FirstName || null,
// agt_ct_ln: adjParty.PersonInfo?.PersonName?.LastName || null, // agt_ct_ln: adjParty.PersonInfo?.PersonName?.LastName || null,
// agt_ct_ph: adjComms.find((c) => c.CommQualifier === "CP")?.CommPhone || null, // agt_ct_ph: adjComms.find((c) => c.CommQualifier === "CP")?.CommPhone || null,
@@ -188,8 +207,8 @@ const extractEstimatorData = (rq) => {
// //
// return { // return {
// servicing_dealer: rfParty.OrgInfo?.CompanyName || null, // servicing_dealer: rfParty.OrgInfo?.CompanyName || null,
// // TODO: The servicing dealer fields are a relic from synergy for a few folks // // TODO (Future): The servicing dealer fields are a relic from synergy for a few folks
// // TODO: I suspect RF data could be ignored since they are the RF. // // TODO (Future): I suspect RF data could be ignored since they are the RF.
// servicing_dealer_contact: // servicing_dealer_contact:
// rfComms.find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null // rfComms.find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null
// }; // };
@@ -294,10 +313,9 @@ 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,
// TODO Need to confirm with exact data, but this is typically a list of options. Not used AFAIK. // 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_options: desc.SubModelDesc || null,
v_type: desc.FuelType || null, v_type: desc.FuelType || null,
// TODO there is a separate driveable flag on the job.
v_cond: rq.VehicleInfo?.Condition?.DrivableInd, v_cond: rq.VehicleInfo?.Condition?.DrivableInd,
v_trimcode: desc.TrimCode || null, v_trimcode: desc.TrimCode || null,
v_tone: exterior.Tone || null, v_tone: exterior.Tone || null,
@@ -345,30 +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" ||
// TODO: manual line tracks manual in IO or not, this woudl presumably always be false // // TODO (FUTURE): manual line tracks manual in IO or not, this woudl presumably always be false
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y"); // (typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y");
} else { // } else {
lineOut.manual_line = null; // 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;
//TODO: if aftermarket part, we have alt_part_no to capture. lineOut.oem_partno = partInfo.OEMPartNum;
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null; lineOut.alt_partno = partInfo?.NonOEM?.NonOEMPartNum;
//TODO: the Db and act price often are different. These should map back to their EMS equivalents.
lineOut.db_price = isNaN(price) ? 0 : price; // THIS NEEDS TO BE CHANGED IN CHANGE REQUEST
lineOut.act_price = isNaN(price) ? 0 : price; 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 (
@@ -384,8 +406,10 @@ const extractJobLines = (rq) => {
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y"); (typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
} }
} }
//TODO: Some nuance here. Usually a part and sublet amount shouldnt be on the same line, but they theoretically
// could.May require additional discussion. //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) { 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
@@ -400,18 +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;
//TODO: can add lbr_op_desc according to mapping available in new partner. const opCodeKey =
lineOut.lbr_op = laborInfo.LaborOperation || null; 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: what's the BMS logic for this? Body and refinish operations can often happen to the same part, //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. // but most systems output a second line for the refinish labor.
//TODO: 2nd line may include a duplicate of the part price, but that can be removed. This is the case for CCC. //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 =
@@ -421,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"; //TODO: _j fields indicate judgement, and are bool type. lineOut.lbr_typ_j = !!refinishInfo?.LaborAmtJudgmentInd;
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs; //TODO: _j fields indicate judgement, and are bool type. lineOut.lbr_hrs_j = !!refinishInfo?.LaborHoursJudgmentInd;
lineOut.lbr_op_j = refinishInfo.LaborOperation || null; //TODO: _j fields indicate judgement, and are bool type. 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;
@@ -494,9 +522,10 @@ const insertOwner = async (ownerInput, logger) => {
// } // }
// const total = parts + labor; // const total = parts + labor;
// //
// //TODO: clm_total is the 100% full amount of the repair including deductible, betterment and taxes. Typically provided by the source system. // //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; // 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.
@@ -506,17 +535,10 @@ const insertOwner = async (ownerInput, logger) => {
*/ */
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,
@@ -534,39 +556,22 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
scheduled_completion, scheduled_completion,
clm_no, clm_no,
policy_no, policy_no,
ded_amt ded_amt,
// status, driveable
} = extractJobData(rq); } = extractJobData(rq);
if (!shopId) {
throw { status: 400, message: "Missing <ShopID> in XML" };
}
// Get default status
const defaultStatus = await getDefaultJobStatus(shopId, logger); const defaultStatus = await getDefaultJobStatus(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,
@@ -578,7 +583,7 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
parts_tax_rates, parts_tax_rates,
clm_no, clm_no,
status: defaultStatus, status: defaultStatus,
clm_total: 0, // computedTotal || null, clm_total: 0,
policy_no, policy_no,
ded_amt, ded_amt,
comment, comment,
@@ -588,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,
@@ -606,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 });

View File

@@ -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,38 +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 || {};
//TODO: In the full BMS world, much more can change, this will need to be expanded
// before it can be considered an generic BMS importer, currently it is bespoke to webest
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,
// TODO: Commented out so they do not blow over with 'Auth Cust' // TODO (future): status omitted intentionally to avoid overwriting with 'Auth Cust'
// status: claim.ClaimStatus || null,
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 || {}];
@@ -87,56 +91,38 @@ 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: 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);
@@ -145,7 +131,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 =
@@ -155,11 +141,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 =
@@ -172,9 +162,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;
} }
@@ -186,85 +174,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 = {};
//TODO: for changed lines, are they deleted and then reinserted? if (updatedSeqs.length > 0) {
//TODO: Updated lines should get an upsert to update things like desc, price, etc. const existing = await client.request(GET_JOBLINE_IDS_BY_JOBID_UNQSEQ, { jobid: job.id, unqSeqs: updatedSeqs });
if (deletedLineIds?.length || updatedSeqs?.length) { if (existing?.joblines) {
const allToDelete = Array.from(new Set([...(deletedLineIds || []), ...(updatedSeqs || [])])); for (const row of existing.joblines) existingIdByUnqSeq[row.unq_seq] = row.id;
if (allToDelete.length) {
await client.request(SOFT_DELETE_JOBLINES_BY_IDS, { jobid: job.id, unqSeqs: allToDelete });
//TODO: appears to soft delete updated lines as well.
} }
} }
if (updatedLines.length > 0) { const toUpdate = [];
// Insert fresh versions after deletion so we dont 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) {

View File

@@ -246,6 +246,58 @@ const DELETE_PARTS_ORDERS_BY_JOB_IDS = `
} }
`; `;
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,
@@ -272,8 +324,10 @@ module.exports = {
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,
// newly added exports
CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS, CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS,
DELETE_PARTS_ORDER_LINES_BY_JOB_IDS, DELETE_PARTS_ORDER_LINES_BY_JOB_IDS,
DELETE_PARTS_ORDERS_BY_JOB_IDS DELETE_PARTS_ORDERS_BY_JOB_IDS,
UPSERT_JOBLINES,
GET_JOBLINE_IDS_BY_JOBID_UNQSEQ,
UPDATE_JOBLINE_BY_PK
}; };