Compare commits

...

111 Commits

Author SHA1 Message Date
Allan Carr
ac6856b136 IO-3373 Dashboard Component Infinite Recursion
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-17 13:59:20 -07:00
Patrick Fic
ddd3b3d056 Merged in feature/IO-3325-amplitude-reverse-proxy (pull request #2566)
IO-3325 Add reverse proxy URL for amplitude.
2025-09-15 21:37:33 +00:00
Patrick Fic
2660466db1 IO-3325 Add reverse proxy URL for amplitude. 2025-09-15 14:13:48 -07:00
Dave Richer
fe67efe47c Merged in release/2025-09-26 (pull request #2562)
Release/2025 09 26  into master-AIO IO-3365, IO-3366, IO-3369
2025-09-11 20:43:43 +00:00
Dave
69a35772e5 release/2025-09-26 - Remove sockets from Parts Management 2025-09-11 16:41:21 -04:00
Dave Richer
38932f4bf9 Merged in feature/IO-3369-Fix-Parts-Status-List-Component (pull request #2559)
feature/IO-3369-Fix-Parts-Status-List-Component - Add Fixes
2025-09-11 19:58:28 +00:00
Dave
3fcb36a28e feature/IO-3369-Fix-Parts-Status-List-Component - Add Fixes 2025-09-11 15:56:47 -04:00
Allan Carr
fe78f5c7ff Merged in feature/IO-3365-Bills-Filters-and-Sorters (pull request #2556)
IO-3365 Bills Filters and Sorters

Approved-by: Dave Richer
2025-09-11 18:49:55 +00:00
Allan Carr
683846c3b0 Merged in feature/IO-3366-Shop-General-Field-Validators (pull request #2557)
IO-3366 Shop General Field Validators

Approved-by: Dave Richer
2025-09-11 18:49:20 +00:00
Dave Richer
2cc0b247b6 Merged in master-AIO (pull request #2555)
Master AIO
2025-09-11 18:18:26 +00:00
Dave Richer
31579354d4 Merged in release/2025-09-12 (pull request #2554)
DO NOT MERGE - Release/2025-09-12 into master-AIO -IO-3255, IO-3310, IO-3352, IO-3355
2025-09-11 15:31:22 +00:00
Dave Richer
0e7531dc54 Merged in feature/IO-3255-simplified-part-management (pull request #2552)
feature/IO-3255-simplified-parts-management -Extra checks
2025-09-11 15:24:38 +00:00
Dave
268b57c38a feature/IO-3255-simplified-parts-management -Extra checks 2025-09-11 11:22:59 -04:00
Allan Carr
2b8b8b8073 IO-3366 Shop General Field Validators
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-10 22:12:15 -07:00
Allan Carr
808eeb91e9 IO-3365 Bills Filters and Sorters
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-10 21:33:10 -07:00
Dave Richer
23dd8fc9de Merged in feature/IO-3255-simplified-part-management (pull request #2548)
feature/IO-3255-simplified-parts-management - Remove some stuff in vendors
2025-09-10 19:59:10 +00:00
Dave
f499859078 Merge remote-tracking branch 'origin/release/2025-09-12' into feature/IO-3255-simplified-part-management 2025-09-10 15:58:06 -04:00
Dave
84d9e3251a feature/IO-3255-simplified-parts-management - Remove some stuff in vendors 2025-09-10 15:57:54 -04:00
Dave Richer
bd7db4dd02 Merged in feature/IO-3255-simplified-part-management (pull request #2546)
feature/IO-3255-simplified-parts-management - Fix Manual line in Change Request
2025-09-10 16:44:59 +00:00
Dave
e9804b736b feature/IO-3255-simplified-parts-management - Fix Manual line in Change Request 2025-09-10 12:44:16 -04:00
Dave Richer
a14874f116 Merged in feature/Reynolds-and-Reynolds-DMS-API-Integration (pull request #2543)
Feature/Reynolds and Reynolds DMS API Integration
2025-09-09 16:00:01 +00:00
Dave
ac9fac458c feature/Reynolds-and-Reynolds-DMS-API-Integration - DB Modifications 2025-09-09 11:59:18 -04:00
Dave
8f9db15852 feature/Reynolds-and-Reynolds-DMS-API-Integration - DB Modifications 2025-09-09 11:59:08 -04:00
Dave Richer
61b3d3c18c Merged in feature/IO-3255-simplified-part-management (pull request #2540)
feature/IO-3255-simplified-parts-management - missing translations
2025-09-05 16:52:39 +00:00
Dave
e6f08d3b1c feature/IO-3255-simplified-parts-management - missing translations 2025-09-05 12:51:12 -04:00
Dave Richer
d5cf0f8371 Merged in feature/IO-3255-simplified-part-management (pull request #2538)
Feature/IO-3255 simplified part management
2025-09-05 16:46:39 +00:00
Dave
52e230fc54 feature/IO-3255-simplified-parts-management - bump deps 2025-09-05 12:45:46 -04:00
Dave
4011237c22 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 12:36:45 -04:00
Dave
c24bfbf655 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 12:31:45 -04:00
Allan Carr
08fe8c3c70 Merged in feature/IO-3355-Job-Cash-Discounting (pull request #2537)
Feature/IO-3355 Job Cash Discounting

Approved-by: Dave Richer
2025-09-05 16:30:47 +00:00
Dave
771a239773 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 12:16:25 -04:00
Dave
82195a0584 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 12:02:17 -04:00
Dave
838c24b3f1 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 11:03:20 -04:00
Dave
13cb68b0af feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 10:18:03 -04:00
Dave
7c84b08707 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 10:12:37 -04:00
Dave
3165957e95 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-05 10:05:23 -04:00
Allan Carr
d9f59fcad4 IO-3355 Correct translation
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-04 15:27:06 -07:00
Allan Carr
edaeb5d77a IO-3355 Job Cash Discounting
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-04 15:16:48 -07:00
Allan Carr
5365d95d6f Merged in feature/IO-3310-Shop-Info-Data-Preservation (pull request #2533)
IO-3310 Shop Info Data Preservation

Approved-by: Dave Richer
2025-09-03 14:48:39 +00:00
Allan Carr
5cfefd5afd Merged in feature/IO-3352-Arrived-Color-on-Schedule (pull request #2532)
IO-3352 Arrived Color on Schedule

Approved-by: Dave Richer
2025-09-03 14:48:10 +00:00
Dave Richer
bbccdb0650 Merged in feature/IO-3255-simplified-part-management (pull request #2534)
feature/IO-3255-simplified-parts-management - Fix deprovision route
2025-09-03 14:47:51 +00:00
Dave
f3535c01af feature/IO-3255-simplified-parts-management - Fix deprovision route 2025-09-03 10:47:04 -04:00
Dave Richer
7f8c82b300 Merged in release/2025-08-29 (pull request #2528)
Release/2025 08 29 into master-AIO - IO-3182, IO-3255, IO-3325, IO-3330, IO-3340
2025-09-03 00:56:28 +00:00
Dave Richer
609ac2bd33 Merged in feature/IO-3255-simplified-part-management (pull request #2530)
Feature/IO-3255 simplified part management
2025-09-02 19:24:59 +00:00
Dave
0883274320 feature/IO-3255-simplified-parts-management - Expand deprovision route 2025-09-02 15:23:44 -04:00
Dave
fa33b88632 feature/IO-3255-simplified-parts-management - Expand deprovision route 2025-09-02 15:23:36 -04:00
Dave
bec32c1d70 feature/IO-3255-simplified-parts-management - Checkpoint 2025-09-02 15:01:56 -04:00
Allan Carr
eb18130e51 IO-3310 Shop Info Data Preservation
Correction to preserve hidden data

Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-01 20:29:39 -07:00
Allan Carr
0fbf63dec8 IO-3352 Arrived Color on Schedule
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-08-31 22:42:24 -07:00
Patrick Fic
f817902d5c Merged in feature/IO-3340-imgproxy-pdf-contenttype (pull request #2527)
IO-3340 Resolve unenforced content-type
2025-08-28 22:08:09 +00:00
Patrick Fic
7a383aaec9 IO-3340 Resolve unenforced content-type 2025-08-28 15:07:18 -07:00
Dave Richer
e5c0ace6cb Merged in feature/IO-3255-simplified-part-management (pull request #2525)
feature/IO-3255-simplified-parts-management - Checkpoint
2025-08-28 20:01:35 +00:00
Dave
814447373a feature/IO-3255-simplified-parts-management - Checkpoint 2025-08-28 16:00:39 -04:00
Dave Richer
d766a468c3 Merged in release/2025-08-29 (pull request #2523)
Release/2025 08 29
2025-08-28 19:36:10 +00:00
Dave
0ede2d0649 Merge remote-tracking branch 'origin/master-AIO' into release/2025-08-29 2025-08-28 15:33:26 -04:00
Dave Richer
54089c2ab3 Merged in feature/IO-3255-simplified-part-management (pull request #2521)
Feature/IO-3255 simplified part management
2025-08-28 19:26:25 +00:00
Dave
73e3d71cf1 feature/IO-3255-simplified-parts-management - Checkpoint 2025-08-28 14:50:26 -04:00
Dave
f071a5cc9e feature/IO-3255-simplified-parts-management - Checkpoint 2025-08-28 14:24:35 -04:00
Dave
67002b8443 Merge remote-tracking branch 'origin/feature/IO-3255-parts-management-review1' into feature/IO-3255-simplified-part-management 2025-08-28 14:06:07 -04:00
Dave
3f83c6afa7 Merge remote-tracking branch 'origin/release/2025-08-29' into feature/IO-3255-simplified-part-management 2025-08-28 13:27:16 -04:00
Dave
b7f57e91aa feature/IO-3255-simplified-parts-management - Checkpoint 2025-08-28 13:26:47 -04:00
Allan Carr
b8465c0cc7 Merged in feature/IO-3330-CARFAX-Query (pull request #2520)
IO-3330 CARFAX Bodyshop Query Adjustment

Approved-by: Dave Richer
2025-08-28 17:25:18 +00:00
Allan Carr
6bae0b8406 IO-3330 CARFAX Bodyshop Query Adjustment
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-08-27 23:41:16 -07:00
Patrick Fic
553c154e46 IO-3255 Review comments. 2025-08-27 11:50:00 -07:00
Dave Richer
35cbb921d2 Merged in feature/IO-3255-simplified-part-management (pull request #2516)
feature/IO-3255-simplified-parts-management - Change Parts Status logic for Parts Management
2025-08-27 18:19:40 +00:00
Dave
6b926401d0 feature/IO-3255-simplified-parts-management - Change Parts Status logic for Parts Management 2025-08-27 14:17:23 -04:00
Patrick Fic
5b400dce4f Merge branch 'feature/IO-3255-simplified-part-management' into feature/IO-3255-parts-management-review1 2025-08-27 09:39:55 -07:00
Dave Richer
914946c264 Merged in feature/IO-3255-simplified-part-management (pull request #2514)
feature/IO-3255-simplified-parts-management - Change Parts Status logic for Parts Management
2025-08-27 16:37:27 +00:00
Dave
2939b5795b feature/IO-3255-simplified-parts-management - Change Parts Status logic for Parts Management 2025-08-27 12:35:53 -04:00
Dave Richer
531f968ce6 Merged in feature/IO-3255-simplified-part-management (pull request #2512)
feature/IO-3255-simplified-parts-management - Change From Claim to JobID (chgrq)
2025-08-27 15:37:47 +00:00
Dave
24cc3fa6a4 feature/IO-3255-simplified-parts-management - Change From Claim to JobID (chgrq) 2025-08-27 11:36:39 -04:00
Dave Richer
a6f17e7db5 Merged in feature/IO-3255-simplified-part-management (pull request #2510)
feature/IO-3255-simplified-parts-management - Typo in chg request route
2025-08-26 14:51:18 +00:00
Dave
29c904feea feature/IO-3255-simplified-parts-management - Typo in chg request route 2025-08-26 10:50:44 -04:00
Dave Richer
0743048e75 Merged in feature/IO-3255-simplified-part-management (pull request #2509)
Feature/IO-3255 simplified part management
2025-08-25 16:58:43 +00:00
Dave
e2f1758378 feature/IO-3255-simplified-parts-management - Simplified Parts print center stacks on top of each other, not beside each other 2025-08-25 11:33:12 -04:00
Dave
b6dc7a4d92 feature/IO-3255-simplified-parts-management - Notes are appended if they are not duplicates 2025-08-25 11:30:18 -04:00
Dave Richer
130745d7e7 Merged in hotfix/2025-08-22 (pull request #2508)
DO NOT MERGE Hotfix/2025 08 22
2025-08-22 19:36:51 +00:00
Patrick Fic
a722ab9758 hotfix/2025-08-22 - Datahog 2025-08-22 15:35:55 -04:00
Patrick Fic
41c9c0be49 Add in amplititude 2025-08-22 15:22:45 -04:00
Patrick Fic
52f62126e1 Merged in feature/IO-3340-imgproxy-pdf-contenttype (pull request #2507)
IO-3340 Change application type on PDFs uploaded via imgproxy to allow inline display.
2025-08-22 19:02:44 +00: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
Allan Carr
eb04a8b6c5 Merged in feature/IO-3349-Chart-Enqueue-and-Label-Required (pull request #2505)
IO-3349 Chart Enqueue and Label Required

Approved-by: Dave Richer
2025-08-22 18:55:58 +00:00
Allan Carr
141b05f558 IO-3349 Chart Enqueue and Label Required
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-08-22 11:29:33 -07:00
Dave Richer
e9ea36fdad Merged in feature/IO-3255-simplified-part-management (pull request #2504)
feature/IO-3255-simplified-parts-management - Bug changes / Socket close cleanup
2025-08-22 18:26:38 +00:00
Dave
e990781ff7 feature/IO-3255-simplified-parts-management - Bug changes / Socket close cleanup 2025-08-22 14:24:03 -04:00
Patrick Fic
e24237e010 Merged in feature/IO-3325-amplitude (pull request #2502)
Feature/IO-3325 amplitude
2025-08-22 18:00:37 +00:00
Patrick Fic
f00f5f2e4a IO-3325 resolve partial commit error. 2025-08-22 10:57:38 -07:00
Patrick Fic
f38b550e75 IO-3325 Added posthog analytics for comparison. 2025-08-22 10:57:25 -07:00
Patrick Fic
fabe1508ac Merged in feature/IO-3325-amplitude (pull request #2500)
IO-3325 Add amplitude tracking.
2025-08-22 17:40:33 +00:00
Patrick Fic
5f3c880d0b IO-3325 Add amplitude tracking. 2025-08-22 10:40:11 -07:00
Patrick Fic
86f0c02c82 Merged in feature/IO-3348-es-vehicletype (pull request #2498)
ES-3348 Ass ES Vehicle type Route.
2025-08-22 15:53:22 +00:00
Patrick Fic
2004eb840f ES-3348 Ass ES Vehicle type Route. 2025-08-22 08:52:08 -07:00
Dave Richer
2f0190e190 Merged in feature/IO-3255-simplified-part-management (pull request #2496)
feature/IO-3255-simplified-parts-management - Missing breadcrumb / title translations
2025-08-22 15:31:40 +00: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
Dave Richer
8c9ef375be Merged in hotfix/background-colors-dark-mode (pull request #2485)
Hotfix/background colors dark mode
2025-08-18 18:39:42 +00:00
126 changed files with 5745 additions and 3483 deletions

View File

@@ -14,3 +14,6 @@ VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=IMEX
TEST_USERNAME="test@imex.dev"
TEST_PASSWORD="test123"
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -16,3 +16,6 @@ VITE_APP_COUNTRY=USA
VITE_APP_INSTANCE=ROME
TEST_USERNAME="test@imex.dev"
TEST_PASSWORD="test123"
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -13,3 +13,6 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.imex.online/
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=IMEX
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -13,3 +13,6 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=ROME
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -13,3 +13,6 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
VITE_APP_IS_TEST=true
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=IMEX
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -13,3 +13,6 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
VITE_APP_IS_TEST=true
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=ROME
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

3394
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,46 +8,48 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@amplitude/analytics-browser": "^2.23.5",
"@ant-design/pro-layout": "^7.22.6",
"@apollo/client": "^3.13.9",
"@emotion/is-prop-valid": "^1.3.1",
"@emotion/is-prop-valid": "^1.4.0",
"@fingerprintjs/fingerprintjs": "^4.6.1",
"@firebase/analytics": "^0.10.17",
"@firebase/app": "^0.14.1",
"@firebase/app": "^0.14.2",
"@firebase/auth": "^1.10.8",
"@firebase/firestore": "^4.8.0",
"@firebase/firestore": "^4.9.1",
"@firebase/messaging": "^0.12.22",
"@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.8.2",
"@sentry/cli": "^2.52.0",
"@reduxjs/toolkit": "^2.9.0",
"@sentry/cli": "^2.53.0",
"@sentry/react": "^9.43.0",
"@sentry/vite-plugin": "^4.1.1",
"@sentry/vite-plugin": "^4.3.0",
"@splitsoftware/splitio-react": "^2.3.1",
"@tanem/react-nprogress": "^5.0.53",
"antd": "^5.27.1",
"antd": "^5.27.3",
"apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^4.4.0",
"autosize": "^6.0.1",
"axios": "^1.11.0",
"classnames": "^2.5.1",
"css-box-model": "^1.2.1",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"dayjs-business-days2": "^1.3.0",
"dinero.js": "^1.9.1",
"dotenv": "^17.2.1",
"dotenv": "^17.2.2",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"graphql": "^16.11.0",
"i18next": "^25.3.6",
"i18next": "^25.5.2",
"i18next-browser-languagedetector": "^8.2.0",
"immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.12.12",
"libphonenumber-js": "^1.12.15",
"logrocket": "^9.0.2",
"markerjs2": "^2.32.6",
"memoize-one": "^6.0.0",
"normalize-url": "^8.0.2",
"object-hash": "^3.0.0",
"phone": "^3.1.67",
"posthog-js": "^1.261.7",
"prop-types": "^15.8.1",
"query-string": "^9.2.2",
"raf-schd": "^4.0.3",
@@ -55,11 +57,12 @@
"react-big-calendar": "^1.19.4",
"react-color": "^2.19.3",
"react-cookie": "^8.0.1",
"lightningcss": "^1.30.1",
"react-dom": "^18.3.1",
"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.3",
"react-icons": "^5.5.0",
"react-image-lightbox": "^5.1.4",
"react-markdown": "^10.1.0",
@@ -78,7 +81,7 @@
"redux-saga": "^1.3.0",
"redux-state-sync": "^3.1.4",
"reselect": "^5.1.1",
"sass": "^1.90.0",
"sass": "^1.92.0",
"socket.io-client": "^4.8.1",
"styled-components": "^6.1.19",
"subscriptions-transport-ws": "^0.11.0",
@@ -137,10 +140,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",
@@ -152,7 +155,7 @@
"jsdom": "^26.0.0",
"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 +163,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

@@ -1,20 +1,20 @@
import { ApolloProvider } from "@apollo/client";
import * as Sentry from "@sentry/react";
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import { useEffect, useMemo } from "react";
import { CookiesProvider } from "react-cookie";
import { useTranslation } from "react-i18next";
import { connect, useSelector } from "react-redux";
import { createStructuredSelector } from "reselect";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import { setDarkMode } from "../redux/application/application.actions";
import { selectDarkMode } from "../redux/application/application.selectors";
import { selectCurrentUser } from "../redux/user/user.selectors.js";
import client from "../utils/GraphQLClient";
import App from "./App";
import * as Sentry from "@sentry/react";
import getTheme from "./themeProvider";
import { CookiesProvider } from "react-cookie";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../redux/user/user.selectors.js";
import { selectDarkMode } from "../redux/application/application.selectors";
import { setDarkMode } from "../redux/application/application.actions";
// Base Split configuration
const config = {
@@ -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

@@ -233,9 +233,7 @@ export function App({
path="/parts/*"
element={
<ErrorBoundary>
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
<PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider>
<PrivateRoute isAuthorized={currentUser.authorized} />
</ErrorBoundary>
}
>

View File

@@ -109,6 +109,13 @@ export function BillsListTableComponent({
key: "vendorname",
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
filters: bills
? [...new Set(bills.map((bill) => bill.vendor.name))].map((name) => ({
text: name,
value: name
}))
: [],
onFilter: (value, record) => record.vendor.name === value,
render: (text, record) => <span>{record.vendor.name}</span>
},
{

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

@@ -80,7 +80,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
};
const handleAddComponent = (e) => {
logImEXEvent("dashboard_add_component", { name: e });
logImEXEvent("dashboard_add_component", { name: e.key });
setState({
...state,
items: [

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";
@@ -18,17 +17,6 @@ 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>
@@ -47,7 +35,6 @@ export function GlobalFooter({ isPartsEntry }) {
rome: t("titles.romeonline")
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
</div>
<WssStatusDisplayComponent />
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>

View File

@@ -12,13 +12,15 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician
technician: selectTechnician,
isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = () => ({});
export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
export function JobLinesPartPriceChange({ job, line, refetch, technician, isPartsEntry }) {
const [loading, setLoading] = useState(false);
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
const notification = useNotification();
@@ -64,6 +66,7 @@ export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
const popcontent =
!technician &&
!isPartsEntry &&
InstanceRenderManager({
imex: null,
rome: (

View File

@@ -481,48 +481,50 @@ export function JobLinesComponent({
{Enhanced_Payroll.treatment === "on" && (
<JobLineBulkAssignComponent selectedLines={selectedLines} setSelectedLines={setSelectedLines} job={job} />
)}
<Button
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
onClick={() => {
setBillEnterContext({
actions: { refetch: refetch },
context: {
disableInvNumber: true,
job: { id: job.id },
bill: {
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
isinhouse: true,
date: dayjs(),
total: 0,
billlines: selectedLines.map((p) => {
return {
joblineid: p.id,
actual_price: p.act_price,
actual_cost: 0, //p.act_price,
line_desc: p.line_desc,
line_remarks: p.line_remarks,
part_type: p.part_type,
quantity: p.quantity || 1,
applicable_taxes: {
local: false,
state: false,
federal: false
}
};
})
{!isPartsEntry && (
<Button
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
onClick={() => {
setBillEnterContext({
actions: { refetch: refetch },
context: {
disableInvNumber: true,
job: { id: job.id },
bill: {
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
isinhouse: true,
date: dayjs(),
total: 0,
billlines: selectedLines.map((p) => {
return {
joblineid: p.id,
actual_price: p.act_price,
actual_cost: 0, //p.act_price,
line_desc: p.line_desc,
line_remarks: p.line_remarks,
part_type: p.part_type,
quantity: p.quantity || 1,
applicable_taxes: {
local: false,
state: false,
federal: false
}
};
})
}
}
}
});
});
//Clear out the selected lines. IO-785
setSelectedLines([]);
}}
>
<HomeOutlined />
{t("parts.actions.orderinhouse")}
{selectedLines.length > 0 && ` (${selectedLines.length})`}
</Button>
//Clear out the selected lines. IO-785
setSelectedLines([]);
}}
>
<HomeOutlined />
{t("parts.actions.orderinhouse")}
{selectedLines.length > 0 && ` (${selectedLines.length})`}
</Button>
)}
<Button
id="job-lines-order-parts-button"
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
@@ -578,7 +580,8 @@ export function JobLinesComponent({
{t("joblines.actions.new")}
</Button>
)}
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
{!isPartsEntry &&
InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search
placeholder={t("general.labels.search")}

View File

@@ -10,9 +10,12 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export const DEFAULT_COL_LAYOUT = { xs: 24, sm: 24, md: 8, lg: 4, xl: 4, xxl: 4 };
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
export function JobPartsQueueCount({ bodyshop, parts, style }) {
export function JobPartsQueueCount({ bodyshop, parts, defaultColLayout = DEFAULT_COL_LAYOUT }) {
const partsStatus = useMemo(() => {
if (!parts) return null;
return parts.reduce(
@@ -35,35 +38,34 @@ export function JobPartsQueueCount({ bodyshop, parts, style }) {
}, [bodyshop, parts]);
if (!parts) return null;
return (
<Row style={style}>
<Col span={4}>
<Row>
<Col {...defaultColLayout}>
<Tooltip title="Total">
<Tag>{partsStatus.total}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title="No Status">
<Tag color="gold">{partsStatus["null"]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
<Tag color="blue">{partsStatus[bodyshop.md_order_statuses.default_ordered]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_received}>
<Tag color="green">{partsStatus[bodyshop.md_order_statuses.default_received]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
<Tag color="orange">{partsStatus[bodyshop.md_order_statuses.default_returned]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
<Tag color="red">{partsStatus[bodyshop.md_order_statuses.default_bo]}</Tag>
</Tooltip>

View File

@@ -20,35 +20,27 @@ export function JobTotalsCashDiscount({ bodyshop, amountDinero }) {
const notification = useNotification();
const fetchData = useCallback(async () => {
if (amountDinero && bodyshop) {
setLoading(true);
let response;
try {
response = await axios.post("/intellipay/checkfee", {
bodyshop: { id: bodyshop.id, imexshopid: bodyshop.imexshopid, state: bodyshop.state },
amount: Dinero(amountDinero).toFormat("0.00")
});
if (!amountDinero || !bodyshop) return;
if (response?.data?.error) {
notification.open({
type: "error",
message:
response.data?.error ||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
});
} else {
setFee(response.data?.fee || 0);
}
} catch (error) {
notification.open({
type: "error",
message:
error.response?.data?.error ||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
});
} finally {
setLoading(false);
setLoading(true);
const errorMessage = "Error encountered when contacting IntelliPay service to determine cash discounted price.";
try {
const { id, imexshopid, state } = bodyshop;
const { data } = await axios.post("/intellipay/checkfee", {
bodyshop: { id, imexshopid, state },
amount: Dinero(amountDinero).toUnit()
});
if (data?.error) {
notification.open({ type: "error", message: data.error || errorMessage });
} else {
setFee(data?.fee ?? 0);
}
} catch (error) {
notification.open({ type: "error", message: error.response?.data?.error || errorMessage });
} finally {
setLoading(false);
}
}, [amountDinero, bodyshop, notification]);

View File

@@ -8,19 +8,20 @@ import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectIsPartsEntry, selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly
jobRO: selectJobReadOnly,
isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
});
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail, isPartsEntry }) {
const { t } = useTranslation();
const [availableStatuses, setAvailableStatuses] = useState([]);
@@ -45,25 +46,43 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
});
};
// Updates available statuses based on job and bodyshop context
useEffect(() => {
//Figure out what scenario were in, populate accodingly
if (job && bodyshop) {
if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) {
setAvailableStatuses(bodyshop.md_ro_statuses.pre_production_statuses);
} else if (bodyshop.md_ro_statuses.production_statuses.includes(job.status)) {
setAvailableStatuses(bodyshop.md_ro_statuses.production_statuses);
} else if (bodyshop.md_ro_statuses.post_production_statuses.includes(job.status)) {
setAvailableStatuses(
bodyshop.md_ro_statuses.post_production_statuses.filter(
(s) => s !== bodyshop.md_ro_statuses.default_invoiced && s !== bodyshop.md_ro_statuses.default_exported
)
);
} else {
console.log("Status didn't match any restrictions. Allowing all status changes.");
setAvailableStatuses(bodyshop.md_ro_statuses.statuses);
}
if (!job || !bodyshop) return;
const { md_ro_statuses } = bodyshop;
const {
parts_statuses,
pre_production_statuses,
production_statuses,
post_production_statuses,
statuses,
default_invoiced,
default_exported
} = md_ro_statuses;
if (isPartsEntry) {
// Set parts-specific statuses for parts entry scenario
setAvailableStatuses(parts_statuses);
return;
}
}, [job, setAvailableStatuses, bodyshop]);
// Handle non-parts entry scenarios based on job status
if (pre_production_statuses.includes(job.status)) {
setAvailableStatuses(pre_production_statuses);
} else if (production_statuses.includes(job.status)) {
setAvailableStatuses(production_statuses);
} else if (post_production_statuses.includes(job.status)) {
// Filter out invoiced and exported statuses for post-production
setAvailableStatuses(
post_production_statuses.filter((status) => status !== default_invoiced && status !== default_exported)
);
} else {
// Default to all statuses if no specific restrictions apply
console.log("Status didn't match any restrictions. Allowing all status changes.");
setAvailableStatuses(statuses);
}
}, [job, bodyshop, isPartsEntry, setAvailableStatuses]);
const statusMenu = {
items: [

View File

@@ -139,17 +139,18 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
<ProductionListColumnComment record={job} />
</DataLabel>
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>{job.ins_co_nm}</DataLabel>
{!isPartsEntry && <DataLabel label={t("jobs.fields.ins_co_nm_short")}>{job.ins_co_nm}</DataLabel>}
<DataLabel label={t("jobs.fields.clm_no")}>{job.clm_no}</DataLabel>
<DataLabel label={t("jobs.fields.ponumber")} hideIfNull>
{job.po_number}
</DataLabel>
<DataLabel label={t("jobs.fields.repairtotal")}>
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
<span style={{ margin: "0rem .5rem" }}>/</span>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</DataLabel>
{!isPartsEntry && (
<DataLabel label={t("jobs.fields.repairtotal")}>
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
<span style={{ margin: "0rem .5rem" }}>/</span>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</DataLabel>
)}
{!isPartsEntry && (
<>
<DataLabel label={t("jobs.fields.alt_transport")}>
@@ -176,7 +177,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
<Checkbox
checked={!!job.estimate_sent_approval}
onChange={(e) => handleCheckboxChange("estimate_sent_approval", e.target.checked)}
disabled={disabled}
disabled={disabled || isPartsEntry}
>
{job.estimate_sent_approval && (
<span style={{ color: "#888" }}>
@@ -191,7 +192,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
<Checkbox
checked={!!job.estimate_approved}
onChange={(e) => handleCheckboxChange("estimate_approved", e.target.checked)}
disabled={disabled}
disabled={disabled || isPartsEntry}
>
{job.estimate_approved && (
<span style={{ color: "#888" }}>
@@ -236,7 +237,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
<Card
style={{ height: "100%" }}
title={
disabled ? (
disabled || isPartsEntry ? (
<>{ownerTitle.length > 0 ? ownerTitle : t("owner.labels.noownerinfo")}</>
) : (
<Link to={`/manage/owners/${job.owner.id}`}>
@@ -247,14 +248,14 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
>
<div>
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
{disabled ? (
{disabled || isPartsEntry ? (
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
) : (
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
)}
</DataLabel>
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
{disabled ? (
{disabled || isPartsEntry ? (
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
) : (
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
@@ -266,7 +267,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
} ${job.ownr_st || ""} ${job.ownr_zip || ""}`}
</DataLabel>
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
{disabled ? (
{disabled || isPartsEntry ? (
<>{job.ownr_ea || ""}</>
) : job.ownr_ea ? (
<a href={`mailto:${job.ownr_ea}`}>{job.ownr_ea}</a>
@@ -316,7 +317,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
<DataLabel label={t("jobs.labels.relatedros")}>
<JobsRelatedRos jobid={job.id} job={job} disabled={disabled} />
</DataLabel>
{job.vehicle && job.vehicle.notes && (
{job.vehicle?.notes && (
<DataLabel
label={t("vehicles.fields.notes")}
valueStyle={{ whiteSpace: "pre-wrap" }}
@@ -326,7 +327,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
{job.vehicle.notes}
</DataLabel>
)}
{job.vehicle && job.vehicle.v_paint_codes && (
{job.vehicle?.v_paint_codes && (
<DataLabel label={t("vehicles.fields.v_paint_codes", { number: "" })}>
<span style={{ whiteSpace: "pre" }}>
{Object.keys(job.vehicle.v_paint_codes)

View File

@@ -1,8 +1,18 @@
import { Space, Tag } from "antd";
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;
const basePath = getPartsBasePath(isPartsEntry);
return (
<Space wrap>
{job.vehicle.jobs
@@ -12,7 +22,7 @@ export default function JobsRelatedRos({ job, disabled }) {
{disabled ? (
<>{`${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.status ? ` | ${j.status}` : ""}`}</Link>
)}
@@ -21,3 +31,4 @@ export default function JobsRelatedRos({ job, disabled }) {
</Space>
);
}
export default connect(mapStateToProps)(JobsRelatedRos);

View File

@@ -227,15 +227,21 @@ export function PartsOrderListTableComponent({
sorter: (a, b) => a.order_date - b.order_date,
sortOrder: state.sortedInfo.columnKey === "order_date" && state.sortedInfo.order,
render: (text, record) => <DateFormatter>{record.order_date}</DateFormatter>
},
{
}
];
if (!isPartsEntry) {
columns.push({
title: t("parts_orders.fields.return"),
dataIndex: "return",
key: "return",
sorter: (a, b) => a.return - b.return,
sortOrder: state.sortedInfo.columnKey === "return" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.return} />
},
});
}
columns.push(
{
title: t("parts_orders.fields.deliver_by"),
dataIndex: "deliver_by",
@@ -256,7 +262,7 @@ export function PartsOrderListTableComponent({
render: (text, record) => recordActions(record, true),
id: "parts-order-list-table-actions"
}
];
);
const handleTableChange = (pagination, filters, 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 PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
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 {
@@ -83,7 +94,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
</Space>
</Tag>
)}
{!isReturn && (
{!isReturn && !isPartsEntry && (
<Form.Item
name="removefrompartsqueue"
label={t("parts_orders.labels.removefrompartsqueue")}
@@ -92,7 +103,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
<Checkbox />
</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">
<Checkbox />
</Form.Item>
@@ -249,7 +260,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
<Radio disabled={is_quote} value={"p"}>
{t("parts_orders.labels.print")}
</Radio>
{OEConnection.treatment === "on" && !isReturn && (
{OEConnection.treatment === "on" && !isReturn && !isPartsEntry && (
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
)}
</Radio.Group>

View File

@@ -233,7 +233,7 @@ export function PartsQueueListComponent({ bodyshop }) {
title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus",
key: "partsstatus",
render: (text, record) => <JobPartsQueueCount style={{ minWidth: "10rem" }} parts={record.joblines_status} />
render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} />
},
{
title: t("jobs.fields.comment"),

View File

@@ -37,7 +37,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
.filter(
(temp) =>
(!temp.regions ||
(temp.regions && temp.regions[bodyshop.region_config]) ||
temp.regions?.[bodyshop.region_config] ||
(temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)) &&
(!temp.dms || temp.dms === false)
)
@@ -46,7 +46,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
.filter(
(temp) =>
!temp.regions ||
(temp.regions && temp.regions[bodyshop.region_config]) ||
temp.regions?.[bodyshop.region_config] ||
(temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
);
@@ -82,7 +82,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
variables: { id: jobId }
},
{
to: job && job.ownr_ea,
to: job?.ownr_ea,
subject: cards.find((c) => c.key === key)?.subject
},
"e",
@@ -129,7 +129,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
const columns = `repeat(${actions.length}, 1fr)`;
return (
<Col key={item.key} xs={24} sm={12}>
<Col key={item.key} xs={24} sm={24} md={24} lg={24} xl={24}>
<Card hoverable style={{ minHeight: 100 }}>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<div style={{ flex: "1 1 70%", minWidth: 0 }}>

View File

@@ -40,27 +40,26 @@ export function ScheduleCalendarWrapperComponent({
const currentView = search.view || defaultView || "week";
const handleEventPropStyles = (event) => {
const hasColor = Boolean(event?.color?.hex || event?.color);
const { color, block, arrived } = event ?? {};
const hasColor = Boolean(color?.hex || color);
const useBg = currentView !== "agenda";
// Prioritize explicit blocked-day background to ensure red in all themes
let bg;
if (useBg) {
if (event?.block) {
bg = "var(--event-block-bg)";
} else if (hasColor) {
bg = event?.color?.hex ?? event?.color;
} else {
bg = "var(--event-bg-fallback)";
}
bg = block
? "var(--event-block-bg)"
: arrived
? "var(--event-arrived-bg)"
: (color?.hex ?? color ?? "var(--event-bg-fallback)");
}
const usedFallback = !hasColor && !event?.block; // only mark as fallback when not blocked
const usedFallback = !hasColor && !block && !arrived; // only mark as fallback when not blocked or arrived
const classes = [
"imex-event",
event.arrived && "imex-event-arrived",
event.block && "imex-event-block",
arrived && "imex-event-arrived",
block && "imex-event-block",
usedFallback && "imex-event-fallback"
]
.filter(Boolean)

View File

@@ -23,13 +23,24 @@ export default function ShopInfoContainer() {
});
const notification = useNotification();
const combinedFeatureConfig = {
...FEATURE_CONFIGS.general,
...FEATURE_CONFIGS.responsibilitycenters
};
const combineFeatureConfigs = (...configs) =>
(configs || [])
.filter(Boolean)
.flatMap((cfg) => Object.entries(cfg))
.reduce((acc, [featureName, fieldPaths]) => {
if (!Array.isArray(fieldPaths)) return acc;
acc[featureName] = [...(acc[featureName] ?? []), ...fieldPaths];
return acc;
}, {});
const combinedFeatureConfig = combineFeatureConfigs(FEATURE_CONFIGS.general, FEATURE_CONFIGS.responsibilitycenters);
// Use form data preservation for all shop-info features
const { createSubmissionHandler } = useFormDataPreservation(form, data?.bodyshops[0], combinedFeatureConfig);
const { createSubmissionHandler, preserveHiddenFormData } = useFormDataPreservation(
form,
data?.bodyshops[0],
combinedFeatureConfig
);
const handleFinish = createSubmissionHandler((values) => {
setSaveLoading(true);
@@ -51,8 +62,11 @@ export default function ShopInfoContainer() {
});
useEffect(() => {
if (data) form.resetFields();
}, [form, data]);
if (!data) return;
form.resetFields();
// After reset, re-apply hidden field preservation so values aren't wiped
preserveHiddenFormData();
}, [data, form, preserveHiddenFormData]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner />;
@@ -64,7 +78,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

@@ -1183,7 +1183,17 @@ export function ShopInfoGeneral({ form, bodyshop }) {
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
@@ -1294,7 +1304,17 @@ export function ShopInfoGeneral({ form, bodyshop }) {
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
@@ -1483,15 +1503,31 @@ export function ShopInfoGeneral({ form, bodyshop }) {
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.md_to_emails_emails")}
key={`${index}emails`}
name={[field.name, "emails"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
<FormItemEmail email={form.getFieldValue([field.name, "emails"])} />
</Form.Item>
<Space>

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect } from "react";
import { useCallback, useEffect, useMemo } from "react";
import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component";
/**
@@ -8,73 +8,57 @@ import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component
* @param {Object} featureConfig - Configuration object defining which features and their associated fields to preserve
*/
export const useFormDataPreservation = (form, bodyshop, featureConfig) => {
const getNestedValue = (obj, path) => {
return path.reduce((current, key) => current?.[key], obj);
};
// Safe nested getters/setters using path arrays
const getNestedValue = (obj, path) => path?.reduce((acc, key) => acc?.[key], obj);
const setNestedValue = (obj, path, value) => {
const lastKey = path[path.length - 1];
const parentPath = path.slice(0, -1);
const parent = parentPath.reduce((current, key) => {
if (!current[key]) current[key] = {};
return current[key];
const parent = path.slice(0, -1).reduce((curr, key) => {
if (!curr[key] || typeof curr[key] !== "object") curr[key] = {};
return curr[key];
}, obj);
parent[lastKey] = value;
};
const preserveHiddenFormData = useCallback(() => {
const preservationData = {};
let hasDataToPreserve = false;
// Paths for features that are NOT accessible
const disabledPaths = useMemo(() => {
const result = [];
if (!featureConfig) return result;
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
if (hasAccess || !Array.isArray(fieldPaths)) return;
fieldPaths.forEach((p) => Array.isArray(p) && p.length && result.push(p));
});
return result;
}, [featureConfig, bodyshop]);
if (!hasAccess) {
fieldPaths.forEach((fieldPath) => {
const currentValues = form.getFieldsValue();
let value = getNestedValue(currentValues, fieldPath);
const preserveHiddenFormData = useCallback(() => {
const currentValues = form.getFieldsValue();
const preservationData = {};
let hasAny = false;
if (value === undefined || value === null) {
value = getNestedValue(bodyshop, fieldPath);
}
if (value !== undefined && value !== null) {
setNestedValue(preservationData, fieldPath, value);
hasDataToPreserve = true;
}
});
disabledPaths.forEach((path) => {
let value = getNestedValue(currentValues, path);
if (value == null) value = getNestedValue(bodyshop, path);
if (value != null) {
setNestedValue(preservationData, path, value);
hasAny = true;
}
});
if (hasDataToPreserve) {
form.setFieldsValue(preservationData);
}
}, [form, featureConfig, bodyshop]);
if (hasAny) form.setFieldsValue(preservationData);
}, [form, bodyshop, disabledPaths]);
const getCompleteFormValues = () => {
const currentFormValues = form.getFieldsValue();
const completeValues = { ...currentFormValues };
const currentValues = form.getFieldsValue();
const complete = { ...currentValues };
Object.entries(featureConfig).forEach(([featureName, fieldPaths]) => {
const hasAccess = HasFeatureAccess({ featureName, bodyshop });
if (!hasAccess) {
fieldPaths.forEach((fieldPath) => {
let value = getNestedValue(currentFormValues, fieldPath);
if (value === undefined || value === null) {
value = getNestedValue(bodyshop, fieldPath);
}
if (value !== undefined && value !== null) {
setNestedValue(completeValues, fieldPath, value);
}
});
}
disabledPaths.forEach((path) => {
let value = getNestedValue(currentValues, path);
if (value == null) value = getNestedValue(bodyshop, path);
if (value != null) setNestedValue(complete, path, value);
});
return completeValues;
return complete;
};
const createSubmissionHandler = (originalHandler) => {
@@ -103,8 +87,8 @@ export const FEATURE_CONFIGS = {
["md_responsibility_centers", "profits"],
["md_responsibility_centers", "defaults"],
["md_responsibility_centers", "dms_defaults"],
["md_responsibility_centers", "taxes", "itemexemptcode"],
["md_responsibility_centers", "taxes", "invoiceexemptcode"],
["md_responsibility_centers", "taxes"],
["md_responsibility_centers", "cieca_pfl"],
["md_responsibility_centers", "ar"],
["md_responsibility_centers", "refund"],
["md_responsibility_centers", "sales_tax_codes"],

View File

@@ -9,7 +9,6 @@ import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { pageLimit } from "../../utils/config";
import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
@@ -96,13 +95,15 @@ export function SimplifiedPartsJobsListComponent({
key: "status",
ellipsis: true,
sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
sorter: search?.search
? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.parts_active_statuses)
: true,
sortOrder: sortcolumn === "status" && sortorder,
render: (text, record) => {
return record.status || t("general.labels.na");
},
filteredValue: filter?.status || null,
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
filters: bodyshop.md_ro_statuses.parts_statuses.map((s) => {
return { text: s, value: [s] };
}),
onFilter: (value, record) => value.includes(record.status)
@@ -142,31 +143,11 @@ export function SimplifiedPartsJobsListComponent({
sortOrder: sortcolumn === "clm_no" && sortorder,
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => {
return record.clm_total ? (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
) : (
t("general.labels.unknown")
);
}
},
{
title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus",
key: "partsstatus",
render: (text, record) => <JobPartsQueueCount style={{ minWidth: "10rem" }} parts={record.joblines_status} />
render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} />
},
{
title: t("jobs.fields.comment"),

View File

@@ -14,16 +14,18 @@ import VendorsPhonebookAdd from "../vendors-phonebook-add/vendors-phonebook-add.
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(VendorsFormComponent);
export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete, selectedvendor }) {
export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete, selectedvendor, isPartsEntry }) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -57,8 +59,7 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
>
{t("general.actions.delete")}
</Button>
<VendorsPhonebookAdd form={form} disabled={form.isFieldsTouched()} />
{!isPartsEntry && <VendorsPhonebookAdd form={form} disabled={form.isFieldsTouched()} />}
</Space>
}
/>
@@ -148,12 +149,18 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("vendors.fields.discount")} name="discount">
<InputNumber min={0} max={1} precision={2} step={0.01} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber min={0} />
</Form.Item>
{!isPartsEntry && (
<>
<Form.Item label={t("vendors.fields.discount")} name="discount">
<InputNumber min={0} max={1} precision={2} step={0.01} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber min={0} />
</Form.Item>
</>
)}
{
// <Form.Item
// label={t("vendors.fields.cost_center")}
@@ -173,7 +180,7 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
<Form.Item
name="tags"
label={t("vendor.fields.tags")}
label={t("vendors.fields.tags")}
rules={[
{
//message: t("general.validation.required"),

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

@@ -4,6 +4,8 @@ import { getAuth, updatePassword, updateProfile } from "@firebase/auth";
import { getFirestore } from "@firebase/firestore";
import { getMessaging, getToken, onMessage } from "@firebase/messaging";
import { store } from "../redux/store";
import * as amplitude from '@amplitude/analytics-browser';
import posthog from 'posthog-js'
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
initializeApp(config);
@@ -71,25 +73,33 @@ onMessage(messaging, (payload) => {
});
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
const state = stateProp || store.getState();
const eventParams = {
shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
...additionalParams
};
// axios.post("/ioevent", {
// useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
// bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
// operationName: eventName,
// variables: additionalParams,
// dbevent: false,
// env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
// });
// console.log(
// "%c[Analytics]",
// "background-color: green ;font-weight:bold;",
// eventName,
// eventParams
// );
logEvent(analytics, eventName, eventParams);
try {
const state = stateProp || store.getState();
const eventParams = {
shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
...additionalParams
};
// axios.post("/ioevent", {
// useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
// bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
// operationName: eventName,
// variables: additionalParams,
// dbevent: false,
// env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
// });
// console.log(
// "%c[Analytics]",
// "background-color: green ;font-weight:bold;",
// eventName,
// eventParams
// );
logEvent(analytics, eventName, eventParams);
amplitude.track(eventName, eventParams);
posthog.capture(eventName, eventParams);
} finally {
//If it fails, just keep going.
}
};

View File

@@ -14,6 +14,9 @@ import { persistor, store } from "./redux/store";
import reportWebVitals from "./reportWebVitals";
import "./translations/i18n";
import "./utils/CleanAxios";
import * as amplitude from "@amplitude/analytics-browser";
import { PostHogProvider } from "posthog-js/react";
import posthog from "posthog-js";
window.global ||= window;
@@ -23,10 +26,33 @@ registerSW({ immediate: true });
// Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN";
amplitude.init("6228a598e57cd66875cfd41604f1f891", {
defaultTracking: true,
serverUrl: import.meta.env.VITE_APP_AMP_URL
// {
// attribution: {
// excludeReferrers: true,
// initialEmptyValue: true,
// resetSessionOnNewCampaign: true,
// },
// fileDownloads: true,
// formInteractions: true,
// pageViews: {
// trackHistoryChanges: 'all'
// },
// sessions: true
// }
});
posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
autocapture: false,
capture_exceptions: true,
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST
});
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);
const router = sentryCreateBrowserRouter(createRoutesFromElements(<Route path="*" element={<AppContainer />} />));
if (import.meta.env.DEV) {
let styles =
"font-weight: bold; font-size: 50px;color: red; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) ";
@@ -37,7 +63,9 @@ function App() {
return (
<PersistGate loading={<LoadingSpinner message="Restoring your settings..." />} persistor={persistor}>
<Provider store={store}>
<RouterProvider router={router} />
<PostHogProvider client={posthog}>
<RouterProvider router={router} />
</PostHogProvider>
</Provider>
</PersistGate>
);

View File

@@ -1,5 +1,6 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import { useQuery } from "@apollo/client";
import axios from "axios";
import queryString from "query-string";
import { useEffect, useState } from "react";
@@ -16,6 +17,7 @@ import { TemplateList } from "../../utils/TemplateConstants";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
@@ -33,25 +35,22 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
});
const Templates = TemplateList("bill");
const { t } = useTranslation();
const { data: vendorsData } = useQuery(QUERY_ALL_VENDORS);
const columns = [
{
title: t("bills.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
// sortObject: (direction) => {
// return {
// vendor: {
// name: direction
// ? direction === "descend"
// ? "desc"
// : "asc"
// : "desc",
// },
// };
// },
// sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
// sortOrder:
// state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
sortObject: (order) => ({
vendor: { name: order === "descend" ? "desc" : "asc" }
}),
filters: (vendorsData?.vendors || []).map((v) => ({ text: v.name, value: v.id })),
filteredValue: state.filteredInfo.vendorname || null,
onFilter: (value, record) => record.vendorid === value,
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
render: (text, record) => <span>{record.vendor.name}</span>
},
{
@@ -65,20 +64,11 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
// sortObject: (direction) => {
// return {
// job: {
// ro_number: direction
// ? direction === "descend"
// ? "desc"
// : "asc"
// : "desc",
// },
// };
// },
// sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
// sortOrder:
// state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortObject: (order) => ({
job: { ro_number: order === "descend" ? "desc" : "asc" }
}),
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => record.job && <Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number}</Link>
},
{
@@ -175,7 +165,8 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
// Persist filters (including vendorname) and sorting
setState({ ...state, filteredInfo: { ...state.filteredInfo, ...filters }, sortedInfo: sorter });
search.page = pagination.current;
if (sorter && sorter.column && sorter.column.sortObject) {
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));

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

@@ -11,7 +11,6 @@ import { createStructuredSelector } from "reselect";
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 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 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";
@@ -133,9 +132,8 @@ export function SimplifiedPartsJobDetailComponent({ setPrintCenterContext, jobRO
<JobLineUpsertModalContainer />
<PageHeader title={<Space>{job.ro_number || t("general.labels.na")}</Space>} extra={menuExtra} />
<JobsDetailHeader job={job} disabled={true} />
<JobsDetailHeader job={job} />
<Divider type="horizontal" />
<JobProfileDataWarning job={job} />
<FormFieldsChanged form={form} />
<Tabs
defaultActiveKey={search.tab}

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

@@ -49,6 +49,8 @@ import {
validatePasswordResetSuccess
} from "./user.actions";
import UserActionTypes from "./user.types";
import * as amplitude from '@amplitude/analytics-browser';
import posthog from 'posthog-js';
const fpPromise = FingerprintJS.load();
@@ -83,8 +85,6 @@ export function* onCheckUserSession() {
export function* isUserAuthenticated() {
try {
logImEXEvent("redux_auth_check");
const user = yield getCurrentUser();
if (!user) {
yield put(unauthorizedUser());
@@ -92,6 +92,8 @@ export function* isUserAuthenticated() {
}
LogRocket.identify(user.email);
amplitude.setUserId(user.email);
posthog.identify(user.email);
const eulaQuery = yield client.query({
query: QUERY_EULA,
@@ -137,6 +139,7 @@ export function* signOutStart() {
imexshopid: state.user.bodyshop.imexshopid,
type: "messaging"
});
amplitude.reset();
} catch {
console.log("No FCM token. Skipping unsubscribe.");
}
@@ -266,11 +269,11 @@ export function* signInSuccessSaga({ payload }) {
instanceSeg,
...(isParts
? [
InstanceRenderManager({
imex: "ImexPartsManagement",
rome: "RomePartsManagement"
})
]
InstanceRenderManager({
imex: "ImexPartsManagement",
rome: "RomePartsManagement"
})
]
: [])
];
window.$crisp.push(["set", "session:segments", [segs]]);
@@ -295,7 +298,7 @@ export function* signInSuccessSaga({ payload }) {
setUserId(analytics, payload.email);
setUserProperties(analytics, payload);
yield logImEXEvent("redux_sign_in_success");
yield;
}
export function* onSendPasswordResetStart() {
@@ -362,6 +365,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
}
try {
amplitude.setGroup('Shop', payload.shopname);
window.$crisp.push(["set", "user:company", [payload.shopname]]);
if (authRecord[0] && authRecord[0].user.validemail) {
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);

View File

@@ -1249,7 +1249,8 @@
"sizelimit": "The selected items exceed the size limit.",
"sub_status": {
"expired": "The subscription for this shop has expired. Please contact Sales to reactivate.",
"trial-expired": "The trial for this shop has expired. Please contact Sales to reactivate."
"trial-expired": "The trial for this shop has expired. Please contact Sales to reactivate.",
"undefined": "The subscription for this shop is removed. Please contact Sales to reactivate."
},
"submit-for-testing": "Error submitting Job for testing."
},
@@ -3523,6 +3524,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 +3533,7 @@
"app": "",
"bc": {
"simplified-parts-jobs": "Jobs",
"parts": "Jobs",
"parts": "Parts",
"parts_settings": "Settings",
"accounting-payables": "Payables",
"accounting-payments": "Payments",

View File

@@ -1249,7 +1249,8 @@
"sizelimit": "",
"sub_status": {
"expired": "",
"trial-expired": ""
"trial-expired": "",
"undefined": ""
},
"submit-for-testing": ""
},
@@ -3523,6 +3524,9 @@
}
},
"titles": {
"simplified-parts-jobs": "",
"parts": "",
"parts_settings": "",
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",

View File

@@ -1249,7 +1249,8 @@
"sizelimit": "",
"sub_status": {
"expired": "",
"trial-expired": ""
"trial-expired": "",
"undefined": ""
},
"submit-for-testing": ""
},
@@ -3523,6 +3524,9 @@
}
},
"titles": {
"simplified-parts-jobs": "",
"parts": "",
"parts_settings": "",
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",

View File

@@ -181,7 +181,10 @@ const cache = new InMemoryCache({
const client = new ApolloClient({
link: ApolloLink.from(middlewares),
cache,
connectToDevTools: import.meta.env.DEV,
devtools: {
name: "Imex Client",
enabled: import.meta.env.DEV
},
defaultOptions: {
watchQuery: {
fetchPolicy: "network-only",

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

@@ -21,7 +21,7 @@ services:
- redis-node-1-data:/data
- redis-lock:/redis-lock
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 5s
retries: 10
@@ -39,7 +39,7 @@ services:
- redis-node-2-data:/data
- redis-lock:/redis-lock
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 5s
retries: 10
@@ -57,7 +57,7 @@ services:
- redis-node-3-data:/data
- redis-lock:/redis-lock
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 5s
retries: 10
@@ -85,7 +85,7 @@ services:
ports:
- "4566:4566"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
test: [ "CMD", "curl", "-f", "http://localhost:4566/_localstack/health" ]
interval: 10s
timeout: 5s
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 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 parts-estimates --create-bucket-configuration LocationConstraint=ca-central-1
"
# Node App: The Main IMEX API
node-app:

View File

@@ -959,6 +959,7 @@
- enforce_referral
- entegral_configuration
- entegral_id
- external_shop_id
- features
- federal_tax_id
- id
@@ -1012,6 +1013,8 @@
- prodtargethrs
- production_config
- region_config
- rr_configuration
- rr_dealerid
- schedule_end_time
- schedule_start_time
- scoreboard_target
@@ -1035,7 +1038,6 @@
- use_fippa
- use_paint_scale_data
- uselocalmediaserver
- external_shop_id
- website
- workingdays
- zip_post
@@ -1068,6 +1070,7 @@
- enforce_conversion_category
- enforce_conversion_csr
- enforce_referral
- external_shop_id
- federal_tax_id
- id
- inhousevendorid
@@ -1113,6 +1116,7 @@
- phone
- prodtargethrs
- production_config
- rr_configuration
- schedule_end_time
- schedule_start_time
- scoreboard_target
@@ -1131,7 +1135,6 @@
- use_fippa
- use_paint_scale_data
- uselocalmediaserver
- external_shop_id
- website
- workingdays
- zip_post

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "rr_configuration" jsonb
-- null default jsonb_build_object();

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "rr_configuration" jsonb
null default jsonb_build_object();

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "rr_dealierid" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "rr_dealierid" text
null;

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" rename column "rr_dealerid" to "rr_dealierid";

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" rename column "rr_dealierid" to "rr_dealerid";

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 {

1656
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.882.0",
"@aws-sdk/client-elasticache": "^3.882.0",
"@aws-sdk/client-s3": "^3.882.0",
"@aws-sdk/client-secrets-manager": "^3.882.0",
"@aws-sdk/client-ses": "^3.882.0",
"@aws-sdk/credential-provider-node": "^3.882.0",
"@aws-sdk/lib-storage": "^3.882.0",
"@aws-sdk/s3-request-presigner": "^3.882.0",
"@opensearch-project/opensearch": "^2.13.0",
"@socket.io/admin-ui": "^0.5.1",
"@socket.io/redis-adapter": "^8.3.0",
@@ -33,18 +33,18 @@
"aws4": "^1.13.2",
"axios": "^1.11.0",
"better-queue": "^3.8.12",
"bullmq": "^5.58.0",
"bullmq": "^5.58.5",
"chart.js": "^4.5.0",
"cloudinary": "^2.7.0",
"compression": "^1.8.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"crisp-status-reporter": "^1.2.2",
"dd-trace": "^5.63.1",
"dd-trace": "^5.65.0",
"dinero.js": "^1.9.1",
"dotenv": "^17.2.1",
"dotenv": "^17.2.2",
"express": "^4.21.1",
"firebase-admin": "^13.4.0",
"firebase-admin": "^13.5.0",
"graphql": "^16.11.0",
"graphql-request": "^6.1.0",
"intuit-oauth": "^4.2.0",
@@ -62,12 +62,12 @@
"query-string": "7.1.3",
"recursive-diff": "^1.0.9",
"rimraf": "^6.0.1",
"skia-canvas": "^2.0.2",
"skia-canvas": "^3.0.6",
"soap": "^1.3.0",
"socket.io": "^4.8.1",
"socket.io-adapter": "^2.5.5",
"ssh2-sftp-client": "^11.0.0",
"twilio": "^5.8.0",
"twilio": "^5.9.0",
"uuid": "^11.1.0",
"winston": "^3.17.0",
"winston-cloudwatch": "^6.3.0",
@@ -76,8 +76,8 @@
"yazl": "^3.3.1"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"eslint": "^9.33.0",
"@eslint/js": "^9.35.0",
"eslint": "^9.35.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^15.15.0",
"mock-require": "^3.0.3",

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

@@ -6,4 +6,5 @@ exports.kaizen = require("./kaizen").default;
exports.usageReport = require("./usageReport").default;
exports.podium = require("./podium").default;
exports.emsUpload = require("./emsUpload").default;
exports.carfax = require("./carfax").default;
exports.carfax = require("./carfax").default;
exports.vehicletype = require("./vehicletype/vehicletype").default;

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

@@ -0,0 +1,126 @@
[
"PROMASTER 1500",
"PROMASTER 2500",
"PROMASTER CITY",
"NV 1500",
"NV 200",
"NV 2500",
"NV 3500",
"NV1500",
"NV200",
"NV2500",
"NV3500",
"SPRINTER",
"E150 ECONOLINE CARGO VAN",
"E150 ECONOLINE XL",
"E250 ECONOLINE CARGO",
"E250 ECONOLINE CARGO (AMALGAM)",
"E250 ECONOLINE CARGO (INSPECT)",
"E250 ECONOLINE CARGO VAN EXT",
"E250 ECONOLINE SUPER CARGO VAN",
"E350 CUTAWAY VAN",
"E350 ECONO SD CARGO VAN EXT",
"E350 ECONOLINE CARGO VAN",
"E350 ECONOLINE CUTAWAY",
"E350 ECONOLINE SD CARGO VAN",
"E350 ECONOLINE SD XL",
"E350 ECONOLINE SD XL EXT",
"E350 ECONOLINE SD XLT",
"E350 ECONOLINE SD XLT EXT",
"E350 SD CUTAWAY",
"E450",
"E450 ECONOLINE",
"E450 ECONOLINE SD",
"E450 ECONOLINE SD CUTAWAY",
"TRANSIT 150 WB 130 CARGO VAN",
"TRANSIT 150 WB 130 XLT",
"TRANSIT 150 WB 148 CARGO VAN",
"TRANSIT 250 WB 130 CARGO VAN",
"TRANSIT 250 WB 148 CARGO VAN",
"TRANSIT 250 WB 148 EL CARGO",
"TRANSIT 350 WB 148 CARGO VAN",
"TRANSIT 350 WB 148 EL CARGO",
"TRANSIT CONNECT XL CARGO VAN",
"TRANSIT CONNECT XLT CARGO VAN",
"250 TRANSIT",
"CITY EXPRESS LS CARGO VAN",
"CITY EXPRESS LT CARGO VAN",
"EXPRESS 1500",
"EXPRESS 1500 CARGO VAN",
"EXPRESS 1500 LS",
"EXPRESS 1500 LT",
"EXPRESS 2500 CARGO VAN",
"EXPRESS 2500 CARGO VAN EXT",
"EXPRESS 2500 LS",
"EXPRESS 2500 LT",
"EXPRESS 3500",
"EXPRESS 3500 CARGO VAN",
"EXPRESS 3500 CARGO VAN EXT",
"EXPRESS 3500 EXT",
"EXPRESS 3500 LS",
"EXPRESS 3500 LS EXT",
"EXPRESS 3500 LT",
"EXPRESS 3500 LT EXT",
"G3500 EXPRESS CUTAWAY",
"SAVANA 1500 CARGO VAN",
"SAVANA 1500 SL",
"SAVANA 1500 SLE",
"SAVANA 2500",
"2500 SAVANA",
"SAVANA 2500 CARGO VAN",
"SAVANA 2500 CARGO VAN EXT",
"SAVANA 2500 LT",
"SAVANA 2500 SLE",
"SAVANA 3500",
"SAVANA 3500 CARGO VAN",
"SAVANA 3500 CARGO VAN EXT",
"SAVANA 3500 EXT",
"SAVANA 3500 LT EXT",
"SAVANA 3500 SLE EXT",
"SAVANA G3500 CUTAWAY",
"SAVANA G4500 CUTAWAY",
"EXPRESS 1500 LS CARGO VAN",
"G20 SPORTVAN",
"NV 3500 S V8 CARGO VAN",
"E-150",
"E-250",
"E-350",
"E-450",
"E150",
"E250",
"E350",
"TRANSIT",
"CITY",
"CITY EXPRESS",
"EXPRESS",
"EXPRESS 2500",
"G3500",
"SAVANA",
"SAVANA 1500",
"CHEVY EXPRESS G2500",
"CLUBWAGON E350",
"TRANSIT CONNECT",
"SPRINTER 2500",
"TRANSIT 150",
"ECONOLINE E250",
"TRANSIT 250",
"ECONOLINE E350",
"NV3500 HD",
"TRANSIT 350HD",
"ECONOLINE E150",
"E250 ECONOLINE",
"C/V",
"E350 CHSCAB",
"G1500 CHEVY EXPRESS",
"2500 SPRINTER",
"E150 ECONOLINE",
"350 TRANSIT",
"E450 CUTAWAY",
"PROMASTER 3500",
"CHEVY EXPRESS G3500",
"SAVANA G3500",
"1500 PROMASTER",
"2500 EXPRESS",
"3500 EXPRESS",
"3500 SPRINTER"
]

View File

@@ -0,0 +1,33 @@
[
"GRAND CARAVAN",
"GRANDCARAVAN",
"GRAND CARAVAN CREW",
"GRAND CARAVAN CV",
"GRAND CARAVAN CVP",
"GRAND CARAVAN SE",
"GRAND CARAVAN SXT",
"CARAVAN CV",
"SIENNA CE V6",
"SIENNA LE V6",
"SIENNA XLE V6",
"SIENNA",
"ODYSSEY",
"SEDONA",
"PACIFICA (NEW)",
"QUEST",
"CARAVAN",
"MONTANA SV6",
"FREESTAR",
"UPLANDER",
"MONTANA",
"VOYAGER",
"ENTOURAGE",
"PACIFICA",
"CARNIVAL",
"VENTURE",
"SAFARI",
"VANAGON",
"WINDSTAR",
"TOWN&COUNTRY",
"ROUTAN"
]

View File

@@ -0,0 +1,485 @@
[
"EDGE SEL",
"ESCAPE",
"ESCAPE SE",
"ESCAPE SEL",
"ESCAPE XLT V6",
"EXPEDITION",
"EXPEDITION LIMITED",
"EXPEDITION MAX",
"EXPEDITION MAX LIMITED",
"EXPLORER",
"EXCURSION",
"EXPLORER LIMITED",
"EXPLORER PLATINUM ECOBOOST",
"EXPLORER XLT",
"FLEX",
"FLEX SE",
"ECOSPORT",
"ESCAPE HYBRID",
"MUSTANG MACH-E",
"BRONCO",
"BRONCO SPORT",
"TRAILBLAZER",
"BLAZER LT",
"CHEROKEE",
"CHEROKEE CLASSIC",
"CHEROKEE COUNTRY",
"CHEROKEE LIMITED",
"CHEROKEE NORTH",
"CHEROKEE OVERLAND",
"CHEROKEE SPORT",
"CHEROKEE TRAILHAWK",
"CJ",
"CJ7",
"CJ7 RENEGADE",
"COMMANDER",
"COMMANDER LIMITED",
"COMMANDER SPORT",
"COMPASS",
"COMPASS HIGH ALTITUDE",
"COMPASS LATITUDE",
"COMPASS LIMITED",
"COMPASS NORTH",
"COMPASS SPORT",
"COMPASS TRAILHAWK",
"GLADIATOR OVERLAND",
"GLADIATOR RUBICON",
"GRAND CHEROKEE LAREDO",
"GRAND CHEROKEE LIMITED",
"GRAND CHEROKEE OVERLAND",
"GRAND CHEROKEE SE",
"GRAND CHEROKEE SRT",
"GRAND CHEROKEE SRT8",
"GRAND CHEROKEE SUMMIT",
"GRAND CHEROKEE TRACKHAWK",
"GRAND CHEROKEE TRAILHAWK",
"GRAND CHEROKEE",
"GRANDCHEROKEE",
"LIBERTY LIMITED",
"LIBERTY RENEGADE",
"LIBERTY SPORT",
"LIBERTY",
"PATRIOT",
"PATRIOT HIGH ALTITUDE",
"PATRIOT LATITUDE",
"PATRIOT LIMITED",
"PATRIOT NORTH",
"PATRIOT SPORT",
"RENEGADE LIMITED",
"RENEGADE NORTH",
"RENEGADE SPORT",
"RENEGADE TRAILHAWK",
"TJ",
"TJ RUBICON",
"TJ SAHARA",
"TJ SPORT",
"TJ UNLIMITED",
"WRANGLER",
"WRANGLER RUBICON",
"WRANGLER SAHARA",
"WRANGLER SPORT",
"WRANGLER UNLIMITED",
"WRANGLER UNLIMITED 70TH ANNIV",
"WRANGLER UNLIMITED RUBICON",
"WRANGLER UNLIMITED SAHARA",
"WRANGLER UNLIMITED SPORT",
"WRANGLER UNLIMITED X",
"WRANGLER X",
"YJ WRANGLER",
"AVIATOR",
"AVIATOR RESERVE",
"MKC",
"MKC RESERVE",
"MKC SELECT",
"MKT",
"MKT ECOBOOST",
"MKX",
"MKX RESERVE",
"NAUTILUS RESERVE",
"NAUTILUS RESERVE V6",
"NAVIGATOR",
"NAVIGATOR L",
"NAVIGATOR L RESERVE",
"NAVIGATOR L SELECT",
"NAVIGATOR RESERVE",
"PILOT",
"PILOT BLACK EDITION",
"PILOT ELITE",
"PILOT EX",
"PILOT EX-L",
"PILOT GRANITE",
"PILOT LX",
"PILOT SE",
"PILOT SE-L",
"PILOT TOURING",
"DURANGO R/T",
"DURANGO SLT PLUS",
"DURANGO SRT",
"DURANGO",
"JOURNEY",
"JOURNEY CROSSROAD",
"JOURNEY CVP",
"JOURNEY LIMITED",
"JOURNEY R/T",
"JOURNEY SXT",
"NITRO SE",
"NITRO",
"K1500 SUBURBAN",
"SUBURBAN 1500 LT",
"SUBURBAN 1500 LTZ",
"SUBURBAN 1500 PREMIER",
"SUBURBAN 2500 LS",
"TAHOE LT",
"TRAVERSE LS",
"TRAVERSE LT",
"TRAVERSE PREMIER",
"TRAX LT",
"TRAX PREMIER",
"UPLANDER LT EXT",
"SUBURBAN",
"TAHOE",
"TRAVERSE",
"TRAX",
"UPLANDER",
"YUKON",
"YUKON DENALI",
"YUKON XL",
"YUKON XL DENALI",
"EQUINOX LS",
"EQUINOX LT",
"EQUINOX PREMIER",
"EQUINOX",
"RAV4 LE",
"RAV4 XLE",
"HIGHLANDER SPORT V6",
"4RUNNER SR5 V6",
"RAV4",
"RAV4 HYBRID",
"RAV4 XLE HYBRID",
"HIGHLANDER",
"4RUNNER",
"SEQUOIA",
"PATHFINDER SE",
"PATHFINDER SL",
"PATHFINDER",
"MURANO PLATINUM",
"MURANO SV",
"MURANO",
"TUCSON",
"TERRAIN",
"SORENTO",
"EDGE",
"KICKS",
"QASHQAI",
"SANTA FE",
"ARMADA",
"TELLURIDE",
"PALISADE",
"SELTOS",
"TORRENT",
"C-HR",
"SPORTAGE",
"VENZA",
"ACADIA",
"CR-V",
"HR-V",
"CX-5",
"CX-50",
"CX-7",
"CX-9",
"CX-3",
"Q3",
"Q5",
"Q7",
"Q8",
"JUKE SV",
"JUKE",
"ROGUE",
"ROGUE SV",
"XTERRA",
"COROLLA CROSS",
"ACADIA DENALI",
"TAURUS X",
"MACAN",
"FJ CRUISER",
"BRONCO SPORT BADLANDS",
"ESCALADE",
"RX 350",
"KONA",
"MDX",
"RDX",
"COOPER COUNTRYMAN",
"V70",
"OUTLANDER",
"RIO5",
"GLC300 COUPE",
"ENCORE",
"SRX",
"SANTA FE SPORT",
"NX 300",
"WRANGLER UNLIMITE",
"WRANGLER JK UNLIM",
"RANGEROVER EVOQUE",
"CROSSTREK",
"FORESTER",
"TIGUAN",
"XV CROSSTREK",
"ENDEAVOR",
"RX 330",
"ATLAS",
"XC90",
"TOUAREG",
"STELVIO",
"RANGE ROVER SPORT",
"GLE350D",
"EX35",
"RVR",
"MONTERO",
"X-TRAIL",
"GRAND VITARA",
"TRIBUTE",
"X3",
"XC60",
"GLK250 BLUETEC",
"ENVOY",
"ML350 BLUETEC",
"ENVISION",
"FX35",
"X1",
"VENUE",
"TAOS",
"KONA ELECTRIC",
"OUTLANDER PHEV",
"PASSPORT",
"H3",
"EXPLORERSPORTTRAC",
"F-PACE",
"ML320 BLUETEC",
"REGAL SPORTBACK",
"DISCOVERY SPORT",
"RENDEZVOUS",
"XC70",
"COMPASS (NEW)",
"CUBE",
"V60 CROSS COUNTRY",
"QX70",
"X6",
"ELEMENT",
"RX 400H",
"VUE",
"RANGE ROVER VELAR",
"E-PACE",
"RAV4 PRIME",
"LX 570",
"GX 470",
"EX37",
"GLE43",
"NAUTILUS",
"XT6",
"RX 450H",
"ESCALADE ESV",
"OUTLOOK",
"CAYENNE",
"XC90 PLUG-IN",
"MODEL X",
"MODEL Y",
"GLC300",
"SANTA FE HYBRID",
"G63",
"XV CROSSTREK HYBR",
"JX35",
"JIMMY",
"TUCSON HYBRID",
"XC40 ELECTRIC",
"RX 300",
"ML320",
"WRANGLER JK UNLIMITED",
"POLICE INTERCEPTOR UTILITY",
"WRANGLER JK",
"TRIBECA",
"E-TRON SPORTBACK",
"500X",
"RX 350H",
"GL350 BLUETEC",
"WRANGLER UNLIMITED 4XE",
"GV80",
"GL550",
"Q5 E",
"H2 SUV",
"Q5 HYBRID",
"IONIQ 5",
"SQ5 SPORTBACK",
"LEVANTE",
"TONALE",
"GLE43 COUPE",
"GRAND CHEROKEE WK",
"DEFENDER",
"NX 450H+",
"ML400",
"LX 600",
"RX 450HL",
"SORENTO HYBRID",
"NX 350",
"TRACKER",
"GLE450",
"Q5 SPORTBACK",
"CR-V HYBRID",
"LX 470",
"EQS580 SUV",
"H2",
"EV9",
"SORENTO PLUG-IN",
"LYRIQ",
"GLE550",
"RX 500H",
"X1 SAV",
"E-TRON S SPORTBACK",
"ML500",
"GRAND HIGHLANDER HYBRID",
"RS Q8",
"GLS550",
"GLS580",
"IX",
"CAYENNE COUPE",
"SOLTERRA",
"PATHFINDER HYBRID",
"Q8 E-TRON",
"TX 350",
"TX 500H",
"EQUINOX EV",
"NAUTILUS HYBRID",
"TRAVERSE LIMITED",
"CX-70",
"SANTA FE XL",
"RENEGADE",
"QX50",
"ECLIPSE CROSS",
"QX80",
"X5",
"X3",
"X1",
"X4",
"ENCLAVE",
"ENCORE GX",
"CAYENNE HYBRID",
"SOUL",
"GX 460",
"UX 250H",
"XT5",
"GLE53",
"XT4",
"SQ7",
"NX 350H",
"GLK350",
"GLE350",
"NX 300H",
"NX 200T",
"RANGE ROVER EVOQUE",
"GLS450",
"TERRAIN DENALI",
"GRAND CHEROKEE L",
"GLE400",
"TUCSON PLUG-IN",
"BLAZER",
"ASCENT",
"HIGHLANDER HYBRID",
"ATLAS CROSS SPORT",
"XC40",
"VENZA HYBRID",
"GLA45",
"GLB250",
"GRAND HIGHLANDER",
"GV70",
"NIRO",
"NIRO EV",
"GLA250",
"ESCAPE PLUG-IN",
"WAGONEER",
"CX-30",
"QX60",
"GRAND CHEROKEE 4XE",
"SPORTAGE HYBRID",
"EV6",
"TONALE PLUG-IN",
"GLC43 COUPE",
"X2",
"RX 350L",
"HORNET",
"ENVISTA",
"LEVANTE S",
"SPORTAGE PLUG-IN",
"ORLANDO",
"X5 M",
"EXPLORER HYBRID",
"FREESTYLE",
"CORSAIR",
"K1500 YUKON XL",
"RANGE ROVER",
"SUV W/O LABOR",
"ID.4",
"CX-90",
"X7",
"CORSAIR PLUG-IN",
"ESCALADE EXT",
"QX55",
"DISCOVERY",
"BOLT EUV",
"C40 ELECTRIC",
"LR4",
"GRAND WAGONEER",
"XC60 PLUG-IN",
"LR2",
"EQE350 SUV",
"COROLLA CROSS HYBRID",
"SOUL EV",
"GRECALE",
"SUV W/O LABOR",
"QX30",
"SQ5",
"NIRO PLUG-IN",
"BORREGO",
"CX-90 PLUG-IN",
"XL-7",
"SUV W/O LABOR",
"SUV W/O LABOR",
"I-PACE",
"HORNET PLUG-IN",
"UX 300H",
"ML320 CDI",
"VERACRUZ",
"SQ8",
"GLE53 COUPE",
"ZDX",
"9-7X",
"ARIYA",
"ASPEN",
"AVIATOR PLUG-IN",
"B9 TRIBECA",
"BRAVADA",
"ENVOY XL",
"EQB350",
"EQB350 SUV",
"ESCALADE-V",
"E-TRON",
"FX37",
"GL320 CDI",
"GLADIATOR",
"GLC43",
"GLE450 COUPE",
"GLE63",
"GV60",
"MKT TOWN CAR",
"ML350",
"ML550",
"ML63",
"NX 250",
"Q4 E-TRON",
"Q8 E-TRON SPORTBACK",
"QX4",
"QX56",
"SANTA FE PLUG-IN",
"UX 200",
"WAGONEER L",
"XB"
]

View File

@@ -0,0 +1,567 @@
[
"MARK LT",
"F-150",
"F-250",
"F-350",
"F-450",
"F-550",
"F-650",
"F100 PICKUP",
"F150 FX2 SUPERCAB",
"F150 FX4 PICKUP",
"F150 FX4 SUPERCAB",
"F150 FX4 SUPERCREW",
"F150 HARLEY DAVIDSON SUPERCAB",
"F150 HARLEY DAVIDSON SUPERCREW",
"F150 KING RANCH SUPERCREW",
"F150 LARIAT FX4 SUPERCREW",
"F150 LARIAT HARLEY DAVIDSON SC",
"F150 LARIAT KING RANCH SUPCREW",
"F150 LARIAT LIMITED SUPERCREW",
"F150 LARIAT PICKUP",
"F150 LARIAT SUPERCAB",
"F150 LARIAT SUPERCAB (AMALGAM)",
"F150 LARIAT SUPERCREW",
"F150 LARIAT SUPERCREW (AMALGA)",
"F150 LIMITED SUPERCREW",
"F150 PICKUP",
"F150 PLATINUM SUPERCREW",
"F150 RAPTOR SUPERCAB",
"F150 RAPTOR SUPERCREW",
"F150 STX PICKUP",
"F150 STX SUPERCAB",
"F150 SUPERCAB",
"F150 SUPERCREW",
"F150 SUPERCREW (AMALGAMATED)",
"F150 SVT RAPTOR SUPERCAB",
"F150 XL PICKUP",
"F150 XL SUPERCAB",
"F150 XL SUPERCREW",
"F150 XLT LARIAT SUPERCAB",
"F150 XLT PICKUP",
"F150 XLT SUPERCAB",
"F150 XLT SUPERCREW",
"F150 XLT SUPERCREW (AMALGAMAT)",
"F150 XTR SUPERCAB",
"F250 PICKUP",
"F250 SD CREW CAB",
"F250 SD FX4 CREW CAB",
"F250 SD FX4 SUPERCAB",
"F250 SD KING RANCH CREW CAB",
"F250 SD LARIAT CREW CAB",
"F250 SD LARIAT CREW CAB (AMAL)",
"F250 SD LARIAT PICKUP",
"F250 SD LARIAT SUPERCAB",
"F250 SD LIMITED CREW CAB",
"F250 SD PLATINUM CREW CAB",
"F250 SD SUPERCAB",
"F250 SD XL CREW CAB",
"F250 SD XL PICKUP",
"F250 SD XL SUPERCAB",
"F250 SD XLT CREW CAB",
"F250 SD XLT PICKUP",
"F250 SD XLT SUPERCAB",
"F250 SUPERCAB",
"F250 XL CREW CAB",
"F350 CREW CAB",
"F350 PICKUP",
"F350 PICKUP 2WD",
"F350 SD CABELAS CREW CAB",
"F350 SD CREW CAB",
"F350 SD FX4 CREW CAB",
"F350 SD FX4 SUPERCAB",
"F350 SD HARLEY DAVIDSON",
"F350 SD KING RANCH CREW CAB",
"F350 SD LARIAT CREW CAB",
"F350 SD LARIAT CREW CAB (AMAL)",
"F350 SD LARIAT KING RANCH",
"F350 SD LARIAT SUPERCAB",
"F350 SD LIMITED CREW CAB",
"F350 SD PICKUP",
"F350 SD PLATINUM CREW CAB",
"F350 SD SUPERCAB",
"F350 SD XL CREW CAB",
"F350 SD XL PICKUP",
"F350 SD XL SUPERCAB",
"F350 SD XLT CREW CAB",
"F350 SD XLT SUPERCAB",
"F350 SUPER DUTY",
"F350 SUPER DUTY XL",
"F350 XL PICKUP",
"F450",
"F450 Pickup",
"F450 SD KING RANCH CREW CAB",
"F450 SD LARIAT CREW CAB",
"F450 SD PICKUP",
"F450 SD PLATINUM CREW CAB",
"F450 SD XL",
"F450 SD XL CREW CAB",
"F450 SD XL PICKUP",
"F450 SD XLT CREW CAB",
"F450 SUPER DUTY XLT",
"F550",
"F550 SD",
"F550 SD XL",
"F550 SD XL PICKUP",
"F550 SD XLT CREW CAB",
"F550 SD XLT SUPERCAB",
"F550 SUPER DUTY",
"F550 SUPER DUTY XL",
"F550 SUPER DUTY XLT",
"F550 SUPER DUTY XLT CREW CAB",
"F550 XL",
"F650 SD XLT SUPERCAB",
"F68",
"F750 XL",
"RANGER",
"RANGER EDGE SUPERCAB",
"RANGER FX4 SUPERCAB",
"RANGER LARIAT SUPERCREW",
"RANGER SPORT SUPERCAB",
"RANGER STX SUPERCAB",
"RANGER SUPERCAB",
"RANGER XL",
"RANGER XL SUPERCAB",
"RANGER XLT",
"RANGER XLT SUPERCAB",
"RANGER XLT SUPERCREW",
"FRONTIER LE CREW CAB V6",
"FRONTIER NISMO CREW CAB V6",
"FRONTIER NISMO KING CAB V6",
"FRONTIER PRO-4X CREW CAB V6",
"FRONTIER PRO-4X KING CAB V6",
"FRONTIER S KING CAB",
"FRONTIER SC CREW CAB V6",
"FRONTIER SC V6",
"FRONTIER SE CREW CAB V6",
"FRONTIER SE KING CAB V6",
"FRONTIER SL CREW CAB V6",
"FRONTIER SV CREW CAB V6",
"FRONTIER SV KING CAB V6",
"FRONTIER XE KING CAB",
"FRONTIER XE KING CAB V6",
"KING CAB",
"TITAN 5.6 LE CREW CAB",
"TITAN 5.6 LE KING CAB",
"TITAN 5.6 MIDNIGHT CREW CAB",
"TITAN 5.6 PLATINUM RESERVE CC",
"TITAN 5.6 PRO-4X CREW CAB",
"TITAN 5.6 PRO-4X KING CAB",
"TITAN 5.6 S CREW CAB",
"TITAN 5.6 SE CREW CAB",
"TITAN 5.6 SE KING CAB",
"TITAN 5.6 SL CREW CAB",
"TITAN 5.6 SV CREW CAB",
"TITAN 5.6 SV KING CAB",
"TITAN 5.6 XE CREW CAB",
"TITAN 5.6 XE KING CAB",
"TITAN XD PLATINUM CREW CAB",
"TITAN XD PRO-4X CREW CAB",
"TITAN XD S CREW CAB",
"TITAN XD SL CREW CAB",
"TITAN XD SV CREW CAB",
"PICKUP SR5",
"TACOMA",
"TACOMA ACCESS CAB",
"TACOMA DOUBLE CAB V6",
"TACOMA LIMITED DOUBLE CAB V6",
"TACOMA PRERUNNER DOUBLE CAB V6",
"TACOMA PRERUNNER V6 ACCESS CAB",
"TACOMA PRERUNNER XTRACAB",
"TACOMA PRERUNNER XTRACAB V6",
"TACOMA SR5 DOUBLE CAB V6",
"TACOMA SR5 V6 ACCESS CAB",
"TACOMA SR5 V6 XTRACAB",
"TACOMA V6 ACCESS CAB",
"TACOMA XTRACAB",
"TACOMA XTRACAB V6",
"TUNDRA ACCESS CAB V8",
"TUNDRA DOUBLE CAB V8",
"TUNDRA LIMITED ACCESS CAB V8",
"TUNDRA LIMITED SR5 DBLCAB V8",
"TUNDRA LIMITED V8",
"TUNDRA LIMITED V8 CREWMAX",
"TUNDRA LIMITED V8 DOUBLE CAB",
"TUNDRA PLATINUM V8 CREWMAX",
"TUNDRA SR DOUBLE CAB V8",
"TUNDRA SR V8",
"TUNDRA SR5 DOUBLE CAB V8",
"TUNDRA SR5 TRD DOUBLE CAB V8",
"TUNDRA SR5 V8 CREWMAX",
"TUNDRA V8",
"TUNDRA V8 CREWMAX",
"XTRACAB LONG BOX",
"AVALANCHE 1500",
"AVALANCHE 1500 LS",
"AVALANCHE 1500 LS Z71",
"AVALANCHE 1500 LT",
"AVALANCHE 1500 LT Z71",
"AVALANCHE 1500 LTZ",
"C/R 10/1500 4+CAB",
"C/R 10/1500 PICKUP",
"C/R 20/2500 4+CAB",
"C/R 20/2500 PICKUP",
"C3500",
"COLORADO",
"COLORADO EXT CAB",
"COLORADO LS",
"COLORADO LS CREW CAB",
"COLORADO LS EXT CAB",
"COLORADO LT",
"COLORADO LT CREW CAB",
"COLORADO LT EXT CAB",
"COLORADO WT CREW CAB",
"COLORADO WT EXT CAB",
"COLORADO Z71 CREW CAB",
"COLORADO Z71 EXT CAB",
"COLORADO ZR2 CREW CAB",
"COLORADO ZR2 EXT CAB",
"HHR LS PANEL",
"K/V 10/1500 4+CAB",
"K/V 10/1500 PICKUP",
"K/V 20/2500 4+CAB",
"K/V 20/2500 PICKUP",
"K/V 30/3500 4+CAB",
"Pickup K3500",
"Pickup Silverado C2500 HD",
"S10 4+CAB",
"S10 LS 4+CAB",
"SILVERADO 1500",
"SILVERADO 1500 CHEYENNE CREW",
"SILVERADO 1500 CREW CAB",
"SILVERADO 1500 CREW CAB (AMAL)",
"SILVERADO 1500 CUST TRAIL DC",
"SILVERADO 1500 CUSTOM CREW CAB",
"SILVERADO 1500 CUSTOM DC",
"SILVERADO 1500 CUSTOM TRAIL CC",
"SILVERADO 1500 DOUBLE (AMALGA)",
"SILVERADO 1500 EXT CAB",
"SILVERADO 1500 HD LS CREW CAB",
"SILVERADO 1500 HD LT CREW CAB",
"SILVERADO 1500 HIGH COUNTRY CC",
"SILVERADO 1500 HYBRID CREW CAB",
"SILVERADO 1500 LS",
"SILVERADO 1500 LS CREW CAB",
"SILVERADO 1500 LS DOUBLE CAB",
"SILVERADO 1500 LS EXT CAB",
"SILVERADO 1500 LT",
"SILVERADO 1500 LT CC (AMALGAM)",
"SILVERADO 1500 LT CREW CAB",
"SILVERADO 1500 LT DOUBLE CAB",
"SILVERADO 1500 LT EXT CAB",
"SILVERADO 1500 LT TRAIL CC",
"SILVERADO 1500 LT TRAIL DC",
"SILVERADO 1500 LTZ CREW CAB",
"SILVERADO 1500 LTZ DOUBLE CAB",
"SILVERADO 1500 LTZ EXT CAB",
"SILVERADO 1500 RST CREW CAB",
"SILVERADO 1500 RST DOUBLE CAB",
"SILVERADO 1500 SS EXT CAB",
"SILVERADO 1500 WT",
"SILVERADO 1500 WT CREW CAB",
"SILVERADO 1500 WT DOUBLE CAB",
"SILVERADO 1500 WT EXT CAB",
"SILVERADO 2500 EXT CAB",
"SILVERADO 2500 HD",
"SILVERADO 2500 HD CREW CAB",
"SILVERADO 2500 HD EXT CAB",
"SILVERADO 2500 HD HC CREW CAB",
"SILVERADO 2500 HD LS CREW CAB",
"SILVERADO 2500 HD LS EXT CAB",
"SILVERADO 2500 HD LT",
"SILVERADO 2500 HD LT CREW CAB",
"SILVERADO 2500 HD LT DBL CAB",
"SILVERADO 2500 HD LT EXT CAB",
"SILVERADO 2500 HD LTZ CREW CAB",
"SILVERADO 2500 HD LTZ DBL CAB",
"SILVERADO 2500 HD LTZ EXT CAB",
"SILVERADO 2500 HD WT",
"SILVERADO 2500 HD WT CREW CAB",
"SILVERADO 2500 HD WT DBL CAB",
"SILVERADO 2500 HD WT EXT CAB",
"SILVERADO 3500",
"SILVERADO 3500 CREW CAB",
"SILVERADO 3500 CREW CAB (AMAL)",
"SILVERADO 3500 EXT CAB",
"SILVERADO 3500 HC CREW CAB",
"SILVERADO 3500 HD (AMALGAMATE)",
"SILVERADO 3500 LS",
"SILVERADO 3500 LS CREW CAB",
"SILVERADO 3500 LS EXT CAB",
"SILVERADO 3500 LT CREW CAB",
"SILVERADO 3500 LT DOUBLE CAB",
"SILVERADO 3500 LT EXT CAB",
"SILVERADO 3500 LTZ CREW CAB",
"SILVERADO 3500 LTZ EXT CAB",
"SILVERADO 3500 WT CREW CAB",
"Silverado 3500HD",
"B250 SPORTSMAN",
"DAKOTA CLUB CAB",
"DAKOTA LARAMIE V8 CLUB CAB",
"DAKOTA LARAMIE V8 QUAD CAB",
"DAKOTA QUAD CAB",
"DAKOTA SLT CREW CAB",
"DAKOTA SLT EXT CAB",
"DAKOTA SLT PLUS QUAD CAB",
"DAKOTA SLT PLUS V8 CLUB CAB",
"DAKOTA SLT PLUS V8 QUAD CAB",
"DAKOTA SLT QUAD CAB",
"DAKOTA SLT V8 CLUB CAB",
"DAKOTA SLT V8 CREW CAB",
"DAKOTA SLT V8 EXT CAB",
"DAKOTA SLT V8 QUAD CAB",
"DAKOTA SPORT V8",
"DAKOTA SPORT V8 CLUB CAB",
"DAKOTA SPORT V8 QUAD CAB",
"DAKOTA ST CLUB CAB",
"DAKOTA ST QUAD CAB",
"DAKOTA ST V8 QUAD CAB",
"DAKOTA SXT CREW CAB",
"DAKOTA SXT EXT CAB",
"DAKOTA SXT V8 CREW CAB",
"DAKOTA SXT V8 EXT CAB",
"DAKOTA V8 CLUB CAB",
"DAKOTA V8 QUAD CAB",
"RAM 1500",
"RAM 1500 BIG HORN CREW CAB",
"RAM 1500 BIG HORN QUAD CAB",
"RAM 1500 CLUB CAB",
"RAM 1500 CREW CAB (AMALGAMATE)",
"RAM 1500 EXPRESS",
"RAM 1500 LARAMIE CREW (AMALGA)",
"RAM 1500 LARAMIE CREW CAB",
"RAM 1500 LARAMIE LONGHORN CREW",
"RAM 1500 LARAMIE MEGA CAB",
"RAM 1500 LARAMIE QUAD CAB",
"RAM 1500 LARAMIE SLT QUAD CAB",
"RAM 1500 LIMITED CREW CAB",
"RAM 1500 LONGHORN CREW CAB",
"RAM 1500 OUTDOORSMAN CREW CAB",
"RAM 1500 OUTDOORSMAN QC (AMAL)",
"RAM 1500 OUTDOORSMAN QUAD CAB",
"RAM 1500 QUAD CAB",
"RAM 1500 R/T",
"RAM 1500 REBEL CREW CAB",
"RAM 1500 REBEL QUAD CAB",
"RAM 1500 SLT",
"RAM 1500 SLT CREW (AMALGAMATE)",
"RAM 1500 SLT CREW CAB",
"RAM 1500 SLT MEGA CAB",
"RAM 1500 SLT QUAD (AMALGAMATE)",
"RAM 1500 SLT QUAD CAB",
"RAM 1500 SPORT",
"RAM 1500 SPORT CLUB CAB",
"RAM 1500 SPORT CREW CAB",
"RAM 1500 SPORT CREW CAB (AMAL)",
"RAM 1500 SPORT QUAD CAB",
"RAM 1500 ST",
"RAM 1500 ST CREW CAB",
"RAM 1500 ST QUAD CAB",
"RAM 1500 SXT CREW CAB",
"RAM 1500 SXT QUAD CAB",
"RAM 1500 TRADESMAN CREW CAB",
"RAM 1500 TRADESMAN QUAD CAB",
"RAM 1500 TRX QUAD CAB",
"RAM 2500",
"RAM 2500 BIG HORN CREW CAB",
"RAM 2500 BIG HORN MEGA CAB",
"RAM 2500 CLUB CAB",
"RAM 2500 LARAMIE CREW CAB",
"RAM 2500 LARAMIE LONGHORN CREW",
"RAM 2500 LARAMIE LONGHORN MEGA",
"RAM 2500 LARAMIE MEGA CAB",
"RAM 2500 LARAMIE QUAD CAB",
"RAM 2500 LARAMIE SLT",
"RAM 2500 LARAMIE SLT QUAD CAB",
"RAM 2500 LIMITED CREW CAB",
"RAM 2500 OUTDOORSMAN CREW CAB",
"RAM 2500 POWER WAGON CREW CAB",
"RAM 2500 QUAD CAB",
"RAM 2500 SLT",
"RAM 2500 SLT CREW CAB",
"RAM 2500 SLT MEGA CAB",
"RAM 2500 SLT QUAD CAB",
"RAM 2500 SLT QUAD CAB (AMALGA)",
"RAM 2500 SPORT QUAD CAB",
"RAM 2500 ST",
"RAM 2500 ST CREW CAB",
"RAM 2500 ST QUAD CAB",
"RAM 2500 SXT QUAD CAB",
"RAM 2500 TRADESMAN",
"RAM 2500 TRADESMAN CREW CAB",
"RAM 2500 TRX CREW CAB",
"RAM 2500 TRX QUAD CAB",
"RAM 3500",
"RAM 3500 4WD",
"RAM 3500 BIG HORN CREW CAB",
"RAM 3500 CREW CAB",
"RAM 3500 CREW CAB (AMALGAMATE)",
"RAM 3500 LARAMIE CREW CAB",
"RAM 3500 LARAMIE LONGHORN CREW",
"RAM 3500 LARAMIE LONGHORN MEGA",
"RAM 3500 LARAMIE MEGA CAB",
"RAM 3500 LARAMIE QUAD CAB",
"RAM 3500 LARAMIE SLT",
"RAM 3500 LARAMIE SLT QUAD CAB",
"RAM 3500 LIMITED MEGA CAB",
"RAM 3500 LONGHORN CREW CAB",
"RAM 3500 QUAD CAB",
"RAM 3500 SLT",
"RAM 3500 SLT CREW CAB",
"RAM 3500 SLT MEGA CAB",
"RAM 3500 SLT QUAD CAB",
"RAM 3500 SPORT QUAD CAB",
"RAM 3500 ST",
"RAM 3500 ST CREW CAB",
"RAM 3500 ST QUAD CAB",
"RAM 3500 TRX QUAD CAB",
"RAM 4500",
"RAM 4500 CREW CAB",
"RAM 5500",
"RAM 5500 CREW CAB",
"W250 TURBO DIESEL",
"C Series 5500",
"C/R 1500 4+CAB",
"C/R 1500 PICKUP",
"C/R 1500 SIERRA SL EXT CAB",
"C/R 3500",
"C/R 3500 PICKUP",
"CANYON ALL TERRAIN CREW CAB",
"CANYON CREW CAB",
"CANYON DENALI CREW CAB",
"CANYON EXT CAB",
"CANYON SL",
"CANYON SL EXT CAB",
"CANYON SLE",
"CANYON SLE CREW CAB",
"CANYON SLE EXT CAB",
"CANYON SLT CREW CAB",
"CANYON SLT CREW CAB (AMALGAMA)",
"K/V 1500 4+CAB",
"K/V 1500 PICKUP",
"K/V 2500 4+CAB",
"K/V 2500 PICKUP",
"K/V 3500 SIERRA SL CREW CAB",
"K/V 3500 SIERRA SLE CREW CAB",
"SIERRA 1500 AT4 CREW CAB",
"SIERRA 1500 AT4 DOUBLE CAB",
"SIERRA 1500 CREW CAB",
"SIERRA 1500 CREW CAB (AMALGAM)",
"SIERRA 1500 DENALI CREW CAB",
"SIERRA 1500 DENALI EXT CAB",
"SIERRA 1500 DOUBLE CAB",
"SIERRA 1500 ELEVATION CREW CAB",
"SIERRA 1500 ELEVATION DC",
"SIERRA 1500 EXT CAB",
"SIERRA 1500 HD CREW CAB",
"SIERRA 1500 HD SLE CREW CAB",
"SIERRA 1500 HD SLT CREW CAB",
"SIERRA 1500 NEVADA EDITION",
"SIERRA 1500 PICKUP",
"SIERRA 1500 SL CREW CAB",
"SIERRA 1500 SL EXT CAB",
"SIERRA 1500 SL PICKUP",
"SIERRA 1500 SLE CREW CAB",
"SIERRA 1500 SLE DC (AMALGAMAT)",
"SIERRA 1500 SLE DOUBLE CAB",
"SIERRA 1500 SLE EXT CAB",
"SIERRA 1500 SLE EXT CAB (AMAL)",
"SIERRA 1500 SLE PICKUP",
"SIERRA 1500 SLT CREW (AMALGAM)",
"SIERRA 1500 SLT CREW CAB",
"SIERRA 1500 SLT DOUBLE CAB",
"SIERRA 1500 SLT EXT CAB",
"SIERRA 1500 WT CREW CAB",
"SIERRA 1500 WT EXT CAB",
"SIERRA 1500 WT PICKUP",
"SIERRA 2500 EXT CAB",
"SIERRA 2500 HD AT4 CREW CAB",
"SIERRA 2500 HD CREW CAB",
"SIERRA 2500 HD DENALI CREW CAB",
"SIERRA 2500 HD DOUBLE CAB",
"SIERRA 2500 HD EXT CAB",
"SIERRA 2500 HD PICKUP",
"SIERRA 2500 HD SL EXT CAB",
"SIERRA 2500 HD SL PICKUP",
"SIERRA 2500 HD SLE CREW CAB",
"SIERRA 2500 HD SLE DOUBLE CAB",
"SIERRA 2500 HD SLE EXT CAB",
"SIERRA 2500 HD SLE PICKUP",
"SIERRA 2500 HD SLT CREW CAB",
"SIERRA 2500 HD SLT DOUBLE CAB",
"SIERRA 2500 HD SLT EXT CAB",
"SIERRA 2500 HD WT CREW CAB",
"SIERRA 2500 HD WT DOUBLE CAB",
"SIERRA 2500 HD WT EXT CAB",
"SIERRA 2500 HD WT PICKUP",
"SIERRA 2500 SLE EXT CAB",
"SIERRA 3500 AT4 CREW CAB",
"SIERRA 3500 CREW CAB",
"SIERRA 3500 DENALI CREW CAB",
"SIERRA 3500 EXT CAB",
"SIERRA 3500 PICKUP",
"SIERRA 3500 SL CREW CAB",
"SIERRA 3500 SLE",
"SIERRA 3500 SLE CREW CAB",
"SIERRA 3500 SLE EXT CAB",
"SIERRA 3500 SLT CREW CAB",
"SIERRA 3500 WT CREW CAB",
"SONOMA",
"SONOMA CREW CAB",
"SONOMA EXT CAB",
"1500",
"1500 Classic",
"Pickup 1500",
"Pickup 3500",
"ProMaster 1500",
"RIDGELINE",
"RIDGELINE BLACK EDITION",
"RIDGELINE DX",
"RIDGELINE EX-L",
"RIDGELINE LX",
"RIDGELINE RT",
"RIDGELINE RTL",
"RIDGELINE RTS",
"RIDGELINE RTX",
"RIDGELINE SE",
"RIDGELINE SPORT",
"RIDGELINE TOURING",
"RIDGELINE VP",
"TITAN",
"TACOMA",
"TUNDRA",
"AVALANCE",
"COLORADO",
"SILVERADO",
"SILVERADO 1500",
"SILVERADO 2500",
"SILVERADO 3500",
"DAKOTA",
"RAM 1500",
"RAM 2500",
"RAM 3500",
"RAM 4500",
"RAM 5500",
"CANYON",
"SIERRA 1500",
"SIERRA 2500",
"SIERRA 3500",
"SONOMA",
"1500"
]

View File

@@ -0,0 +1,39 @@
const logger = require("../../utils/logger");
const TrucksList = require("./trucks.json");
const CargoVanList = require("./cargovans.json");
const PassengerVanList = require("./passengervans.json");
const SuvList = require("./suvs.json");
const vehicletype = async (req, res) => {
try {
const { model } = req.body;
if (!model || model.trim() === "") {
res.status(400).json({ success: false, error: "Please provide a model" });
} else {
const type = getVehicleType(model.trim())
res.status(200).json({ success: true, ...type });
}
} catch (error) {
logger.log("vehicletype-error", "ERROR", req?.user?.email, null, {
error: error.message,
stack: error.stack
});
res.status(500).json({ error: error.message, stack: error.stack });
}
};
function getVehicleType(model) {
const inTrucks = TrucksList.includes(model.toUpperCase());
const inPV = PassengerVanList.includes(model.toUpperCase());
const inSuv = SuvList.includes(model.toUpperCase());
const inCv = CargoVanList.includes(model.toUpperCase());
if (inTrucks) return { type: "TK", match: true };
else if (inPV) return { type: "PC", match: true };
else if (inSuv) return { type: "SUV", match: true };
else if (inCv) return { type: "VN", match: true };
else return { type: "PC", match: false };
}
exports.default = vehicletype;

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

Some files were not shown because too many files have changed in this diff Show More