Compare commits

...

20 Commits

Author SHA1 Message Date
Patrick Fic
7a383aaec9 IO-3340 Resolve unenforced content-type 2025-08-28 15:07:18 -07:00
Patrick Fic
cfdfb8110b IO-3340 Change application type on PDFs uploaded via imgproxy to allow inline display. 2025-08-22 12:01:16 -07:00
Dave
e990781ff7 feature/IO-3255-simplified-parts-management - Bug changes / Socket close cleanup 2025-08-22 14:24:03 -04:00
Dave
e5a48531a0 feature/IO-3255-simplified-parts-management - Missing breadcrumb / title translations 2025-08-22 11:31:01 -04:00
Dave
9860447e42 feature/IO-3255-simplified-parts-management - Fix Breadcrumbs 2025-08-22 11:03:34 -04:00
Dave
364813193f feature/IO-3255-simplified-parts-management - DO NOT SPLIT LABOR / PARTS 2025-08-21 18:35:10 -04:00
Dave
2c8f3a173e feature/IO-3255-simplified-parts-management - Soft Deletes 2025-08-21 18:20:53 -04:00
Dave
1577921334 feature/IO-3255-simplified-parts-management -Bug fixes 2025-08-21 18:12:56 -04:00
Dave
a934249c02 feature/IO-3255-simplified-parts-management - Revert dashboard 2025-08-21 16:40:07 -04:00
Dave
6486de3c61 feature/IO-3255-simplified-parts-management - Update and fix dashboard-grid.component.jsx 2025-08-21 16:09:36 -04:00
Dave
ed6a8b210d feature/IO-3255-simplified-parts-management - Update and fix dashboard-grid.component.jsx 2025-08-21 15:51:10 -04:00
Dave
96e38d509f feature/IO-3255-simplified-parts-management - cleanup 2025-08-21 15:42:06 -04:00
Dave
42e072e8ff feature/IO-3255-simplified-parts-management - Package Updates 2025-08-21 15:11:41 -04:00
Dave
6942d6e4f9 feature/IO-3255-simplified-parts-management - Cleanup / Bug fixes 2025-08-21 14:57:55 -04:00
Dave
43d5a77d60 feature/IO-3255-simplified-parts-management - Checkoint 2025-08-21 12:51:14 -04:00
Dave
a74ce063ec feature/IO-3255-simplified-parts-management - Checkoint 2025-08-21 12:39:15 -04:00
Dave
9c45b49ab9 feature/IO-3255-simplified-parts-management - Remove unnecessary dotenv calls / fix import bug 2025-08-21 12:00:26 -04:00
Dave
d690790dfe feature/IO-3255-simplified-parts-management - Remove unnecessary dotenv calls / fix import bug 2025-08-21 11:57:12 -04:00
Dave
70fa638c37 feature/IO-3255-simplified-parts-management - Remove unnecessary dotenv calls 2025-08-21 11:45:38 -04:00
Dave Richer
77cacdec91 Merged in feature/IO-3343-The-Great-Lint-Cleanup (pull request #2488)
Lint all the things
2025-08-21 14:53:59 +00:00
71 changed files with 2910 additions and 1303 deletions

318
client/package-lock.json generated
View File

@@ -39,10 +39,10 @@
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"graphql": "^16.11.0",
"i18next": "^25.3.6",
"i18next": "^25.4.0",
"i18next-browser-languagedetector": "^8.2.0",
"immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.12.12",
"libphonenumber-js": "^1.12.13",
"logrocket": "^9.0.2",
"markerjs2": "^2.32.6",
"memoize-one": "^6.0.0",
@@ -60,7 +60,7 @@
"react-drag-listview": "^2.0.0",
"react-grid-gallery": "^1.0.1",
"react-grid-layout": "1.3.4",
"react-i18next": "^15.6.1",
"react-i18next": "^15.7.1",
"react-icons": "^5.5.0",
"react-image-lightbox": "^5.1.4",
"react-markdown": "^10.1.0",
@@ -95,10 +95,10 @@
"@emotion/babel-plugin": "^11.13.5",
"@emotion/react": "^11.14.0",
"@eslint/js": "^9.33.0",
"@playwright/test": "^1.54.2",
"@playwright/test": "^1.55.0",
"@sentry/webpack-plugin": "^4.1.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.7.0",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@vitejs/plugin-react": "^4.6.0",
"browserslist": "^4.25.3",
@@ -108,9 +108,10 @@
"eslint-plugin-react": "^7.37.5",
"globals": "^15.15.0",
"jsdom": "^26.0.0",
"lightningcss": "^1.30.1",
"memfs": "^4.36.3",
"os-browserify": "^0.3.0",
"playwright": "^1.54.2",
"playwright": "^1.55.0",
"react-error-overlay": "^6.1.0",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3",
@@ -118,7 +119,7 @@
"vite-plugin-babel": "^1.3.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-node-polyfills": "^0.24.0",
"vite-plugin-pwa": "^1.0.2",
"vite-plugin-pwa": "^1.0.3",
"vite-plugin-style-import": "^2.0.0",
"vitest": "^3.2.4",
"workbox-window": "^7.3.0"
@@ -3261,13 +3262,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz",
"integrity": "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==",
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz",
"integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.54.2"
"playwright": "1.55.0"
},
"bin": {
"playwright": "cli.js"
@@ -4678,9 +4679,9 @@
}
},
"node_modules/@testing-library/jest-dom": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.7.0.tgz",
"integrity": "sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==",
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz",
"integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9046,9 +9047,9 @@
}
},
"node_modules/i18next": {
"version": "25.3.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.6.tgz",
"integrity": "sha512-dThZ0CTCM3sUG/qS0ZtQYZQcUI6DtBN8yBHK+SKEqihPcEYmjVWh/YJ4luic73Iq6Uxhp6q7LJJntRK5+1t7jQ==",
"version": "25.4.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.0.tgz",
"integrity": "sha512-UH5aiamXsO3cfrZFurCHiB6YSs3C+s+XY9UaJllMMSbmaoXILxFgqDEZu4NbfzJFjmUo3BNMa++Rjkr3ofjfLw==",
"funding": [
{
"type": "individual",
@@ -10204,11 +10205,260 @@
}
},
"node_modules/libphonenumber-js": {
"version": "1.12.12",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.12.tgz",
"integrity": "sha512-aWVR6xXYYRvnK0v/uIwkf5Lthq9Jpn0N8TISW/oDTWlYB2sOimuiLn9Q26aUw4KxkJoiT8ACdiw44Y8VwKFIfQ==",
"version": "1.12.13",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.13.tgz",
"integrity": "sha512-QZXnR/OGiDcBjF4hGk0wwVrPcZvbSSyzlvkjXv5LFfktj7O2VZDrt4Xs8SgR/vOFco+qk1i8J43ikMXZoTrtPw==",
"license": "MIT"
},
"node_modules/lightningcss": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-darwin-arm64": "1.30.1",
"lightningcss-darwin-x64": "1.30.1",
"lightningcss-freebsd-x64": "1.30.1",
"lightningcss-linux-arm-gnueabihf": "1.30.1",
"lightningcss-linux-arm64-gnu": "1.30.1",
"lightningcss-linux-arm64-musl": "1.30.1",
"lightningcss-linux-x64-gnu": "1.30.1",
"lightningcss-linux-x64-musl": "1.30.1",
"lightningcss-win32-arm64-msvc": "1.30.1",
"lightningcss-win32-x64-msvc": "1.30.1"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss/node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -12014,13 +12264,13 @@
}
},
"node_modules/playwright": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.2.tgz",
"integrity": "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==",
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz",
"integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.54.2"
"playwright-core": "1.55.0"
},
"bin": {
"playwright": "cli.js"
@@ -12033,9 +12283,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.2.tgz",
"integrity": "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==",
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz",
"integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -13097,16 +13347,16 @@
}
},
"node_modules/react-i18next": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.1.tgz",
"integrity": "sha512-uGrzSsOUUe2sDBG/+FJq2J1MM+Y4368/QW8OLEKSFvnDflHBbZhSd1u3UkW0Z06rMhZmnB/AQrhCpYfE5/5XNg==",
"version": "15.7.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.1.tgz",
"integrity": "sha512-o4VsKh30fy7p0z5ACHuyWqB6xu9WpQIQy2/ZcbCqopNnrnTVOPn/nAv9uYP4xYAWg99QMpvZ9Bu/si3eGurzGw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"i18next": ">= 23.4.0",
"react": ">= 16.8.0",
"typescript": "^5"
},
@@ -16224,9 +16474,9 @@
}
},
"node_modules/vite-plugin-pwa": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.0.2.tgz",
"integrity": "sha512-O3UwjsCnoDclgJANoOgzzqW7SFgwXE/th2OmUP/ILxHKwzWxxKDBu+B/Xa9Cv4IgSVSnj2HgRVIJ7F15+vQFkA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.0.3.tgz",
"integrity": "sha512-/OpqIpUldALGxcsEnv/ekQiQ5xHkQ53wcoN5ewX4jiIDNGs3W+eNcI1WYZeyOLmzoEjg09D7aX0O89YGjen1aw==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -38,10 +38,10 @@
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"graphql": "^16.11.0",
"i18next": "^25.3.6",
"i18next": "^25.4.0",
"i18next-browser-languagedetector": "^8.2.0",
"immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.12.12",
"libphonenumber-js": "^1.12.13",
"logrocket": "^9.0.2",
"markerjs2": "^2.32.6",
"memoize-one": "^6.0.0",
@@ -59,7 +59,7 @@
"react-drag-listview": "^2.0.0",
"react-grid-gallery": "^1.0.1",
"react-grid-layout": "1.3.4",
"react-i18next": "^15.6.1",
"react-i18next": "^15.7.1",
"react-icons": "^5.5.0",
"react-image-lightbox": "^5.1.4",
"react-markdown": "^10.1.0",
@@ -137,10 +137,10 @@
"@emotion/babel-plugin": "^11.13.5",
"@emotion/react": "^11.14.0",
"@eslint/js": "^9.33.0",
"@playwright/test": "^1.54.2",
"@playwright/test": "^1.55.0",
"@sentry/webpack-plugin": "^4.1.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.7.0",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@vitejs/plugin-react": "^4.6.0",
"browserslist": "^4.25.3",
@@ -150,9 +150,10 @@
"eslint-plugin-react": "^7.37.5",
"globals": "^15.15.0",
"jsdom": "^26.0.0",
"lightningcss": "^1.30.1",
"memfs": "^4.36.3",
"os-browserify": "^0.3.0",
"playwright": "^1.54.2",
"playwright": "^1.55.0",
"react-error-overlay": "^6.1.0",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3",
@@ -160,7 +161,7 @@
"vite-plugin-babel": "^1.3.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-node-polyfills": "^0.24.0",
"vite-plugin-pwa": "^1.0.2",
"vite-plugin-pwa": "^1.0.3",
"vite-plugin-style-import": "^2.0.0",
"vitest": "^3.2.4",
"workbox-window": "^7.3.0"

View File

@@ -85,7 +85,7 @@ function AppContainer({ currentUser, setDarkMode }) {
theme={theme}
form={{
validateMessages: {
required: t("general.validation.required", { label: "{{label}}" })
required: t("general.validation.required", { label: "${label}" })
}
}}
>

View File

@@ -22,9 +22,9 @@ export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
} = useSplitTreatments({
attributes: {},
names: ["OpenSearch"],
splitKey: bodyshop && bodyshop.imexshopid
splitKey: bodyshop?.imexshopid
});
// TODO - Client Update - Technically key is not doing anything here
return (
<Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}>
@@ -35,7 +35,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
key: "home",
title: (
<Link to={isPartsEntry ? `/parts/` : `/manage/`}>
<HomeFilled /> {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || ""}
<HomeFilled /> {(bodyshop?.shopname && `(${bodyshop.shopname})`) || ""}
</Link>
)
},

View File

@@ -67,11 +67,14 @@ export const uploadToS3 = async (
}
//Key should be same as we provided to maintain backwards compatibility.
const { presignedUrl: preSignedUploadUrlToS3, key: s3Key } = signedURLResponse.data.signedUrls[0];
const { presignedUrl: preSignedUploadUrlToS3, key: s3Key, contentType } = signedURLResponse.data.signedUrls[0];
const options = {
onUploadProgress: (e) => {
if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
},
headers: {
...contentType ? { "Content-Type": fileType } : {}
}
};

View File

@@ -102,14 +102,15 @@ const Eula = ({ currentEula, currentUser, acceptEula }) => {
size="large"
htmlType="submit"
disabled={!formReady}
children={t("eula.buttons.accept")}
/>
>
{t("eula.buttons.accept")}
</Button>
)}
closable={false}
>
<Card type="inner" className="eula-markdown-card" onScroll={handleScroll} ref={markdownCardRef}>
<div id="markdowndiv" className="eula-markdown-div">
<Markdown children={currentEula?.content?.replace(/\\n|\\r|\\n\\r|\\r\\n/g, "\n")} />
<Markdown>{currentEula?.content?.replace(/\\n|\\r|\\n\\r|\\r\\n/g, "\n")}</Markdown>
</div>
</Card>

View File

@@ -4,13 +4,8 @@ import parsePhoneNumber from "libphonenumber-js";
import { forwardRef } from "react";
import "./phone-form-item.styles.scss";
function FormItemPhone(props) {
return (
<Input
// country="ca" ref={ref} className="ant-input"
{...props}
/>
);
function FormItemPhone(props, ref) {
return <Input ref={ref} {...props} />;
}
export default forwardRef(FormItemPhone);

View File

@@ -1,6 +1,5 @@
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
import { Button, Layout, Space } from "antd";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
@@ -17,18 +16,7 @@ const mapStateToProps = createStructuredSelector({
export function GlobalFooter({ isPartsEntry }) {
const { t } = useTranslation();
useEffect(() => {
// Canny Not Required on Parts Entry
if (isPartsEntry) return;
window.Canny("initChangelog", {
appID: "680bd2c7ee501290377f6686",
position: "top",
align: "left",
theme: "light" // options: light [default], dark, auto
});
}, [isPartsEntry]);
if (isPartsEntry) {
return (
<Footer>

View File

@@ -64,7 +64,7 @@ export default function ShopInfoContainer() {
onFinish={handleFinish}
initialValues={
data
? data.bodyshops[0].accountingconfig.ClosingPeriod
? data?.bodyshops?.[0]?.accountingconfig?.ClosingPeriod
? {
...data.bodyshops[0],
accountingconfig: {

View File

@@ -167,7 +167,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
socketRef.current = socketInstance;
const handleBodyshopMessage = (message) => {
if (!message || !message.type) return;
if (!message?.type) return;
switch (message.type) {
case "alert-update":
store.dispatch(addAlerts(message.payload));
@@ -512,21 +512,20 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
};
const unsubscribe = auth.onIdTokenChanged(async (user) => {
if (user) {
const token = await user.getIdToken();
if (socketRef.current) {
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
} else {
initializeSocket(token).catch((err) =>
console.error(`Something went wrong Initializing Sockets: ${err?.message || ""}`)
);
}
if (!user) {
socketRef.current?.disconnect();
socketRef.current = null;
setIsConnected(false);
return;
}
const token = await user.getIdToken();
if (socketRef.current) {
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
} else {
if (socketRef.current) {
socketRef.current.disconnect();
socketRef.current = null;
setIsConnected(false);
}
initializeSocket(token).catch((err) =>
console.error("Something went wrong Initializing Sockets:", err?.message || "")
);
}
});

View File

@@ -2,7 +2,7 @@ import { FloatButton, Layout, Spin } from "antd";
import { Route, Routes } from "react-router-dom";
// import preval from "preval.macro";
import { lazy, Suspense, useEffect, useState } from "react";
import { lazy, Suspense, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -23,6 +23,7 @@ import UpdateAlert from "../../components/update-alert/update-alert.component";
import { selectBodyshop, selectInstanceConflict, selectPartsManagementOnly } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import useAlertsNotifications from "../../hooks/useAlertsNotifications.jsx";
import { selectDarkMode } from "../../redux/application/application.selectors.js";
const PrintCenterModalContainer = lazy(
() => import("../../components/print-center-modal/print-center-modal.container")
@@ -107,22 +108,26 @@ const { Content } = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
bodyshop: selectBodyshop,
partsManagementOnly: selectPartsManagementOnly
partsManagementOnly: selectPartsManagementOnly,
isDarkMode: selectDarkMode
});
export function Manage({ conflict, bodyshop, partsManagementOnly }) {
export function Manage({ conflict, bodyshop, partsManagementOnly, isDarkMode }) {
const { t } = useTranslation();
const [chatVisible] = useState(false);
const didMount = useRef(false);
// Centralized alerts handling (fetch + dedupe + notifications)
useAlertsNotifications();
useEffect(() => {
if (didMount.current) return; // prevents dev StrictMode double-run
didMount.current = true;
window.Canny("initChangelog", {
appID: "680bd2c7ee501290377f6686",
position: "top",
align: "left",
theme: "light" // options: light [default], dark, auto
theme: !isDarkMode ? "light" : "dark"
});
}, []);

View File

@@ -60,19 +60,19 @@ function SimplifiedPartsJobsDetailContainer({ setBreadcrumbs, addRecentItem, set
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
}),
ro_number: (data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
ro_number: data.jobs_by_pk?.ro_number || t("general.labels.na")
});
setBreadcrumbs([
{ link: "/parts/", label: t("titles.bc.jobs") },
{ link: "/parts", label: t("titles.bc.parts") },
{
link: `/parts/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
number: (data?.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
})
}
]);
if (data && data.jobs_by_pk) {
if (data?.jobs_by_pk) {
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
addRecentItem(

View File

@@ -22,7 +22,7 @@ export function SimplifiedPartsJobsPage({ setBreadcrumbs, setSelectedHeader }) {
})
});
setSelectedHeader("parts-queue");
setBreadcrumbs([{ link: "/parts", label: t("titles.bc.simplified-parts-jobs") }]);
setBreadcrumbs([{ link: "/parts", label: t("titles.bc.parts") }]);
}, [setBreadcrumbs, t, setSelectedHeader]);
return (

View File

@@ -3,7 +3,7 @@ import { FloatButton, Layout, Spin } from "antd";
import { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Route, Routes } from "react-router-dom";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component.jsx";
import ConflictComponent from "../../components/conflict/conflict.component.jsx";
@@ -39,6 +39,15 @@ const mapStateToProps = createStructuredSelector({
export function SimplifiedPartsPage({ conflict, bodyshop }) {
const { t } = useTranslation();
// Redirector to strip '/parts/jobs' from path for non-detail routes
function JobsStripRedirect() {
// lazy import to avoid top-level import churn
const location = useLocation();
const { pathname, search, hash } = location;
const target = pathname.replace("/parts/jobs", "/parts") + (search || "") + (hash || "");
return <Navigate to={target} replace />;
}
// Centralized alerts handling (fetch + dedupe + notifications)
useAlertsNotifications();
@@ -67,6 +76,10 @@ export function SimplifiedPartsPage({ conflict, bodyshop }) {
<EmailOverlayContainer />
<PrintCenterModalContainer />
<Routes>
{/* Redirect legacy or relative routes that include '/jobs' segment */}
<Route path="jobs" element={<JobsStripRedirect />} />
<Route path="jobs/*" element={<JobsStripRedirect />} />
<Route
path="/"
element={

View File

@@ -42,33 +42,31 @@ export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelec
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
}),
vehicle:
data && data.vehicles_by_pk
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}`
: ""
vehicle: data?.vehicles_by_pk
? `${data.vehicles_by_pk?.v_model_yr || ""} ${
data.vehicles_by_pk?.v_make_desc || ""
} ${data.vehicles_by_pk?.v_model_desc || ""}`
: ""
});
setSelectedHeader("vehicles");
const crumbs = [];
if (isPartsEntry) crumbs.push({ link: "/parts/", label: t("titles.bc.jobs") });
if (isPartsEntry) crumbs.push({ link: "/parts", label: t("titles.bc.parts") });
crumbs.push({ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") });
crumbs.push({
link: `${basePath}/vehicles/${vehId}`,
label: t("titles.bc.vehicle-details", {
vehicle:
data && data.vehicles_by_pk
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}`
: ""
vehicle: data?.vehicles_by_pk
? `${data.vehicles_by_pk?.v_model_yr || ""} ${
data.vehicles_by_pk?.v_make_desc || ""
} ${data.vehicles_by_pk?.v_model_desc || ""}`
: ""
})
});
setBreadcrumbs(crumbs);
if (data && data.vehicles_by_pk)
if (data?.vehicles_by_pk)
addRecentItem(
CreateRecentItem(
vehId,

View File

@@ -33,7 +33,7 @@ export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader, isPar
if (isPartsEntry) {
setBreadcrumbs([
{ link: "/parts/", label: t("titles.bc.jobs") },
{ link: "/parts", label: t("titles.bc.parts") },
{ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") }
]);
} else {

View File

@@ -3523,6 +3523,8 @@
}
},
"titles": {
"parts_settings": "Parts Management Settings | {{app}}",
"simplified-parts-jobs": "Parts Management | {{app}}",
"accounting-payables": "Payables | {{app}}",
"accounting-payments": "Payments | {{app}}",
"accounting-receivables": "Receivables | {{app}}",
@@ -3530,7 +3532,7 @@
"app": "",
"bc": {
"simplified-parts-jobs": "Jobs",
"parts": "Jobs",
"parts": "Parts",
"parts_settings": "Settings",
"accounting-payables": "Payables",
"accounting-payments": "Payments",

View File

@@ -3523,6 +3523,9 @@
}
},
"titles": {
"simplified-parts-jobs": "",
"parts": "",
"parts_settings": "",
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",

View File

@@ -3523,6 +3523,9 @@
}
},
"titles": {
"simplified-parts-jobs": "",
"parts": "",
"parts_settings": "",
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",

View File

@@ -1,5 +1,4 @@
/* eslint-disable */
import { sentryVitePlugin } from "@sentry/vite-plugin";
import react from "@vitejs/plugin-react";
import chalk from "chalk";
@@ -10,21 +9,23 @@ import { ViteEjsPlugin } from "vite-plugin-ejs";
import eslint from "vite-plugin-eslint";
import { VitePWA } from "vite-plugin-pwa";
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
import browserslist from "browserslist";
import { browserslistToTargets } from "lightningcss";
// Ensure your environment variables are set correctly for Vite 6
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
timeZone: "America/Los_Angeles"
});
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", { timeZone: "America/Los_Angeles" });
const commitHash = child.execSync("git rev-parse HEAD").toString().trimEnd();
process.env.VITE_GIT_COMMIT_HASH = commitHash;
const currentDatePST = new Date()
.toLocaleDateString("en-US", {
timeZone: "America/Los_Angeles",
year: "numeric",
month: "2-digit",
day: "2-digit"
// Resolve browserslist from package.json and map to Lightning CSS targets
const lightningCssTargets = browserslistToTargets(
browserslist(undefined, {
path: process.cwd(),
env: process.env.NODE_ENV === "production" ? "production" : "development"
})
);
const currentDatePST = new Date()
.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles", year: "numeric", month: "2-digit", day: "2-digit" })
.split("/")
.reverse()
.join("-");
@@ -32,18 +33,21 @@ const currentDatePST = new Date()
const getFormattedTimestamp = () =>
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
export const logger = createLogger("info", {
allowClearScreen: false
});
export const logger = createLogger("info", { allowClearScreen: false });
export default defineConfig({
base: "/",
plugins: [
// Ensure all plugins are Vite 6 compatible
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
// PWA only for production builds (faster dev)
VitePWA({
apply: "build",
injectRegister: "auto",
registerType: "prompt",
workbox: {
navigateFallbackDenylist: [/^\/api\//] // prevent caching API routes
},
manifest: {
short_name: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
@@ -94,13 +98,15 @@ export default defineConfig({
gcm_sender_id: "103953800507"
}
}),
react(),
eslint(),
// Sentry only for production builds (no dev overhead)
sentryVitePlugin({
apply: "build",
org: "imex",
reactComponentAnnotation: {
enabled: true
},
reactComponentAnnotation: { enabled: true },
release: {
name: `${process.env.VITE_APP_IS_TEST ? "test" : "production"}-${currentDatePST}-${commitHash}`.trim()
},
@@ -111,10 +117,12 @@ export default defineConfig({
})
})
],
define: {
APP_VERSION: JSON.stringify(process.env.npm_package_version),
__COMMIT_HASH__: JSON.stringify(commitHash)
},
server: {
host: true,
port: 3000,
@@ -122,13 +130,11 @@ export default defineConfig({
proxy: {
"/ws": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/wss": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
@@ -148,18 +154,17 @@ export default defineConfig({
},
https: {
key: await fsPromises.readFile("../certs/key.pem"),
cert: await fsPromises.readFile("../certs/cert.pem"),
allowHTTP1: false // Force HTTP/2
cert: await fsPromises.readFile("../certs/cert.pem")
}
},
preview: {
port: 6000,
host: true,
open: true,
https: {
key: await fsPromises.readFile("../certs/key.pem"),
cert: await fsPromises.readFile("../certs/cert.pem"),
allowHTTP1: false // Force HTTP/2
cert: await fsPromises.readFile("../certs/cert.pem")
},
proxy: {
"/ws": {
@@ -182,7 +187,10 @@ export default defineConfig({
}
}
},
build: {
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
@@ -207,8 +215,14 @@ export default defineConfig({
}
},
sourcemap: true
cssMinify: "lightningcss"
},
// Strip console/debugger in prod to shrink bundles
esbuild: {
drop: ["console", "debugger"]
},
optimizeDeps: {
include: [
"react",
@@ -231,19 +245,17 @@ export default defineConfig({
"@firebase/util"
],
esbuildOptions: {
// Update for Vite 6: Use proper file extensions
loader: {
".jsx": "jsx",
".tsx": "tsx"
}
loader: { ".jsx": "jsx", ".tsx": "tsx" }
}
},
css: {
transformer: "lightningcss",
lightningcss: {
targets: lightningCssTargets
},
preprocessorOptions: {
scss: {
api: "modern-compiler",
quietDeps: true // Quite Deprecation Warnings, should be disabled occasionally before major upgrades
}
scss: { quietDeps: true }
}
}
});

View File

@@ -1,6 +1,3 @@
require("dotenv").config({
path: require("path").resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const { isNil } = require("lodash");
const aws4 = require("aws4");
const { Connection, Client } = require("@opensearch-project/opensearch");

View File

@@ -4,7 +4,7 @@ require("dotenv").config({
const { omit } = require("lodash");
const gqlClient = require("./server/graphql-client/graphql-client").client;
const getClient = require("./libs/awsUtils");
const { getClient } = require("./libs/awsUtils");
async function OpenSearchUpdateHandler(req, res) {
try {

2965
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"
},
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.864.0",
"@aws-sdk/client-elasticache": "^3.864.0",
"@aws-sdk/client-s3": "^3.864.0",
"@aws-sdk/client-secrets-manager": "^3.864.0",
"@aws-sdk/client-ses": "^3.864.0",
"@aws-sdk/credential-provider-node": "^3.864.0",
"@aws-sdk/lib-storage": "^3.864.0",
"@aws-sdk/s3-request-presigner": "^3.864.0",
"@aws-sdk/client-cloudwatch-logs": "^3.873.0",
"@aws-sdk/client-elasticache": "^3.872.0",
"@aws-sdk/client-s3": "^3.872.0",
"@aws-sdk/client-secrets-manager": "^3.872.0",
"@aws-sdk/client-ses": "^3.872.0",
"@aws-sdk/credential-provider-node": "^3.873.0",
"@aws-sdk/lib-storage": "^3.872.0",
"@aws-sdk/s3-request-presigner": "^3.872.0",
"@opensearch-project/opensearch": "^2.13.0",
"@socket.io/admin-ui": "^0.5.1",
"@socket.io/redis-adapter": "^8.3.0",
@@ -40,7 +40,7 @@
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"crisp-status-reporter": "^1.2.2",
"dd-trace": "^5.63.1",
"dd-trace": "^5.63.3",
"dinero.js": "^1.9.1",
"dotenv": "^17.2.1",
"express": "^4.21.1",
@@ -62,7 +62,7 @@
"query-string": "7.1.3",
"recursive-diff": "^1.0.9",
"rimraf": "^6.0.1",
"skia-canvas": "^2.0.2",
"skia-canvas": "^3.0.3",
"soap": "^1.3.0",
"socket.io": "^4.8.1",
"socket.io-adapter": "^2.5.5",

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const GraphQLClient = require("graphql-request").GraphQLClient;
const queries = require("../../graphql-client/queries");

View File

@@ -1,8 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const IMEX_PBS_USER = process.env.IMEX_PBS_USER,
IMEX_PBS_PASSWORD = process.env.IMEX_PBS_PASSWORD;
const PBS_CREDENTIALS = {

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const GraphQLClient = require("graphql-request").GraphQLClient;
const AxiosLib = require("axios").default;
const queries = require("../../graphql-client/queries");

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const OAuthClient = require("intuit-oauth");
const logger = require("../../utils/logger");

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../../utils/logger");
const OAuthClient = require("intuit-oauth");
const client = require("../../graphql-client/graphql-client").client;
@@ -13,7 +9,7 @@ const oauthClient = new OAuthClient({
clientId: process.env.QBO_CLIENT_ID,
clientSecret: process.env.QBO_SECRET,
environment: process.env.NODE_ENV === "production" ? "production" : "sandbox",
redirectUri: process.env.QBO_REDIRECT_URI,
redirectUri: process.env.QBO_REDIRECT_URI
});
const url = InstanceEndpoints();

View File

@@ -1,9 +1,6 @@
const urlBuilder = require("./qbo").urlBuilder;
const StandardizeName = require("./qbo").StandardizeName;
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../../utils/logger");
const Dinero = require("dinero.js");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
@@ -396,7 +393,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
qbo_realmId,
"query",
`select *
From TaxCode`
From TaxCode`
),
method: "POST",
headers: {
@@ -416,7 +413,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
qbo_realmId,
"query",
`select *
From Class`
From Class`
),
method: "POST",
headers: {

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../../utils/logger");
const Dinero = require("dinero.js");
@@ -274,7 +270,13 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef)
async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditMemo, parentTierRef, bodyshopid) {
const invoice = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`),
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Invoice
where DocNumber like '${ro_number}%'`
),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -290,7 +292,12 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
email: req.user.email
});
const paymentMethods = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`),
url: urlBuilder(
qbo_realmId,
"query",
`select *
From PaymentMethod`
),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -341,7 +348,12 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
if (isCreditMemo) {
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
url: urlBuilder(
qbo_realmId,
"query",
`select *
From TaxCode`
),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -357,7 +369,12 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
email: req.user.email
});
const items = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Item`),
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Item`
),
method: "POST",
headers: {
"Content-Type": "application/json"

View File

@@ -1,10 +1,6 @@
const urlBuilder = require("./qbo").urlBuilder;
const StandardizeName = require("./qbo").StandardizeName;
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../../utils/logger");
const apiGqlClient = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
@@ -492,8 +488,8 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
qbo_realmId,
"query",
`select *
From Item
where active = true maxresults 1000`
From Item
where active = true maxresults 1000`
),
method: "POST",
headers: {
@@ -515,8 +511,8 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
qbo_realmId,
"query",
`select *
From TaxCode
where active = true`
From TaxCode
where active = true`
),
method: "POST",
headers: {
@@ -537,7 +533,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
qbo_realmId,
"query",
`select *
From Class`
From Class`
),
method: "POST",
headers: {

View File

@@ -1,8 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
function urlBuilder(realmId, object, query = null) {
return `https://${
process.env.NODE_ENV === "production" ? "" : "sandbox-"

View File

@@ -1,4 +1,3 @@
const path = require("path");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
@@ -8,10 +7,6 @@ const moment = require("moment-timezone");
const logger = require("../../utils/logger");
const InstanceManager = require("../../utils/instanceMgr").default;
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
exports.default = async (req, res) => {
const { bills: billsToQuery } = req.body;

View File

@@ -1,4 +1,3 @@
const path = require("path");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
@@ -8,10 +7,6 @@ const QbXmlUtils = require("./qbxml-utils");
const QbxmlReceivables = require("./qbxml-receivables");
const logger = require("../../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
exports.default = async (req, res) => {

View File

@@ -1,4 +1,3 @@
const path = require("path");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
@@ -9,10 +8,6 @@ const CreateInvoiceLines = require("../qb-receivables-lines").default;
const logger = require("../../utils/logger");
const InstanceManager = require("../../utils/instanceMgr").default;
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
Dinero.globalRoundingMode = "HALF_EVEN";
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;

View File

@@ -1,10 +1,4 @@
const path = require("path");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;
exports.createAssociation = async (req, res) => {

View File

@@ -1,13 +1,8 @@
const path = require("path");
const logger = require("../utils/logger");
const queries = require("../graphql-client/queries");
const GraphQLClient = require("graphql-request").GraphQLClient;
const moment = require("moment-timezone");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
exports.generatePpc = async (req, res) => {
const { jobid } = req.body;
const BearerToken = req.headers.authorization;

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const GraphQLClient = require("graphql-request").GraphQLClient;
const queries = require("../graphql-client/queries");
@@ -248,7 +244,7 @@ function calculateAllocations(connectionData, job) {
// Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting),
// typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting)
// );
if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) {
if (bodyshop?.cdk_configuration?.sendmaterialscosting) {
//Manually send the percentage of the costing.
//Paint Mat

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const soap = require("soap");
const queries = require("../graphql-client/queries");

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const GraphQLClient = require("graphql-request").GraphQLClient;
const soap = require("soap");
const queries = require("../graphql-client/queries");

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const CdkBase = require("../web-sockets/web-socket");
const IMEX_CDK_USER = process.env.IMEX_CDK_USER,

View File

@@ -1,9 +1,5 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;

View File

@@ -1,9 +1,5 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;

View File

@@ -1,4 +1,3 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
@@ -6,9 +5,7 @@ const fs = require("fs");
const storage = require("node-persist");
const _ = require("lodash");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const soap = require("soap");
const { sendServerEmail } = require("../email/sendemail");
@@ -24,7 +21,7 @@ const momentFormat = "yyyy-MM-DDTHH:mm:ss.SSS";
function pollFunc(fn, timeout, interval) {
var startTime = new Date().getTime();
(interval = interval || 1000), (canPoll = true);
((interval = interval || 1000), (canPoll = true));
(function p() {
canPoll = timeout === 0 ? true : new Date().getTime() - startTime <= timeout;

View File

@@ -1,13 +1,10 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;

View File

@@ -1,4 +1,3 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
@@ -11,9 +10,6 @@ const { sendServerEmail } = require("../email/sendemail");
const { uploadFileToS3 } = require("../utils/s3");
const crypto = require("crypto");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let Client = require("ssh2-sftp-client");
const AHDateFormat = "YYYY-MM-DD";
@@ -124,7 +120,7 @@ exports.default = async (req, res) => {
async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDateFilter, allXMLResults, allErrors) {
for (const bodyshop of shopsToProcess) {
const shopid = bodyshop.imexshopid?.toLowerCase() || bodyshop.shopname.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()
const shopid = bodyshop.imexshopid?.toLowerCase() || bodyshop.shopname.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
const erroredJobs = [];
try {
logger.log("CARFAX-start-shop-extract", "DEBUG", "api", bodyshop.id, {

View File

@@ -1,11 +1,9 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const moment = require("moment-timezone");
const converter = require("json-2-csv");
const logger = require("../utils/logger");
const fs = require("fs");
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) });
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
@@ -77,7 +75,15 @@ exports.default = async (req, res) => {
await sendServerEmail({
subject: `Chatter Report ${moment().format("MM-DD-YY")}`,
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\n
Uploaded:\n${JSON.stringify({ filename: csvToUpload.filename, count: csvToUpload.count, result: csvToUpload.result }, null, 2)}`
Uploaded:\n${JSON.stringify(
{
filename: csvToUpload.filename,
count: csvToUpload.count,
result: csvToUpload.result
},
null,
2
)}`
});
logger.log("chatter-end", "DEBUG", "api", null, null);
@@ -107,7 +113,8 @@ async function processBatch(shopsToProcess, start, end, allChatterObjects, allEr
transaction_id: j.ro_number,
email: j.ownr_ea,
phone_number: j.ownr_ph1,
transaction_time: (j.actual_delivery && moment(j.actual_delivery).tz(bodyshop.timezone).format("YYYYMMDD-HHmm")) || ""
transaction_time:
(j.actual_delivery && moment(j.actual_delivery).tz(bodyshop.timezone).format("YYYYMMDD-HHmm")) || ""
};
});
allChatterObjects.push(...chatterObject);

View File

@@ -1,13 +1,10 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;

View File

@@ -1,13 +1,10 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;

View File

@@ -1,12 +1,9 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const moment = require("moment-timezone");
const converter = require("json-2-csv");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;
const emailer = require("../email/sendemail");
const moment = require("moment-timezone");

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const axios = require("axios");
const InstanceManager = require("../utils/instanceMgr").default;
const logger = require("../utils/logger");
@@ -101,7 +97,10 @@ const sendWelcomeEmail = async ({ to, resetLink, dateLine, features, bcc }) => {
imex: "Welcome to the ImEX Online platform.",
rome: "Welcome to the Rome Online platform."
}),
subHeader: `Your ${InstanceManager({imex: features?.allAccess ? "ImEX Online": "ImEX Lite", rome: features?.allAccess ? "RO Manager" : "RO Basic"})} shop setup has been completed, and this email will include all the information you need to begin.`,
subHeader: `Your ${InstanceManager({
imex: features?.allAccess ? "ImEX Online" : "ImEX Lite",
rome: features?.allAccess ? "RO Manager" : "RO Basic"
})} shop setup has been completed, and this email will include all the information you need to begin.`,
body: `
<p style="color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 0 0 0 0px; Margin: 0 0 0 0px; line-height: 1.2; margin-bottom: 0px; Margin-bottom: 0px; font-size: 90%;">To finish setting up your account, visit this link and enter your desired password. <a href=${resetLink} style="color: #2199e8; text-decoration: none; font-weight: normal; padding: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%; line-height: 1.2;">Reset Password</a></p>
</td></tr>
@@ -110,12 +109,25 @@ const sendWelcomeEmail = async ({ to, resetLink, dateLine, features, bcc }) => {
<table class="row" style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; padding: 0; width: 100%; position: relative; display: table;"><tbody style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; display: table-row-group;"><tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;">
<th class="small-12 large-12 columns first last" style="word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; line-height: 1.2; margin: 0 auto; Margin: 0 auto; padding-bottom: 16px; width: 734px; padding-left: 8px; padding-right: 8px; border-collapse: collapse;"><table style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; width: 100%;">
<tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;"><td style="word-wrap: break-word; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin: 0; Margin: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; word-break: keep-all; -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; line-height: 1.2; border-collapse: collapse;">
<p style="color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 0 0 0 0px; Margin: 0 0 0 0px; line-height: 1.2; margin-bottom: 0px; Margin-bottom: 0px; font-size: 90%;">To access your ${InstanceManager({imex: features.allAccess ? "ImEX Online": "ImEX Lite", rome: features.allAccess ? "RO Manager" : "RO Basic"})} shop, visit <a href=${InstanceManager({imex: "https://imex.online/", rome: "https://romeonline.io/"})} style="color: #2199e8; text-decoration: none; font-weight: normal; padding: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%; line-height: 1.2;">${InstanceManager({imex: "imex.online", rome: "romeonline.io"})}</a>. Your username is your email, and your password is what you previously set up. Contact support for additional logins.</p>
<p style="color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 0 0 0 0px; Margin: 0 0 0 0px; line-height: 1.2; margin-bottom: 0px; Margin-bottom: 0px; font-size: 90%;">To access your ${InstanceManager(
{
imex: features.allAccess ? "ImEX Online" : "ImEX Lite",
rome: features.allAccess ? "RO Manager" : "RO Basic"
}
)} shop, visit <a href=${InstanceManager({
imex: "https://imex.online/",
rome: "https://romeonline.io/"
})} style="color: #2199e8; text-decoration: none; font-weight: normal; padding: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%; line-height: 1.2;">${InstanceManager(
{
imex: "imex.online",
rome: "romeonline.io"
}
)}</a>. Your username is your email, and your password is what you previously set up. Contact support for additional logins.</p>
</td></tr>
</tbody></table></th>
</tr></tbody></table>
${InstanceManager({
rome: `
rome: `
<table class="row" style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; padding: 0; width: 100%; position: relative; display: table;"><tbody style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; display: table-row-group;"><tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;">
<th class="small-12 large-12 columns first last" style="word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; line-height: 1.2; margin: 0 auto; Margin: 0 auto; padding-bottom: 16px; width: 734px; padding-left: 8px; padding-right: 8px; border-collapse: collapse;"><table style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; width: 100%;">
<tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;"><td style="word-wrap: break-word; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin: 0; Margin: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; word-break: keep-all; -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; line-height: 1.2; border-collapse: collapse;">
@@ -161,7 +173,12 @@ const sendWelcomeEmail = async ({ to, resetLink, dateLine, features, bcc }) => {
<table class="row" style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; padding: 0; width: 100%; position: relative; display: table;"><tbody style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; display: table-row-group;"><tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;">
<th class="small-12 large-12 columns first last" style="word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; line-height: 1.2; margin: 0 auto; Margin: 0 auto; padding-bottom: 16px; width: 734px; padding-left: 8px; padding-right: 8px; border-collapse: collapse;"><table style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; width: 100%;">
<tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;"><td style="word-wrap: break-word; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin: 0; Margin: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; word-break: keep-all; -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; line-height: 1.2; border-collapse: collapse;">
<p style="color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 0 0 0 0px; Margin: 0 0 0 0px; line-height: 1.2; margin-bottom: 0px; Margin-bottom: 0px; font-size: 90%;">In addition to the training tour, you can also book a live one-on-one demo to see exactly how our system can help streamline the repair process at your shop, schedule by clicking this link - <a href="https://outlook.office.com/bookwithme/user/0aa3ae2c6d59497d9f93fb72479848dc@imexsystems.ca/meetingtype/Qy7CsXl5MkuUJ0NRD7B1AA2?anonymous&ep=mlink" style="color: #2199e8; text-decoration: none; font-weight: normal; padding: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%; line-height: 1.2;">${InstanceManager({imex: "ImEX Lite", rome: "Rome Basic"})} Demo Booking</a></p>
<p style="color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 0 0 0 0px; Margin: 0 0 0 0px; line-height: 1.2; margin-bottom: 0px; Margin-bottom: 0px; font-size: 90%;">In addition to the training tour, you can also book a live one-on-one demo to see exactly how our system can help streamline the repair process at your shop, schedule by clicking this link - <a href="https://outlook.office.com/bookwithme/user/0aa3ae2c6d59497d9f93fb72479848dc@imexsystems.ca/meetingtype/Qy7CsXl5MkuUJ0NRD7B1AA2?anonymous&ep=mlink" style="color: #2199e8; text-decoration: none; font-weight: normal; padding: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%; line-height: 1.2;">${InstanceManager(
{
imex: "ImEX Lite",
rome: "Rome Basic"
}
)} Demo Booking</a></p>
</td></tr>
</tbody></table></th>
</tr></tbody></table>
@@ -175,7 +192,12 @@ const sendWelcomeEmail = async ({ to, resetLink, dateLine, features, bcc }) => {
<table class="row" style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; padding: 0; width: 100%; position: relative; display: table;"><tbody style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; display: table-row-group;"><tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;">
<th class="small-12 large-12 columns first last" style="word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; line-height: 1.2; margin: 0 auto; Margin: 0 auto; padding-bottom: 8px; width: 734px; padding-left: 0px; padding-right: 8px; border-collapse: collapse;"><table style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; width: 100%;">
<tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;"><td style="word-wrap: break-word; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin: 0; Margin: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; word-break: keep-all; -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; line-height: 1.2; border-collapse: collapse;">
<p style="color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 0 0 0 0px; Margin: 0 0 0 0px; line-height: 1.2; margin-bottom: 0px; Margin-bottom: 0px; font-size: 90%;">The ${InstanceManager({imex: "ImEX Online", rome: "Rome Online"})} Team</p>
<p style="color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 0 0 0 0px; Margin: 0 0 0 0px; line-height: 1.2; margin-bottom: 0px; Margin-bottom: 0px; font-size: 90%;">The ${InstanceManager(
{
imex: "ImEX Online",
rome: "Rome Online"
}
)} Team</p>
`,
dateLine
})

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const InstanceManager = require("../utils/instanceMgr").default;
const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client;
@@ -23,7 +19,7 @@ const tasksEmailQueueCleanup = async () => {
// Example async operation
// console.log("Performing Tasks Email Reminder process cleanup...");
await new Promise((resolve) => tasksEmailQueue.destroy(() => resolve()));
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line no-unused-vars
} catch (err) {
// console.error("Tasks Email Reminder process cleanup failed:", err);
}
@@ -264,7 +260,7 @@ const tasksRemindEmail = async (req, res) => {
<li style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%;">
<a href="${InstanceEndpoints()}/manage/tasks/alltasks?taskid=${task.id}" style="color: #2199e8; text-decoration: none; font-weight: normal; padding: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%; line-height: 1.2;">${task.title} - Priority: ${formatTaskPriority(task.priority)} ${task.due_date ? `${formatDate(task.due_date)}` : ""} | Bodyshop: ${task.bodyshop.shopname}</a>
</li>
`.trim()
`.trim()
)
.join("")}
</ul>`

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const Queue = require("better-queue");
const moment = require("moment");
const { client } = require("../graphql-client/graphql-client");

View File

@@ -16,8 +16,6 @@ const {
// Defaults
const FALLBACK_DEFAULT_ORDER_STATUS = "OPEN";
// Config: include labor lines and labor in totals (default true)
const INCLUDE_LABOR = true;
/**
* Fetches the default order status for a bodyshop.
* @param {string} shopId - The bodyshop UUID.
@@ -76,8 +74,6 @@ const extractJobData = (rq) => {
category: doc.DocumentType || null,
classType: doc.DocumentStatus || null,
comment: doc.Comment || null,
// TODO: This causes the job to be read only in the UI
// date_exported: doc.TransmitDateTime || null,
asgn_no: asgn.AssignmentNumber || null,
asgn_type: asgn.AssignmentType || null,
asgn_date: asgn.AssignmentDate || null,
@@ -329,10 +325,6 @@ const extractJobLines = (rq) => {
const refinishInfo = line.RefinishLaborInfo || {};
const subletInfo = line.SubletInfo || {};
let jobLineType = "PART";
if (Object.keys(subletInfo).length > 0) jobLineType = "SUBLET";
else if (Object.keys(laborInfo).length > 0 && Object.keys(partInfo).length === 0) jobLineType = "LABOR";
const base = {
line_no: parseInt(line.LineNum || 0, 10),
unq_seq: parseInt(line.UniqueSequenceNum || 0, 10),
@@ -341,134 +333,87 @@ const extractJobLines = (rq) => {
notes: line.LineMemo || null
};
if (jobLineType === "PART") {
const lineOut = { ...base };
// Manual line flag coercion
if (line.ManualLineInd !== undefined) {
lineOut.manual_line =
line.ManualLineInd === true ||
line.ManualLineInd === 1 ||
line.ManualLineInd === "1" ||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y");
} else {
lineOut.manual_line = null;
}
// Parts (preferred) or Sublet (fallback when no PartInfo)
const hasPart = Object.keys(partInfo).length > 0;
const hasSublet = Object.keys(subletInfo).length > 0;
if (hasPart) {
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
// Push the part line with ONLY part pricing/fields
out.push({
...base,
part_type: partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null,
part_qty: parseFloat(partInfo.Quantity || 0) || 1,
oem_partno: partInfo.OEMPartNum || partInfo.PartNum || null,
db_price: price,
act_price: price,
// Tax flag from PartInfo.TaxableInd when provided
...(partInfo.TaxableInd !== undefined &&
lineOut.part_type = partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null;
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
lineOut.db_price = isNaN(price) ? 0 : price;
lineOut.act_price = isNaN(price) ? 0 : price;
// Tax flag from PartInfo.TaxableInd when provided
if (
partInfo.TaxableInd !== undefined &&
(typeof partInfo.TaxableInd === "string" ||
typeof partInfo.TaxableInd === "number" ||
typeof partInfo.TaxableInd === "boolean")
? {
tax_part:
partInfo.TaxableInd === true ||
partInfo.TaxableInd === 1 ||
partInfo.TaxableInd === "1" ||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y")
}
: {}),
// Manual line flag coercion
...(line.ManualLineInd !== undefined
? {
manual_line:
line.ManualLineInd === true ||
line.ManualLineInd === 1 ||
line.ManualLineInd === "1" ||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
}
: { manual_line: null })
});
// If labor is present on the same damage line, split it to a separate LABOR jobline
// TODO: Verify with patrick this is desired.
if (INCLUDE_LABOR) {
const hrs = parseFloat(laborInfo.LaborHours || 0);
const amt = parseFloat(laborInfo.LaborAmt || 0);
const hasLabor =
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
(!isNaN(hrs) && hrs !== 0) ||
(!isNaN(amt) && amt !== 0);
if (hasLabor) {
out.push({
...base,
// tweak unq_seq to avoid collisions in later upserts
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 400000,
mod_lbr_ty: laborInfo.LaborType || null,
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
lbr_op: laborInfo.LaborOperation || null,
lbr_amt: isNaN(amt) ? 0 : amt,
...(line.ManualLineInd !== undefined
? {
manual_line:
line.ManualLineInd === true ||
line.ManualLineInd === 1 ||
line.ManualLineInd === "1" ||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
}
: { manual_line: null })
});
}
) {
lineOut.tax_part =
partInfo.TaxableInd === true ||
partInfo.TaxableInd === 1 ||
partInfo.TaxableInd === "1" ||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
}
} else if (jobLineType === "SUBLET") {
out.push({
...base,
part_type: "PAS",
part_qty: 1,
act_price: parseFloat(subletInfo.SubletAmount || 0),
// Manual line flag
...(line.ManualLineInd !== undefined
? {
manual_line:
line.ManualLineInd === true ||
line.ManualLineInd === 1 ||
line.ManualLineInd === "1" ||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
}
: { manual_line: null })
});
} else if (INCLUDE_LABOR) {
// Labor-only line (only when enabled)
out.push({
...base,
mod_lbr_ty: laborInfo.LaborType || null,
mod_lb_hrs: parseFloat(laborInfo.LaborHours || 0),
lbr_op: laborInfo.LaborOperation || null,
lbr_amt: parseFloat(laborInfo.LaborAmt || 0),
...(line.ManualLineInd !== undefined
? {
manual_line:
line.ManualLineInd === true ||
line.ManualLineInd === 1 ||
line.ManualLineInd === "1" ||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
}
: { manual_line: null })
});
} else if (hasSublet) {
const amt = parseFloat(subletInfo.SubletAmount || 0);
lineOut.part_type = "PAS"; // Sublet as parts-as-service
lineOut.part_qty = 1;
lineOut.act_price = isNaN(amt) ? 0 : amt;
}
// Add a separate refinish labor line if present and enabled
if (INCLUDE_LABOR && Object.keys(refinishInfo).length > 0) {
const hrs = parseFloat(refinishInfo.LaborHours || 0);
const amt = parseFloat(refinishInfo.LaborAmt || 0);
if (!isNaN(hrs) || !isNaN(amt)) {
out.push({
...base,
// tweak unq_seq to avoid collisions in later upserts
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 500000,
line_desc: base.line_desc || "Refinish",
mod_lbr_ty: "LAR",
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
lbr_op: refinishInfo.LaborOperation || null,
lbr_amt: isNaN(amt) ? 0 : amt,
...(line.ManualLineInd !== undefined
? {
manual_line:
line.ManualLineInd === true ||
line.ManualLineInd === 1 ||
line.ManualLineInd === "1" ||
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
}
: { manual_line: null })
});
}
// Primary labor (if present) recorded on the same line
const hrs = parseFloat(laborInfo.LaborHours || 0);
const amt = parseFloat(laborInfo.LaborAmt || 0);
const hasLabor =
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
(!isNaN(hrs) && hrs !== 0) ||
(!isNaN(amt) && amt !== 0);
if (hasLabor) {
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
lineOut.lbr_op = laborInfo.LaborOperation || null;
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
}
// Refinish labor (if present) recorded on the same line using secondary labor fields
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
const hasRefinish =
Object.keys(refinishInfo).length > 0 &&
((refinishInfo.LaborType && String(refinishInfo.LaborType).length > 0) ||
!isNaN(rHrs) ||
!isNaN(rAmt) ||
!!refinishInfo.LaborOperation);
if (hasRefinish) {
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
// Aggregate refinish labor amount into the total labor amount for the line
if (!isNaN(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.PaintTonesNum !== undefined) lineOut.paint_tone = refinishInfo.PaintTonesNum;
}
out.push(lineOut);
}
return out;
@@ -517,14 +462,14 @@ const computeLinesTotal = (joblines = []) => {
let parts = 0;
let labor = 0;
for (const jl of joblines) {
if (jl && jl.part_type) {
if (jl?.part_type) {
const qty = Number.isFinite(jl.part_qty) ? jl.part_qty : 1;
const price = Number.isFinite(jl.act_price) ? jl.act_price : 0;
parts += price * (qty || 1);
} else if (!jl.part_type && Number.isFinite(jl.act_price)) {
parts += jl.act_price;
}
if (INCLUDE_LABOR && Number.isFinite(jl.lbr_amt)) {
if (Number.isFinite(jl.lbr_amt)) {
labor += jl.lbr_amt;
}
}

View File

@@ -8,7 +8,7 @@ const { extractPartsTaxRates } = require("./lib/extractPartsTaxRates");
const {
GET_JOB_BY_CLAIM,
UPDATE_JOB_BY_ID,
DELETE_JOBLINES_BY_IDS,
SOFT_DELETE_JOBLINES_BY_IDS,
INSERT_JOBLINES
} = require("../partsManagement.queries");
@@ -56,11 +56,10 @@ const extractUpdatedJobData = (rq) => {
};
/**
* Extracts updated job lines from the request payload, mirroring the AddRq splitting rules:
* - PART lines carry only part pricing (act_price) and related fields
* - If LaborInfo exists on a part line, add a separate LABOR line at unq_seq + 400000
* - If RefinishLaborInfo exists, add a separate LABOR line at unq_seq + 500000 with mod_lbr_ty=LAR
* - SUBLET lines become PAS part_type with act_price=SubletAmount
* Extracts updated job lines from the request payload without splitting parts and labor:
* - Keep part and labor on the same jobline
* - Aggregate RefinishLabor into secondary labor fields and add its amount to lbr_amt
* - SUBLET-only lines become PAS part_type with act_price = SubletAmount
*/
const extractUpdatedJobLines = (addsChgs = {}, jobId) => {
const linesIn = Array.isArray(addsChgs.DamageLineInfo) ? addsChgs.DamageLineInfo : [addsChgs.DamageLineInfo || {}];
@@ -88,74 +87,74 @@ const extractUpdatedJobLines = (addsChgs = {}, jobId) => {
manual_line: line.ManualLineInd !== undefined ? coerceManual(line.ManualLineInd) : null
};
const lineOut = { ...base };
const hasPart = Object.keys(partInfo).length > 0;
const hasLaborOnly = Object.keys(laborInfo).length > 0 && !hasPart && Object.keys(subletInfo).length === 0;
const hasSublet = Object.keys(subletInfo).length > 0;
if (hasPart) {
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
out.push({
...base,
part_type: partInfo.PartType ? String(partInfo.PartType).toUpperCase() : null,
part_qty: parseFloat(partInfo.Quantity || 0) || 1,
oem_partno: partInfo.OEMPartNum || partInfo.PartNum || null,
db_price: isNaN(price) ? 0 : price,
act_price: isNaN(price) ? 0 : price
});
lineOut.part_type = partInfo.PartType ? String(partInfo.PartType).toUpperCase() : null;
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
lineOut.db_price = isNaN(price) ? 0 : price;
lineOut.act_price = isNaN(price) ? 0 : price;
// Split any attached labor on the part line into a derived labor jobline
const hrs = parseFloat(laborInfo.LaborHours || 0);
const amt = parseFloat(laborInfo.LaborAmt || 0);
const hasLabor =
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
(!isNaN(hrs) && hrs !== 0) ||
(!isNaN(amt) && amt !== 0);
if (hasLabor) {
out.push({
...base,
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 400000,
mod_lbr_ty: laborInfo.LaborType || null,
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
lbr_op: laborInfo.LaborOperation || null,
lbr_amt: isNaN(amt) ? 0 : amt
});
// Optional: taxability flag for parts
if (
partInfo.TaxableInd !== undefined &&
(typeof partInfo.TaxableInd === "string" ||
typeof partInfo.TaxableInd === "number" ||
typeof partInfo.TaxableInd === "boolean")
) {
lineOut.tax_part =
partInfo.TaxableInd === true ||
partInfo.TaxableInd === 1 ||
partInfo.TaxableInd === "1" ||
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
}
} else if (hasSublet) {
out.push({
...base,
part_type: "PAS",
part_qty: 1,
act_price: parseFloat(subletInfo.SubletAmount || 0) || 0
});
const amt = parseFloat(subletInfo.SubletAmount || 0);
lineOut.part_type = "PAS";
lineOut.part_qty = 1;
lineOut.act_price = isNaN(amt) ? 0 : amt;
}
// Labor-only line (no PartInfo): still upsert as a labor entry
if (hasLaborOnly) {
out.push({
...base,
mod_lbr_ty: laborInfo.LaborType || null,
mod_lb_hrs: parseFloat(laborInfo.LaborHours || 0) || 0,
lbr_op: laborInfo.LaborOperation || null,
lbr_amt: parseFloat(laborInfo.LaborAmt || 0) || 0
});
// Primary labor on same line
const hrs = parseFloat(laborInfo.LaborHours || 0);
const amt = parseFloat(laborInfo.LaborAmt || 0);
const hasLabor =
(!!laborInfo.LaborType && String(laborInfo.LaborType).length > 0) ||
(!isNaN(hrs) && hrs !== 0) ||
(!isNaN(amt) && amt !== 0);
if (hasLabor) {
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
lineOut.lbr_op = laborInfo.LaborOperation || null;
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
}
// Separate refinish labor line
if (Object.keys(refinishInfo).length > 0) {
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
if (!isNaN(rHrs) || !isNaN(rAmt)) {
out.push({
...base,
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 500000,
line_desc: base.line_desc || "Refinish",
mod_lbr_ty: "LAR",
mod_lb_hrs: isNaN(rHrs) ? 0 : rHrs,
lbr_op: refinishInfo.LaborOperation || null,
lbr_amt: isNaN(rAmt) ? 0 : rAmt
});
// Refinish labor on same line using secondary fields; aggregate amount into lbr_amt
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
const hasRefinish =
Object.keys(refinishInfo).length > 0 &&
((refinishInfo.LaborType && String(refinishInfo.LaborType).length > 0) ||
!isNaN(rHrs) ||
!isNaN(rAmt) ||
!!refinishInfo.LaborOperation);
if (hasRefinish) {
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
if (!isNaN(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.PaintTonesNum !== undefined) lineOut.paint_tone = refinishInfo.PaintTonesNum;
}
out.push(lineOut);
}
return out;
@@ -207,13 +206,13 @@ const partsManagementVehicleDamageEstimateChgRq = async (req, res) => {
// Build a set of unq_seq that will be updated (replaced). We delete them first to avoid duplicates.
const updatedSeqs = Array.from(
new Set((updatedLines || []).map((l) => l && l.unq_seq).filter((v) => Number.isInteger(v)))
new Set((updatedLines || []).map((l) => l?.unq_seq).filter((v) => Number.isInteger(v)))
);
if ((deletedLineIds && deletedLineIds.length) || (updatedSeqs && updatedSeqs.length)) {
if (deletedLineIds?.length || updatedSeqs?.length) {
const allToDelete = Array.from(new Set([...(deletedLineIds || []), ...(updatedSeqs || [])]));
if (allToDelete.length) {
await client.request(DELETE_JOBLINES_BY_IDS, { jobid: job.id, unqSeqs: allToDelete });
await client.request(SOFT_DELETE_JOBLINES_BY_IDS, { jobid: job.id, unqSeqs: allToDelete });
}
}

View File

@@ -102,6 +102,18 @@ const DELETE_JOBLINES_BY_IDS = `
}
`;
// Soft delete joblines by marking removed=true instead of hard-deleting
const SOFT_DELETE_JOBLINES_BY_IDS = `
mutation SoftDeleteJoblinesByIds($jobid: uuid!, $unqSeqs: [Int!]!) {
update_joblines(
where: { jobid: { _eq: $jobid }, unq_seq: { _in: $unqSeqs } },
_set: { removed: true }
) {
affected_rows
}
}
`;
const INSERT_JOBLINES = `
mutation InsertJoblines($joblines: [joblines_insert_input!]!) {
insert_joblines(objects: $joblines) {
@@ -243,6 +255,7 @@ module.exports = {
UPSERT_JOBLINES,
DELETE_JOBLINES_BY_JOBID,
DELETE_JOBLINES_BY_IDS,
SOFT_DELETE_JOBLINES_BY_IDS,
INSERT_JOBLINES,
CHECK_EXTERNAL_SHOP_ID,
CREATE_SHOP,

View File

@@ -1,10 +1,6 @@
const path = require("path");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
// Emit this to bodyshop room
exports.default = async (req, res) => {

View File

@@ -5,13 +5,8 @@ const logger = require("../utils/logger");
// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN";
const path = require("path");
const client = require("../graphql-client/graphql-client").client;
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
async function StatusTransition(req, res) {
const { id: jobid, status: value, shopid: bodyshopid } = req.body.event.data.new;

View File

@@ -44,13 +44,34 @@ const generateSignedUploadUrls = async (req, res) => {
for (const filename of filenames) {
const key = filename;
const client = new S3Client({ region: InstanceRegion() });
const command = new PutObjectCommand({
// Check if filename indicates PDF and set content type accordingly
const isPdf = filename.toLowerCase().endsWith('.pdf');
const commandParams = {
Bucket: imgproxyDestinationBucket,
Key: key,
StorageClass: "INTELLIGENT_TIERING"
};
if (isPdf) {
commandParams.ContentType = "application/pdf";
}
const command = new PutObjectCommand(commandParams);
// For PDFs, we need to add conditions to the presigned URL to enforce content type
const presignedUrlOptions = { expiresIn: 360 };
if (isPdf) {
presignedUrlOptions.signableHeaders = new Set(['content-type']);
}
const presignedUrl = await getSignedUrl(client, command, presignedUrlOptions);
signedUrls.push({
filename,
presignedUrl,
key,
...(isPdf && { contentType: "application/pdf" })
});
const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 });
signedUrls.push({ filename, presignedUrl, key });
}
logger.log("imgproxy-upload-success", "DEBUG", req.user?.email, jobid, { signedUrls });

View File

@@ -3,10 +3,6 @@ const xml2js = require("xml2js");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
exports.mixdataUpload = async (req, res) => {
const client = req.userGraphQLClient;

View File

@@ -1,7 +1,3 @@
require("dotenv").config({
path: require("path").resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
//const client = require("../graphql-client/graphql-client").client;
const logger = require("../utils/logger");
const queries = require("../graphql-client/queries");

View File

@@ -1,13 +1,8 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const moment = require("moment-timezone");
const logger = require("../utils/logger");
const _ = require("lodash");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
exports.job = async (req, res) => {
const { jobId } = req.body;

View File

@@ -3,12 +3,6 @@
* If required, remember to re-install stripe 14.19.0
*/
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const processor = async (req, res) => {

View File

@@ -1,7 +1,3 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;
const emailer = require("../email/sendemail");
const moment = require("moment-timezone");

View File

@@ -1,10 +1,6 @@
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const path = require("path");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
exports.techLogin = async (req, res) => {
const { shopid, employeeid, pin } = req.body;

View File

@@ -1,9 +1,4 @@
// Load environment variables THIS MUST BE AT THE TOP
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const { networkInterfaces, hostname } = require("node:os");
const getHostNameOrIP = () => {

View File

@@ -1,9 +1,4 @@
// Load environment variables THIS MUST BE AT THE TOP
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const InstanceManager = require("../utils/instanceMgr").default;
const winston = require("winston");
const WinstonCloudWatch = require("winston-cloudwatch");

View File

@@ -2,6 +2,7 @@
const path = require("path");
const getHostNameOrIP = require("./getHostNameOrIP");
const logger = require("./logger");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});

View File

@@ -1,7 +1,4 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const { io } = require("../../server");
const { admin } = require("../firebase/firebase-handler");