Compare commits

...

131 Commits

Author SHA1 Message Date
Allan Carr
e202bf9a89 IO-3027 Add in bodyshop.id to logging in SFTP
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-21 16:18:23 -08:00
Allan Carr
8ad1dd83c6 IO-3027 Datapump Refactor
Remove Batch and sftp transfer for each shop during processing

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-21 14:24:31 -08:00
Allan Carr
7cbabf8697 IO-3027 Turn Off SFTP Logging for Production ENV
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-18 12:02:08 -08:00
Allan Carr
78678dd3dc IO-3027 Datapumps Refactor
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-15 10:04:03 -08:00
Allan Carr
ce9a77efcf IO-3027 Datapumps Refactor
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-14 16:15:17 -08:00
Dave Richer
357d916e0a Merged in release/2024-11-15 (pull request #1908)
[DO NOT MERGE] - Release/2024 11 15

Approved-by: Patrick Fic
2024-11-13 00:30:22 +00:00
Dave Richer
6ed12ebe7d Merged in feature/IO-3026-Enhanced-Notifications (pull request #1909)
feature/IO-3026-Enhanced-Notifications - final revisions
2024-11-12 22:52:13 +00:00
Dave Richer
6703bc025d feature/IO-3026-Enhanced-Notifications - final revisions
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-12 14:51:50 -08:00
Dave Richer
387dac6779 Merged in feature/IO-3026-Enhanced-Notifications (pull request #1906)
Feature/IO-3026 Enhanced Notifications
2024-11-12 22:23:35 +00:00
Dave Richer
6f454dd4cb feature/IO-3026-Enhanced-Notifications - final revisions
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-12 14:20:49 -08:00
Dave Richer
1440a60228 feature/IO-3026-Enhanced-Notifications - Initial commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-12 12:31:46 -08:00
Allan Carr
f2aa3960aa Merged in release/2024-11-08 (pull request #1905)
Release/2024 11 08 IO-2921 IO-3025
2024-11-09 08:37:27 +00:00
Allan Carr
06508f3ad8 Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1903)
Feature/IO-2921 CARSTAR Canada Chatter Integration
2024-11-09 08:33:32 +00:00
Allan Carr
8d4195b596 IO-2921 Adjust SFTP setup
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-09 00:35:18 -08:00
Allan Carr
9e190e7fb7 Merged in feature/IO-3025-Autohouse-Datapump-Refactor (pull request #1902)
IO-3025 Adjust for promise and change processing
2024-11-09 08:32:49 +00:00
Allan Carr
5cbf00b0c8 IO-3025 Adjust for promise and change processing
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-09 00:32:51 -08:00
Allan Carr
655aeb86fc IO-2921 Adjust for Promise and change processing
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-08 23:43:18 -08:00
Allan Carr
225549275d Merged in release/2024-11-08 (pull request #1901)
Release/2024 11 08 IO-2921 IO-3025
2024-11-09 06:33:14 +00:00
Allan Carr
f0717b8b36 IO-2921 Shift Email outside of Batch
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-08 22:30:22 -08:00
Allan Carr
78771ae750 IO-3025 Shift Email send to outside of batch
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-08 22:28:02 -08:00
Allan Carr
0389908398 Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1899)
IO-2921 Shift Email outside of Batch
2024-11-09 06:27:57 +00:00
Allan Carr
54bee763df Merged in feature/IO-3025-Autohouse-Datapump-Refactor (pull request #1898)
IO-3025 Shift Email send to outside of batch
2024-11-09 06:25:39 +00:00
Dave Richer
1117a94930 Merged in release/2024-11-08 (pull request #1897)
release/2024-11-08 - Small fix to font script
2024-11-09 05:21:03 +00:00
Dave Richer
5fbfb992c7 release/2024-11-08 - Small fix to font script
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-08 21:18:58 -08:00
Dave Richer
87b3b65f3e Merged in release/2024-11-08 (pull request #1893)
Release/2024-11-08 into master-AIO - IO-2921, IO-2969, IO-3001, IO-3015, IO-3017, IO-3018, IO-3025

Approved-by: Allan Carr
2024-11-09 04:52:01 +00:00
Allan Carr
9970190909 Merged in feature/IO-3025-Autohouse-Datapump-Refactor (pull request #1894)
IO-3025 Autohouse Datapump Refactor
2024-11-09 03:31:37 +00:00
Allan Carr
8eee371a90 IO-3025 Autohouse Datapump Refactor
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-08 19:30:33 -08:00
Dave Richer
ba97b1efef Merged in feature/IO-3015-addl-prod-indexes (pull request #1891)
Feature/IO-3015 addl prod indexes
2024-11-08 17:57:47 +00:00
Allan Carr
8d8887c28e Merged in feature/IO-3017-Lifecycle-Average-Time (pull request #1889)
IO-3017 Lifecycle Average Time

Approved-by: Dave Richer
2024-11-08 17:33:31 +00:00
Dave Richer
3b19432974 feature/IO-3017-Lifecycle-Average-Time - Small fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-08 09:32:58 -08:00
Allan Carr
a14b2340b0 IO-3017 Lifecycle NaN prevention
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-08 09:24:58 -08:00
Allan Carr
624f8e77cb IO-3017 Lifecycle Average Time
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-07 13:56:53 -08:00
Dave Richer
fb624c817d Merged in feature/IO-2969-Fonts-For-Production (pull request #1888)
Feature/IO-2969 Fonts For Production into release
2024-11-07 20:42:26 +00:00
Dave Richer
c2b4b66ed1 hotfix/IO-2969-Fonts-For-Production
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-07 12:40:13 -08:00
Dave Richer
ffec03ab6c Merge remote-tracking branch 'origin/master-AIO' into hotfix/IO-2969-Fonts-For-Production 2024-11-07 12:37:01 -08:00
Allan Carr
552163d7b9 IO-2921 Upload directory
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-06 15:53:51 -08:00
Allan Carr
db1f59578c Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1887)
IO-2921 Re-factor as batch and get docker compose dev working for sftp testing

Approved-by: Dave Richer
2024-11-06 23:51:46 +00:00
Allan Carr
8ec5831ec5 IO-2921 Re-factor as batch and get docker compose dev working for sftp testing
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-06 15:42:14 -08:00
Allan Carr
0146ac5b7b Merged in feature/IO-3018-QBO-Standarize-name (pull request #1886)
IO-3018 QBO Standardize name

Approved-by: Dave Richer
2024-11-06 16:37:47 +00:00
Allan Carr
a603e5c0b8 Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1885)
IO-2921 Adjustment for SFTP Private Key

Approved-by: Dave Richer
2024-11-06 16:36:52 +00:00
Allan Carr
338906e288 IO-3018 QBO Standardize name
Trim StandardizeName

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-05 11:53:42 -08:00
Allan Carr
542997b1a7 IO-2921 Adjustment for SFTP Private Key
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-05 11:02:18 -08:00
Dave Richer
5fce548666 Merged in release/2024-11-01 (pull request #1884)
Release/2024-11-01 into master-AIO - IO-2921, IO-3006, IO-3008, IO-3009, IO-3010
2024-11-02 15:14:06 +00:00
Dave Richer
80322caad0 release/2024-11-01 - Update Trigger for job_updated - Make the callback work with old and new Hasura
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-02 08:11:22 -07:00
Patrick Fic
56472d24d9 IO-3015 add additional indexs. 2024-11-01 21:33:03 -07:00
Patrick Fic
db5dcc271d IO-30015 add new indexes to production. 2024-11-01 20:35:44 -07:00
Allan Carr
73ab02225e IO-2921 Adjustment to getting Secret
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-01 17:55:33 -07:00
Allan Carr
83a1b7690d Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1881)
IO-2921 Adjustment to getting Secret
2024-11-02 00:54:56 +00:00
Allan Carr
c9e28b1ed2 Merge branch 'master-AIO' into feature/IO-2921-CARSTAR-Canada-Chatter-Integration
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-01 17:52:54 -07:00
Allan Carr
c25c66d00f Merged in feature/IO-3009-Clear-Dates (pull request #1879)
IO-3009 Correction for nulls
2024-11-01 17:16:23 +00:00
Allan Carr
d319ab49d4 IO-3009 Correction for nulls
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-01 10:18:14 -07:00
Allan Carr
a069989ea7 Merged in feature/IO-3014-Timeticket-UI-Sort (pull request #1876)
IO-3014 Change Polling Intervals

Approved-by: Dave Richer
2024-10-31 18:13:51 +00:00
Dave Richer
8e3aa186cb Merged in hotfix/2024-10-31-Database-Issues (pull request #1877)
Hotfix/2024 10 31 Database Issues into master-AIO - IO-3012 IO-3009 IO-3014
2024-10-31 18:06:08 +00:00
Allan Carr
01c55d6277 IO-3014 Change Polling Intervals
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-31 11:04:05 -07:00
Dave Richer
3438907d8d Merge remote-tracking branch 'origin/feature/IO-3014-Timeticket-UI-Sort' into hotfix/2024-10-31-Database-Issues 2024-10-31 11:02:09 -07:00
Allan Carr
ae020b651e IO-3014 Further Query Refinements for T/T
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-31 10:56:05 -07:00
Allan Carr
d22988df15 Merged in feature/IO-3014-Timeticket-UI-Sort (pull request #1875)
IO-3014 TimeTicket UI Sort

Approved-by: Dave Richer
2024-10-31 17:54:36 +00:00
Dave Richer
8136a56ad2 Merge remote-tracking branch 'origin/feature/IO-3014-Timeticket-UI-Sort' into hotfix/2024-10-31-Database-Issues 2024-10-31 10:53:56 -07:00
Allan Carr
830f6c0eea IO-3014 TimeTicket UI Sort
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-31 10:48:40 -07:00
Dave Richer
4c1849289a Merge remote-tracking branch 'origin/feature/IO-3014-Timeticket-UI-Sort' into hotfix/2024-10-31-Database-Issues 2024-10-31 10:46:21 -07:00
Dave Richer
c45a4780e3 Merge remote-tracking branch 'origin/feature/IO-3012-Remove-Sort-for-SB-TimeTickets-Query' into hotfix/2024-10-31-Database-Issues 2024-10-31 10:34:56 -07:00
Allan Carr
d4adc4c1aa Merged in feature/IO-3009-Clear-Dates (pull request #1872)
IO-3009 Clear Dates

Approved-by: Dave Richer
2024-10-31 17:06:38 +00:00
Allan Carr
d9e71423f5 Merged in feature/IO-3010-Task-Table-UI-Mods (pull request #1873)
IO-3010 Task Table UI refactor

Approved-by: Dave Richer
2024-10-31 17:06:02 +00:00
Allan Carr
6cac0f9594 IO-3010 Task Table UI refactor
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-30 17:38:09 -07:00
Allan Carr
2ab4615642 IO-3009 Clear Dates
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-30 12:48:27 -07:00
Dave Richer
dd5961d419 release/2024-11-01 - Update Trigger for job_updated
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-30 12:07:50 -07:00
Allan Carr
8190958ba3 Merged in feature/IO-3012-Remove-Sort-for-SB-TimeTickets-Query (pull request #1871)
IO-3012 Remove Sort from SB Timeticket Query

Approved-by: Dave Richer
2024-10-30 19:03:18 +00:00
Allan Carr
77e009f316 IO-3012 Remove Sort from SB Timeticket Query
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-30 11:19:46 -07:00
Dave Richer
2b2738a8d1 Merge branch 'release/2024-11-01' of bitbucket.org:snaptsoft/bodyshop into release/2024-11-01 2024-10-30 09:38:56 -07:00
Dave Richer
3d10c9da7f release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-30 09:38:27 -07:00
Allan Carr
e82c77d119 Merged in feature/IO-3008-Save-&-New-Time-Ticket (pull request #1868)
IO-3008 Save and New Flat Rate value

Approved-by: Dave Richer
2024-10-30 16:12:21 +00:00
Allan Carr
855a78be05 Merged in feature/IO-3006-CDK-PBS-Error-Log-INSERT_EXPORT_LOG (pull request #1867)
IO-3006 CDK PBS Error Log on INSERT_EXPORT_LOG

Approved-by: Dave Richer
2024-10-30 16:11:49 +00:00
Dave Richer
a29e840797 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-30 09:09:16 -07:00
Allan Carr
1b30c1ab58 IO-3008 Save and New Flat Rate value
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-29 20:03:58 -07:00
Allan Carr
80f235f12e IO-3006 CDK PBS Error Log on INSERT_EXPORT_LOG
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-29 13:33:40 -07:00
Dave Richer
9b67148522 Merged in release/2024-11-01 (pull request #1866)
Release/2024-11-01 into master-AIO - IO-2979 - Misc Logger related fixes
2024-10-29 20:10:02 +00:00
Dave Richer
6b501e4619 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-29 11:02:08 -07:00
Dave Richer
42f1d6fa13 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-29 10:53:09 -07:00
Dave Richer
3f247a9227 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-29 09:54:45 -07:00
Dave Richer
63b914731b release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-29 08:25:54 -07:00
Dave Richer
23f8f69bbe release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-29 08:19:58 -07:00
Dave Richer
fc3ea2bdf8 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-29 07:37:40 -07:00
Dave Richer
96e970faf7 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 18:16:48 -07:00
Dave Richer
c133195607 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 17:48:43 -07:00
Dave Richer
d75ea2b1a6 release/2024-11-01 - Misc fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 17:38:46 -07:00
Dave Richer
ec0fd840e4 Merged in feature/IO-2979-DST (pull request #1856)
IO-2979-DST into release/2024-11-01 - DST Related Fixes
2024-10-28 21:05:57 +00:00
Dave Richer
971a81fc27 feature/IO-2979-DST - Finish DST Stuff
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 14:04:14 -07:00
Dave Richer
19050d31f7 Merged in release/2024-11-01 (pull request #1853)
Release/2024-11-01 into master-AIO  - Fix Logging Issues
2024-10-28 20:39:28 +00:00
Dave Richer
e605433379 Merge remote-tracking branch 'origin/release/2024-11-01' into feature/IO-2979-DST 2024-10-28 13:10:08 -07:00
Dave Richer
b9ebb70b7a release/2024-11-01 - Remove Trace Log Level
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 13:06:47 -07:00
Dave Richer
79ed6f2388 release/2024-11-01 - Remove Trace Log Level
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 12:24:40 -07:00
Dave Richer
785449a986 release/2024-11-01 - Remove Trace Log Level
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 12:03:48 -07:00
Dave Richer
0b7d469e0e feature/IO-2979-DST - Finish DST
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 11:55:13 -07:00
Dave Richer
a57156756e feature/IO-2979-DST - Normalize usages of dayjs to dayjs not day, move locale hook
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 11:03:30 -07:00
Dave Richer
c4c30d98d4 release/2024-11-01 - Adjust hostname check
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 10:07:10 -07:00
Dave Richer
d4e8803b13 release/2024-11-01 - Adjust client body buffer size
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 09:47:27 -07:00
Dave Richer
1f2786ddec release/2024-11-01 - Fix some log things
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-28 09:31:37 -07:00
Dave Richer
e90cda07e4 Merged in release/2024-10-25 (pull request #1848)
Release/2024-10-25 - IO-2921, IO-2966, IO-2973, IO-2974, IO-2978, IO-2992, IO-2996, IO-2998
2024-10-26 04:48:05 +00:00
Dave Richer
9793daa04c release/2024-10-25 - Add The local email viewer to the reference folder.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-25 11:58:50 -07:00
Allan Carr
117ced8fe7 Merged in feature/IO-2973-Created-By-Tasks (pull request #1846)
IO-2973 Created By Tasks

Approved-by: Dave Richer
2024-10-25 18:53:11 +00:00
Dave Richer
e7909205d1 feature/IO-2973-Created-By-Tasks - Merge in release, fix conflicts
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-25 11:50:11 -07:00
Dave Richer
18028a70ab feature/IO-2973-Created-By-Tasks - Merge in release, fix conflicts
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-25 11:22:59 -07:00
Dave Richer
eeb8d8d26f release/2024-10-25 - Clean up referenceDocuments
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-25 11:21:10 -07:00
Allan Carr
23659fc412 IO-2973 Created By Tasks
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-25 11:19:40 -07:00
Patrick Fic
ba65057782 Merged in feature/IO-2974-add-comment-to-schedule (pull request #1842)
IO-2974 add comment to schedule.

Approved-by: Dave Richer
2024-10-25 17:03:06 +00:00
Dave Richer
60a859cac8 Merged in feature/IO-2998-enhanced-api-logging (pull request #1843)
feature/IO-2998-enhanced-api-logging - Missing package
2024-10-25 15:38:46 +00:00
Dave Richer
0cfe26093c feature/IO-2998-enhanced-api-logging - Missing package
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-25 08:37:56 -07:00
Patrick Fic
d085a9c7c9 IO-2974 add comment to schedule. 2024-10-25 08:33:17 -07:00
Dave Richer
ec518a0593 Merged in feature/IO-2998-enhanced-api-logging (pull request #1840)
feature/IO-2998-enhanced-api-logging - Missing package
2024-10-25 15:30:26 +00:00
Dave Richer
1cd64ab6f1 feature/IO-2998-enhanced-api-logging - Missing package
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-25 08:29:51 -07:00
Dave Richer
26836f662a Merged in feature/IO-2998-enhanced-api-logging (pull request #1838)
feature/IO-2998-enhanced-api-logging - Finish
2024-10-25 15:12:24 +00:00
Dave Richer
111f280674 feature/IO-2998-enhanced-api-logging - Finish
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-25 08:11:33 -07:00
Dave Richer
6e88faa9d8 Merged in feature/IO-2998-enhanced-api-logging (pull request #1836)
Feature/IO-2998 into Release/2024-10-25
2024-10-25 14:11:26 +00:00
Dave Richer
1ca8b2a78d feature/IO-2998-enhanced-api-logging - Finish
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-24 14:36:35 -07:00
Dave Richer
cd2a7cad7f feature/IO-2998-enhanced-api-logging - Finish
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-24 14:21:35 -07:00
Dave Richer
ed16156957 Merge remote-tracking branch 'origin/release/2024-10-25' into feature/IO-2998-enhanced-api-logging 2024-10-24 13:18:21 -07:00
Patrick Fic
8dc1f7e08f IO-2998 remove graylog and additional erroneous console logs. 2024-10-24 12:18:39 -07:00
Patrick Fic
2d3c13c587 IO-2998 Add winston and replace logger.js 2024-10-24 11:50:54 -07:00
Allan Carr
5486907639 Merged in feature/IO-2978-Production-not-in-Production-Status (pull request #1834)
IO-2978 Production not in Production Status

Approved-by: Dave Richer
2024-10-24 16:49:59 +00:00
Allan Carr
9233cef23a IO-2978 Production not in Production Status
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-23 17:02:45 -07:00
Dave Richer
c16eafe892 Merged in feature/IO-2996-Package-Updates-Docker-Debugging (pull request #1832)
feature/IO-2996-Package-Updates-Docker-Debugging - Maintenance
2024-10-23 17:03:44 +00:00
Allan Carr
4201f61548 Merged in feature/IO-2966-Contract-Create-Page-Leave-Warning (pull request #1831)
IO-2966 Contract Create Page Leave Warning

Approved-by: Dave Richer
2024-10-23 14:58:22 +00:00
Allan Carr
d04fc76840 IO-2966 Contract Create Page Leave Warning
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-22 17:48:40 -07:00
Allan Carr
0f84adc752 IO-2966 Contract Create Page Leave Warning
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-22 17:35:54 -07:00
Allan Carr
fbefd80959 Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1830)
IO-2921 CHATTER correct secertmanager call from undefined variable to string

Approved-by: Dave Richer
2024-10-22 15:08:11 +00:00
Allan Carr
6a691b54c8 Merged in feature/IO-2992-Bill-Line-UI-Table (pull request #1829)
IO-2992 Bill Line column Width with Word Breaks

Approved-by: Dave Richer
2024-10-22 15:07:43 +00:00
Allan Carr
fc75717d32 IO-2921 CHATTER correct secertmanager call from undefined variable to string
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-21 12:43:20 -07:00
Allan Carr
1459c6e993 Merge branch 'master-AIO' into feature/IO-2921-CARSTAR-Canada-Chatter-Integration
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-21 12:42:45 -07:00
Allan Carr
f50292f9bf IO-2992 Bill Line column Width with Word Breaks
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-21 10:53:32 -07:00
Dave Richer
5b00ded5f6 hotfix/IO-2969-Fonts-For-Production - Register fonts
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 19:29:26 -04:00
Dave Richer
c5b19d8f22 hotfix/IO-2969-Fonts-For-Production - Register fonts
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 19:20:19 -04:00
130 changed files with 7360 additions and 4630 deletions

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Install required packages
dnf install -y fontconfig freetype
# Move to the /tmp directory for temporary download and extraction
cd /tmp
# Download the Montserrat font zip file
wget https://images.imex.online/fonts/montserrat.zip -O montserrat.zip
# Unzip the downloaded font file
unzip montserrat.zip -d montserrat
# Move the font files to the system fonts directory
mv montserrat/montserrat/*.ttf /usr/share/fonts
# Rebuild the font cache
fc-cache -fv
# Clean up
rm -rf /tmp/montserrat /tmp/montserrat.zip
echo "Montserrat fonts installed and cached successfully."

View File

@@ -1 +1,2 @@
client_max_body_size 50M;
client_max_body_size 50M;
client_body_buffer_size 5M;

30
.vscode/settings.json vendored
View File

@@ -8,5 +8,35 @@
"pattern": "**/IMEX.xml",
"systemId": "logs/IMEX.xsd"
}
],
"cSpell.words": [
"antd",
"appointmentconfirmation",
"appt",
"autohouse",
"autohouseid",
"billlines",
"bodyshop",
"bodyshopid",
"bodyshops",
"CIECA",
"claimscorp",
"claimscorpid",
"Dinero",
"driveable",
"IMEX",
"imexshopid",
"jobid",
"joblines",
"Kaizen",
"labhrs",
"larhrs",
"mixdata",
"ownr",
"promanager",
"shopname",
"smartscheduling",
"timetickets",
"touchtime"
]
}

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,7 @@
This will connect to your dockers local stack session and render the email in HTML.
```shell
node index.js
```
http://localhost:3334

View File

@@ -0,0 +1,116 @@
// index.js
import express from 'express';
import fetch from 'node-fetch';
import {simpleParser} from 'mailparser';
const app = express();
const PORT = 3334;
app.get('/', async (req, res) => {
try {
const response = await fetch('http://localhost:4566/_aws/ses');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
const messagesHtml = await parseMessages(data.messages);
res.send(renderHtml(messagesHtml));
} catch (error) {
console.error('Error fetching messages:', error);
res.status(500).send('Error fetching messages');
}
});
async function parseMessages(messages) {
const parsedMessages = await Promise.all(
messages.map(async (message, index) => {
try {
const parsed = await simpleParser(message.RawData);
return `
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
<div class="mb-2">
<span class="font-bold text-lg">Message ${index + 1}</span>
</div>
<div class="mb-2">
<span class="font-semibold">From:</span> ${message.Source}
</div>
<div class="mb-2">
<span class="font-semibold">Region:</span> ${message.Region}
</div>
<div class="mb-2">
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
</div>
</div>
<div class="prose">
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
</div>
</div>
`;
} catch (error) {
console.error('Error parsing email:', error);
return `
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
<div class="mb-2">
<span class="font-bold text-lg">Message ${index + 1}</span>
</div>
<div class="mb-2">
<span class="font-semibold">From:</span> ${message.Source}
</div>
<div class="mb-2">
<span class="font-semibold">Region:</span> ${message.Region}
</div>
<div class="mb-2">
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
</div>
<div class="text-red-500">
Error parsing email content
</div>
</div>
`;
}
})
);
return parsedMessages.join('');
}
function renderHtml(messagesHtml) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Messages Viewer</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background-color: #f3f4f6;
font-family: Arial, sans-serif;
}
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.prose {
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container bg-white shadow-lg rounded-lg p-6">
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
<div id="messages-container">
${messagesHtml}
</div>
</div>
</body>
</html>
`;
}
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
{
"name": "localemailviewer",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.21.1",
"mailparser": "^3.7.1",
"node-fetch": "^3.3.2"
}
}

View File

@@ -2,8 +2,6 @@ import { ApolloProvider } from "@apollo/client";
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import dayjs from "../utils/day";
import "dayjs/locale/en";
import React from "react";
import { useTranslation } from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
@@ -19,8 +17,6 @@ if (import.meta.env.DEV) {
Userpilot.initialize("NX-69145f08");
}
dayjs.locale("en");
const config = {
core: {
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,

View File

@@ -7,10 +7,10 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -72,7 +72,14 @@ export function BillEnterModalLinesComponent({
<BillLineSearchSelect
disabled={disabled}
options={lineData}
style={{ width: "100%", minWidth: "10rem" }}
style={{
width: "20rem",
maxWidth: "20rem",
minWidth: "10rem",
whiteSpace: "normal",
height: "auto",
minHeight: "32px" // default height of Ant Design inputs
}}
allowRemoved={form.getFieldValue("is_credit_memo") || false}
onSelect={(value, opt) => {
setFieldsValue({
@@ -105,7 +112,7 @@ export function BillEnterModalLinesComponent({
title: t("billlines.fields.line_desc"),
dataIndex: "line_desc",
editable: true,
width: "20rem",
formItemProps: (field) => {
return {
key: `${field.index}line_desc`,
@@ -119,7 +126,7 @@ export function BillEnterModalLinesComponent({
]
};
},
formInput: (record, index) => <Input disabled={disabled} />
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
},
{
title: t("billlines.fields.quantity"),

View File

@@ -11,7 +11,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
disabled={disabled}
ref={ref}
showSearch
popupMatchSelectWidth={false}
popupMatchSelectWidth={true}
optionLabelProp={"name"}
// optionFilterProp="line_desc"
filterOption={(inputValue, option) => {
@@ -43,7 +43,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
label: (
<>
<div style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
<span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
@@ -57,7 +57,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
</span>
</>
</div>
)
}))
]}

View File

@@ -45,7 +45,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
if (fcmToken) {
setpollInterval(0);
} else {
setpollInterval(60000);
setpollInterval(90000);
}
}, [fcmToken]);

View File

@@ -1,15 +1,17 @@
import { WarningFilled } from "@ant-design/icons";
import { Form, Input, InputNumber, Space } from "antd";
import dayjs from "../../utils/day";
import React from "react";
import { useTranslation } from "react-i18next";
import { DateFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day";
//import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import {
default as DateTimePicker,
default as FormDateTimePicker
} from "../form-date-time-picker/form-date-time-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
@@ -18,10 +20,10 @@ import ContractFormJobPrefill from "./contract-form-job-prefill.component";
export default function ContractFormComponent({ form, create = false, selectedJobState, selectedCar }) {
const { t } = useTranslation();
return (
<div>
<FormFieldsChanged form={form} />
<>
{!create && <FormFieldsChanged form={form} />}
<LayoutFormRow>
{create ? null : (
{!create && (
<Form.Item
label={t("contracts.fields.status")}
name="status"
@@ -50,7 +52,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
<Form.Item label={t("contracts.fields.scheduledreturn")} name="scheduledreturn">
<FormDateTimePicker />
</Form.Item>
{create ? null : (
{!create && (
<Form.Item label={t("contracts.fields.actualreturn")} name="actualreturn">
<FormDateTimePicker />
</Form.Item>
@@ -122,7 +124,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
}}
</Form.Item>
)}
{create ? null : (
{!create && (
<Form.Item label={t("contracts.fields.kmend")} name="kmend">
<InputNumber />
</Form.Item>
@@ -145,25 +147,21 @@ export default function ContractFormComponent({ form, create = false, selectedJo
>
<CourtesyCarFuelSlider />
</Form.Item>
{create ? null : (
{!create && (
<Form.Item label={t("contracts.fields.fuelin")} name="fuelin" span={8}>
<CourtesyCarFuelSlider />
</Form.Item>
)}
</LayoutFormRow>
<div>
<Space wrap>
{selectedJobState && (
<div>
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
</div>
)}
{
//<ContractLicenseDecodeButton form={form} />
}
</Space>
</div>
<LayoutFormRow header={t("contracts.labels.driverinformation")}>
<Space wrap>
{create && selectedJobState && (
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
)}
{/* {<ContractLicenseDecodeButton form={form} />} */}
</Space>
</LayoutFormRow>
<LayoutFormRow noDivider={true}>
<Form.Item
label={t("contracts.fields.driver_dlnumber")}
name="driver_dlnumber"
@@ -183,9 +181,8 @@ export default function ContractFormComponent({ form, create = false, selectedJo
const dlExpiresBeforeReturn = dayjs(form.getFieldValue("driver_dlexpiry")).isBefore(
dayjs(form.getFieldValue("scheduledreturn"))
);
return (
<div>
<>
<Form.Item
label={t("contracts.fields.driver_dlexpiry")}
name="driver_dlexpiry"
@@ -204,11 +201,10 @@ export default function ContractFormComponent({ form, create = false, selectedJo
<span>{t("contracts.labels.dlexpirebeforereturn")}</span>
</Space>
)}
</div>
</>
);
}}
</Form.Item>
<Form.Item label={t("contracts.fields.driver_dlst")} name="driver_dlst">
<Input />
</Form.Item>
@@ -315,6 +311,6 @@ export default function ContractFormComponent({ form, create = false, selectedJo
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
</div>
</>
);
}

View File

@@ -1,10 +1,10 @@
import { Card, Table, Tag } from "antd";
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
import { useTranslation } from "react-i18next";
import React, { useEffect, useState } from "react";
import dayjs from "../../../utils/day";
import DashboardRefreshRequired from "../refresh-required.component";
import axios from "axios";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import dayjs from "../../../utils/day";
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
import DashboardRefreshRequired from "../refresh-required.component";
const fortyFiveDaysAgo = () => dayjs().subtract(45, "day").toLocaleString();
@@ -46,6 +46,11 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
dataIndex: "humanReadable",
key: "humanReadable"
},
{
title: t("job_lifecycle.columns.average_human_readable"),
dataIndex: "averageHumanReadable",
key: "averageHumanReadable"
},
{
title: t("job_lifecycle.columns.status_count"),
key: "statusCount",

View File

@@ -40,13 +40,11 @@ export function DmsLogEvents({ socket, logs, bodyshop }) {
function LogLevelHierarchy(level) {
switch (level) {
case "TRACE":
return "pink";
case "DEBUG":
return "orange";
case "INFO":
return "blue";
case "WARNING":
case "WARN":
return "yellow";
case "ERROR":
return "red";

View File

@@ -8,7 +8,7 @@ import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
import { useMutation } from "@apollo/client";
import { acceptEula } from "../../redux/user/user.actions";
import { useTranslation } from "react-i18next";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import "./eula.styles.scss";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
@@ -208,7 +208,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
{
required: true,
validator: (_, value) => {
if (day(value).isSame(day(), "day")) {
if (dayjs(value).isSame(dayjs(), "day")) {
return Promise.resolve();
}
return Promise.reject(new Error(t("eula.messages.date_accepted")));

View File

@@ -2,21 +2,38 @@ import { DatePicker } from "antd";
import PropTypes from "prop-types";
import React, { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors.js";
import dayjs from "../../utils/day";
import { fuzzyMatchDate } from "./formats.js";
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const DateTimePicker = ({
value,
onChange,
onBlur,
id,
onlyFuture,
onlyToday,
isDateOnly = false,
bodyshop,
...restProps
}) => {
const [isManualInput, setIsManualInput] = useState(false);
const { t } = useTranslation();
const handleChange = useCallback(
(newDate) => {
if (onChange) {
onChange(newDate || null);
onChange(bodyshop?.timezone && newDate ? dayjs(newDate).tz(bodyshop.timezone, true) : newDate);
}
setIsManualInput(false);
},
[onChange]
[onChange, bodyshop?.timezone]
);
const handleBlur = useCallback(
@@ -102,4 +119,4 @@ DateTimePicker.propTypes = {
isDateOnly: PropTypes.bool
};
export default React.memo(DateTimePicker);
export default connect(mapStateToProps, null)(DateTimePicker);

View File

@@ -116,18 +116,15 @@ function Header({
const { t } = useTranslation();
const deleteBetaCookie = () => {
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
if (cookieExists) {
const domain = window.location.hostname.split(".").slice(-2).join(".");
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
console.log(`betaSwitchImex cookie deleted`);
} else {
console.log(`betaSwitchImex cookie does not exist`);
}
};
deleteBetaCookie();
// const deleteBetaCookie = () => {
// const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
// if (cookieExists) {
// const domain = window.location.hostname.split(".").slice(-2).join(".");
// document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
// }
// };
//
// deleteBetaCookie();
const accountingChildren = [];

View File

@@ -23,6 +23,7 @@ import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component";
import { useMutation } from "@apollo/client";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -127,6 +128,9 @@ export function ScheduleEventComponent({
{(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} />
</DataLabel>
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
<ProductionListColumnComment record={event && event.job} />
</DataLabel>
<ScheduleEventNote event={event} />
</div>
) : (
@@ -316,6 +320,7 @@ export function ScheduleEventComponent({
})`}
{event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
{event?.job?.comment && `C: ${event.job.comment}`}
</Space>
) : (
<div

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from "react";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import axios from "axios";
import { Badge, Card, Space, Table, Tag } from "antd";
import { gql, useQuery } from "@apollo/client";
@@ -72,7 +72,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
dataIndex: "start",
key: "start",
render: (text) => DateTimeFormatterFunction(text),
sorter: (a, b) => day(a.start).unix() - day(b.start).unix()
sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix()
},
{
title: t("job_lifecycle.columns.relative_start"),
@@ -90,7 +90,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
}
return isEmpty(a.end) ? 1 : -1;
}
return day(a.end).unix() - day(b.end).unix();
return dayjs(a.end).unix() - dayjs(b.end).unix();
},
render: (text) => (isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text))
},

View File

@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { useMutation } from "@apollo/client";
import { Button, Form, notification, Popover, Select, Space } from "antd";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -48,7 +48,7 @@ export function JobLineDispatchButton({
const result = await dispatchLines({
variables: {
partsDispatch: {
dispatched_at: day(),
dispatched_at: dayjs(),
employeeid: values.employeeid,
jobid: job.id,
dispatched_by: currentUser.email,
@@ -138,7 +138,11 @@ export function JobLineDispatchButton({
return (
<Popover open={visible} content={popMenu}>
<Button disabled={selectedLines.length === 0 || jobRO || disabled} loading={loading} onClick={() => setVisible(true)}>
<Button
disabled={selectedLines.length === 0 || jobRO || disabled}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
</Button>
</Popover>

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Col, notification, Row, Table } from "antd";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import React from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_PARTS_DISPATCH_LINE } from "../../graphql/parts-dispatch.queries";
@@ -11,7 +11,7 @@ export default function PartsDispatchExpander({ dispatch, job }) {
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
const handleAccept = async ({ partsDispatchLineId }) => {
const accepted_at = day();
const accepted_at = dayjs();
const result = await updateDispatchLine({
variables: { id: partsDispatchLineId, line: { accepted_at } },
optimisticResponse: {

View File

@@ -1,12 +1,14 @@
import { SyncOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import Board from "./trello-board/index";
import { Button, notification, Skeleton, Space } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import { useApolloClient } from "@apollo/client";
import { Button, notification, Skeleton, Space } from "antd";
import cloneDeep from "lodash/cloneDeep";
import isEqual from "lodash/isEqual";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
@@ -15,14 +17,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
import ProductionListPrint from "../production-list-table/production-list-print.component.jsx";
import CardColorLegend from "./production-board-kanban-card-color-legend.component.jsx";
import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js";
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
import cloneDeep from "lodash/cloneDeep";
import isEqual from "lodash/isEqual";
import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
import Board from "./trello-board/index";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -214,6 +215,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
bodyshop={bodyshop}
data={data}
/>
<ProductionListPrint />
</Space>
}
/>

View File

@@ -77,7 +77,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
const matchingColumnConfig = useMemo(() => {
return bodyshop?.production_config?.find((p) => p.name === defaultView);
}, [bodyshop.production_config]);
}, [bodyshop.production_config, defaultView]);
useEffect(() => {
const newColumns =

View File

@@ -120,14 +120,6 @@ var formats = {
};
const localizer = (dayjsLib) => {
// load dayjs plugins
dayjsLib.extend(isBetween);
dayjsLib.extend(isSameOrAfter);
dayjsLib.extend(isSameOrBefore);
dayjsLib.extend(localeData);
dayjsLib.extend(localizedFormat);
dayjsLib.extend(minMax);
dayjsLib.extend(utc);
var locale = function locale(dj, c) {
return c ? dj.locale(c) : dj;
};
@@ -136,7 +128,8 @@ const localizer = (dayjsLib) => {
// then use the timezone aware version
//TODO This was the issue entirely...
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
var dayjs = dayjsLib;
function getTimezoneOffset(date) {

View File

@@ -20,6 +20,7 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
problemJobs: selectProblemJobs
});
const localizer = local(dayjs);
export function ScheduleCalendarWrapperComponent({

View File

@@ -4,12 +4,12 @@ import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component";
import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component";
import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component";
import { useApolloClient, useQuery } from "@apollo/client";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day";
import { useApolloClient, useQuery } from "@apollo/client";
import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -26,7 +26,7 @@ export function ScoreboardDisplayComponent({ bodyshop }) {
start: dayjs().startOf("month"),
end: dayjs().endOf("month")
},
pollInterval: 60000
pollInterval: 60000*5
});
const { data } = scoreboardSubscription;

View File

@@ -1,13 +1,13 @@
import { useQuery } from "@apollo/client";
import { Col, Row } from "antd";
import _ from "lodash";
import dayjs from "../../utils/day";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
@@ -86,7 +86,7 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
pollInterval: 60000,
pollInterval: 60000*5,
skip: !fixedPeriods
});

View File

@@ -1,11 +1,11 @@
import { useQuery } from "@apollo/client";
import { Col, Row } from "antd";
import _ from "lodash";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useMemo } from "react";
import { useLocation } from "react-router-dom";
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
import dayjs from "../../utils/day";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
@@ -68,7 +68,7 @@ export default function ScoreboardTimeTickets() {
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
pollInterval: 60000,
pollInterval: 60000*5,
skip: !fixedPeriods
});

View File

@@ -1,10 +1,3 @@
import { Button, Card, Space, Switch, Table } from "antd";
import queryString from "query-string";
import React, { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { pageLimit } from "../../utils/config";
import dayjs from "../../utils/day";
import {
CheckCircleFilled,
CheckCircleOutlined,
@@ -15,9 +8,16 @@ import {
PlusCircleFilled,
SyncOutlined
} from "@ant-design/icons";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
import { Button, Card, Space, Switch, Table } from "antd";
import queryString from "query-string";
import React, { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { setModalContext } from "../../redux/modals/modals.actions";
import { pageLimit } from "../../utils/config";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
import dayjs from "../../utils/day";
/**
* Task List Component
@@ -140,6 +140,17 @@ function TaskListComponent({
render: (text, record) => <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
});
columns.push({
title: t("tasks.fields.created_by"),
dataIndex: "created_by",
key: "created_by",
width: "8%",
defaultSortOrder: "descend",
sorter: true,
sortOrder: sortcolumn === "created_by" && sortorder,
render: (text, record) => record.created_by
});
if (!onlyMine) {
columns.push({
title: t("tasks.fields.assigned_to"),
@@ -155,65 +166,70 @@ function TaskListComponent({
});
}
if (showRo) {
columns.push({
title: t("tasks.fields.job.ro_number"),
dataIndex: ["job", "ro_number"],
key: "job.ro_number",
width: "8%",
render: (text, record) =>
record.job ? (
<Link to={`/manage/jobs/${record.job.id}?tab=tasks`}>{record.job.ro_number || t("general.labels.na")}</Link>
) : (
t("general.labels.na")
)
});
}
columns.push({
title: t("tasks.fields.related_items"),
key: "related_items",
width: "12%",
render: (text, record) => {
const items = [];
// Job
if (showRo && record.job) {
items.push(
<Link key="job" to={`/manage/jobs/${record.job.id}?tab=tasks`}>
{t("tasks.fields.job.ro_number")}: {record.job.ro_number}
</Link>
);
}
if (showRo && !record.job) {
items.push(`${t("tasks.fields.job.ro_number")}: ${t("general.labels.na")}`);
}
// Jobline
if (record.jobline?.line_desc) {
items.push(
<span key="jobline">
{t("tasks.fields.jobline")}: {record.jobline.line_desc}
</span>
);
}
// Parts Order
if (record.parts_order) {
const { order_number, vendor } = record.parts_order;
const partsOrderText =
order_number && vendor?.name ? `${order_number} - ${vendor.name}` : t("general.labels.na");
items.push(
<Link
key="parts_order"
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}
>
{t("tasks.fields.parts_order")}: {partsOrderText}
</Link>
);
}
// Bill
if (record.bill) {
const { invoice_number, vendor } = record.bill;
const billText = invoice_number && vendor?.name ? `${invoice_number} - ${vendor.name}` : t("general.labels.na");
items.push(
<Link key="bill" to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
{t("tasks.fields.bill")}: {billText}
</Link>
);
}
return items.length > 0 ? <Space direction="vertical">{items}</Space> : null;
}
});
columns.push(
{
title: t("tasks.fields.jobline"),
dataIndex: ["jobline", "id"],
key: "jobline.id",
width: "8%",
render: (text, record) => record?.jobline?.line_desc || ""
},
{
title: t("tasks.fields.parts_order"),
dataIndex: ["parts_order", "id"],
key: "part_order.id",
width: "8%",
render: (text, record) =>
record.parts_order ? (
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
: t("general.labels.na")}
</Link>
) : (
""
)
},
{
title: t("tasks.fields.bill"),
dataIndex: ["bill", "id"],
key: "bill.id",
width: "10%",
render: (text, record) =>
record.bill ? (
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
: t("general.labels.na")}
</Link>
) : (
""
)
},
{
title: t("tasks.fields.title"),
dataIndex: "title",
key: "title",
minWidth: "20%",
sorter: true,
sortOrder: sortcolumn === "title" && sortorder
},
@@ -247,7 +263,7 @@ function TaskListComponent({
{
title: t("tasks.fields.actions"),
key: "toggleCompleted",
width: "5%",
width: "8%",
render: (text, record) => (
<Space direction="horizontal">
<Button

View File

@@ -39,7 +39,7 @@ export function TimeTicketList({
extra
}) {
const [state, setState] = useState({
sortedInfo: {},
sortedInfo: { columnKey: 'date', order: 'descend' },
filteredInfo: { text: "" }
});

View File

@@ -1,7 +1,7 @@
import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, Modal, notification, Space } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import dayjs from "../../utils/day";
import { useMutation, useQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Form, Modal, notification, Space } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -11,9 +11,9 @@ import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timeti
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectTimeTicket } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import TimeTicketModalComponent from "./time-ticket-modal.component";
import dayjs from "../../utils/day";
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import TimeTicketModalComponent from "./time-ticket-modal.component";
const mapStateToProps = createStructuredSelector({
timeTicketModal: selectTimeTicket,
@@ -87,7 +87,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
if (enterAgain) {
//Capture the existing information and repopulate it.
const prev = form.getFieldsValue(["date", "employeeid"]);
const prev = form.getFieldsValue(["date", "employeeid", "flat_rate"]);
form.resetFields();

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -29,7 +29,7 @@ export function TimeTicketsCommit({ bodyshop, currentUser, timeticket, disabled,
? { commited_by: null, committed_at: null }
: {
commited_by: currentUser.email,
committed_at: day()
committed_at: dayjs()
};
const result = await updateTimeTicket({

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -34,7 +34,7 @@ export function TimeTicketsCommit({
timeticketIds: timetickets.map((ticket) => ticket.id),
timeticket: {
commited_by: currentUser.email,
committed_at: day()
committed_at: dayjs()
}
},
update(cache) {
@@ -47,7 +47,7 @@ export function TimeTicketsCommit({
return {
...ticket,
commited_by: currentUser.email,
committed_at: day()
committed_at: dayjs()
};
}
return ticket;

View File

@@ -1,7 +1,7 @@
import { useApolloClient } from "@apollo/client";
import { Button, notification } from "antd";
import _ from "lodash";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -49,7 +49,7 @@ export function TtApproveButton({
})),
approvalIds: selectedTickets,
approvalUpdate: {
approved_at: day(),
approved_at: dayjs(),
approved_by: currentUser.email
}
}

View File

@@ -1,8 +1,9 @@
import { useEffect, useState, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import SocketIO from "socket.io-client";
import { auth } from "../../firebase/firebase.utils";
import { store } from "../../redux/store";
import { setWssStatus } from "../../redux/application/application.actions";
import { addAlerts, setWssStatus } from "../../redux/application/application.actions";
const useSocket = (bodyshop) => {
const socketRef = useRef(null);
const [clientId, setClientId] = useState(null);
@@ -31,6 +32,14 @@ const useSocket = (bodyshop) => {
socketRef.current = socketInstance;
const handleBodyshopMessage = (message) => {
if (!message || !message?.type) return;
switch (message.type) {
case "alert-update":
store.dispatch(addAlerts(message.payload));
break;
}
if (!import.meta.env.DEV) return;
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
};
@@ -39,22 +48,22 @@ const useSocket = (bodyshop) => {
console.log("Socket connected:", socketInstance.id);
socketInstance.emit("join-bodyshop-room", bodyshop.id);
setClientId(socketInstance.id);
store.dispatch(setWssStatus("connected"))
store.dispatch(setWssStatus("connected"));
};
const handleReconnect = (attempt) => {
console.log(`Socket reconnected after ${attempt} attempts`);
store.dispatch(setWssStatus("connected"))
store.dispatch(setWssStatus("connected"));
};
const handleConnectionError = (err) => {
console.error("Socket connection error:", err);
store.dispatch(setWssStatus("error"))
store.dispatch(setWssStatus("error"));
};
const handleDisconnect = () => {
console.log("Socket disconnected");
store.dispatch(setWssStatus("disconnected"))
store.dispatch(setWssStatus("disconnected"));
};
socketInstance.on("connect", handleConnect);

View File

@@ -48,6 +48,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
v_model_desc
est_ct_fn
est_ct_ln
comment
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {

View File

@@ -2,7 +2,7 @@ import { gql } from "@apollo/client";
export const QUERY_TICKETS_BY_JOBID = gql`
query QUERY_TICKETS_BY_JOBID($jobid: uuid!) {
timetickets(where: { jobid: { _eq: $jobid } }, order_by: { date: desc_nulls_first }) {
timetickets(where: { jobid: { _eq: $jobid } }) {
actualhrs
cost_center
ciecacode
@@ -26,7 +26,7 @@ export const QUERY_TICKETS_BY_JOBID = gql`
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) {
timetickets(where: { date: { _gte: $start, _lte: $end } }, order_by: { date: desc_nulls_first }) {
timetickets(where: { date: { _gte: $start, _lte: $end } }) {
actualhrs
ciecacode
clockoff
@@ -69,7 +69,6 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
) {
timetickets(
where: { date: { _gte: $start, _lte: $end }, employeeid: { _eq: $employeeid } }
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
@@ -101,7 +100,6 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
}
fixedperiod: timetickets(
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, employeeid: { _eq: $employeeid } }
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
@@ -145,7 +143,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
) {
timetickets(
where: { date: { _gte: $start, _lte: $end }, cost_center: { _neq: "timetickets.labels.shift" } }
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
@@ -180,7 +177,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
}
fixedperiod: timetickets(
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: { _neq: "timetickets.labels.shift" } }
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
@@ -335,7 +331,6 @@ export const UPDATE_TIME_TICKETS = gql`
export const QUERY_ACTIVE_TIME_TICKETS = gql`
query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) {
timetickets(
order_by: { date: desc_nulls_first }
where: {
_and: {
clockoff: { _is_null: true }

View File

@@ -71,7 +71,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
...logs,
{
timestamp: new Date(),
level: "WARNING",
level: "WARN",
message: "Reconnected to CDK Export Service"
}
];
@@ -123,10 +123,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
socket.emit("set-log-level", value);
}}
>
<Select.Option key="TRACE">TRACE</Select.Option>
<Select.Option key="DEBUG">DEBUG</Select.Option>
<Select.Option key="INFO">INFO</Select.Option>
<Select.Option key="WARNING">WARNING</Select.Option>
<Select.Option key="WARN">WARN</Select.Option>
<Select.Option key="ERROR">ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>

View File

@@ -90,7 +90,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
...logs,
{
timestamp: new Date(),
level: "WARNING",
level: "warn",
message: "Reconnected to CDK Export Service"
}
];
@@ -173,10 +173,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
socket.emit("set-log-level", value);
}}
>
<Select.Option key="TRACE">TRACE</Select.Option>
<Select.Option key="DEBUG">DEBUG</Select.Option>
<Select.Option key="INFO">INFO</Select.Option>
<Select.Option key="WARNING">WARNING</Select.Option>
<Select.Option key="WARN">WARN</Select.Option>
<Select.Option key="ERROR">ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>

View File

@@ -1,4 +1,4 @@
import { FloatButton, Layout, Spin } from "antd";
import { FloatButton, Layout, notification, Spin } from "antd";
// import preval from "preval.macro";
import React, { lazy, Suspense, useContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -21,11 +21,12 @@ import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-st
import { requestForToken } from "../../firebase/firebase.utils";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import "./manage.page.styles.scss";
import WssStatusDisplayComponent from "../../components/wss-status-display/wss-status-display.component.jsx";
import { selectAlerts } from "../../redux/application/application.selectors.js";
import { addAlerts } from "../../redux/application/application.actions.js";
const JobsPage = lazy(() => import("../jobs/jobs.page"));
@@ -104,16 +105,80 @@ const { Content, Footer } = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
alerts: selectAlerts
});
const mapDispatchToProps = (dispatch) => ({});
const ALERT_FILE_URL = InstanceRenderManager({
imex: "https://images.imex.online/alerts/alerts-imex.json",
rome: "https://images.imex.online/alerts/alerts-rome.json"
});
export function Manage({ conflict, bodyshop }) {
const mapDispatchToProps = (dispatch) => ({
setAlerts: (alerts) => dispatch(addAlerts(alerts))
});
export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
const { t } = useTranslation();
const [chatVisible] = useState(false);
const { socket, clientId } = useContext(SocketContext);
// State to track displayed alerts
const [displayedAlertIds, setDisplayedAlertIds] = useState([]);
// Fetch displayed alerts from localStorage on mount
useEffect(() => {
const displayedAlerts = JSON.parse(localStorage.getItem("displayedAlerts") || "[]");
setDisplayedAlertIds(displayedAlerts);
}, []);
// Fetch alerts from the JSON file and dispatch to Redux store
useEffect(() => {
const fetchAlerts = async () => {
try {
const response = await fetch(ALERT_FILE_URL);
const fetchedAlerts = await response.json();
setAlerts(fetchedAlerts);
} catch (error) {
console.error("Error fetching alerts:", error);
}
};
fetchAlerts();
}, []);
// Use useEffect to watch for new alerts
useEffect(() => {
if (alerts && Object.keys(alerts).length > 0) {
// Convert the alerts object into an array
const alertArray = Object.values(alerts);
// Filter out alerts that have already been dismissed
const newAlerts = alertArray.filter((alert) => !displayedAlertIds.includes(alert.id));
newAlerts.forEach((alert) => {
// Display the notification
notification.open({
key: "notification-alerts-" + alert.id,
message: alert.message,
description: alert.description,
type: alert.type || "info",
duration: 0,
placement: "bottomRight",
closable: true,
onClose: () => {
// When the notification is closed, update displayed alerts state and localStorage
setDisplayedAlertIds((prevIds) => {
const updatedIds = [...prevIds, alert.id];
localStorage.setItem("displayedAlerts", JSON.stringify(updatedIds));
return updatedIds;
});
}
});
});
}
}, [alerts, displayedAlertIds]);
useEffect(() => {
const widgetId = InstanceRenderManager({
imex: "IABVNO4scRKY11XBQkNr",

View File

@@ -67,6 +67,12 @@ export const setUpdateAvailable = (isUpdateAvailable) => ({
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable
});
export const addAlerts = (alerts) => ({
type: ApplicationActionTypes.ADD_ALERTS,
payload: alerts
});
export const setWssStatus = (status) => ({
type: ApplicationActionTypes.SET_WSS_STATUS,
payload: status

View File

@@ -15,7 +15,8 @@ const INITIAL_STATE = {
error: null
},
jobReadOnly: false,
partnerVersion: null
partnerVersion: null,
alerts: {}
};
const applicationReducer = (state = INITIAL_STATE, action) => {
@@ -91,6 +92,18 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
case ApplicationActionTypes.SET_WSS_STATUS: {
return { ...state, wssStatus: action.payload };
}
case ApplicationActionTypes.ADD_ALERTS: {
const newAlertsMap = { ...state.alerts };
action.payload.alerts.forEach((alert) => {
newAlertsMap[alert.id] = alert;
});
return {
...state,
alerts: newAlertsMap
};
}
default:
return state;
}

View File

@@ -23,3 +23,4 @@ export const selectOnline = createSelector([selectApplication], (application) =>
export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs);
export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable);
export const selectWssStatus = createSelector([selectApplication], (application) => application.wssStatus);
export const selectAlerts = createSelector([selectApplication], (application) => application.alerts);

View File

@@ -13,6 +13,7 @@ const ApplicationActionTypes = {
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
SET_WSS_STATUS: "SET_WSS_STATUS"
SET_WSS_STATUS: "SET_WSS_STATUS",
ADD_ALERTS: "ADD_ALERTS"
};
export default ApplicationActionTypes;

View File

@@ -28,7 +28,7 @@ import {
} from "../../firebase/firebase.utils";
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import client from "../../utils/GraphQLClient";
import day from "../../utils/day";
import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import {
checkInstanceId,
@@ -96,7 +96,7 @@ export function* isUserAuthenticated() {
const eulaQuery = yield client.query({
query: QUERY_EULA,
variables: {
now: day()
now: dayjs()
}
});
@@ -314,8 +314,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
try {
console.log("Setting shop timezone.");
day.tz.setDefault(payload.timezone);
dayjs.tz.setDefault(payload.timezone);
} catch (error) {
console.log(error);
}

View File

@@ -1338,6 +1338,8 @@
},
"job_lifecycle": {
"columns": {
"average_human_readable": "Average Human Readable",
"average_value": "Average Value",
"duration": "Duration",
"end": "End",
"human_readable": "Human Readable",
@@ -3044,6 +3046,7 @@
"production_by_target_date": "Production by Target Date",
"production_by_technician": "Production by Technician",
"production_by_technician_one": "Production filtered by Technician",
"production_not_production_status": "Production not in Production Status",
"production_over_time": "Production Level over Time",
"psr_by_make": "Percent of Sales by Vehicle Make",
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
@@ -3186,6 +3189,7 @@
"billid": "Bill",
"completed": "Completed",
"created_at": "Created At",
"created_by": "Created By",
"description": "Description",
"due_date": "Due Date",
"job": {
@@ -3202,6 +3206,7 @@
"medium": "Medium"
},
"priority": "Priority",
"related_items": "Related Items",
"remind_at": "Remind At",
"title": "Title"
},

View File

@@ -1338,6 +1338,8 @@
},
"job_lifecycle": {
"columns": {
"average_human_readable": "",
"average_value": "",
"duration": "",
"end": "",
"human_readable": "",
@@ -3044,6 +3046,7 @@
"production_by_target_date": "",
"production_by_technician": "",
"production_by_technician_one": "",
"production_not_production_status": "",
"production_over_time": "",
"psr_by_make": "",
"purchase_return_ratio_grouped_by_vendor_detail": "",
@@ -3186,6 +3189,7 @@
"billid": "",
"completed": "",
"created_at": "",
"created_by": "",
"description": "",
"due_date": "",
"job": {
@@ -3202,6 +3206,7 @@
"medium": ""
},
"priority": "",
"related_items": "",
"remind_at": "",
"title": ""
},

File diff suppressed because it is too large Load Diff

View File

@@ -2333,6 +2333,14 @@ export const TemplateList = (type, context) => {
key: "production_by_technician",
//idtype: "vendor",
disabled: false
},
production_not_production_status: {
title: i18n.t("reportcenter.templates.production_not_production_status"),
description: "",
subject: i18n.t("reportcenter.templates.production_not_production_status"),
key: "production_not_production_status",
//idtype: "vendor",
disabled: false
}
}
: {}),

View File

@@ -1,5 +1,6 @@
import dayjs from "dayjs";
import "dayjs/locale/en";
import dayjsBusinessDays from "dayjs-business-days2";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
import updateLocale from "dayjs/plugin/updateLocale";
@@ -64,4 +65,6 @@ dayjs.extend(minMax);
dayjs.extend(isBetween);
dayjs.extend(dayjsBusinessDays);
dayjs.locale("en");
export default dayjs;

View File

@@ -74,7 +74,7 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- SERVICES=ses,secretsmanager
- SERVICES=ses,secretsmanager,cloudwatch,logs
- DEBUG=0
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
@@ -115,6 +115,7 @@ services:
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/id_rsa
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
"
# Node App: The Main IMEX API
node-app:
@@ -166,6 +167,28 @@ services:
# volumes:
# - redis-insight-data:/db
# ##Optional Container for SFTP/SSH Server for testing
# ssh-sftp-server:
# image: atmoz/sftp:alpine # Using an image with SFTP support
# container_name: ssh-sftp-server
# hostname: ssh-sftp-server
# networks:
# - redis-cluster-net
# ports:
# - "2222:22" # Expose port 22 for SSH/SFTP (mapped to 2222 on the host)
# volumes:
# - ./certs/id_rsa.pub:/home/user/.ssh/keys/id_rsa.pub:ro # Mount the SSH public key
# - ./upload:/home/user/upload # Mount a local directory for SFTP uploads
# environment:
# - SFTP_USERS=user:password:1000::upload
# command: >
# /bin/sh -c "
# chmod -R 007 /home/user/upload &&
# echo 'Match User user' >> /etc/ssh/sshd_config &&
# sed -i -e 's#ForceCommand internal-sftp#ForceCommand internal-sftp -d /upload#' /etc/ssh/sshd_config &&
# /usr/sbin/sshd -D
# "
networks:
redis-cluster-net:
driver: bridge

View File

@@ -69,7 +69,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
jobline:
job:
@@ -180,7 +179,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -387,7 +385,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -504,7 +501,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bill:
job:
@@ -671,7 +667,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
_and:
- job:
@@ -1285,7 +1280,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
courtesycar:
bodyshop:
@@ -1526,7 +1520,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -1786,7 +1779,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -1920,7 +1912,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
_or:
- job:
@@ -2105,7 +2096,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
employee_team:
bodyshop:
@@ -2268,7 +2258,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
employee:
bodyshop:
@@ -2449,7 +2438,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -2696,7 +2684,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -2808,7 +2795,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
conversation:
bodyshop:
@@ -3123,7 +3109,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
job:
bodyshop:
@@ -4232,7 +4217,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -4248,41 +4232,41 @@
enable_manual: false
update:
columns:
- clm_no
- v_make_desc
- date_next_contact
- status
- employee_csr
- employee_prep
- clm_total
- suspended
- employee_body
- ro_number
- actual_in
- ownr_co_nm
- v_model_yr
- comment
- job_totals
- v_vin
- ownr_fn
- scheduled_completion
- special_coverage_policy
- v_color
- ca_gst_registrant
- scheduled_delivery
- actual_delivery
- actual_completion
- kanbanparent
- est_ct_fn
- alt_transport
- v_model_desc
- clm_no
- v_make_desc
- date_next_contact
- status
- employee_csr
- actual_in
- v_model_yr
- comment
- job_totals
- ownr_fn
- v_color
- ca_gst_registrant
- employee_refinish
- ownr_ph1
- date_last_contacted
- alt_transport
- inproduction
- est_ct_ln
- production_vars
- category
- v_model_desc
- date_invoiced
- est_co_nm
- ownr_ln
@@ -4295,6 +4279,12 @@
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
body:
action: transform
template: |-
{
"data": {{$body?.event?.data?.new}}
}
method: POST
query_params: {}
template_engine: Kriti
@@ -4496,7 +4486,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
conversation:
bodyshop:
@@ -4670,7 +4659,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
job:
bodyshop:
@@ -4805,7 +4793,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -5110,7 +5097,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
parts_order:
job:
@@ -5243,7 +5229,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
job:
bodyshop:
@@ -5419,7 +5404,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
job:
bodyshop:
@@ -5559,7 +5543,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -5670,7 +5653,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
_or:
- parentjob_rel:
@@ -5760,7 +5742,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
job:
bodyshop:
@@ -6045,7 +6026,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -6541,7 +6521,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:
@@ -6698,7 +6677,6 @@
delete_permissions:
- role: user
permission:
backend_only: false
filter:
bodyshop:
associations:

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_timetickets_date ON timetickets (date );

View File

@@ -0,0 +1 @@
CREATE INDEX idx_timetickets_date ON timetickets (date );

View File

@@ -0,0 +1,9 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_jobs_ownr_fn ON jobs USING gin (ownr_fn gin_trgm_ops);
-- CREATE INDEX idx_jobs_ownr_ln ON jobs USING gin (ownr_ln gin_trgm_ops);
-- CREATE INDEX idx_jobs_ownr_co_nm ON jobs USING gin (ownr_co_nm gin_trgm_ops);
-- CREATE INDEX idx_jobs_clm_no ON jobs USING gin (clm_no gin_trgm_ops);
-- CREATE INDEX idx_jobs_v_make_desc ON jobs USING gin (v_make_desc gin_trgm_ops);
-- CREATE INDEX idx_jobs_v_model_desc ON jobs USING gin (v_model_desc gin_trgm_ops);
-- CREATE INDEX idx_jobs_plate_no ON jobs USING gin (plate_no gin_trgm_ops);

View File

@@ -0,0 +1,7 @@
CREATE INDEX idx_jobs_ownr_fn ON jobs USING gin (ownr_fn gin_trgm_ops);
CREATE INDEX idx_jobs_ownr_ln ON jobs USING gin (ownr_ln gin_trgm_ops);
CREATE INDEX idx_jobs_ownr_co_nm ON jobs USING gin (ownr_co_nm gin_trgm_ops);
CREATE INDEX idx_jobs_clm_no ON jobs USING gin (clm_no gin_trgm_ops);
CREATE INDEX idx_jobs_v_make_desc ON jobs USING gin (v_make_desc gin_trgm_ops);
CREATE INDEX idx_jobs_v_model_desc ON jobs USING gin (v_model_desc gin_trgm_ops);
CREATE INDEX idx_jobs_plate_no ON jobs USING gin (plate_no gin_trgm_ops);

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_exportlog_createdat_desc ON exportlog (created_at desc);

View File

@@ -0,0 +1 @@
CREATE INDEX idx_exportlog_createdat_desc ON exportlog (created_at desc);

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE index idx_messages_unread_agg ON messages (read, isoutbound)
-- WHERE read = false AND isoutbound = false;

View File

@@ -0,0 +1,2 @@
CREATE index idx_messages_unread_agg ON messages (read, isoutbound)
WHERE read = false AND isoutbound = false;

827
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
},
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.679.0",
"@aws-sdk/client-elasticache": "^3.675.0",
"@aws-sdk/client-secrets-manager": "^3.675.0",
"@aws-sdk/client-ses": "^3.675.0",
@@ -44,7 +45,6 @@
"firebase-admin": "^12.6.0",
"graphql": "^16.9.0",
"graphql-request": "^6.1.0",
"graylog2": "^0.2.1",
"inline-css": "^4.0.2",
"intuit-oauth": "^4.1.2",
"ioredis": "^5.4.1",
@@ -63,9 +63,11 @@
"soap": "^1.1.5",
"socket.io": "^4.8.0",
"socket.io-adapter": "^2.5.5",
"ssh2-sftp-client": "^10.0.3",
"ssh2-sftp-client": "^11.0.0",
"twilio": "^4.23.0",
"uuid": "^10.0.0",
"winston": "^3.15.0",
"winston-cloudwatch": "^6.3.0",
"xml2js": "^0.6.2",
"xmlbuilder2": "^3.1.1"
},

View File

@@ -1,5 +1,10 @@
const cors = require("cors");
// Load environment variables THIS MUST BE AT THE TOP
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const cors = require("cors");
const http = require("http");
const Redis = require("ioredis");
const express = require("express");
@@ -18,11 +23,6 @@ const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
const { ElastiCacheClient, DescribeCacheClustersCommand } = require("@aws-sdk/client-elasticache");
const { default: InstanceManager } = require("./server/utils/instanceMgr");
// Load environment variables
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const CLUSTER_RETRY_BASE_DELAY = 100;
const CLUSTER_RETRY_MAX_DELAY = 5000;
const CLUSTER_RETRY_JITTER = 100;
@@ -153,18 +153,13 @@ const connectToRedisCluster = async () => {
} else {
// Use the Dockerized Redis cluster in development
if (isEmpty(process.env?.REDIS_URL) || !isString(process.env?.REDIS_URL)) {
logger.log(`[${process.env.NODE_ENV}] No or Malformed REDIS_URL present.`, "ERROR", "redis", "api");
logger.log(`No or Malformed REDIS_URL present.`, "ERROR", "redis", "api");
process.exit(1);
}
try {
redisServers = JSON.parse(process.env.REDIS_URL);
} catch (error) {
logger.log(
`[${process.env.NODE_ENV}] Failed to parse REDIS_URL: ${error.message}. Exiting...`,
"ERROR",
"redis",
"api"
);
logger.log(`Failed to parse REDIS_URL: ${error.message}. Exiting...`, "ERROR", "redis", "api");
process.exit(1);
}
}
@@ -172,12 +167,7 @@ const connectToRedisCluster = async () => {
const clusterRetryStrategy = (times) => {
const delay =
Math.min(CLUSTER_RETRY_BASE_DELAY + times * 50, CLUSTER_RETRY_MAX_DELAY) + Math.random() * CLUSTER_RETRY_JITTER;
logger.log(
`[${process.env.NODE_ENV}] Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`,
"ERROR",
"redis",
"api"
);
logger.log(`Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`, "WARN", "redis", "api");
return delay;
};
@@ -194,12 +184,12 @@ const connectToRedisCluster = async () => {
return new Promise((resolve, reject) => {
redisCluster.on("ready", () => {
logger.log(`[${process.env.NODE_ENV}] Redis cluster connection established.`, "INFO", "redis", "api");
logger.log(`Redis cluster connection established.`, "INFO", "redis", "api");
resolve(redisCluster);
});
redisCluster.on("error", (err) => {
logger.log(`[${process.env.NODE_ENV}] Redis cluster connection failed: ${err.message}`, "ERROR", "redis", "api");
logger.log(`Redis cluster connection failed: ${err.message}`, "ERROR", "redis", "api");
reject(err);
});
});
@@ -215,7 +205,7 @@ const applySocketIO = async ({ server, app }) => {
// Handle errors
redisCluster.on("error", (err) => {
logger.log(`[${process.env.NODE_ENV}] Redis ERROR`, "ERROR", "redis", "api");
logger.log(`Redis ERROR`, "ERROR", "redis", "api");
});
const pubClient = redisCluster;
@@ -249,7 +239,7 @@ const applySocketIO = async ({ server, app }) => {
});
if (isString(process.env.REDIS_ADMIN_PASS) && !isEmpty(process.env.REDIS_ADMIN_PASS)) {
logger.log(`[${process.env.NODE_ENV}] Initializing Redis Admin UI....`, "INFO", "redis", "api");
logger.log(`Initializing Redis Admin UI....`, "INFO", "redis", "api");
instrument(ioRedis, {
auth: {
type: "basic",
@@ -312,9 +302,9 @@ const main = async () => {
try {
await server.listen(port);
logger.log(`[${process.env.NODE_ENV}] Server started on port ${port}`, "INFO", "api");
logger.log(`Server started on port ${port}`, "INFO", "api");
} catch (error) {
logger.log(`[${process.env.NODE_ENV}] Server failed to start on port ${port}`, "ERROR", "api", error);
logger.log(`Server failed to start on port ${port}`, "ERROR", "api", error);
}
};

View File

@@ -24,9 +24,9 @@ axios.interceptors.request.use((x) => {
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
//console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
return x;
});
@@ -35,8 +35,8 @@ axios.interceptors.response.use((x) => {
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
//console.log(printable);
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
return x;
});
@@ -181,7 +181,7 @@ async function QueryBillData(socket, billids) {
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });
CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
CdkBase.createLogEvent(socket, "SILLY", `Bill data query result ${JSON.stringify(result, null, 2)}`);
return result;
}

View File

@@ -26,9 +26,9 @@ axios.interceptors.request.use((x) => {
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
//console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
return x;
});
@@ -37,8 +37,8 @@ axios.interceptors.response.use((x) => {
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
//console.log(printable);
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
return x;
});
@@ -118,7 +118,7 @@ async function CheckForErrors(socket, response) {
CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`);
} else {
CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`);
CdkBase.createLogEvent(socket, "SILLY", `Error received from DMS: ${JSON.stringify(response)}`);
}
}
@@ -130,7 +130,7 @@ async function QueryJobData(socket, jobid) {
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid });
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
@@ -611,7 +611,7 @@ async function InsertFailedExportLog(socket, error) {
bodyshopid: socket.JobData.bodyshop.id,
jobid: socket.JobData.id,
successful: false,
message: [error],
message: JSON.stringify(error),
useremail: socket.user.email
}
});

View File

@@ -63,7 +63,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
);
if (!account) {
logger.log("qbxml-receivables-no-account", "warn", null, jobline.id, null);
logger.log("qbxml-receivables-no-account", "warn", null, jobline.id);
throw new Error(
`A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}`
);
@@ -207,7 +207,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
});
if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) {
// console.log("Adding MAPA Line Manually.");
// //console.log("Adding MAPA Line Manually.");
const mapaAccountName = responsibilityCenters.defaults.profits.MAPA;
const mapaAccount = responsibilityCenters.profits.find((c) => c.name === mapaAccountName);
@@ -272,12 +272,12 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
});
}
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
////console.log("NO MAPA ACCOUNT FOUND!!");
}
}
if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) {
// console.log("Adding MASH Line Manually.");
// //console.log("Adding MASH Line Manually.");
const mashAccountName = responsibilityCenters.defaults.profits.MASH;
@@ -341,7 +341,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
});
}
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
// //console.log("NO MASH ACCOUNT FOUND!!");
}
}
@@ -795,7 +795,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
: taxCodes[taxAccountCode];
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
const taxAmount = Dinero(job_totals.totals.us_sales_tax_breakdown[`ty${tyCounter}Tax`]);
console.log(`Tax ${tyCounter}`, taxAmount.toFormat());
//console.log(`Tax ${tyCounter}`, taxAmount.toFormat());
if (taxAmount.getAmount() > 0) {
if (qbo) {
InvoiceLineAdd.push({

View File

@@ -45,7 +45,7 @@ exports.default = async (req, res) => {
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery);
logger.log("qbo-payable-create", "DEBUG", req.user.email, null, { billsToQuery });
const result = await client
.setHeaders({ Authorization: BearerToken })
@@ -91,6 +91,12 @@ exports.default = async (req, res) => {
ret.push({ billid: bill.id, success: true });
} catch (error) {
logger.log("qbo-paybles-create-error", "ERROR", req.user.email, null, {
error:
(error && error.authResponse && error.authResponse.body) ||
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
(error && error.message)
});
ret.push({
billid: bill.id,
success: false,
@@ -121,8 +127,8 @@ exports.default = async (req, res) => {
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payable-create-error", "ERROR", req.user.email, {
//console.log(error);
logger.log("qbo-payable-create-error", "ERROR", req.user.email, null, {
error: error.message,
stack: error.stack
});
@@ -161,7 +167,7 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
const Vendor = {
DisplayName: bill.vendor.name
DisplayName: StandardizeName(bill.vendor.name)
};
try {
const result = await oauthClient.makeApiCall({

View File

@@ -49,7 +49,7 @@ exports.default = async (req, res) => {
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
logger.log("qbo-payment-create", "DEBUG", req.user.email, null, { paymentsToQuery });
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
payments: paymentsToQuery
@@ -152,7 +152,7 @@ exports.default = async (req, res) => {
ret.push({ paymentid: payment.id, success: true });
} catch (error) {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
error: (error && error.authResponse && error.authResponse.body) || (error && error.message)
});
//Add the export log error.
@@ -182,8 +182,8 @@ exports.default = async (req, res) => {
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
//console.log(error);
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
error: error.message,
stack: error.stack
});

View File

@@ -185,7 +185,7 @@ exports.default = async (req, res) => {
error?.response?.data ||
error?.message
});
console.log(error);
//console.log(error);
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
error: error.message,
stack: error.stack
@@ -211,7 +211,7 @@ exports.default = async (req, res) => {
res.status(200).json(ret);
} catch (error) {
console.log(error);
//console.log(error);
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
error: error.message,
stack: error.stack

View File

@@ -10,7 +10,7 @@ function urlBuilder(realmId, object, query = null) {
}
function StandardizeName(str) {
return str.replace(new RegExp(/'/g), "\\'");
return str.replace(new RegExp(/'/g), "\\'").trim();
}
exports.urlBuilder = urlBuilder;

View File

@@ -42,9 +42,10 @@ exports.default = async (req, res) => {
//For each invoice.
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log("qbxml-payable-error", "ERROR", req.user.email, req.body.billsToQuery, {
error: error.message,
stack: error.stack
logger.log("qbxml-payable-error", "ERROR", req?.user?.email, null, {
billsToQuery: req?.body?.billsToQuery,
error: error?.message,
stack: error?.stack
});
res.status(400).send(JSON.stringify(error));
}

View File

@@ -21,7 +21,9 @@ exports.default = async (req, res) => {
const client = req.userGraphQLClient;
try {
logger.log("qbxml-payments-create", "DEBUG", req.user.email, req.body.paymentsToQuery, null);
logger.log("qbxml-payments-create", "DEBUG", req?.user?.email, null, {
paymentsToQuery: req.body?.paymentsToQuery
});
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
payments: paymentsToQuery
@@ -62,7 +64,8 @@ exports.default = async (req, res) => {
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log("qbxml-payments-error", "error", req.user.email, req.body.paymentsToQuery, {
logger.log("qbxml-payments-error", "error", req?.user?.email, null, {
paymentsToQuery: req.body?.paymentsToQuery,
error: error.message,
stack: error.stack
});

View File

@@ -23,7 +23,9 @@ exports.default = async (req, res) => {
const client = req.userGraphQLClient;
try {
logger.log("qbxml-receivables-create", "DEBUG", req.user.email, req.body.jobIds, null);
logger.log("qbxml-receivables-create", "DEBUG", req?.user?.email, null, {
jobIds: req?.body?.jobIds
});
const result = await client
.setHeaders({ Authorization: BearerToken })
@@ -74,7 +76,8 @@ exports.default = async (req, res) => {
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log("qbxml-receivables-error", "error", req.user.email, req.body.jobIds, {
logger.log("qbxml-receivables-error", "error", req?.user?.email, null, {
jobIds: req.body?.jobIds,
error: error.message,
stack: error.stack
});

View File

@@ -9,7 +9,7 @@ require("dotenv").config({
const client = require("../graphql-client/graphql-client").client;
exports.createAssociation = async (req, res) => {
logger.log("admin-create-association", "ADMIN", req.user.email, null, {
logger.log("admin-create-association", "debug", req.user.email, null, {
request: req.body,
ioadmin: true
});
@@ -31,7 +31,7 @@ exports.createAssociation = async (req, res) => {
res.json(result);
};
exports.createShop = async (req, res) => {
logger.log("admin-create-shop", "ADMIN", req.user.email, null, {
logger.log("admin-create-shop", "debug", req.user.email, null, {
request: req.body,
ioadmin: true
});
@@ -64,7 +64,7 @@ exports.createShop = async (req, res) => {
}
};
exports.updateCounter = async (req, res) => {
logger.log("admin-update-counter", "ADMIN", req.user.email, null, {
logger.log("admin-update-counter", "debug", req.user.email, null, {
request: req.body,
ioadmin: true
});
@@ -91,7 +91,7 @@ exports.updateCounter = async (req, res) => {
}
};
exports.updateShop = async (req, res) => {
logger.log("admin-update-shop", "ADMIN", req.user.email, null, {
logger.log("admin-update-shop", "debug", req.user.email, null, {
request: req.body,
ioadmin: true
});

View File

@@ -0,0 +1,76 @@
const axios = require("axios");
const _ = require("lodash");
const { default: InstanceMgr } = require("../utils/instanceMgr"); // For deep object comparison
// Constants
const ALERTS_REDIS_KEY = "alerts_data"; // The key under which we'll store alerts in Redis
const GLOBAL_SOCKET_ID = "global"; // Use 'global' as a socketId to store global data
const ALERT_FILE_URL = InstanceMgr({
imex: "https://images.imex.online/alerts/alerts-imex.json",
rome: "https://images.imex.online/alerts/alerts-rome.json"
});
const alertCheck = async (req, res) => {
// Access Redis helper functions
const { ioRedis, logger } = req;
const { getSessionData, setSessionData } = req.sessionUtils;
try {
// Get the JSON Alert file from the server
const response = await axios.get(ALERT_FILE_URL);
const currentAlerts = response.data;
// Retrieve stored alerts from Redis using a global socketId
const storedAlerts = await getSessionData(GLOBAL_SOCKET_ID, ALERTS_REDIS_KEY);
if (!storedAlerts) {
// Alerts not in Redis, store them
await setSessionData(GLOBAL_SOCKET_ID, ALERTS_REDIS_KEY, currentAlerts);
logger.logger.debug("Alerts added to Redis for the first time.");
// Emit to clients
if (ioRedis) {
ioRedis.emit("bodyshop-message", {
type: "alert-update",
payload: currentAlerts
});
logger.logger.debug("Alerts emitted to clients for the first time.");
} else {
logger.log("Socket.IO instance not found. (1)", "error");
}
return res.status(200).send("Alerts added to Redis and emitted to clients.");
} else {
// Alerts are in Redis, compare them
if (!_.isEqual(currentAlerts, storedAlerts)) {
// Alerts are different, update Redis and emit to clients
await setSessionData(GLOBAL_SOCKET_ID, ALERTS_REDIS_KEY, currentAlerts);
logger.logger.debug("Alerts updated in Redis.");
// Emit the new alerts to all connected clients
if (ioRedis) {
ioRedis.emit("bodyshop-message", {
type: "alert-update",
payload: currentAlerts
});
logger.logger.debug("Alerts emitted to clients after update.");
} else {
logger.log("Socket.IO instance not found. (2)", "error");
}
return res.status(200).send("Alerts updated in Redis and emitted to clients.");
} else {
return res.status(200).send("No changes in alerts.");
}
}
} catch (error) {
logger.log("Error in alertCheck:", "error", null, null, {
error: {
message: error.message,
stack: error.stack
}
});
return res.status(500).send("Internal server error.");
}
};
module.exports = { alertCheck };

View File

@@ -22,7 +22,7 @@ exports.defaultRoute = async function (req, res) {
const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
return res.status(200).json({ data: calculateAllocations(req, jobData) });
} catch (error) {
console.log(error);
////console.log(error);
CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
}
@@ -33,7 +33,7 @@ exports.default = async function (socket, jobid) {
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid);
return calculateAllocations(socket, jobData);
} catch (error) {
console.log(error);
////console.log(error);
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
}
};
@@ -42,7 +42,7 @@ async function QueryJobData(connectionData, token, jobid) {
CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
CdkBase.createLogEvent(connectionData, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
@@ -212,7 +212,7 @@ function calculateAllocations(connectionData, job) {
});
if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) {
// console.log("Adding MAPA Line Manually.");
// //console.log("Adding MAPA Line Manually.");
const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mapaAccountName);
@@ -224,7 +224,7 @@ function calculateAllocations(connectionData, job) {
Dinero(job.job_totals.rates.mapa.total)
);
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
////console.log("NO MAPA ACCOUNT FOUND!!");
}
}
@@ -373,13 +373,19 @@ function calculateAllocations(connectionData, job) {
});
//profile level adjustments for labor and materials
Object.keys(job.job_totals.rates).forEach((key) => {
if (job.job_totals.rates[key] && job.job_totals.rates[key].adjustment && Dinero(job.job_totals.rates[key].adjustment).isZero() === false) {
if (
job.job_totals.rates[key] &&
job.job_totals.rates[key].adjustment &&
Dinero(job.job_totals.rates[key].adjustment).isZero() === false
) {
const accountName = selectedDmsAllocationConfig.profits[key.toUpperCase()];
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
if (otherAccount) {
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
profitCenterHash[accountName] = profitCenterHash[accountName].add(Dinero(job.job_totals.rates[key].adjustments));
profitCenterHash[accountName] = profitCenterHash[accountName].add(
Dinero(job.job_totals.rates[key].adjustments)
);
} else {
CdkBase.createLogEvent(
connectionData,

View File

@@ -39,8 +39,8 @@ exports.default = async function ReloadCdkMakes(req, res) {
const deleteResult = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.DELETE_ALL_DMS_VEHICLES, {});
console.log("🚀 ~ file: cdk-get-makes.js ~ line 53 ~ deleteResult", deleteResult);
// logger.logger.debug("🚀 ~ file: cdk-get-makes.js ~ line 53 ~ deleteResult", { deleteResult });
//Insert the new ones.
const insertResult = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_DMS_VEHICLES, {
@@ -59,6 +59,7 @@ exports.default = async function ReloadCdkMakes(req, res) {
cdk_dealerid,
count: newList.length
});
res.sendStatus(200);
} catch (error) {
logger.log("cdk-replace-makes-models-error", "ERROR", req.user.email, null, {

View File

@@ -151,7 +151,7 @@ async function QueryJobData(socket, jobid) {
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid });
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
@@ -171,7 +171,7 @@ async function CalculateDmsVid(socket, JobData) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientVehicleInsertUpdate.getVehIdsAsync Result ${JSON.stringify(result, null, 2)}`
);
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
@@ -214,7 +214,7 @@ async function QueryDmsVehicleById(socket, JobData, DMSVid) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientVehicleInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.readAsync response.`);
@@ -246,7 +246,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.readAsync response.`);
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientCustomerInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
);
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
@@ -295,7 +295,7 @@ async function QueryDmsCustomerByName(socket, JobData) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(result, null, 2)}`
);
CheckCdkResponseForError(socket, soapResponseCustomerSearch);
@@ -337,7 +337,7 @@ async function GenerateDmsCustomerNumber(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(result, null, 2)}`
);
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
@@ -425,7 +425,7 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.insertAsync response.`);
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
);
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
@@ -505,7 +505,7 @@ async function InsertDmsVehicle(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientVehicleInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.insertAsync response.`);
@@ -611,7 +611,7 @@ async function UpdateDmsVehicle(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"DEBUG",
`soapClientVehicleInsertUpdate.updateAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.updateAsync response.`);
@@ -650,7 +650,7 @@ async function InsertServiceVehicleHistory(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientServiceHistoryInsert.serviceHistoryHeaderInsert Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.`);
@@ -690,7 +690,7 @@ async function InsertDmsStartWip(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientAccountingGLInsertUpdate.doStartWIPAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.`);
@@ -721,7 +721,7 @@ async function InsertDmsBatchWip(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.`);
@@ -885,7 +885,7 @@ async function PostDmsBatchWip(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
@@ -914,7 +914,7 @@ async function QueryDmsErrWip(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"DEBUG",
`soapClientAccountingGLInsertUpdate.doErrWIPAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doErrWIPAsync response.`);
@@ -945,7 +945,7 @@ async function DeleteDmsWip(socket) {
CdkBase.createLogEvent(
socket,
"TRACE",
"SILLY",
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
@@ -995,7 +995,7 @@ async function InsertFailedExportLog(socket, error) {
bodyshopid: socket.JobData.bodyshop.id,
jobid: socket.JobData.id,
successful: false,
message: [error],
message: JSON.stringify(error),
useremail: socket.user.email
}
});

View File

@@ -20,7 +20,7 @@ function CheckCdkResponseForError(socket, soapResponse) {
//The response was null, this might be ok, it might not.
CdkBase.createLogEvent(
socket,
"WARNING",
"warn",
`Warning detected in CDK Response - it appears to be null. Stack: ${new Error().stack}`
);
return;

View File

@@ -820,7 +820,7 @@ exports.default = async (req, res) => {
job: JSON.stringify({ id: job.id, ro_number: job.ro_number }),
error: error.message || JSON.stringify(error)
});
console.log(error);
//console.log(error);
}
} catch (error) {
logger.log("arms-failed-job", "ERROR", "api", job.shopid, {
@@ -886,7 +886,10 @@ exports.default = async (req, res) => {
const [result, rawResponse, , rawRequest] = entegralResponse;
} catch (error) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
console.log(error);
//console.log(error);
logger.log("arms-error-shop", "ERROR", "api", bodyshop.id, {
error
});
}
} catch (error) {
//Error at the shop level.

View File

@@ -13,6 +13,7 @@ let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const AHDineroFormat = "0.00";
const AhDateFormat = "MMDDYYYY";
@@ -24,9 +25,12 @@ const ftpSetup = {
port: process.env.AUTOHOUSE_PORT,
username: process.env.AUTOHOUSE_USER,
password: process.env.AUTOHOUSE_PASSWORD,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
debug:
process.env.NODE_ENV !== "production"
? (message, ...data) => logger.log(message, "DEBUG", "api", null, data)
: () => {},
algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss"]
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
}
};
@@ -36,160 +40,176 @@ exports.default = async (req, res) => {
res.sendStatus(403);
return;
}
//Query for the List of Bodyshop Clients.
logger.log("autohouse-start", "DEBUG", "api", null, null);
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
const specificShopIds = req.body.bodyshopIds; // ['uuid]
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
// Only process if the appropriate token is provided.
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401);
return;
}
const allxmlsToUpload = [];
const allErrors = [];
// Send immediate response and continue processing.
res.status(202).json({
success: true,
message: "Processing request ...",
timestamp: new Date().toISOString()
});
try {
for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) {
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
const erroredJobs = [];
try {
const { jobs, bodyshops_by_pk } = await client.request(queries.AUTOHOUSE_QUERY, {
bodyshopid: bodyshop.id,
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).endOf("day") })
});
logger.log("autohouse-start", "DEBUG", "api", null, null);
const allXMLResults = [];
const allErrors = [];
const autoHouseObject = {
AutoHouseExport: {
RepairOrder: jobs.map((j) =>
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
})
)
}
};
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); //Query for the List of Bodyshop Clients.
const specificShopIds = req.body.bodyshopIds; // ['uuid];
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
if (erroredJobs.length > 0) {
logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
});
}
const shopsToProcess =
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null);
var ret = builder
.create(
{
// version: "1.0",
// encoding: "UTF-8",
//keepNullNodes: true,
},
autoHouseObject
)
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: autoHouseObject.AutoHouseExport.RepairOrder.length,
xml: ret,
filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`
});
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
} catch (error) {
//Error at the shop level.
logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, {
...error
});
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
autuhouseid: bodyshop.autuhouseid,
fatal: true,
errors: [error.toString()]
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
autohouseid: bodyshop.autohouseid,
errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number,
jobid: ej.job?.id,
error: ej.error
}))
});
}
}
if (skipUpload) {
for (const xmlObj of allxmlsToUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
}
res.json(allxmlsToUpload);
sendServerEmail({
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
null,
2
)}
`
});
if (shopsToProcess.length === 0) {
logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null);
return;
}
let sftp = new Client();
sftp.on("error", (errors) =>
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
...errors
})
);
await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors);
await sendServerEmail({
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
allXMLResults.map((x) => ({
imexshopid: x.imexshopid,
filename: x.filename,
count: x.count,
result: x.result
})),
null,
2
)}`
});
logger.log("autohouse-end", "DEBUG", "api", null, null);
} catch (error) {
logger.log("autohouse-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
}
};
async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) {
for (const bodyshop of shopsToProcess) {
const erroredJobs = [];
try {
//Connect to the FTP and upload all.
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
await sftp.connect(ftpSetup);
const { jobs, bodyshops_by_pk } = await client.request(queries.AUTOHOUSE_QUERY, {
bodyshopid: bodyshop.id,
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).endOf("day") })
});
for (const xmlObj of allxmlsToUpload) {
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename
});
const autoHouseObject = {
AutoHouseExport: {
RepairOrder: jobs.map((j) =>
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
})
)
}
};
const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`);
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
uploadResult
if (erroredJobs.length > 0) {
logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
});
}
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
const xmlObj = {
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
xml: builder.create({}, autoHouseObject).end({ allowEmptyTags: true }),
filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`,
count: autoHouseObject.AutoHouseExport.RepairOrder.length
};
if (skipUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
} else {
await uploadViaSFTP(xmlObj);
}
allXMLResults.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
autohouseid: bodyshop.autohouseid,
count: xmlObj.count,
filename: xmlObj.filename,
result: xmlObj.result
});
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
} catch (error) {
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
...error
//Error at the shop level.
logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
autohouseid: bodyshop.autohouseid,
fatal: true,
errors: [error.toString()]
});
} finally {
sftp.end();
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
autohouseid: bodyshop.autohouseid,
errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number,
jobid: ej.job?.id,
error: ej.error
}))
});
}
sendServerEmail({
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
null,
2
)}
`
});
res.sendStatus(200);
} catch (error) {
res.status(200).json(error);
}
};
}
async function uploadViaSFTP(xmlObj) {
const sftp = new Client();
sftp.on("error", (errors) =>
logger.log("autohouse-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, {
error: errors.message,
stack: errors.stack
})
);
try {
//Connect to the FTP and upload all.
await sftp.connect(ftpSetup);
try {
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
logger.log("autohouse-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, {
imexshopid: xmlObj.imexshopid,
filename: xmlObj.filename,
result: xmlObj.result
});
} catch (error) {
logger.log("autohouse-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, {
filename: xmlObj.filename,
error: error.message,
stack: error.stack
});
throw error;
}
} catch (error) {
logger.log("autohouse-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { error: error.message, stack: error.stack });
throw error;
} finally {
sftp.end();
}
}
const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2
@@ -206,9 +226,6 @@ const CreateRepairOrderTag = (job, errorCallback) => {
const repairCosts = CreateCosts(job);
if (job.ro_number === "QBD209") {
console.log("Stop here");
}
//Calculate detail only lines.
const detailAdjustments = job.joblines
.filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty)
@@ -290,8 +307,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
InsuranceCo: job.ins_co_nm || "",
CompanyName: job.ins_co_nm || "",
Address: job.ins_addr1 || "",
City: job.ins_addr1 || "",
State: job.ins_city || "",
City: job.ins_city || "",
State: job.ins_st || "",
Zip: job.ins_zip || "",
Phone: job.ins_ph1 || "",
Fax: job.ins_fax || "",
@@ -604,10 +621,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
};
return ret;
} catch (error) {
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, {
error
});
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
}
};

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